diff --git a/test/e2e/default_config.yaml b/test/e2e/default_config.yaml index 246e4ee90d..cca1b5f139 100644 --- a/test/e2e/default_config.yaml +++ b/test/e2e/default_config.yaml @@ -24,7 +24,6 @@ testData: 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" sshUser: "cloud" diff --git a/test/e2e/internal/config/config.go b/test/e2e/internal/config/config.go index e51880836f..7cd7078b9c 100644 --- a/test/e2e/internal/config/config.go +++ b/test/e2e/internal/config/config.go @@ -92,7 +92,6 @@ type TestData struct { 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"` diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 62bd1e2b34..3038ec23be 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -308,7 +308,7 @@ func RebootVirtualMachineByVMOP(f *framework.Framework, vm *v1alpha2.VirtualMach func RebootVirtualMachineByPodDeletion(f *framework.Framework, vm *v1alpha2.VirtualMachine) { GinkgoHelper() - activePodName, err := getActivePodName(vm) + activePodName, err := GetActivePodName(vm) Expect(err).NotTo(HaveOccurred()) Expect(activePodName).NotTo(BeEmpty()) @@ -323,7 +323,34 @@ func RebootVirtualMachineByPodDeletion(f *framework.Framework, vm *v1alpha2.Virt Expect(err).NotTo(HaveOccurred()) } -func getActivePodName(vm *v1alpha2.VirtualMachine) (string, error) { +func GetVirtualMachineAndActivePod(ctx context.Context, f *framework.Framework, vm *v1alpha2.VirtualMachine) (*v1alpha2.VirtualMachine, *corev1.Pod, error) { + var currentVM v1alpha2.VirtualMachine + err := f.GenericClient().Get(ctx, client.ObjectKey{ + Namespace: vm.Namespace, + Name: vm.Name, + }, ¤tVM) + if err != nil { + return nil, nil, err + } + + activePodName, err := GetActivePodName(¤tVM) + if err != nil { + return nil, nil, err + } + + var activePod corev1.Pod + err = f.GenericClient().Get(ctx, client.ObjectKey{ + Namespace: vm.Namespace, + Name: activePodName, + }, &activePod) + if err != nil { + return nil, nil, err + } + + return ¤tVM, &activePod, nil +} + +func GetActivePodName(vm *v1alpha2.VirtualMachine) (string, error) { for _, pod := range vm.Status.VirtualMachinePods { if pod.Active { return pod.Name, nil diff --git a/test/e2e/legacy/testdata/vm-evacuation/kustomization.yaml b/test/e2e/legacy/testdata/vm-evacuation/kustomization.yaml deleted file mode 100644 index 2930a85896..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation diff --git a/test/e2e/legacy/testdata/vm-evacuation/ns.yaml b/test/e2e/legacy/testdata/vm-evacuation/ns.yaml deleted file mode 100644 index 5efde875b6..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/ns.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: default diff --git a/test/e2e/legacy/testdata/vm-evacuation/transformer.yaml b/test/e2e/legacy/testdata/vm-evacuation/transformer.yaml deleted file mode 100644 index ec70d37fcd..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/base/cfg/cloudinit.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/base/cfg/cloudinit.yaml deleted file mode 100644 index 2ec8f0c999..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/vm/base/cfg/cloudinit.yaml +++ /dev/null @@ -1,23 +0,0 @@ -#cloud-config -package_update: true -packages: - - qemu-guest-agent - - curl - - bash - - sudo - - iputils -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-evacuation/vm/base/kustomization.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/base/kustomization.yaml deleted file mode 100644 index 8ff06116f3..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/base/transformer.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/base/transformer.yaml deleted file mode 100644 index 1dc146a3af..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/base/vd-blank.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/base/vd-blank.yaml deleted file mode 100644 index fcc4b94ea8..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/base/vd-root.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/base/vd-root.yaml deleted file mode 100644 index 3c66723071..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/base/vm.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/base/vm.yaml deleted file mode 100644 index 3b96c4d4b4..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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: 100% - memory: - size: 256Mi - 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-evacuation/vm/kustomization.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/kustomization.yaml deleted file mode 100644 index 178f21cff4..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/overlays/migration-bios/kustomization.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/overlays/migration-bios/kustomization.yaml deleted file mode 100644 index bec3478a3a..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/overlays/migration-bios/vd.image.patch.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/overlays/migration-bios/vd.image.patch.yaml deleted file mode 100644 index 6d7b3e4063..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/overlays/migration-uefi/kustomization.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/overlays/migration-uefi/kustomization.yaml deleted file mode 100644 index 2303fd1580..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/overlays/migration-uefi/vd.image.patch.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/overlays/migration-uefi/vd.image.patch.yaml deleted file mode 100644 index 6c1a1ad8c9..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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-evacuation/vm/overlays/migration-uefi/vm.bootloader.patch.yaml b/test/e2e/legacy/testdata/vm-evacuation/vm/overlays/migration-uefi/vm.bootloader.patch.yaml deleted file mode 100644 index 3ef9892ace..0000000000 --- a/test/e2e/legacy/testdata/vm-evacuation/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_evacuation.go b/test/e2e/legacy/vm_evacuation.go deleted file mode 100644 index 1c91795303..0000000000 --- a/test/e2e/legacy/vm_evacuation.go +++ /dev/null @@ -1,143 +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 ( - "context" - "fmt" - "slices" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - policyv1 "k8s.io/api/policy/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - - "github.com/deckhouse/virtualization/api/core/v1alpha2" - "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("VirtualMachineEvacuation", Ordered, label.Legacy(), Label(precheck.NoPrecheck), func() { - testCaseLabel := map[string]string{"testcase": "vm-evacuation"} - - var ( - ns string - kubeClient kubernetes.Interface - ) - BeforeAll(func() { - kustomization := fmt.Sprintf("%s/%s", conf.TestData.VMEvacuation, "kustomization.yaml") - var err error - ns, err = kustomize.GetNamespace(kustomization) - Expect(err).NotTo(HaveOccurred(), "%w", err) - - CreateNamespace(ns) - - kubeClient = framework.GetClients().KubeClient() - }) - - BeforeEach(func() { - res := kubectl.Apply(kc.ApplyOptions{ - Filename: []string{conf.TestData.VMEvacuation}, - 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.VMEvacuation, - }) - } - }) - - evacuate := func(vms []string) { - pods := &corev1.PodList{} - err := GetObjects(kc.ResourcePod, pods, kc.GetOptions{Labels: testCaseLabel, Namespace: ns}) - Expect(err).NotTo(HaveOccurred()) - Expect(pods.Items).Should(HaveLen(len(vms))) - - for _, pod := range pods.Items { - err := kubeClient.CoreV1().Pods(pod.GetNamespace()).EvictV1(context.Background(), &policyv1.Eviction{ - ObjectMeta: metav1.ObjectMeta{ - Name: pod.GetName(), - Namespace: pod.GetNamespace(), - }, - }) - Expect(err).To(HaveOccurred()) - Expect(err).To(MatchError(ContainSubstring("Eviction triggered evacuation of VMI"))) - } - } - - It("Evacuation", 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() - } - - By("Evacuate virtual machines") - evacuate(vmNames) - - By("Waiting for all VMOPs to be finished") - Eventually(func() error { - vmops := &v1alpha2.VirtualMachineOperationList{} - err := GetObjects(kc.ResourceVMOP, vmops, kc.GetOptions{Namespace: ns}) - if err != nil { - return err - } - - finishedVMOPs := 0 - - for _, vmop := range vmops.Items { - if !slices.Contains(vmNames, vmop.Spec.VirtualMachine) { - continue - } - if _, exists := vmop.GetAnnotations()["virtualization.deckhouse.io/evacuation"]; !exists { - continue - } - if vmop.Status.Phase == v1alpha2.VMOPPhaseFailed || vmop.Status.Phase == v1alpha2.VMOPPhaseCompleted { - finishedVMOPs++ - } - - } - - if len(vmNames) != finishedVMOPs { - return fmt.Errorf("expected %d finished VMOPs, got %d", len(vmNames), finishedVMOPs) - } - return nil - }).WithTimeout(MaxWaitTimeout).WithPolling(time.Second).ShouldNot(HaveOccurred()) - }) -}) diff --git a/test/e2e/vm/evacuation.go b/test/e2e/vm/evacuation.go new file mode 100644 index 0000000000..011cd104e5 --- /dev/null +++ b/test/e2e/vm/evacuation.go @@ -0,0 +1,174 @@ +/* +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" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + crclient "sigs.k8s.io/controller-runtime/pkg/client" + + vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd" + vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm" + "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" +) + +const evacuationAnnotation = "virtualization.deckhouse.io/evacuation" + +var _ = Describe("VirtualMachineEvacuation", Label(precheck.NoPrecheck), func() { + var f *framework.Framework + + BeforeEach(func() { + f = framework.NewFramework("vm-evacuation") + DeferCleanup(f.After) + f.Before() + }) + + It("evacuates virtual machines after active pod eviction", func() { + ctx := context.Background() + + By("Environment preparation") + vmBIOS, vdRootBIOS, vdBlankBIOS := newEvacuationVM( + "vm-evacuation-bios", + f.Namespace().Name, + object.PrecreatedCVIAlpineBIOS, + v1alpha2.BIOS, + ) + vmUEFI, vdRootUEFI, vdBlankUEFI := newEvacuationVM( + "vm-evacuation-uefi", + f.Namespace().Name, + object.PrecreatedCVIAlpineUEFI, + v1alpha2.EFI, + ) + + err := f.CreateWithDeferredDeletion( + ctx, + 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) + + By("Evacuate virtual machines by active pod eviction") + evacuateVirtualMachines(ctx, f, vmBIOS, vmUEFI) + + By("Waiting for evacuation VMOPs to finish") + vmNames := map[string]struct{}{ + vmBIOS.Name: {}, + vmUEFI.Name: {}, + } + + Eventually(func(g Gomega) { + vmops := &v1alpha2.VirtualMachineOperationList{} + err := f.GenericClient().List(ctx, vmops, crclient.InNamespace(f.Namespace().Name)) + g.Expect(err).NotTo(HaveOccurred()) + + finishedVMOPs := make(map[string]struct{}, len(vmNames)) + for _, vmop := range vmops.Items { + if _, exists := vmNames[vmop.Spec.VirtualMachine]; !exists { + continue + } + if _, exists := vmop.Annotations[evacuationAnnotation]; !exists { + continue + } + if vmop.Status.Phase == v1alpha2.VMOPPhaseFailed || vmop.Status.Phase == v1alpha2.VMOPPhaseCompleted { + finishedVMOPs[vmop.Spec.VirtualMachine] = struct{}{} + } + } + + g.Expect(finishedVMOPs).To(HaveLen(len(vmNames))) + }).WithTimeout(framework.LongTimeout).WithPolling(time.Second).Should(Succeed()) + }) +}) + +func newEvacuationVM(name, namespace, cviName string, bootloader v1alpha2.BootloaderType) ( + *v1alpha2.VirtualMachine, + *v1alpha2.VirtualDisk, + *v1alpha2.VirtualDisk, +) { + vdRoot := object.NewVDFromCVI( + name+"-root", + namespace, + cviName, + vdbuilder.WithSize(ptr.To(resource.MustParse("350Mi"))), + ) + + vdBlank := object.NewBlankVD( + name+"-blank", + namespace, + nil, + ptr.To(resource.MustParse("100Mi")), + ) + + vm := object.NewMinimalVM( + "", + namespace, + vmbuilder.WithName(name), + vmbuilder.WithBootloader(bootloader), + vmbuilder.WithBlockDeviceRefs( + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.VirtualDiskKind, + Name: vdRoot.Name, + }, + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.VirtualDiskKind, + Name: vdBlank.Name, + }, + ), + ) + + return vm, vdRoot, vdBlank +} + +func evacuateVirtualMachines(ctx context.Context, f *framework.Framework, vms ...*v1alpha2.VirtualMachine) { + GinkgoHelper() + + var pods []corev1.Pod + Eventually(func(g Gomega) { + pods = []corev1.Pod{} + for _, vm := range vms { + _, pod, err := util.GetVirtualMachineAndActivePod(ctx, f, vm) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(pod).NotTo(BeNil()) + pods = append(pods, *pod) + } + }).WithTimeout(framework.MiddleTimeout).WithPolling(framework.PollingInterval).Should(Succeed()) + + for _, pod := range pods { + err := f.KubeClient().CoreV1().Pods(pod.GetNamespace()).EvictV1(ctx, &policyv1.Eviction{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod.GetName(), + Namespace: pod.GetNamespace(), + }, + }) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(ContainSubstring("Eviction triggered evacuation of VMI"))) + } +} diff --git a/test/e2e/vm/label_annotation.go b/test/e2e/vm/label_annotation.go index 0910a18740..627998c9bd 100644 --- a/test/e2e/vm/label_annotation.go +++ b/test/e2e/vm/label_annotation.go @@ -22,8 +22,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" crclient "sigs.k8s.io/controller-runtime/pkg/client" vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm" @@ -124,7 +122,7 @@ func expectLabelState(ctx context.Context, f *framework.Framework, vm *v1alpha2. GinkgoHelper() Eventually(func(g Gomega) { - currentVM, activePod, err := getVirtualMachineAndActivePod(ctx, f, vm) + currentVM, activePod, err := util.GetVirtualMachineAndActivePod(ctx, f, vm) g.Expect(err).NotTo(HaveOccurred()) if isPresent { @@ -142,7 +140,7 @@ func expectAnnotationState(ctx context.Context, f *framework.Framework, vm *v1al GinkgoHelper() Eventually(func(g Gomega) { - currentVM, activePod, err := getVirtualMachineAndActivePod(ctx, f, vm) + currentVM, activePod, err := util.GetVirtualMachineAndActivePod(ctx, f, vm) g.Expect(err).NotTo(HaveOccurred()) if isPresent { @@ -155,32 +153,3 @@ func expectAnnotationState(ctx context.Context, f *framework.Framework, vm *v1al g.Expect(activePod.Annotations).NotTo(HaveKey(metadataSpecialKey)) }).WithTimeout(framework.LongTimeout).WithPolling(framework.PollingInterval).Should(Succeed()) } - -func getVirtualMachineAndActivePod(ctx context.Context, f *framework.Framework, vm *v1alpha2.VirtualMachine) (*v1alpha2.VirtualMachine, *corev1.Pod, error) { - currentVM, err := f.VirtClient().VirtualMachines(vm.Namespace).Get(ctx, vm.Name, metav1.GetOptions{}) - if err != nil { - return nil, nil, err - } - - activePodName, err := getActiveVirtualMachinePodName(currentVM) - if err != nil { - return nil, nil, err - } - - activePod, err := f.KubeClient().CoreV1().Pods(vm.Namespace).Get(ctx, activePodName, metav1.GetOptions{}) - if err != nil { - return nil, nil, err - } - - return currentVM, activePod, nil -} - -func getActiveVirtualMachinePodName(vm *v1alpha2.VirtualMachine) (string, error) { - for _, pod := range vm.Status.VirtualMachinePods { - if pod.Active { - return pod.Name, nil - } - } - - return "", fmt.Errorf("active pod was not found for vm %s/%s", vm.Namespace, vm.Name) -}