diff --git a/tests/e2e/lib/virt_helpers.go b/tests/e2e/lib/virt_helpers.go index d0b94f681d..0f99533598 100644 --- a/tests/e2e/lib/virt_helpers.go +++ b/tests/e2e/lib/virt_helpers.go @@ -183,14 +183,14 @@ func (v *VirtOperator) checkNamespace() bool { // Checks for the existence of the virtualization operator group func (v *VirtOperator) checkOperatorGroup() bool { group := operatorsv1.OperatorGroup{} - err := v.Client.Get(context.TODO(), client.ObjectKey{Namespace: v.Namespace, Name: "kubevirt-hyperconverged-group"}, &group) + err := v.Client.Get(context.Background(), client.ObjectKey{Namespace: v.Namespace, Name: "kubevirt-hyperconverged-group"}, &group) return err == nil } // Checks if there is a virtualization subscription func (v *VirtOperator) checkSubscription() bool { subscription := operatorsv1alpha1.Subscription{} - err := v.Client.Get(context.TODO(), client.ObjectKey{Namespace: v.Namespace, Name: "hco-operatorhub"}, &subscription) + err := v.Client.Get(context.Background(), client.ObjectKey{Namespace: v.Namespace, Name: "hco-operatorhub"}, &subscription) return err == nil } @@ -621,7 +621,7 @@ func (v *VirtOperator) GetVmStatus(namespace, name string) (string, error) { // /apis/subresources.kubevirt.io/v1/namespaces/{namespace:[a-z0-9]}/virtualmachines/{name:[a-z0-9][a-z0-9\-]}/stop func (v *VirtOperator) StopVm(namespace, name string) error { path := fmt.Sprintf(stopVmPath, namespace, name) - return v.Clientset.RESTClient().Put().AbsPath(path).Do(context.TODO()).Error() + return v.Clientset.RESTClient().Put().AbsPath(path).Do(context.Background()).Error() } func (v *VirtOperator) checkVmExists(namespace, name string) bool { @@ -630,7 +630,7 @@ func (v *VirtOperator) checkVmExists(namespace, name string) bool { } func (v *VirtOperator) removeVm(namespace, name string) error { - if err := v.Dynamic.Resource(virtualMachineGvr).Namespace(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}); err != nil { + if err := v.Dynamic.Resource(virtualMachineGvr).Namespace(namespace).Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil { if !apierrors.IsNotFound(err) { return fmt.Errorf("error deleting VM %s/%s: %w", namespace, name, err) } diff --git a/tests/e2e/lib/virt_storage_helpers.go b/tests/e2e/lib/virt_storage_helpers.go index 167de86161..845c18c7e6 100644 --- a/tests/e2e/lib/virt_storage_helpers.go +++ b/tests/e2e/lib/virt_storage_helpers.go @@ -2,16 +2,19 @@ package lib import ( "context" + "errors" "fmt" "log" "strings" "time" + storagev1 "k8s.io/api/storage/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/utils/ptr" ) var dataVolumeGVR = schema.GroupVersionResource{ @@ -203,3 +206,41 @@ func (v *VirtOperator) CreateDataSourceFromPvc(namespace, name string) error { return nil } + +// Check the VolumeBindingMode of the default storage class, and make an +// Immediate-mode copy if it is set to WaitForFirstConsumer. +func (v *VirtOperator) CreateImmediateModeStorageClass(name string) error { + // Find the default storage class + storageClasses, err := v.Clientset.StorageV1().StorageClasses().List(context.Background(), metav1.ListOptions{}) + if err != nil { + return err + } + var defaultStorageClass *storagev1.StorageClass + for _, storageClass := range storageClasses.Items { + if storageClass.Annotations["storageclass.kubernetes.io/is-default-class"] == "true" { + log.Printf("Found default storage class: %s", storageClass.Name) + defaultStorageClass = storageClass.DeepCopy() + if storageClass.VolumeBindingMode != nil && *storageClass.VolumeBindingMode == storagev1.VolumeBindingImmediate { + log.Println("Default storage class already set to Immediate") + return nil + } + break + } + } + if defaultStorageClass == nil { + return errors.New("no default storage class found") + } + + immediateStorageClass := defaultStorageClass + immediateStorageClass.VolumeBindingMode = ptr.To[storagev1.VolumeBindingMode](storagev1.VolumeBindingImmediate) + immediateStorageClass.Name = name + immediateStorageClass.ResourceVersion = "" + immediateStorageClass.Annotations["storageclass.kubernetes.io/is-default-class"] = "false" + + _, err = v.Clientset.StorageV1().StorageClasses().Create(context.Background(), immediateStorageClass, metav1.CreateOptions{}) + return err +} + +func (v *VirtOperator) RemoveStorageClass(name string) error { + return v.Clientset.StorageV1().StorageClasses().Delete(context.Background(), name, metav1.DeleteOptions{}) +} diff --git a/tests/e2e/sample-applications/virtual-machines/cirros-test/cirros-test-immediate.yaml b/tests/e2e/sample-applications/virtual-machines/cirros-test/cirros-test-immediate.yaml new file mode 100644 index 0000000000..6c61975954 --- /dev/null +++ b/tests/e2e/sample-applications/virtual-machines/cirros-test/cirros-test-immediate.yaml @@ -0,0 +1,48 @@ +apiVersion: v1 +kind: List +items: + - apiVersion: kubevirt.io/v1 + kind: VirtualMachine + metadata: + name: cirros-test + namespace: cirros-test + spec: + dataVolumeTemplates: + - apiVersion: cdi.kubevirt.io/v1beta1 + kind: DataVolume + metadata: + name: cirros-test-disk + spec: + sourceRef: + kind: DataSource + name: cirros + namespace: openshift-virtualization-os-images + storage: + resources: + requests: + storage: 150Mi + storageClassName: test-sc-immediate + running: true + template: + metadata: + annotations: + vm.kubevirt.io/flavor: tiny + spec: + domain: + devices: + disks: + - disk: + bus: virtio + name: rootdisk + firmware: + bootloader: + efi: + secureBoot: false + resources: + requests: + cpu: 1 + memory: 256Mi + volumes: + - name: rootdisk + persistentVolumeClaim: + claimName: cirros-test-disk \ No newline at end of file diff --git a/tests/e2e/virt_backup_restore_suite_test.go b/tests/e2e/virt_backup_restore_suite_test.go index 297e09afa1..97cd85c0cf 100644 --- a/tests/e2e/virt_backup_restore_suite_test.go +++ b/tests/e2e/virt_backup_restore_suite_test.go @@ -184,6 +184,9 @@ var _ = ginkgo.Describe("VM backup and restore tests", ginkgo.Ordered, func() { gomega.Expect(err).To(gomega.BeNil()) dpaCR.VeleroDefaultPlugins = append(dpaCR.VeleroDefaultPlugins, v1alpha1.DefaultPluginKubeVirt) + + err = v.CreateImmediateModeStorageClass("test-sc-immediate") + gomega.Expect(err).To(gomega.BeNil()) }) var _ = ginkgo.AfterAll(func() { @@ -193,6 +196,9 @@ var _ = ginkgo.Describe("VM backup and restore tests", ginkgo.Ordered, func() { if v != nil && wasInstalledFromTest { v.EnsureVirtRemoval() } + + err := v.RemoveStorageClass("test-sc-immediate") + gomega.Expect(err).To(gomega.BeNil()) }) var _ = ginkgo.AfterEach(func(ctx ginkgo.SpecContext) { @@ -242,6 +248,58 @@ var _ = ginkgo.Describe("VM backup and restore tests", ginkgo.Ordered, func() { }, }, nil), + ginkgo.Entry("immediate binding no-application CSI datamover backup and restore, CirrOS VM", ginkgo.Label("virt"), VmBackupRestoreCase{ + Template: "./sample-applications/virtual-machines/cirros-test/cirros-test-immediate.yaml", + InitDelay: 2 * time.Minute, // Just long enough to get to login prompt, VM is marked running while kernel messages are still scrolling by + BackupRestoreCase: BackupRestoreCase{ + Namespace: "cirros-test", + Name: "cirros-test", + SkipVerifyLogs: true, + BackupRestoreType: lib.CSIDataMover, + BackupTimeout: 20 * time.Minute, + }, + }, nil), + + ginkgo.Entry("immediate binding no-application CSI backup and restore, CirrOS VM", ginkgo.Label("virt"), VmBackupRestoreCase{ + Template: "./sample-applications/virtual-machines/cirros-test/cirros-test-immediate.yaml", + InitDelay: 2 * time.Minute, // Just long enough to get to login prompt, VM is marked running while kernel messages are still scrolling by + BackupRestoreCase: BackupRestoreCase{ + Namespace: "cirros-test", + Name: "cirros-test", + SkipVerifyLogs: true, + BackupRestoreType: lib.CSI, + BackupTimeout: 20 * time.Minute, + }, + }, nil), + + ginkgo.Entry("immediate binding no-application CSI+datamover backup and restore, powered-off CirrOS VM", ginkgo.Label("virt"), VmBackupRestoreCase{ + Template: "./sample-applications/virtual-machines/cirros-test/cirros-test-immediate.yaml", + InitDelay: 2 * time.Minute, + PowerState: "Stopped", + BackupRestoreCase: BackupRestoreCase{ + Namespace: "cirros-test", + Name: "cirros-test", + SkipVerifyLogs: true, + BackupRestoreType: lib.CSIDataMover, + BackupTimeout: 20 * time.Minute, + PreBackupVerify: vmPoweredOff("cirros-test", "cirros-test"), + }, + }, nil), + + ginkgo.Entry("immediate binding no-application CSI backup and restore, powered-off CirrOS VM", ginkgo.Label("virt"), VmBackupRestoreCase{ + Template: "./sample-applications/virtual-machines/cirros-test/cirros-test-immediate.yaml", + InitDelay: 2 * time.Minute, + PowerState: "Stopped", + BackupRestoreCase: BackupRestoreCase{ + Namespace: "cirros-test", + Name: "cirros-test", + SkipVerifyLogs: true, + BackupRestoreType: lib.CSI, + BackupTimeout: 20 * time.Minute, + PreBackupVerify: vmPoweredOff("cirros-test", "cirros-test"), + }, + }, nil), + ginkgo.Entry("todolist CSI backup and restore, in a Fedora VM", ginkgo.Label("virt"), VmBackupRestoreCase{ Template: "./sample-applications/virtual-machines/fedora-todolist/fedora-todolist.yaml", InitDelay: 3 * time.Minute, // For cloud-init