diff --git a/test/e2e/default_config.yaml b/test/e2e/default_config.yaml index 246e4ee90d..89d9568d9e 100644 --- a/test/e2e/default_config.yaml +++ b/test/e2e/default_config.yaml @@ -23,7 +23,6 @@ testData: sizingPolicy: "/tmp/testdata/sizing-policy" vmConfiguration: "/tmp/testdata/vm-configuration" vmMigration: "/tmp/testdata/vm-migration" - vmMigrationCancel: "/tmp/testdata/vm-migration-cancel" vmEvacuation: "/tmp/testdata/vm-evacuation" vdSnapshots: "/tmp/testdata/vd-snapshots" sshKey: "/tmp/testdata/sshkeys/id_ed" diff --git a/test/e2e/internal/config/config.go b/test/e2e/internal/config/config.go index e51880836f..df05ef5f49 100644 --- a/test/e2e/internal/config/config.go +++ b/test/e2e/internal/config/config.go @@ -88,14 +88,13 @@ type Config struct { } type TestData struct { - DiskResizing string `yaml:"diskResizing"` - ImageHotplug string `yaml:"imageHotplug"` - VMMigration string `yaml:"vmMigration"` - VMMigrationCancel string `yaml:"vmMigrationCancel"` - VMEvacuation string `yaml:"vmEvacuation"` - VdSnapshots string `yaml:"vdSnapshots"` - Sshkey string `yaml:"sshKey"` - SSHUser string `yaml:"sshUser"` + DiskResizing string `yaml:"diskResizing"` + ImageHotplug string `yaml:"imageHotplug"` + VMMigration string `yaml:"vmMigration"` + VMEvacuation string `yaml:"vmEvacuation"` + VdSnapshots string `yaml:"vdSnapshots"` + Sshkey string `yaml:"sshKey"` + SSHUser string `yaml:"sshUser"` } type StorageClass struct { diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 62bd1e2b34..8c17b8ad6b 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -28,6 +28,7 @@ import ( corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -333,6 +334,23 @@ func getActivePodName(vm *v1alpha2.VirtualMachine) (string, error) { return "", fmt.Errorf("no active pod found for virtual machine %s", vm.Name) } +func GetKVVMI(ctx context.Context, f *framework.Framework, name, namespace string) (*virtv1.VirtualMachineInstance, error) { + unstructuredKVVMI, err := f.DynamicClient().Resource(rewrite.VirtualMachineInstance{}.GVR()). + Namespace(namespace). + Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + var kvvmi virtv1.VirtualMachineInstance + err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredKVVMI.Object, &kvvmi) + if err != nil { + return nil, err + } + + return &kvvmi, nil +} + func UntilVirtualMachineRebooted(key client.ObjectKey, previousRunningTime time.Time, timeout time.Duration) { GinkgoHelper() diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/kustomization.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/kustomization.yaml deleted file mode 100644 index 6064a3a5cc..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/kustomization.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: testcases -namePrefix: pr-number-or-commit-hash- -resources: - - vm - - ns.yaml -configurations: - - transformer.yaml -labels: - - includeSelectors: true - pairs: - id: pr-number-or-commit-hash - testcase: vm-migration-cancel diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/ns.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/ns.yaml deleted file mode 100644 index 5efde875b6..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/ns.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: default diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/transformer.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/transformer.yaml deleted file mode 100644 index ec70d37fcd..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/transformer.yaml +++ /dev/null @@ -1,52 +0,0 @@ -namespace: - - kind: ClusterVirtualImage - path: spec/dataSource/objectRef/namespace -nameReference: - - kind: VirtualImage - version: v1alpha2 # optional - fieldSpecs: - - path: spec/dataSource/objectRef/name - kind: ClusterVirtualImage - - path: spec/dataSource/objectRef/name - kind: VirtualImage - - path: spec/dataSource/objectRef/name - kind: VirtualDisk - - path: spec/blockDeviceRefs/name - kind: VirtualMachine - - kind: ClusterVirtualImage - version: v1alpha2 # optional - fieldSpecs: - - path: spec/dataSource/objectRef/name - kind: ClusterVirtualImage - - path: spec/dataSource/objectRef/name - kind: VirtualImage - - path: spec/dataSource/objectRef/name - kind: VirtualDisk - - path: spec/blockDeviceRefs/name - kind: VirtualMachine - - kind: VirtualDisk - version: v1alpha2 # optional - fieldSpecs: - - path: spec/blockDeviceRefs/name - kind: VirtualMachine - - path: spec/blockDeviceRef/name - kind: VirtualMachineBlockDeviceAttachment - - kind: Secret - fieldSpecs: - - path: spec/provisioning/userDataRef/name - kind: VirtualMachine - - kind: VirtualMachineIPAddress - version: v1alpha2 - fieldSpecs: - - path: spec/virtualMachineIPAddressName - kind: VirtualMachine - - kind: VirtualMachine - version: v1alpha2 - fieldSpecs: - - path: spec/virtualMachineName - kind: VirtualMachineBlockDeviceAttachment - - kind: VirtualMachineClass - version: v1alpha3 - fieldSpecs: - - path: spec/virtualMachineClassName - kind: VirtualMachine diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/cfg/cloudinit.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/cfg/cloudinit.yaml deleted file mode 100644 index dde99db581..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/cfg/cloudinit.yaml +++ /dev/null @@ -1,24 +0,0 @@ -#cloud-config -package_update: true -packages: - - qemu-guest-agent - - curl - - bash - - sudo - - iputils - - iperf3 -users: - - name: cloud - # passwd: cloud - passwd: $6$rounds=4096$vln/.aPHBOI7BMYR$bBMkqQvuGs5Gyd/1H5DP4m9HjQSy.kgrxpaGEHwkX7KEFV8BS.HZWPitAtZ2Vd8ZqIZRqmlykRCagTgPejt1i. - shell: /bin/bash - sudo: ALL=(ALL) NOPASSWD:ALL - lock_passwd: false - ssh_authorized_keys: - # testcases - - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFxcXHmwaGnJ8scJaEN5RzklBPZpVSic4GdaAsKjQoeA your_email@example.com -final_message: "\U0001F525\U0001F525\U0001F525 The system is finally up, after ${updame} \U0001F525\U0001F525\U0001F525" -runcmd: - - "echo \"\U0001F7E1 Starting runcmd at $(date +%H:%M:%S)\"" - - "rc-update add qemu-guest-agent && rc-service qemu-guest-agent start" - - "echo \"\U0001F7E1 Finished runcmd at $(date +%H:%M:%S)\"" diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/kustomization.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/kustomization.yaml deleted file mode 100644 index 8ff06116f3..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/kustomization.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - ./vm.yaml - - ./vd-root.yaml - - ./vd-blank.yaml -configurations: - - transformer.yaml -generatorOptions: - disableNameSuffixHash: true -secretGenerator: - - files: - - userData=cfg/cloudinit.yaml - name: cloud-init - type: provisioning.virtualization.deckhouse.io/cloud-init diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/transformer.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/transformer.yaml deleted file mode 100644 index 1dc146a3af..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/transformer.yaml +++ /dev/null @@ -1,54 +0,0 @@ -# https://github.com/kubernetes-sigs/kustomize/blob/master/examples/transformerconfigs/README.md#transformer-configurations - -namespace: - - kind: ClusterVirtualImage - path: spec/dataSource/objectRef/namespace -nameReference: - - kind: VirtualImage - version: v1alpha2 # optional - fieldSpecs: - - path: spec/dataSource/objectRef/name - kind: ClusterVirtualImage - - path: spec/dataSource/objectRef/name - kind: VirtualImage - - path: spec/dataSource/objectRef/name - kind: VirtualDisk - - path: spec/blockDeviceRefs/name - kind: VirtualMachine - - kind: ClusterVirtualImage - version: v1alpha2 # optional - fieldSpecs: - - path: spec/dataSource/objectRef/name - kind: ClusterVirtualImage - - path: spec/dataSource/objectRef/name - kind: VirtualImage - - path: spec/dataSource/objectRef/name - kind: VirtualDisk - - path: spec/blockDeviceRefs/name - kind: VirtualMachine - - kind: VirtualDisk - version: v1alpha2 # optional - fieldSpecs: - - path: spec/blockDeviceRefs/name - kind: VirtualMachine - - path: spec/blockDeviceRef/name - kind: VirtualMachineBlockDeviceAttachment - - kind: Secret - fieldSpecs: - - path: spec/provisioning/userDataRef/name - kind: VirtualMachine - - kind: VirtualMachineIPAddress - version: v1alpha2 - fieldSpecs: - - path: spec/virtualMachineIPAddressName - kind: VirtualMachine - - kind: VirtualMachine - version: v1alpha2 - fieldSpecs: - - path: spec/virtualMachineName - kind: VirtualMachineBlockDeviceAttachment - - kind: VirtualMachineClass - version: v1alpha3 - fieldSpecs: - - path: spec/virtualMachineClassName - kind: VirtualMachine diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vd-blank.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vd-blank.yaml deleted file mode 100644 index fcc4b94ea8..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vd-blank.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualDisk -metadata: - name: vd-blank -spec: - persistentVolumeClaim: - storageClassName: "{{ .STORAGE_CLASS_NAME }}" - size: 100Mi diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vd-root.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vd-root.yaml deleted file mode 100644 index 3c66723071..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vd-root.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualDisk -metadata: - name: vd-root -spec: - persistentVolumeClaim: - storageClassName: "{{ .STORAGE_CLASS_NAME }}" - size: 350Mi - dataSource: - type: ObjectRef - objectRef: - kind: ClusterVirtualImage - name: v12n-e2e-alpine-bios diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vm.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vm.yaml deleted file mode 100644 index e20330950f..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/base/vm.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualMachine -metadata: - name: vm -spec: - bootloader: EFI - virtualMachineClassName: generic - cpu: - cores: 1 - coreFraction: 50% - memory: - size: 4Gi - disruptions: - restartApprovalMode: Manual - provisioning: - type: UserDataRef - userDataRef: - kind: Secret - name: cloud-init - blockDeviceRefs: - - kind: VirtualDisk - name: vd-root - - kind: VirtualDisk - name: vd-blank diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/kustomization.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/kustomization.yaml deleted file mode 100644 index 178f21cff4..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/kustomization.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - overlays/migration-bios - - overlays/migration-uefi diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-bios/kustomization.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-bios/kustomization.yaml deleted file mode 100644 index bec3478a3a..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-bios/kustomization.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -nameSuffix: -migration-bios -resources: - - ../../base -patches: - - path: vd.image.patch.yaml - - patch: |- - - op: replace - path: /spec/bootloader - value: BIOS - target: - kind: VirtualMachine - name: vm diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-bios/vd.image.patch.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-bios/vd.image.patch.yaml deleted file mode 100644 index 6d7b3e4063..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-bios/vd.image.patch.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualDisk -metadata: - name: vd-root -spec: - dataSource: - type: ObjectRef - objectRef: - kind: ClusterVirtualImage - name: v12n-e2e-alpine-bios diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/kustomization.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/kustomization.yaml deleted file mode 100644 index 2303fd1580..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -nameSuffix: -migration-uefi -resources: - - ../../base -patches: - - path: vd.image.patch.yaml - - path: vm.bootloader.patch.yaml diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/vd.image.patch.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/vd.image.patch.yaml deleted file mode 100644 index 6c1a1ad8c9..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/vd.image.patch.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualDisk -metadata: - name: vd-root -spec: - dataSource: - type: ObjectRef - objectRef: - kind: ClusterVirtualImage - name: v12n-e2e-alpine-uefi diff --git a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/vm.bootloader.patch.yaml b/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/vm.bootloader.patch.yaml deleted file mode 100644 index 3ef9892ace..0000000000 --- a/test/e2e/legacy/testdata/vm-migration-cancel/vm/overlays/migration-uefi/vm.bootloader.patch.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualMachine -metadata: - name: vm -spec: - bootloader: EFI diff --git a/test/e2e/legacy/vm_migration_cancel.go b/test/e2e/legacy/vm_migration_cancel.go deleted file mode 100644 index b321b6b7a2..0000000000 --- a/test/e2e/legacy/vm_migration_cancel.go +++ /dev/null @@ -1,198 +0,0 @@ -/* -Copyright 2025 Flant JSC - -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. -*/ - -package legacy - -import ( - "fmt" - "slices" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - virtv1 "kubevirt.io/api/core/v1" - - "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/test/e2e/internal/d8" - "github.com/deckhouse/virtualization/test/e2e/internal/framework" - kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" - "github.com/deckhouse/virtualization/test/e2e/internal/label" - "github.com/deckhouse/virtualization/test/e2e/internal/precheck" -) - -var _ = Describe("VirtualMachineCancelMigration", Ordered, label.Legacy(), Label(precheck.NoPrecheck), func() { - testCaseLabel := map[string]string{"testcase": "vm-migration-cancel"} - var ns string - - BeforeAll(func() { - // TODO: Remove Skip after fixing the issue. - Skip("This test case is not working everytime. Should be fixed.") - - kustomization := fmt.Sprintf("%s/%s", conf.TestData.VMMigrationCancel, "kustomization.yaml") - var err error - ns, err = kustomize.GetNamespace(kustomization) - Expect(err).NotTo(HaveOccurred(), "%w", err) - - CreateNamespace(ns) - }) - - BeforeEach(func() { - res := kubectl.Apply(kc.ApplyOptions{ - Filename: []string{conf.TestData.VMMigrationCancel}, - FilenameOption: kc.Kustomize, - }) - Expect(res.WasSuccess()).To(Equal(true), res.StdErr()) - }) - - AfterEach(func() { - if CurrentSpecReport().Failed() { - SaveTestCaseDump(testCaseLabel, CurrentSpecReport().LeafNodeText, ns) - } - if conf.IsCleanupNeeded { - DeleteTestCaseResources(ns, ResourcesToDelete{ - KustomizationDir: conf.TestData.VMMigrationCancel, - }) - } - }) - - It("Cancel migrate", func() { - By("Virtual machine agents should be ready") - WaitVMAgentReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - - vms := &v1alpha2.VirtualMachineList{} - err := GetObjects(kc.ResourceVM, vms, kc.GetOptions{Labels: testCaseLabel, Namespace: ns}) - Expect(err).NotTo(HaveOccurred()) - - vmNames := make([]string, len(vms.Items)) - for i, vm := range vms.Items { - vmNames[i] = vm.GetName() - } - - for _, name := range vmNames { - By(fmt.Sprintf("Exec SSHCommand for virtualmachine %s/%s", ns, name)) - res := framework.GetClients().D8Virtualization().SSHCommand(name, "sudo nohup stress-ng --vm 1 --vm-bytes 100% --timeout 300s &>/dev/null &", d8.SSHOptions{ - Namespace: ns, - Username: conf.TestData.SSHUser, - IdentityFile: conf.TestData.Sshkey, - Timeout: ShortTimeout, - }) - Expect(res.WasSuccess()).To(BeTrue(), res.StdErr()) - } - - By("Wait until stress-ng loads the memory more heavily") - time.Sleep(20 * time.Second) - - By("Starting migrations for virtual machines") - MigrateVirtualMachines(testCaseLabel, ns, vmNames...) - - someCompleted := false - - Eventually(func() error { - vmops := &v1alpha2.VirtualMachineOperationList{} - err := GetObjects(kc.ResourceVMOP, vmops, kc.GetOptions{Labels: testCaseLabel, Namespace: ns}) - if err != nil { - return err - } - - if len(vmops.Items) == 0 { - return nil - } - - kvvmis := &virtv1.VirtualMachineInstanceList{} - err = GetObjects(kc.ResourceKubevirtVMI, kvvmis, kc.GetOptions{Labels: testCaseLabel, Namespace: ns}) - if err != nil { - return err - } - - kvvmisByName := make(map[string]*virtv1.VirtualMachineInstance, len(kvvmis.Items)) - for _, kvvmi := range kvvmis.Items { - kvvmisByName[kvvmi.Name] = &kvvmi - } - - migrationReady := make(map[string]struct{}) - for _, vmop := range vmops.Items { - if kvvmi := kvvmisByName[vmop.Spec.VirtualMachine]; kvvmi != nil { - if kvvmi.Status.MigrationState != nil && !kvvmi.Status.MigrationState.StartTimestamp.IsZero() { - migrationReady[vmop.Name] = struct{}{} - } - } - } - - for _, vmop := range vmops.Items { - switch vmop.Status.Phase { - case v1alpha2.VMOPPhaseInProgress: - _, readyToDelete := migrationReady[vmop.Name] - - if readyToDelete && vmop.GetDeletionTimestamp().IsZero() { - res := kubectl.Delete(kc.DeleteOptions{ - Resource: kc.ResourceVMOP, - Name: vmop.GetName(), - Namespace: vmop.GetNamespace(), - }) - if !res.WasSuccess() { - return res.Error() - } - } - case v1alpha2.VMOPPhaseFailed, v1alpha2.VMOPPhaseCompleted: - someCompleted = true - return nil - } - } - return fmt.Errorf("retry because not all vmops canceled") - }).WithTimeout(MaxWaitTimeout).WithPolling(time.Second).ShouldNot(HaveOccurred()) - - Expect(someCompleted).Should(BeFalse()) - - By("Abort status should be exists in Kubevirt VMIs") - - validAbortStatuses := []virtv1.MigrationAbortStatus{ - virtv1.MigrationAbortInProgress, - virtv1.MigrationAbortSucceeded, - virtv1.MigrationAbortFailed, - } - - Eventually(func() error { - kvvmis := &virtv1.VirtualMachineInstanceList{} - err = GetObjects(kc.ResourceKubevirtVMI, kvvmis, kc.GetOptions{Labels: testCaseLabel, Namespace: ns}) - if err != nil { - return err - } - for _, kvvmi := range kvvmis.Items { - migrationState := kvvmi.Status.MigrationState - - if migrationState == nil { - return fmt.Errorf("retry because migration state is nil") - } - if !migrationState.AbortRequested { - return fmt.Errorf("retry because migration abort requested is false") - } - - if !slices.Contains(validAbortStatuses, migrationState.AbortStatus) { - return fmt.Errorf("retry because migration abort status is %s", migrationState.AbortStatus) - } - - if migrationState.EndTimestamp.IsZero() { - return fmt.Errorf("retry because migration is not finished yet") - } - } - return nil - }).WithTimeout(LongWaitDuration).WithPolling(time.Second).ShouldNot(HaveOccurred()) - }) -}) diff --git a/test/e2e/vm/migration_cancel.go b/test/e2e/vm/migration_cancel.go new file mode 100644 index 0000000000..be14be7bbe --- /dev/null +++ b/test/e2e/vm/migration_cancel.go @@ -0,0 +1,250 @@ +/* +Copyright 2026 Flant JSC + +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. +*/ + +package vm + +import ( + "context" + "errors" + "fmt" + "slices" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/utils/ptr" + virtv1 "kubevirt.io/api/core/v1" + crclient "sigs.k8s.io/controller-runtime/pkg/client" + + vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm" + vmopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmop" + "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/test/e2e/internal/framework" + "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/precheck" + "github.com/deckhouse/virtualization/test/e2e/internal/util" +) + +var validAbortStatuses = []virtv1.MigrationAbortStatus{ + virtv1.MigrationAbortInProgress, + virtv1.MigrationAbortSucceeded, + virtv1.MigrationAbortFailed, +} + +const stressCommand = "nohup stress-ng --cpu 4 --vm 4 --vm-bytes 90% --vm-keep --vm-populate --vm-method all --timeout 1h /dev/null 2>errlog &" + +var _ = Describe("VirtualMachineCancelMigration", Label(precheck.NoPrecheck), func() { + var ( + f *framework.Framework + + vmBIOS *v1alpha2.VirtualMachine + vmUEFI *v1alpha2.VirtualMachine + + migrationVMOPNames []string + ) + + BeforeEach(func() { + f = framework.NewFramework("vm-migration-cancel") + DeferCleanup(f.After) + f.Before() + }) + + It("Cancel migrate", func() { + By("Environment preparation") + vdRootBIOS := object.NewVDFromCVI("vd-root-bios", f.Namespace().Name, object.PrecreatedCVIUbuntu) + vdBlankBIOS := object.NewBlankVD("vd-blank-bios", f.Namespace().Name, nil, ptr.To(resource.MustParse("100Mi"))) + + vdRootUEFI := object.NewVDFromCVI("vd-root-uefi", f.Namespace().Name, object.PrecreatedCVIUbuntu) + vdBlankUEFI := object.NewBlankVD("vd-blank-uefi", f.Namespace().Name, nil, ptr.To(resource.MustParse("100Mi"))) + + vmBIOS = object.NewMinimalVM("", f.Namespace().Name, + vmbuilder.WithName("vm-bios"), + vmbuilder.WithBootloader(v1alpha2.BIOS), + vmbuilder.WithCPU(4, ptr.To("100%")), + vmbuilder.WithMemory(resource.MustParse("4Gi")), + vmbuilder.WithLiveMigrationPolicy(v1alpha2.PreferSafeMigrationPolicy), + vmbuilder.WithBlockDeviceRefs( + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.VirtualDiskKind, + Name: vdRootBIOS.Name, + }, + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.VirtualDiskKind, + Name: vdBlankBIOS.Name, + }, + ), + ) + vmUEFI = object.NewMinimalVM("", f.Namespace().Name, + vmbuilder.WithName("vm-uefi"), + vmbuilder.WithBootloader(v1alpha2.EFI), + vmbuilder.WithCPU(4, ptr.To("100%")), + vmbuilder.WithMemory(resource.MustParse("4Gi")), + vmbuilder.WithLiveMigrationPolicy(v1alpha2.PreferSafeMigrationPolicy), + vmbuilder.WithBlockDeviceRefs( + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.VirtualDiskKind, + Name: vdRootUEFI.Name, + }, + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.VirtualDiskKind, + Name: vdBlankUEFI.Name, + }, + ), + ) + + err := f.CreateWithDeferredDeletion(context.Background(), + vdRootBIOS, vdBlankBIOS, vmBIOS, + vdRootUEFI, vdBlankUEFI, vmUEFI, + ) + Expect(err).NotTo(HaveOccurred()) + + util.UntilVMAgentReady(context.Background(), crclient.ObjectKeyFromObject(vmBIOS), framework.LongTimeout) + util.UntilVMAgentReady(context.Background(), crclient.ObjectKeyFromObject(vmUEFI), framework.LongTimeout) + util.UntilSSHReady(f, vmBIOS, framework.MiddleTimeout) + util.UntilSSHReady(f, vmUEFI, framework.MiddleTimeout) + + By("Create memory pressure inside virtual machines") + vmList := []*v1alpha2.VirtualMachine{vmBIOS, vmUEFI} + for _, vm := range vmList { + By(fmt.Sprintf("Exec StressNG command for virtualmachine %s/%s", vm.Namespace, vm.Name)) + _, err := f.SSHCommand(vm.Name, vm.Namespace, stressCommand) + Expect(err).NotTo(HaveOccurred()) + } + + By("Wait until stress-ng loads the memory more heavily") + Consistently(func() error { + for _, vm := range vmList { + _, err := f.SSHCommand(vm.Name, vm.Namespace, "test ! -s errlog") + if err != nil { + return err + } + } + + return nil + }).WithTimeout(20 * time.Second).WithPolling(time.Second).ShouldNot(HaveOccurred()) + + By("Create migration VMOPs") + for _, vm := range []*v1alpha2.VirtualMachine{vmBIOS, vmUEFI} { + vmop := vmopbuilder.New( + vmopbuilder.WithGenerateName(fmt.Sprintf("%s-evict-", util.VmopE2ePrefix)), + vmopbuilder.WithNamespace(vm.Namespace), + vmopbuilder.WithType(v1alpha2.VMOPTypeEvict), + vmopbuilder.WithVirtualMachine(vm.Name), + ) + + err := f.CreateWithDeferredDeletion(context.Background(), vmop) + Expect(err).NotTo(HaveOccurred()) + migrationVMOPNames = append(migrationVMOPNames, vmop.Name) + } + + By("Collect migration VMOP names, delete them and verify they are gone") + var migrationVMOPs []*v1alpha2.VirtualMachineOperation + + Eventually(func() error { + migrationVMOPs = nil + vmopInProgressCount := 0 + + for _, vmopName := range migrationVMOPNames { + vmop := &v1alpha2.VirtualMachineOperation{} + err := f.GenericClient().Get(context.Background(), crclient.ObjectKey{Namespace: f.Namespace().Name, Name: vmopName}, vmop) + if err != nil { + return err + } + migrationVMOPs = append(migrationVMOPs, vmop) + + switch vmop.Status.Phase { + case v1alpha2.VMOPPhaseCompleted, v1alpha2.VMOPPhaseFailed: + Fail(fmt.Sprintf("vmop %s is in %s", vmop.Name, vmop.Status.Phase)) + case v1alpha2.VMOPPhaseInProgress: + vmopInProgressCount++ + default: + return fmt.Errorf("retry, waiting while vmop %s will be in InProgress phase", vmop.Name) + } + } + + if vmopInProgressCount != len(migrationVMOPNames) { + return errors.New("retry: not all VMOPs are in InProgress phase") + } + + return nil + }).WithTimeout(framework.ShortTimeout).WithPolling(time.Second).ShouldNot(HaveOccurred()) + + Eventually(func() error { + for _, vm := range []*v1alpha2.VirtualMachine{vmBIOS, vmUEFI} { + kvvmi, err := util.GetKVVMI(context.Background(), f, vm.Name, vm.Namespace) + if err != nil { + return err + } + + if kvvmi.Status.MigrationState == nil { + return fmt.Errorf("%s kvvmi migration state is empty", kvvmi.Name) + } + + } + + return nil + }).WithTimeout(framework.MiddleTimeout).WithPolling(time.Second).ShouldNot(HaveOccurred()) + + for _, vmop := range migrationVMOPs { + err := f.GenericClient().Delete(context.Background(), vmop) + Expect(err).NotTo(HaveOccurred()) + } + + Eventually(func() error { + for _, vmop := range migrationVMOPs { + err := f.GenericClient().Get(context.Background(), crclient.ObjectKey{Namespace: f.Namespace().Name, Name: vmop.Name}, vmop) + if err == nil { + return fmt.Errorf("retry because VMOP %s/%s still exists", vmop.Namespace, vmop.Name) + } + if !k8serrors.IsNotFound(err) { + return fmt.Errorf("unexpected error while checking VMOP %s/%s deletion: %w", vmop.Namespace, vmop.Name, err) + } + } + + return nil + }).WithTimeout(framework.ShortTimeout).WithPolling(time.Second).ShouldNot(HaveOccurred()) + + By("Abort status should be exists in Kubevirt VMIs") + Eventually(func() error { + vmList := []*v1alpha2.VirtualMachine{vmBIOS, vmUEFI} + for _, vm := range vmList { + kvvmi, err := util.GetKVVMI(context.Background(), f, vm.Name, vm.Namespace) + if err != nil { + return fmt.Errorf("retry because %w", err) + } + + migrationState := kvvmi.Status.MigrationState + if migrationState == nil { + return fmt.Errorf("retry because migration state is nil for VMI %s/%s", vm.Namespace, vm.Name) + } + if !migrationState.AbortRequested { + return fmt.Errorf("retry because migration abort requested is false for VMI %s/%s", vm.Namespace, vm.Name) + } + + if !slices.Contains(validAbortStatuses, migrationState.AbortStatus) { + return fmt.Errorf("retry because migration abort status is %s for VMI %s/%s", migrationState.AbortStatus, vm.Namespace, vm.Name) + } + + if migrationState.EndTimestamp.IsZero() { + return fmt.Errorf("retry because migration is not finished yet for VMI %s/%s", vm.Namespace, vm.Name) + } + } + return nil + }).WithTimeout(framework.LongTimeout).WithPolling(time.Second).ShouldNot(HaveOccurred()) + }) +})