Merge pull request #4913 from qiuming-best/order-resources-test

Add schedule ordered resources E2E test
pull/4927/head
Wenkai Yin(尹文开) 2022-05-18 16:32:35 +08:00 committed by GitHub
commit 51de990a8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 306 additions and 48 deletions

View File

@ -0,0 +1 @@
Add schedule ordered resources E2E test

View File

@ -30,6 +30,7 @@ import (
. "github.com/vmware-tanzu/velero/test/e2e/basic"
. "github.com/vmware-tanzu/velero/test/e2e/basic/resources-check"
. "github.com/vmware-tanzu/velero/test/e2e/bsl-mgmt"
. "github.com/vmware-tanzu/velero/test/e2e/orderedresources"
. "github.com/vmware-tanzu/velero/test/e2e/privilegesmgmt"
. "github.com/vmware-tanzu/velero/test/e2e/resource-filtering"
. "github.com/vmware-tanzu/velero/test/e2e/scale"
@ -105,6 +106,8 @@ var _ = Describe("[PrivilegesMgmt][SSR] Velero test on ssr object when controlle
var _ = Describe("[BSL][Deletion][Snapshot] Local backups will be deleted once the corresponding backup storage location is deleted", BslDeletionWithSnapshots)
var _ = Describe("[BSL][Deletion][Restic] Local backups and restic repos will be deleted once the corresponding backup storage location is deleted", BslDeletionWithRestic)
var _ = Describe("[Schedule][OrederedResources] Backup resources should follow the specific order in schedule", ScheduleOrderedResources)
func TestE2e(t *testing.T) {
// Skip running E2E tests when running only "short" tests because:
// 1. E2E tests are long running tests involving installation of Velero and performing backup and restore operations.

View File

@ -0,0 +1,191 @@
package orderedresources
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//the ordered resources test related to https://github.com/vmware-tanzu/velero/issues/4561
import (
"context"
"flag"
"fmt"
"math/rand"
"strings"
"time"
"github.com/google/uuid"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
waitutil "k8s.io/apimachinery/pkg/util/wait"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
. "github.com/vmware-tanzu/velero/test/e2e"
. "github.com/vmware-tanzu/velero/test/e2e/test"
. "github.com/vmware-tanzu/velero/test/e2e/util/k8s"
. "github.com/vmware-tanzu/velero/test/e2e/util/velero"
)
type OrderedResources struct {
Namespace string
ScheduleName string
OrderMap map[string]string
ScheduleArgs []string
TestCase
}
func ScheduleOrderedResources() {
BeforeEach(func() {
flag.Parse()
if VeleroCfg.InstallVelero {
Expect(VeleroInstall(context.Background(), &VeleroCfg, false)).To(Succeed())
}
})
AfterEach(func() {
if VeleroCfg.InstallVelero && !VeleroCfg.Debug {
Expect(VeleroUninstall(context.Background(), VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace)).To(Succeed())
}
})
It("Create a schedule to backup resources in a specific order should be successful", func() {
test := &OrderedResources{}
err := test.Init()
Expect(err).To(Succeed(), err)
defer func() {
Expect(DeleteNamespace(test.Ctx, test.Client, test.Namespace, false)).To(Succeed(), fmt.Sprintf("Failed to delete the namespace %s", test.Namespace))
err = VeleroScheduleDelete(test.Ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace, test.ScheduleName)
Expect(err).To(Succeed(), fmt.Sprintf("Failed to delete schedule with err %v", err))
}()
By(fmt.Sprintf("Prepare workload as target to backup in base namespace %s", test.Namespace), func() {
err = test.CreateResources()
Expect(err).To(Succeed(), fmt.Sprintf("Failed to create resources to backup with err %v", err))
})
By(fmt.Sprintf("Create schedule the workload in %s namespace", test.Namespace), func() {
err = VeleroScheduleCreate(test.Ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace, test.ScheduleName, test.ScheduleArgs)
Expect(err).To(Succeed(), fmt.Sprintf("Failed to create schedule %s with err %v", test.ScheduleName, err))
})
By(fmt.Sprintf("Checking resource order in %s schedule cr", test.ScheduleName), func() {
err = CheckScheduleWithResourceOrder(test.Ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace, test.ScheduleName, test.OrderMap)
Expect(err).To(Succeed(), fmt.Sprintf("Failed to check schedule %s with err %v", test.ScheduleName, err))
})
By("Checking resource order in backup cr", func() {
backupList := new(velerov1api.BackupList)
err = waitutil.PollImmediate(10*time.Second, time.Minute*5, func() (bool, error) {
if err = test.Client.Kubebuilder.List(test.Ctx, backupList, &kbclient.ListOptions{Namespace: VeleroCfg.VeleroNamespace}); err != nil {
return false, fmt.Errorf("failed to list backup object in %s namespace with err %v", VeleroCfg.VeleroNamespace, err)
}
for _, backup := range backupList.Items {
if err = CheckBackupWithResourceOrder(test.Ctx, VeleroCfg.VeleroCLI, VeleroCfg.VeleroNamespace, backup.Name, test.OrderMap); err == nil {
return true, nil
}
}
fmt.Printf("still finding backup created by schedule %s ...\n", test.ScheduleName)
return false, nil
})
Expect(err).To(Succeed(), fmt.Sprintf("Failed to check schedule %s created backup with err %v", test.ScheduleName, err))
})
})
}
func (o *OrderedResources) Init() error {
rand.Seed(time.Now().UnixNano())
UUIDgen, _ = uuid.NewRandom()
client, err := NewTestClient()
if err != nil {
return fmt.Errorf("failed to init ordered resources test with err %v", err)
}
o.Client = client
o.ScheduleName = "schedule-ordered-resources-" + UUIDgen.String()
o.NSBaseName = "schedule-ordered-resources"
o.Namespace = o.NSBaseName + "-" + UUIDgen.String()
o.OrderMap = map[string]string{
"deployments": fmt.Sprintf("deploy-%s", o.NSBaseName),
"secrets": fmt.Sprintf("secret-%s", o.NSBaseName),
"configmaps": fmt.Sprintf("configmap-%s", o.NSBaseName),
}
o.ScheduleArgs = []string{"--schedule", "@every 1m",
"--include-namespaces", o.Namespace, "--default-volumes-to-restic", "--ordered-resources"}
var orderStr string
for kind, resource := range o.OrderMap {
orderStr += fmt.Sprintf("%s=%s;", kind, resource)
}
o.ScheduleArgs = append(o.ScheduleArgs, strings.TrimRight(orderStr, ";"))
return nil
}
func (o *OrderedResources) CreateResources() error {
o.Ctx, _ = context.WithTimeout(context.Background(), 5*time.Minute)
label := map[string]string{
"orderedresources": "true",
}
fmt.Printf("Creating resources in %s namespace ...\n", o.Namespace)
if err := CreateNamespace(o.Ctx, o.Client, o.Namespace); err != nil {
return errors.Wrapf(err, "failed to create namespace %s", o.Namespace)
}
serviceAccountName := "default"
// wait until the service account is created before patch the image pull secret
if err := WaitUntilServiceAccountCreated(o.Ctx, o.Client, o.Namespace, serviceAccountName, 10*time.Minute); err != nil {
return errors.Wrapf(err, "failed to wait the service account %q created under the namespace %q", serviceAccountName, o.Namespace)
}
// add the image pull secret to avoid the image pull limit issue of Docker Hub
if err := PatchServiceAccountWithImagePullSecret(o.Ctx, o.Client, o.Namespace, serviceAccountName, VeleroCfg.RegistryCredentialFile); err != nil {
return errors.Wrapf(err, "failed to patch the service account %q under the namespace %q", serviceAccountName, o.Namespace)
}
//Create deployment
deploymentName := fmt.Sprintf("deploy-%s", o.NSBaseName)
fmt.Printf("Creating deployment %s in %s namespaces ...\n", deploymentName, o.Namespace)
deployment := NewDeployment(deploymentName, o.Namespace, 1, label)
deployment, err := CreateDeployment(o.Client.ClientGo, o.Namespace, deployment)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to create namespace %q with err %v", o.Namespace, err))
}
err = WaitForReadyDeployment(o.Client.ClientGo, o.Namespace, deployment.Name)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to ensure job completion in namespace: %q", o.Namespace))
}
//Create Secret
secretName := fmt.Sprintf("secret-%s", o.NSBaseName)
fmt.Printf("Creating secret %s in %s namespaces ...\n", secretName, o.Namespace)
_, err = CreateSecret(o.Client.ClientGo, o.Namespace, secretName, label)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to create secret in the namespace %q", o.Namespace))
}
err = WaitForSecretsComplete(o.Client.ClientGo, o.Namespace, secretName)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to ensure secret completion in namespace: %q", o.Namespace))
}
//Create Configmap
configmapName := fmt.Sprintf("configmap-%s", o.NSBaseName)
fmt.Printf("Creating configmap %s in %s namespaces ...\n", configmapName, o.Namespace)
_, err = CreateConfigMap(o.Client.ClientGo, o.Namespace, configmapName, label)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to create configmap in the namespace %q", o.Namespace))
}
err = WaitForConfigMapComplete(o.Client.ClientGo, o.Namespace, configmapName)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("failed to ensure secret completion in namespace: %q", o.Namespace))
}
return nil
}

View File

@ -28,6 +28,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strings"
@ -158,6 +159,36 @@ func getProviderVeleroInstallOptions(
return io, nil
}
func CMDExecWithOutput(checkCMD *exec.Cmd) (*[]byte, error) {
stdoutPipe, err := checkCMD.StdoutPipe()
if err != nil {
return nil, err
}
jsonBuf := make([]byte, 16*1024) // If the YAML is bigger than 16K, there's probably something bad happening
err = checkCMD.Start()
if err != nil {
return nil, err
}
bytesRead, err := io.ReadFull(stdoutPipe, jsonBuf)
if err != nil && err != io.ErrUnexpectedEOF {
return nil, err
}
if bytesRead == len(jsonBuf) {
return nil, errors.New("yaml returned bigger than max allowed")
}
jsonBuf = jsonBuf[0:bytesRead]
err = checkCMD.Wait()
if err != nil {
return nil, err
}
return &jsonBuf, err
}
// checkBackupPhase uses VeleroCLI to inspect the phase of a Velero backup.
func checkBackupPhase(ctx context.Context, veleroCLI string, veleroNamespace string, backupName string,
expectedPhase velerov1api.BackupPhase) error {
@ -165,34 +196,12 @@ func checkBackupPhase(ctx context.Context, veleroCLI string, veleroNamespace str
backupName)
fmt.Printf("get backup cmd =%v\n", checkCMD)
stdoutPipe, err := checkCMD.StdoutPipe()
if err != nil {
return err
}
jsonBuf := make([]byte, 16*1024) // If the YAML is bigger than 16K, there's probably something bad happening
err = checkCMD.Start()
if err != nil {
return err
}
bytesRead, err := io.ReadFull(stdoutPipe, jsonBuf)
if err != nil && err != io.ErrUnexpectedEOF {
return err
}
if bytesRead == len(jsonBuf) {
return errors.New("yaml returned bigger than max allowed")
}
jsonBuf = jsonBuf[0:bytesRead]
err = checkCMD.Wait()
jsonBuf, err := CMDExecWithOutput(checkCMD)
if err != nil {
return err
}
backup := velerov1api.Backup{}
err = json.Unmarshal(jsonBuf, &backup)
err = json.Unmarshal(*jsonBuf, &backup)
if err != nil {
return err
}
@ -209,34 +218,12 @@ func checkRestorePhase(ctx context.Context, veleroCLI string, veleroNamespace st
restoreName)
fmt.Printf("get restore cmd =%v\n", checkCMD)
stdoutPipe, err := checkCMD.StdoutPipe()
if err != nil {
return err
}
jsonBuf := make([]byte, 16*1024) // If the YAML is bigger than 16K, there's probably something bad happening
err = checkCMD.Start()
if err != nil {
return err
}
bytesRead, err := io.ReadFull(stdoutPipe, jsonBuf)
if err != nil && err != io.ErrUnexpectedEOF {
return err
}
if bytesRead == len(jsonBuf) {
return errors.New("yaml returned bigger than max allowed")
}
jsonBuf = jsonBuf[0:bytesRead]
err = checkCMD.Wait()
jsonBuf, err := CMDExecWithOutput(checkCMD)
if err != nil {
return err
}
restore := velerov1api.Restore{}
err = json.Unmarshal(jsonBuf, &restore)
err = json.Unmarshal(*jsonBuf, &restore)
if err != nil {
return err
}
@ -246,6 +233,67 @@ func checkRestorePhase(ctx context.Context, veleroCLI string, veleroNamespace st
return nil
}
func checkSchedulePhase(ctx context.Context, veleroCLI, veleroNamespace, scheduleName string) error {
checkCMD := exec.CommandContext(ctx, veleroCLI, "--namespace", veleroNamespace, "schedule", "get", scheduleName, "-ojson")
jsonBuf, err := CMDExecWithOutput(checkCMD)
if err != nil {
return err
}
schedule := velerov1api.Schedule{}
err = json.Unmarshal(*jsonBuf, &schedule)
if err != nil {
return err
}
if schedule.Status.Phase != velerov1api.SchedulePhaseEnabled {
return errors.Errorf("Unexpected restore phase got %s, expecting %s", schedule.Status.Phase, velerov1api.SchedulePhaseEnabled)
}
return nil
}
func CheckScheduleWithResourceOrder(ctx context.Context, veleroCLI, veleroNamespace, scheduleName string, order map[string]string) error {
checkCMD := exec.CommandContext(ctx, veleroCLI, "--namespace", veleroNamespace, "schedule", "get", scheduleName, "-ojson")
jsonBuf, err := CMDExecWithOutput(checkCMD)
if err != nil {
return err
}
schedule := velerov1api.Schedule{}
err = json.Unmarshal(*jsonBuf, &schedule)
if err != nil {
return err
}
if schedule.Status.Phase != velerov1api.SchedulePhaseEnabled {
return errors.Errorf("Unexpected restore phase got %s, expecting %s", schedule.Status.Phase, velerov1api.SchedulePhaseEnabled)
}
if reflect.DeepEqual(schedule.Spec.Template.OrderedResources, order) {
return nil
} else {
return fmt.Errorf("resource order %v set in schedule command is not equal with order %v stored in schedule cr", order, schedule.Spec.Template.OrderedResources)
}
}
func CheckBackupWithResourceOrder(ctx context.Context, veleroCLI, veleroNamespace, backupName string, order map[string]string) error {
checkCMD := exec.CommandContext(ctx, veleroCLI, "--namespace", veleroNamespace, "get", "backup", backupName, "-ojson")
jsonBuf, err := CMDExecWithOutput(checkCMD)
if err != nil {
return err
}
backup := velerov1api.Backup{}
err = json.Unmarshal(*jsonBuf, &backup)
if err != nil {
return err
}
if backup.Status.Phase != velerov1api.BackupPhaseCompleted {
return errors.Errorf("Unexpected restore phase got %s, expecting %s", backup.Status.Phase, velerov1api.BackupPhaseCompleted)
}
if reflect.DeepEqual(backup.Spec.OrderedResources, order) {
return nil
} else {
return fmt.Errorf("resource order %v set in backup command is not equal with order %v stored in backup cr", order, backup.Spec.OrderedResources)
}
}
// VeleroBackupNamespace uses the veleroCLI to backup a namespace.
func VeleroBackupNamespace(ctx context.Context, veleroCLI, veleroNamespace string, backupCfg BackupConfig) error {
args := []string{
@ -322,6 +370,21 @@ func VeleroBackupExec(ctx context.Context, veleroCLI string, veleroNamespace str
return checkBackupPhase(ctx, veleroCLI, veleroNamespace, backupName, velerov1api.BackupPhaseCompleted)
}
func VeleroScheduleDelete(ctx context.Context, veleroCLI string, veleroNamespace string, scheduleName string) error {
args := []string{"--namespace", veleroNamespace, "delete", "schedule", scheduleName, "--confirm"}
return VeleroCmdExec(ctx, veleroCLI, args)
}
func VeleroScheduleCreate(ctx context.Context, veleroCLI string, veleroNamespace string, scheduleName string, args []string) error {
args = append([]string{
"--namespace", veleroNamespace, "create", "schedule", scheduleName,
}, args...)
if err := VeleroCmdExec(ctx, veleroCLI, args); err != nil {
return err
}
return checkSchedulePhase(ctx, veleroCLI, veleroNamespace, scheduleName)
}
func VeleroCmdExec(ctx context.Context, veleroCLI string, args []string) error {
cmd := exec.CommandContext(ctx, veleroCLI, args...)
cmd.Stdout = os.Stdout