diff --git a/cmd/velero-backup-restore/velero-backup-restore.sh b/cmd/velero-backup-restore/velero-backup-restore.sh index 7995c821..bc86a944 100755 --- a/cmd/velero-backup-restore/velero-backup-restore.sh +++ b/cmd/velero-backup-restore/velero-backup-restore.sh @@ -93,13 +93,13 @@ create_backup() { local backup_cmd="$VELERO_CLI create backup $backup_name --namespace $namespace --include-namespaces $include_ns --wait" if [ -n "$selector" ]; then - backup_cmd+=("--selector" "$selector") + backup_cmd="$backup_cmd --selector $selector" fi if [ -n "$include_resources" ]; then - backup_cmd+=("--include-resources" "$include_resources") + backup_cmd="$backup_cmd --include-resources $include_resources" fi if [ -n "$snapshot_location" ]; then - backup_cmd+=("--volume-snapshot-locations" "$snapshot_location") + backup_cmd="$backup_cmd --volume-snapshot-locations $snapshot_location" fi # Execute backup command @@ -257,4 +257,3 @@ case $command in esac echo "Exiting..." - diff --git a/pkg/plugin/vm_backup_item_action.go b/pkg/plugin/vm_backup_item_action.go index d0be530b..b0e868b6 100644 --- a/pkg/plugin/vm_backup_item_action.go +++ b/pkg/plugin/vm_backup_item_action.go @@ -22,6 +22,7 @@ package plugin import ( "context" "fmt" + "strings" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -141,8 +142,18 @@ func (p *VMBackupItemAction) canBeSafelyBackedUp(vm *kvcore.VirtualMachine, back func (p *VMBackupItemAction) addVMObjectGraph(vm *kvcore.VirtualMachine, extra []velero.ResourceIdentifier) []velero.ResourceIdentifier { if vm.Spec.Instancetype != nil { - switch vm.Spec.Instancetype.Kind { - //TODO handle VirtualMachineClusterInstancetype + switch strings.ToLower(vm.Spec.Instancetype.Kind) { + case "virtualmachineclusterinstancetype": + p.log.Infof("Adding cluster instance type %s to the backup", vm.Spec.Instancetype.Name) + extra = append(extra, velero.ResourceIdentifier{ + GroupResource: schema.GroupResource{Group: "instancetype.kubevirt.io", Resource: "virtualmachineclusterinstancetype"}, + Name: vm.Spec.Instancetype.Name, + }) + extra = append(extra, velero.ResourceIdentifier{ + GroupResource: schema.GroupResource{Group: "apps", Resource: "controllerrevisions"}, + Namespace: vm.Namespace, + Name: vm.Spec.Instancetype.RevisionName, + }) case "virtualmachineinstancetype": p.log.Infof("Adding instance type %s to the backup", vm.Spec.Instancetype.Name) extra = append(extra, velero.ResourceIdentifier{ @@ -159,8 +170,18 @@ func (p *VMBackupItemAction) addVMObjectGraph(vm *kvcore.VirtualMachine, extra [ } if vm.Spec.Preference != nil { - //TODO handle VirtualMachineClusterPreference - switch vm.Spec.Preference.Kind { + switch strings.ToLower(vm.Spec.Preference.Kind) { + case "virtualmachineclusterpreference": + p.log.Infof("Adding cluster preference %s to the backup", vm.Spec.Preference.Name) + extra = append(extra, velero.ResourceIdentifier{ + GroupResource: schema.GroupResource{Group: "instancetype.kubevirt.io", Resource: "virtualmachineclusterpreference"}, + Name: vm.Spec.Preference.Name, + }) + extra = append(extra, velero.ResourceIdentifier{ + GroupResource: schema.GroupResource{Group: "apps", Resource: "controllerrevisions"}, + Namespace: vm.Namespace, + Name: vm.Spec.Preference.RevisionName, + }) case "virtualmachinepreference": p.log.Infof("Adding preference %s to the backup", vm.Spec.Preference.Name) extra = append(extra, velero.ResourceIdentifier{ diff --git a/tests/framework/kubectl.go b/tests/framework/kubectl.go index 13c4c5fd..194344ba 100644 --- a/tests/framework/kubectl.go +++ b/tests/framework/kubectl.go @@ -12,7 +12,7 @@ import ( "github.com/pkg/errors" ) -//RunKubectlCommand runs a kubectl Cmd and returns output and err +// RunKubectlCommand runs a kubectl Cmd and returns output and err func (f *Framework) RunKubectlCommand(args ...string) error { cmd := f.CreateKubectlCommand(args...) outBytes, err := cmd.CombinedOutput() @@ -84,7 +84,7 @@ func (f *Framework) KubectlDescribeVeleroBackup(ctx context.Context, podName, ba if err != nil { return result, err } - json.Unmarshal(jsonBuf, &result) + err = json.Unmarshal(jsonBuf, &result) if err != nil { return result, err } diff --git a/tests/framework/manifests_utils.go b/tests/framework/manifests_utils.go index ad260126..61f31a20 100644 --- a/tests/framework/manifests_utils.go +++ b/tests/framework/manifests_utils.go @@ -5,11 +5,21 @@ func (f *Framework) CreateInstancetype() error { return err } +func (f *Framework) CreateClusterInstancetype() error { + err := f.RunKubectlCommand("create", "-f", "manifests/cluster-instancetype.yaml", "-n", f.Namespace.Name) + return err +} + func (f *Framework) CreatePreference() error { err := f.RunKubectlCommand("create", "-f", "manifests/preference.yaml", "-n", f.Namespace.Name) return err } +func (f *Framework) CreateClusterPreference() error { + err := f.RunKubectlCommand("create", "-f", "manifests/cluster-preference.yaml", "-n", f.Namespace.Name) + return err +} + func (f *Framework) CreateConfigMap() error { err := f.RunKubectlCommand("create", "-f", "manifests/configmap.yaml", "-n", f.Namespace.Name) return err @@ -45,6 +55,11 @@ func (f *Framework) CreateVMWithInstancetypeAndPreference() error { return err } +func (f *Framework) CreateVMWithClusterInstancetypeAndClusterPreference() error { + err := f.RunKubectlCreateYamlCommand("manifests/vm_with_clusterinstancetype_and_clusterpreference.yaml") + return err +} + func (f *Framework) CreateVMWithDifferentVolumes() error { err := f.RunKubectlCreateYamlCommand("manifests/vm_with_different_volume_types.yaml") return err diff --git a/tests/manifests/cluster-instancetype.yaml b/tests/manifests/cluster-instancetype.yaml new file mode 100644 index 00000000..7d0ea1cc --- /dev/null +++ b/tests/manifests/cluster-instancetype.yaml @@ -0,0 +1,9 @@ +apiVersion: instancetype.kubevirt.io/v1beta1 +kind: VirtualMachineClusterInstancetype +metadata: + name: test-vm-instancetype +spec: + cpu: + guest: 1 + memory: + guest: 256Mi diff --git a/tests/manifests/cluster-preference.yaml b/tests/manifests/cluster-preference.yaml new file mode 100644 index 00000000..b6d80f52 --- /dev/null +++ b/tests/manifests/cluster-preference.yaml @@ -0,0 +1,7 @@ +apiVersion: instancetype.kubevirt.io/v1beta1 +kind: VirtualMachineClusterPreference +metadata: + name: test-vm-preference +spec: + cpu: + preferredCPUTopology: "preferSockets" diff --git a/tests/manifests/vm_with_clusterinstancetype_and_clusterpreference.yaml b/tests/manifests/vm_with_clusterinstancetype_and_clusterpreference.yaml new file mode 100644 index 00000000..6032129d --- /dev/null +++ b/tests/manifests/vm_with_clusterinstancetype_and_clusterpreference.yaml @@ -0,0 +1,76 @@ +apiVersion: kubevirt.io/v1 +kind: VirtualMachine +metadata: + name: test-vm-with-instancetype-and-preference + labels: + a.test.label: included +spec: + instancetype: + name: test-vm-instancetype + kind: VirtualMachineClusterInstancetype + preference: + name: test-vm-preference + kind: VirtualMachineClusterPreference + dataVolumeTemplates: + - metadata: + name: test-dv + spec: + pvc: + accessModes: + - ReadWriteOnce + volumeMode: Block + resources: + requests: + storage: 1Gi + storageClassName: {{KVP_STORAGE_CLASS}} + source: + registry: + pullMethod: node + url: docker://quay.io/kubevirt/alpine-with-test-tooling-container-disk:v0.57.1 + running: true + template: + metadata: + creationTimestamp: null + name: test-vm-with-instancetype-and-preference + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: volume0 + - disk: + bus: virtio + name: volume1 + interfaces: + - masquerade: {} + name: default + rng: {} + machine: + type: q35 + networks: + - name: default + pod: {} + terminationGracePeriodSeconds: 0 + volumes: + - dataVolume: + name: test-dv + name: volume0 + - cloudInitNoCloud: + networkData: |- + ethernets: + eth0: + addresses: + - fd10:0:2::2/120 + dhcp4: true + gateway6: fd10:0:2::1 + match: {} + nameservers: + addresses: + - 10.96.0.10 + search: + - default.svc.cluster.local + - svc.cluster.local + - cluster.local + version: 2 + name: volume1 diff --git a/tests/vm_backup_test.go b/tests/vm_backup_test.go index 2a3933a2..d6b2bdbb 100644 --- a/tests/vm_backup_test.go +++ b/tests/vm_backup_test.go @@ -200,63 +200,98 @@ var _ = Describe("[smoke] VM Backup", func() { }) Context("VM and VMI object graph backup", func() { - It("[test_id:10270]with instancetype and preference", Label("PartnerComp"), func() { - By("Create instancetype and preference") - err := f.CreateInstancetype() - Expect(err).ToNot(HaveOccurred()) - err = f.CreatePreference() - Expect(err).ToNot(HaveOccurred()) - - By("Starting a VM") - err = f.CreateVMWithInstancetypeAndPreference() - Expect(err).ToNot(HaveOccurred()) - vm, err = framework.WaitVirtualMachineRunning(f.KvClient, f.Namespace.Name, "test-vm-with-instancetype-and-preference", dvName) - Expect(err).ToNot(HaveOccurred()) - - By("Wait instance type controller revision to be updated on VM spec") - Eventually(func(g Gomega) { - vm, err = f.KvClient.VirtualMachine(f.Namespace.Name).Get(context.Background(), vm.Name, &metav1.GetOptions{}) - g.Expect(err).ToNot(HaveOccurred()) - g.Expect(vm.Spec.Instancetype.RevisionName).ToNot(BeEmpty()) - g.Expect(vm.Spec.Preference.RevisionName).ToNot(BeEmpty()) - _, err := f.KvClient.AppsV1().ControllerRevisions(f.Namespace.Name).Get(context.Background(), vm.Spec.Instancetype.RevisionName, metav1.GetOptions{}) - g.Expect(err).ToNot(HaveOccurred()) - _, err = f.KvClient.AppsV1().ControllerRevisions(f.Namespace.Name).Get(context.Background(), vm.Spec.Preference.RevisionName, metav1.GetOptions{}) - g.Expect(err).ToNot(HaveOccurred()) - }, 2*time.Minute, 2*time.Second).Should(Succeed()) + Context("with instancetypes and preferences", func() { + nsDelFunc := func() { + err := f.KvClient.VirtualMachineInstancetype(f.Namespace.Name). + Delete(context.Background(), instancetypeName, metav1.DeleteOptions{}) + Expect(err).ToNot(HaveOccurred()) + err = f.KvClient.VirtualMachinePreference(f.Namespace.Name). + Delete(context.Background(), preferenceName, metav1.DeleteOptions{}) + Expect(err).ToNot(HaveOccurred()) + } - By("Creating backup") - err = f.RunBackupScript(timeout, backupName, "", "a.test.label=included", f.Namespace.Name, snapshotLocation, f.BackupNamespace) - Expect(err).ToNot(HaveOccurred()) + clusterDelFunc := func() { + err := f.KvClient.VirtualMachineClusterInstancetype(). + Delete(context.Background(), instancetypeName, metav1.DeleteOptions{}) + Expect(err).ToNot(HaveOccurred()) + err = f.KvClient.VirtualMachineClusterPreference(). + Delete(context.Background(), preferenceName, metav1.DeleteOptions{}) + Expect(err).ToNot(HaveOccurred()) + } - By("Deleting VM, instancetype and preference") - err = f.KvClient.VirtualMachineInstancetype(f.Namespace.Name). - Delete(context.Background(), instancetypeName, metav1.DeleteOptions{}) - Expect(err).ToNot(HaveOccurred()) - err = f.KvClient.VirtualMachinePreference(f.Namespace.Name). - Delete(context.Background(), preferenceName, metav1.DeleteOptions{}) - Expect(err).ToNot(HaveOccurred()) - ok, err := framework.DeleteVirtualMachineAndWait(f.KvClient, f.Namespace.Name, vm.Name) - Expect(err).ToNot(HaveOccurred()) - Expect(ok).To(BeTrue()) + clusterCleanup := func() { + err := f.KvClient.VirtualMachineClusterInstancetype(). + Delete(context.Background(), instancetypeName, metav1.DeleteOptions{}) + if err != nil { + Expect(errors.IsNotFound(err)).To(BeTrue()) + } + err = f.KvClient.VirtualMachineClusterPreference(). + Delete(context.Background(), preferenceName, metav1.DeleteOptions{}) + if err != nil { + Expect(errors.IsNotFound(err)).To(BeTrue()) + } + } - // Wait until ControllerRevision is deleted - Eventually(func(g Gomega) metav1.StatusReason { - _, err := f.KvClient.AppsV1().ControllerRevisions(f.Namespace.Name).Get(context.Background(), vm.Spec.Instancetype.RevisionName, metav1.GetOptions{}) - if err != nil && errors.ReasonForError(err) != metav1.StatusReasonNotFound { - return errors.ReasonForError(err) + DescribeTable("with instancetype and preference", Label("PartnerComp"), func(itFunc func() error, pFunc func() error, vmFunc func() error, delFunc func(), cleanupFunc func()) { + if cleanupFunc != nil { + defer cleanupFunc() } - _, err = f.KvClient.AppsV1().ControllerRevisions(f.Namespace.Name).Get(context.Background(), vm.Spec.Preference.RevisionName, metav1.GetOptions{}) - return errors.ReasonForError(err) - }, 2*time.Minute, 2*time.Second).Should(Equal(metav1.StatusReasonNotFound)) + By("Create instancetype and preference") + err := itFunc() + Expect(err).ToNot(HaveOccurred()) + err = pFunc() + Expect(err).ToNot(HaveOccurred()) + + By("Starting a VM") + err = vmFunc() + Expect(err).ToNot(HaveOccurred()) + vm, err = framework.WaitVirtualMachineRunning(f.KvClient, f.Namespace.Name, "test-vm-with-instancetype-and-preference", dvName) + Expect(err).ToNot(HaveOccurred()) + + By("Wait instance type controller revision to be updated on VM spec") + Eventually(func(g Gomega) { + vm, err = f.KvClient.VirtualMachine(f.Namespace.Name).Get(context.Background(), vm.Name, &metav1.GetOptions{}) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(vm.Spec.Instancetype.RevisionName).ToNot(BeEmpty()) + g.Expect(vm.Spec.Preference.RevisionName).ToNot(BeEmpty()) + _, err := f.KvClient.AppsV1().ControllerRevisions(f.Namespace.Name).Get(context.Background(), vm.Spec.Instancetype.RevisionName, metav1.GetOptions{}) + g.Expect(err).ToNot(HaveOccurred()) + _, err = f.KvClient.AppsV1().ControllerRevisions(f.Namespace.Name).Get(context.Background(), vm.Spec.Preference.RevisionName, metav1.GetOptions{}) + g.Expect(err).ToNot(HaveOccurred()) + }, 2*time.Minute, 2*time.Second).Should(Succeed()) + + By("Creating backup") + err = f.RunBackupScript(timeout, backupName, "", "a.test.label=included", f.Namespace.Name, snapshotLocation, f.BackupNamespace) + Expect(err).ToNot(HaveOccurred()) + + By("Deleting VM, instancetype and preference") + delFunc() + + ok, err := framework.DeleteVirtualMachineAndWait(f.KvClient, f.Namespace.Name, vm.Name) + Expect(err).ToNot(HaveOccurred()) + Expect(ok).To(BeTrue()) + + // Wait until ControllerRevision is deleted + Eventually(func(g Gomega) metav1.StatusReason { + _, err := f.KvClient.AppsV1().ControllerRevisions(f.Namespace.Name).Get(context.Background(), vm.Spec.Instancetype.RevisionName, metav1.GetOptions{}) + if err != nil && errors.ReasonForError(err) != metav1.StatusReasonNotFound { + return errors.ReasonForError(err) + } + _, err = f.KvClient.AppsV1().ControllerRevisions(f.Namespace.Name).Get(context.Background(), vm.Spec.Preference.RevisionName, metav1.GetOptions{}) + return errors.ReasonForError(err) + }, 2*time.Minute, 2*time.Second).Should(Equal(metav1.StatusReasonNotFound)) - By("Creating restore") - err = f.RunRestoreScript(timeout, backupName, restoreName, f.BackupNamespace) - Expect(err).ToNot(HaveOccurred()) + By("Creating restore") + err = f.RunRestoreScript(timeout, backupName, restoreName, f.BackupNamespace) + Expect(err).ToNot(HaveOccurred()) - By("Verifying VM") - err = framework.WaitForVirtualMachineStatus(f.KvClient, f.Namespace.Name, vm.Name, kvv1.VirtualMachineStatusRunning) - Expect(err).ToNot(HaveOccurred()) + By("Verifying VM") + err = framework.WaitForVirtualMachineStatus(f.KvClient, f.Namespace.Name, vm.Name, kvv1.VirtualMachineStatusRunning) + Expect(err).ToNot(HaveOccurred()) + }, + Entry("[test_id:10270]namespace scope", f.CreateInstancetype, f.CreatePreference, f.CreateVMWithInstancetypeAndPreference, nsDelFunc, nil), + Entry("[test_id:10274]cluster scope", f.CreateClusterInstancetype, f.CreateClusterPreference, f.CreateVMWithClusterInstancetypeAndClusterPreference, clusterDelFunc, clusterCleanup), + ) }) It("[test_id:10271]with configmap, secret and serviceaccount", Label("PartnerComp"), func() {