From 1430e5df4f5b69ac40e09491410dd1a60c251697 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 24 Nov 2025 15:33:18 +0300 Subject: [PATCH 01/10] refactoring Signed-off-by: Valeriy Khorunzhin --- .../pkg/builder/vm/option.go | 9 + test/e2e/internal/config/config.go | 1 - test/e2e/internal/util/vm.go | 39 +++ .../vm-configuration/base/cfg/cloudinit.yaml | 11 - .../vm-configuration/base/kustomization.yaml | 15 - .../vm-configuration/base/transformer.yaml | 54 --- .../vm-configuration/base/vd-attach.yaml | 8 - .../vm-configuration/base/vd-blank.yaml | 8 - .../vm-configuration/base/vd-root.yaml | 13 - .../testdata/vm-configuration/base/vm.yaml | 24 -- .../vm-configuration/kustomization.yaml | 16 - .../legacy/testdata/vm-configuration/ns.yaml | 4 - .../overlays/automatic/kustomization.yaml | 24 -- .../overlays/manual/kustomization.yaml | 17 - .../vm-configuration/transformer.yaml | 52 --- .../vm-configuration/vi/kustomization.yaml | 4 - .../vm-configuration/vi/vi-alpine-http.yaml | 11 - test/e2e/legacy/util.go | 16 + test/e2e/legacy/vm_configuration.go | 310 ------------------ test/e2e/vm/configuration.go | 123 +++++++ 20 files changed, 187 insertions(+), 572 deletions(-) delete mode 100644 test/e2e/legacy/testdata/vm-configuration/base/cfg/cloudinit.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/base/kustomization.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/base/transformer.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/base/vd-attach.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/base/vd-blank.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/base/vd-root.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/base/vm.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/kustomization.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/ns.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/overlays/automatic/kustomization.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/overlays/manual/kustomization.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/transformer.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/vi/kustomization.yaml delete mode 100644 test/e2e/legacy/testdata/vm-configuration/vi/vi-alpine-http.yaml delete mode 100644 test/e2e/legacy/vm_configuration.go create mode 100644 test/e2e/vm/configuration.go diff --git a/images/virtualization-artifact/pkg/builder/vm/option.go b/images/virtualization-artifact/pkg/builder/vm/option.go index 755adf1ed8..5e35f43805 100644 --- a/images/virtualization-artifact/pkg/builder/vm/option.go +++ b/images/virtualization-artifact/pkg/builder/vm/option.go @@ -118,3 +118,12 @@ func WithProvisioningUserData(cloudInit string) Option { UserData: cloudInit, }) } + +func WithRestartApprovalMode(restartApprovalMode v1alpha2.RestartApprovalMode) Option { + return func(vm *v1alpha2.VirtualMachine) { + if vm.Spec.Disruptions == nil { + vm.Spec.Disruptions = &v1alpha2.Disruptions{} + } + vm.Spec.Disruptions.RestartApprovalMode = restartApprovalMode + } +} diff --git a/test/e2e/internal/config/config.go b/test/e2e/internal/config/config.go index 3b385e5645..52e872abe2 100644 --- a/test/e2e/internal/config/config.go +++ b/test/e2e/internal/config/config.go @@ -85,7 +85,6 @@ type TestData struct { DiskResizing string `yaml:"diskResizing"` SizingPolicy string `yaml:"sizingPolicy"` ImageHotplug string `yaml:"imageHotplug"` - VMConfiguration string `yaml:"vmConfiguration"` VMLabelAnnotation string `yaml:"vmLabelAnnotation"` VMMigration string `yaml:"vmMigration"` VMMigrationCancel string `yaml:"vmMigrationCancel"` diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 063727d8a3..63b9b3cc9e 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -101,3 +101,42 @@ func StopVirtualMachineFromOS(f *framework.Framework, vm *v1alpha2.VirtualMachin } return err } + +func RebootVirtualMachineBySSH(f *framework.Framework, vm *v1alpha2.VirtualMachine) { + GinkgoHelper() + + _, err := f.SSHCommand(vm.Name, vm.Namespace, "sudo reboot") + Expect(err).NotTo(HaveOccurred()) +} + +func RebootVirtualMachineByVMOP(f *framework.Framework, vm *v1alpha2.VirtualMachine) { + GinkgoHelper() + + vmop := vmopbuilder.New( + vmopbuilder.WithGenerateName("vmop-e2e-reboot-"), + vmopbuilder.WithNamespace(vm.Namespace), + vmopbuilder.WithType(v1alpha2.VMOPTypeRestart), + vmopbuilder.WithVirtualMachine(vm.Name), + ) + err := f.CreateWithDeferredDeletion(context.Background(), vmop) + Expect(err).NotTo(HaveOccurred()) +} + +func UntilVirtualMachineRebooted(key client.ObjectKey, previousRunningTime time.Time, timeout time.Duration) { + vm := &v1alpha2.VirtualMachine{} + Eventually(func() error { + err := framework.GetClients().GenericClient().Get(context.Background(), key, vm) + if err != nil { + return fmt.Errorf("failed to get virtual machine: %w", err) + } + + runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, vm.Status.Conditions) + + if runningCondition.LastTransitionTime.Time.After(previousRunningTime) { + return nil + } + + return fmt.Errorf("virtual machine %s is not rebooted", key.Name) + }, framework.LongTimeout, time.Second).Should(Succeed()) + UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, vm) +} diff --git a/test/e2e/legacy/testdata/vm-configuration/base/cfg/cloudinit.yaml b/test/e2e/legacy/testdata/vm-configuration/base/cfg/cloudinit.yaml deleted file mode 100644 index b2372f3279..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/base/cfg/cloudinit.yaml +++ /dev/null @@ -1,11 +0,0 @@ -#cloud-config -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 diff --git a/test/e2e/legacy/testdata/vm-configuration/base/kustomization.yaml b/test/e2e/legacy/testdata/vm-configuration/base/kustomization.yaml deleted file mode 100644 index 8ff06116f3..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/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-configuration/base/transformer.yaml b/test/e2e/legacy/testdata/vm-configuration/base/transformer.yaml deleted file mode 100644 index 1dc146a3af..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/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-configuration/base/vd-attach.yaml b/test/e2e/legacy/testdata/vm-configuration/base/vd-attach.yaml deleted file mode 100644 index a68911289e..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/base/vd-attach.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualDisk -metadata: - name: vd-attach -spec: - persistentVolumeClaim: - storageClassName: "{{ .STORAGE_CLASS_NAME }}" - size: 100Mi diff --git a/test/e2e/legacy/testdata/vm-configuration/base/vd-blank.yaml b/test/e2e/legacy/testdata/vm-configuration/base/vd-blank.yaml deleted file mode 100644 index fcc4b94ea8..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/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-configuration/base/vd-root.yaml b/test/e2e/legacy/testdata/vm-configuration/base/vd-root.yaml deleted file mode 100644 index 0f98ff3633..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/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: 512Mi - dataSource: - type: ObjectRef - objectRef: - kind: VirtualImage - name: vi-alpine-http diff --git a/test/e2e/legacy/testdata/vm-configuration/base/vm.yaml b/test/e2e/legacy/testdata/vm-configuration/base/vm.yaml deleted file mode 100644 index 6a38cd9ee8..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/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: 5% - 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-configuration/kustomization.yaml b/test/e2e/legacy/testdata/vm-configuration/kustomization.yaml deleted file mode 100644 index e0cfe1dffb..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/kustomization.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -namespace: testcases -namePrefix: pr-number-or-commit-hash- -resources: - - ns.yaml - - vi - - overlays/automatic - - overlays/manual -configurations: - - transformer.yaml -labels: - - includeSelectors: true - pairs: - id: pr-number-or-commit-hash - testcase: vm-configuration diff --git a/test/e2e/legacy/testdata/vm-configuration/ns.yaml b/test/e2e/legacy/testdata/vm-configuration/ns.yaml deleted file mode 100644 index 5efde875b6..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/ns.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: default diff --git a/test/e2e/legacy/testdata/vm-configuration/overlays/automatic/kustomization.yaml b/test/e2e/legacy/testdata/vm-configuration/overlays/automatic/kustomization.yaml deleted file mode 100644 index 32ee399c63..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/overlays/automatic/kustomization.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -nameSuffix: -automatic-conf -resources: - - ../../base -patches: - - patch: |- - - op: replace - path: /spec/runPolicy - value: AlwaysOn - target: - kind: VirtualMachine - name: vm - - patch: |- - - op: replace - path: /spec/disruptions/restartApprovalMode - value: Automatic - target: - kind: VirtualMachine - name: vm -labels: - - includeSelectors: true - pairs: - vm: automatic-conf diff --git a/test/e2e/legacy/testdata/vm-configuration/overlays/manual/kustomization.yaml b/test/e2e/legacy/testdata/vm-configuration/overlays/manual/kustomization.yaml deleted file mode 100644 index 01ad7addbe..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/overlays/manual/kustomization.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -nameSuffix: -manual-conf -resources: - - ../../base -patches: - - patch: |- - - op: replace - path: /spec/runPolicy - value: AlwaysOn - target: - kind: VirtualMachine - name: vm -labels: - - includeSelectors: true - pairs: - vm: manual-conf diff --git a/test/e2e/legacy/testdata/vm-configuration/transformer.yaml b/test/e2e/legacy/testdata/vm-configuration/transformer.yaml deleted file mode 100644 index ec70d37fcd..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/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-configuration/vi/kustomization.yaml b/test/e2e/legacy/testdata/vm-configuration/vi/kustomization.yaml deleted file mode 100644 index b807d8e2d2..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/vi/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - vi-alpine-http.yaml diff --git a/test/e2e/legacy/testdata/vm-configuration/vi/vi-alpine-http.yaml b/test/e2e/legacy/testdata/vm-configuration/vi/vi-alpine-http.yaml deleted file mode 100644 index abac06b48c..0000000000 --- a/test/e2e/legacy/testdata/vm-configuration/vi/vi-alpine-http.yaml +++ /dev/null @@ -1,11 +0,0 @@ ---- -apiVersion: virtualization.deckhouse.io/v1alpha2 -kind: VirtualImage -metadata: - name: vi-alpine-http -spec: - storage: ContainerRegistry - dataSource: - type: HTTP - http: - url: https://89d64382-20df-4581-8cc7-80df331f67fa.selstorage.ru/alpine/alpine-3-21-uefi-perf.qcow2 diff --git a/test/e2e/legacy/util.go b/test/e2e/legacy/util.go index dc0feaf0eb..dea3f0bac0 100644 --- a/test/e2e/legacy/util.go +++ b/test/e2e/legacy/util.go @@ -831,3 +831,19 @@ func CheckExternalConnection(host, httpCode, vmNamespace string, vmNames ...stri CheckResultSSHCommand(vmNamespace, vmName, cmd, httpCode) } } + +func ExecSSHCommand(vmNamespace, vmName, cmd string) { + GinkgoHelper() + + Eventually(func() error { + res := framework.GetClients().D8Virtualization().SSHCommand(vmName, cmd, d8.SSHOptions{ + Namespace: vmNamespace, + Username: conf.TestData.SSHUser, + IdentityFile: conf.TestData.Sshkey, + }) + if res.Error() != nil { + return fmt.Errorf("cmd: %s\nstderr: %s", res.GetCmd(), res.StdErr()) + } + return nil + }).WithTimeout(Timeout).WithPolling(Interval).ShouldNot(HaveOccurred()) +} diff --git a/test/e2e/legacy/vm_configuration.go b/test/e2e/legacy/vm_configuration.go deleted file mode 100644 index 6e3516149f..0000000000 --- a/test/e2e/legacy/vm_configuration.go +++ /dev/null @@ -1,310 +0,0 @@ -/* -Copyright 2024 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" - "strconv" - "strings" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/test/e2e/internal/config" - d8 "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" -) - -const ( - AutomaticMode = "automatic" - ManualMode = "manual" - StageBefore = "before" - StageAfter = "after" -) - -var _ = Describe("VirtualMachineConfiguration", Ordered, func() { - var ( - testCaseLabel = map[string]string{"testcase": "vm-configuration"} - automaticLabel = map[string]string{"vm": "automatic-conf"} - manualLabel = map[string]string{"vm": "manual-conf"} - ns string - ) - - BeforeAll(func() { - kustomization := fmt.Sprintf("%s/%s", conf.TestData.VMConfiguration, "kustomization.yaml") - var err error - ns, err = kustomize.GetNamespace(kustomization) - Expect(err).NotTo(HaveOccurred(), "%w", err) - - CreateNamespace(ns) - }) - - AfterEach(func() { - if CurrentSpecReport().Failed() { - SaveTestCaseDump(testCaseLabel, CurrentSpecReport().LeafNodeText, ns) - } - }) - - Context("When resources are applied", func() { - It("result should be succeeded", func() { - res := kubectl.Apply(kc.ApplyOptions{ - Filename: []string{conf.TestData.VMConfiguration}, - FilenameOption: kc.Kustomize, - }) - Expect(res.WasSuccess()).To(Equal(true), res.StdErr()) - }) - }) - - Context("When virtual images are applied", func() { - It("checks VIs phases", func() { - By(fmt.Sprintf("VIs should be in %s phases", PhaseReady)) - WaitPhaseByLabel(kc.ResourceVI, PhaseReady, kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - }) - - Context("When virtual disks are applied", func() { - It(fmt.Sprintf("should be in %s phase", PhaseReady), func() { - WaitPhaseByLabel(kc.ResourceVD, PhaseReady, kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - }) - - Context("When virtual machines are applied", func() { - It("should be ready", func() { - WaitVMAgentReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - }) - - Describe(fmt.Sprintf("Manual restart approval mode %d", GinkgoParallelProcess()), func() { - var oldCPUCores int - var newCPUCores int - - Context("When virtual machine agents are ready", func() { - It("changes the number of processor cores", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: manualLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) - - vmNames := strings.Split(res.StdOut(), " ") - Expect(vmNames).NotTo(BeEmpty()) - - vmResource := v1alpha2.VirtualMachine{} - err := GetObject(kc.ResourceVM, vmNames[0], &vmResource, kc.GetOptions{Namespace: ns}) - Expect(err).NotTo(HaveOccurred()) - - oldCPUCores = vmResource.Spec.CPU.Cores - newCPUCores = 1 + (vmResource.Spec.CPU.Cores & 1) - - CheckCPUCoresNumber(ManualMode, StageBefore, oldCPUCores, ns, vmNames...) - ChangeCPUCoresNumber(newCPUCores, ns, vmNames...) - }) - }) - - Context("When virtual machine is patched", func() { - It("checks the number of processor cores in specification", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: manualLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.WasSuccess()).To(Equal(true), res.StdErr()) - - vmNames := strings.Split(res.StdOut(), " ") - CheckCPUCoresNumber(ManualMode, StageAfter, newCPUCores, ns, vmNames...) - }) - }) - - Context("When virtual machine is restarted", func() { - It("should be ready", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: manualLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.WasSuccess()).To(Equal(true), res.StdErr()) - - vmNames := strings.Split(res.StdOut(), " ") - for _, vmName := range vmNames { - cmd := "sudo nohup reboot -f > /dev/null 2>&1 &" - ExecSSHCommand(ns, vmName, cmd) - } - WaitVMAgentReady(kc.WaitOptions{ - Labels: manualLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - }) - - Context("When virtual machine agents are ready", func() { - It("checks that the number of processor cores was changed", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: manualLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.WasSuccess()).To(Equal(true), res.StdErr()) - - vmNames := strings.Split(res.StdOut(), " ") - CheckCPUCoresNumberFromVirtualMachine(strconv.FormatInt(int64(newCPUCores), 10), ns, vmNames...) - }) - }) - }) - - Describe(fmt.Sprintf("Automatic restart approval mode %d", GinkgoParallelProcess()), func() { - var oldCPUCores int - var newCPUCores int - - Context(fmt.Sprintf("When virtual machine is in %s phase", PhaseRunning), func() { - It("changes the number of processor cores", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: automaticLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.WasSuccess()).To(Equal(true), res.StdErr()) - - vmNames := strings.Split(res.StdOut(), " ") - Expect(vmNames).NotTo(BeEmpty()) - - vmResource := v1alpha2.VirtualMachine{} - err := GetObject(kc.ResourceVM, vmNames[0], &vmResource, kc.GetOptions{Namespace: ns}) - Expect(err).NotTo(HaveOccurred(), "%v", err) - - oldCPUCores = vmResource.Spec.CPU.Cores - newCPUCores = 1 + (vmResource.Spec.CPU.Cores & 1) - - CheckCPUCoresNumber(AutomaticMode, StageBefore, oldCPUCores, ns, vmNames...) - ChangeCPUCoresNumber(newCPUCores, ns, vmNames...) - }) - }) - - Context("When virtual machine is patched", func() { - It("checks the number of processor cores in specification", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: automaticLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.WasSuccess()).To(Equal(true), res.StdErr()) - - vmNames := strings.Split(res.StdOut(), " ") - CheckCPUCoresNumber(AutomaticMode, StageAfter, newCPUCores, ns, vmNames...) - }) - }) - - Context("When virtual machine is restarted", func() { - It("should be ready", func() { - WaitVMAgentReady(kc.WaitOptions{ - Labels: automaticLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - }) - - Context("When virtual machine agents are ready", func() { - It("checks that the number of processor cores was changed", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: automaticLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) - - vmNames := strings.Split(res.StdOut(), " ") - CheckCPUCoresNumberFromVirtualMachine(strconv.FormatInt(int64(newCPUCores), 10), ns, vmNames...) - }) - }) - }) - - Context("When test is completed", func() { - It("deletes test case resources", func() { - var resourcesToDelete ResourcesToDelete - - if config.IsCleanUpNeeded() { - resourcesToDelete.KustomizationDir = conf.TestData.VMConfiguration - } - - DeleteTestCaseResources(ns, resourcesToDelete) - }) - }) -}) - -func ExecSSHCommand(vmNamespace, vmName, cmd string) { - GinkgoHelper() - - Eventually(func() error { - res := framework.GetClients().D8Virtualization().SSHCommand(vmName, cmd, d8.SSHOptions{ - Namespace: vmNamespace, - Username: conf.TestData.SSHUser, - IdentityFile: conf.TestData.Sshkey, - }) - if res.Error() != nil { - return fmt.Errorf("cmd: %s\nstderr: %s", res.GetCmd(), res.StdErr()) - } - return nil - }).WithTimeout(Timeout).WithPolling(Interval).ShouldNot(HaveOccurred()) -} - -func ChangeCPUCoresNumber(cpuNumber int, vmNamespace string, vmNames ...string) { - vms := strings.Join(vmNames, " ") - cmd := fmt.Sprintf("patch %s --namespace %s %s --type merge --patch '{\"spec\":{\"cpu\":{\"cores\":%d}}}'", kc.ResourceVM, vmNamespace, vms, cpuNumber) - By("Patching virtual machine specification") - patchRes := kubectl.RawCommand(cmd, ShortWaitDuration) - Expect(patchRes.Error()).NotTo(HaveOccurred(), patchRes.StdErr()) -} - -func CheckCPUCoresNumber(approvalMode, stage string, requiredValue int, vmNamespace string, vmNames ...string) { - for _, vmName := range vmNames { - By(fmt.Sprintf("Checking the number of processor cores %s changing", stage)) - vmResource := v1alpha2.VirtualMachine{} - err := GetObject(kc.ResourceVM, vmName, &vmResource, kc.GetOptions{Namespace: vmNamespace}) - Expect(err).NotTo(HaveOccurred(), "%v", err) - Expect(vmResource.Spec.CPU.Cores).To(Equal(requiredValue)) - switch { - case approvalMode == ManualMode && stage == StageAfter: - Expect(vmResource.Status.RestartAwaitingChanges).ShouldNot(BeNil()) - case approvalMode == AutomaticMode && stage == StageAfter: - Expect(vmResource.Status.RestartAwaitingChanges).ShouldNot(BeNil()) - } - } -} - -func CheckCPUCoresNumberFromVirtualMachine(requiredValue, vmNamespace string, vmNames ...string) { - By("Checking the number of processor cores after changing from virtual machine") - for _, vmName := range vmNames { - cmd := "nproc --all" - CheckResultSSHCommand(vmNamespace, vmName, cmd, requiredValue) - } -} diff --git a/test/e2e/vm/configuration.go b/test/e2e/vm/configuration.go new file mode 100644 index 0000000000..c50d7eaea2 --- /dev/null +++ b/test/e2e/vm/configuration.go @@ -0,0 +1,123 @@ +/* +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 vm + +import ( + "context" + "fmt" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/resource" + "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-controller/pkg/controller/conditions" + "github.com/deckhouse/virtualization/api/core/v1alpha2" + "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" + "github.com/deckhouse/virtualization/test/e2e/internal/framework" + "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/util" +) + +const ( + initialCPUCores = 1 + initialMemorySize = "256Mi" + changedCPUCores = 2 + changedMemorySize = "512Mi" +) + +var _ = Describe("VirtualMachineConfiguration", func() { + DescribeTable("the configuration should be applied", func(restartApprovalMode v1alpha2.RestartApprovalMode) { + f := framework.NewFramework(fmt.Sprintf("vm-configuration-%s", strings.ToLower(string(restartApprovalMode)))) + DeferCleanup(f.After) + f.Before() + + By("Environment preparation") + vm, vd := generateConfigurationResources(f.Namespace().Name, restartApprovalMode) + f.CreateWithDeferredDeletion(context.Background(), vm, vd) + + By("Waiting for VM agent to be ready") + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.LongTimeout) + + By("Checking initial configuration") + err := f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + Expect(err).NotTo(HaveOccurred()) + Expect(vm.Status.Resources.CPU.Cores).To(Equal(initialCPUCores)) + Expect(vm.Status.Resources.Memory.Size).To(Equal(resource.MustParse(initialMemorySize))) + + By("Applying changes") + err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + Expect(err).NotTo(HaveOccurred()) + runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, vm.Status.Conditions) + previousRunningTime := runningCondition.LastTransitionTime.Time + + vm.Spec.CPU.Cores = changedCPUCores + vm.Spec.Memory.Size = resource.MustParse(changedMemorySize) + err = f.Clients.GenericClient().Update(context.Background(), vm) + Expect(err).NotTo(HaveOccurred()) + + if restartApprovalMode == v1alpha2.Manual { + util.RebootVirtualMachineBySSH(f, vm) + } + + By("Waiting for VM to be rebooted") + util.UntilVirtualMachineRebooted(crclient.ObjectKeyFromObject(vm), previousRunningTime, framework.LongTimeout) + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.ShortTimeout) + + By("Checking changed configuration") + err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + Expect(err).NotTo(HaveOccurred()) + Expect(vm.Status.Resources.CPU.Cores).To(Equal(changedCPUCores)) + Expect(vm.Status.Resources.Memory.Size).To(Equal(resource.MustParse(changedMemorySize))) + }, + Entry("when changes are applied manually", v1alpha2.Manual), + Entry("when changes are applied automatically", v1alpha2.Automatic), + ) +}) + +func generateConfigurationResources(namespace string, restartApprovalMode v1alpha2.RestartApprovalMode) (vm *v1alpha2.VirtualMachine, vd *v1alpha2.VirtualDisk) { + vd = vdbuilder.New( + vdbuilder.WithName("vd"), + vdbuilder.WithNamespace(namespace), + vdbuilder.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{ + URL: object.ImageURLUbuntu, + }), + ) + + vm = vmbuilder.New( + vmbuilder.WithName("vm"), + vmbuilder.WithNamespace(namespace), + vmbuilder.WithCPU(1, ptr.To("5%")), + vmbuilder.WithMemory(resource.MustParse(initialMemorySize)), + vmbuilder.WithLiveMigrationPolicy(v1alpha2.AlwaysSafeMigrationPolicy), + vmbuilder.WithVirtualMachineClass(object.DefaultVMClass), + vmbuilder.WithProvisioningUserData(object.DefaultCloudInit), + vmbuilder.WithBlockDeviceRefs( + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.DiskDevice, + Name: vd.Name, + }, + ), + vmbuilder.WithRestartApprovalMode(restartApprovalMode), + ) + + return +} From a47543cce23d587b3932e83cdeaf392e682a6064 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 24 Nov 2025 16:46:50 +0300 Subject: [PATCH 02/10] check err Signed-off-by: Valeriy Khorunzhin --- test/e2e/vm/configuration.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/e2e/vm/configuration.go b/test/e2e/vm/configuration.go index c50d7eaea2..13631b0139 100644 --- a/test/e2e/vm/configuration.go +++ b/test/e2e/vm/configuration.go @@ -52,13 +52,14 @@ var _ = Describe("VirtualMachineConfiguration", func() { By("Environment preparation") vm, vd := generateConfigurationResources(f.Namespace().Name, restartApprovalMode) - f.CreateWithDeferredDeletion(context.Background(), vm, vd) + err := f.CreateWithDeferredDeletion(context.Background(), vm, vd) + Expect(err).NotTo(HaveOccurred()) By("Waiting for VM agent to be ready") util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.LongTimeout) By("Checking initial configuration") - err := f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) Expect(err).NotTo(HaveOccurred()) Expect(vm.Status.Resources.CPU.Cores).To(Equal(initialCPUCores)) Expect(vm.Status.Resources.Memory.Size).To(Equal(resource.MustParse(initialMemorySize))) From f7f6a0181cac89ed3d831fbf2672b0d7c64d24a2 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 24 Nov 2025 17:02:11 +0300 Subject: [PATCH 03/10] fix until rebooted Signed-off-by: Valeriy Khorunzhin --- test/e2e/internal/util/vm.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 63b9b3cc9e..21eb52204f 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -137,6 +137,6 @@ func UntilVirtualMachineRebooted(key client.ObjectKey, previousRunningTime time. } return fmt.Errorf("virtual machine %s is not rebooted", key.Name) - }, framework.LongTimeout, time.Second).Should(Succeed()) - UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, vm) + }, timeout/2, time.Second).Should(Succeed()) + UntilObjectPhase(string(v1alpha2.MachineRunning), timeout/2, vm) } From d4afc3006bbbd16b41e3a6f2d0debbebc63c77b4 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Mon, 24 Nov 2025 17:04:39 +0300 Subject: [PATCH 04/10] refactoring Signed-off-by: Valeriy Khorunzhin --- test/e2e/internal/util/vm.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 21eb52204f..47013c196f 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -123,8 +123,8 @@ func RebootVirtualMachineByVMOP(f *framework.Framework, vm *v1alpha2.VirtualMach } func UntilVirtualMachineRebooted(key client.ObjectKey, previousRunningTime time.Time, timeout time.Duration) { - vm := &v1alpha2.VirtualMachine{} Eventually(func() error { + vm := &v1alpha2.VirtualMachine{} err := framework.GetClients().GenericClient().Get(context.Background(), key, vm) if err != nil { return fmt.Errorf("failed to get virtual machine: %w", err) @@ -132,11 +132,10 @@ func UntilVirtualMachineRebooted(key client.ObjectKey, previousRunningTime time. runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, vm.Status.Conditions) - if runningCondition.LastTransitionTime.Time.After(previousRunningTime) { + if runningCondition.LastTransitionTime.Time.After(previousRunningTime) && vm.Status.Phase == v1alpha2.MachineRunning { return nil } return fmt.Errorf("virtual machine %s is not rebooted", key.Name) - }, timeout/2, time.Second).Should(Succeed()) - UntilObjectPhase(string(v1alpha2.MachineRunning), timeout/2, vm) + }, timeout, time.Second).Should(Succeed()) } From 089ec992e44c5d12f58810f52df33bcc80a25918 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Tue, 25 Nov 2025 18:37:19 +0300 Subject: [PATCH 05/10] add disk and core fraction check Signed-off-by: Valeriy Khorunzhin --- test/e2e/vm/configuration.go | 109 +++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/test/e2e/vm/configuration.go b/test/e2e/vm/configuration.go index 13631b0139..7301575aa6 100644 --- a/test/e2e/vm/configuration.go +++ b/test/e2e/vm/configuration.go @@ -20,10 +20,12 @@ import ( "context" "fmt" "strings" + "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "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" @@ -38,75 +40,115 @@ import ( ) const ( - initialCPUCores = 1 - initialMemorySize = "256Mi" - changedCPUCores = 2 - changedMemorySize = "512Mi" + initialCPUCores = 1 + initialMemorySize = "256Mi" + initialCoreFraction = "5%" + changedCPUCores = 2 + changedMemorySize = "512Mi" + changedCoreFraction = "10%" ) var _ = Describe("VirtualMachineConfiguration", func() { DescribeTable("the configuration should be applied", func(restartApprovalMode v1alpha2.RestartApprovalMode) { f := framework.NewFramework(fmt.Sprintf("vm-configuration-%s", strings.ToLower(string(restartApprovalMode)))) + t := NewConfigurationTest(f) + DeferCleanup(f.After) f.Before() By("Environment preparation") - vm, vd := generateConfigurationResources(f.Namespace().Name, restartApprovalMode) - err := f.CreateWithDeferredDeletion(context.Background(), vm, vd) + t.GenerateConfigurationResources(restartApprovalMode) + err := f.CreateWithDeferredDeletion(context.Background(), t.VM, t.VDRoot, t.VDBlank) Expect(err).NotTo(HaveOccurred()) By("Waiting for VM agent to be ready") - util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.LongTimeout) + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(t.VM), framework.LongTimeout) By("Checking initial configuration") - err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) Expect(err).NotTo(HaveOccurred()) - Expect(vm.Status.Resources.CPU.Cores).To(Equal(initialCPUCores)) - Expect(vm.Status.Resources.Memory.Size).To(Equal(resource.MustParse(initialMemorySize))) + Expect(t.VM.Status.Resources.CPU.Cores).To(Equal(initialCPUCores)) + Expect(t.VM.Status.Resources.Memory.Size).To(Equal(resource.MustParse(initialMemorySize))) + Expect(t.VM.Status.Resources.CPU.CoreFraction).To(Equal(initialCoreFraction)) + Expect(t.IsVDInVMStatus(t.VDBlank, t.VM)).To(BeFalse()) By("Applying changes") - err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) Expect(err).NotTo(HaveOccurred()) - runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, vm.Status.Conditions) + runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, t.VM.Status.Conditions) previousRunningTime := runningCondition.LastTransitionTime.Time - vm.Spec.CPU.Cores = changedCPUCores - vm.Spec.Memory.Size = resource.MustParse(changedMemorySize) - err = f.Clients.GenericClient().Update(context.Background(), vm) + t.VM.Spec.CPU.Cores = changedCPUCores + t.VM.Spec.Memory.Size = resource.MustParse(changedMemorySize) + t.VM.Spec.CPU.CoreFraction = changedCoreFraction + t.VM.Spec.BlockDeviceRefs = append(t.VM.Spec.BlockDeviceRefs, v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.DiskDevice, + Name: t.VDBlank.Name, + }) + err = f.Clients.GenericClient().Update(context.Background(), t.VM) Expect(err).NotTo(HaveOccurred()) if restartApprovalMode == v1alpha2.Manual { - util.RebootVirtualMachineBySSH(f, vm) + time.Sleep(time.Second) // Avoid race condition with need restart condition calculation + err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) + Expect(err).NotTo(HaveOccurred()) + needRestart, _ := conditions.GetCondition(vmcondition.TypeAwaitingRestartToApplyConfiguration, t.VM.Status.Conditions) + Expect(needRestart.Status).To(Equal(metav1.ConditionTrue)) + Expect(t.VM.Status.RestartAwaitingChanges).NotTo(BeNil()) + + util.RebootVirtualMachineBySSH(f, t.VM) } By("Waiting for VM to be rebooted") - util.UntilVirtualMachineRebooted(crclient.ObjectKeyFromObject(vm), previousRunningTime, framework.LongTimeout) - util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.ShortTimeout) + util.UntilVirtualMachineRebooted(crclient.ObjectKeyFromObject(t.VM), previousRunningTime, framework.LongTimeout) + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(t.VM), framework.ShortTimeout) By("Checking changed configuration") - err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) Expect(err).NotTo(HaveOccurred()) - Expect(vm.Status.Resources.CPU.Cores).To(Equal(changedCPUCores)) - Expect(vm.Status.Resources.Memory.Size).To(Equal(resource.MustParse(changedMemorySize))) + Expect(t.VM.Status.Resources.CPU.Cores).To(Equal(changedCPUCores)) + Expect(t.VM.Status.Resources.Memory.Size).To(Equal(resource.MustParse(changedMemorySize))) + Expect(t.VM.Status.Resources.CPU.CoreFraction).To(Equal(changedCoreFraction)) + Expect(t.IsVDInVMStatus(t.VDBlank, t.VM)).To(BeTrue()) }, Entry("when changes are applied manually", v1alpha2.Manual), Entry("when changes are applied automatically", v1alpha2.Automatic), ) }) -func generateConfigurationResources(namespace string, restartApprovalMode v1alpha2.RestartApprovalMode) (vm *v1alpha2.VirtualMachine, vd *v1alpha2.VirtualDisk) { - vd = vdbuilder.New( - vdbuilder.WithName("vd"), - vdbuilder.WithNamespace(namespace), +type configurationTest struct { + Framework *framework.Framework + + VM *v1alpha2.VirtualMachine + VDRoot *v1alpha2.VirtualDisk + VDBlank *v1alpha2.VirtualDisk +} + +func NewConfigurationTest(f *framework.Framework) *configurationTest { + return &configurationTest{ + Framework: f, + } +} + +func (c *configurationTest) GenerateConfigurationResources(restartApprovalMode v1alpha2.RestartApprovalMode) { + c.VDRoot = vdbuilder.New( + vdbuilder.WithName("vd-root"), + vdbuilder.WithNamespace(c.Framework.Namespace().Name), vdbuilder.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{ URL: object.ImageURLUbuntu, }), ) - vm = vmbuilder.New( + c.VDBlank = vdbuilder.New( + vdbuilder.WithName("vd-blank"), + vdbuilder.WithNamespace(c.Framework.Namespace().Name), + vdbuilder.WithSize(ptr.To(resource.MustParse("100Mi"))), + ) + + c.VM = vmbuilder.New( vmbuilder.WithName("vm"), - vmbuilder.WithNamespace(namespace), - vmbuilder.WithCPU(1, ptr.To("5%")), + vmbuilder.WithNamespace(c.Framework.Namespace().Name), + vmbuilder.WithCPU(1, ptr.To(initialCoreFraction)), vmbuilder.WithMemory(resource.MustParse(initialMemorySize)), vmbuilder.WithLiveMigrationPolicy(v1alpha2.AlwaysSafeMigrationPolicy), vmbuilder.WithVirtualMachineClass(object.DefaultVMClass), @@ -114,11 +156,18 @@ func generateConfigurationResources(namespace string, restartApprovalMode v1alph vmbuilder.WithBlockDeviceRefs( v1alpha2.BlockDeviceSpecRef{ Kind: v1alpha2.DiskDevice, - Name: vd.Name, + Name: c.VDRoot.Name, }, ), vmbuilder.WithRestartApprovalMode(restartApprovalMode), ) +} - return +func (c *configurationTest) IsVDInVMStatus(vd *v1alpha2.VirtualDisk, vm *v1alpha2.VirtualMachine) bool { + for _, bda := range vm.Status.BlockDeviceRefs { + if bda.Kind == v1alpha2.DiskDevice && bda.Name == vd.Name { + return true + } + } + return false } From 29298b4fccf4403abcb210b90d0a22b15578ff08 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Tue, 25 Nov 2025 23:12:49 +0300 Subject: [PATCH 06/10] resolve Signed-off-by: Valeriy Khorunzhin --- test/e2e/vm/configuration.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/test/e2e/vm/configuration.go b/test/e2e/vm/configuration.go index 7301575aa6..07296eca10 100644 --- a/test/e2e/vm/configuration.go +++ b/test/e2e/vm/configuration.go @@ -70,7 +70,7 @@ var _ = Describe("VirtualMachineConfiguration", func() { Expect(t.VM.Status.Resources.CPU.Cores).To(Equal(initialCPUCores)) Expect(t.VM.Status.Resources.Memory.Size).To(Equal(resource.MustParse(initialMemorySize))) Expect(t.VM.Status.Resources.CPU.CoreFraction).To(Equal(initialCoreFraction)) - Expect(t.IsVDInVMStatus(t.VDBlank, t.VM)).To(BeFalse()) + Expect(t.IsVDAttached(t.VDBlank, t.VM)).To(BeFalse()) By("Applying changes") err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) @@ -89,12 +89,14 @@ var _ = Describe("VirtualMachineConfiguration", func() { Expect(err).NotTo(HaveOccurred()) if restartApprovalMode == v1alpha2.Manual { - time.Sleep(time.Second) // Avoid race condition with need restart condition calculation - err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) - Expect(err).NotTo(HaveOccurred()) - needRestart, _ := conditions.GetCondition(vmcondition.TypeAwaitingRestartToApplyConfiguration, t.VM.Status.Conditions) - Expect(needRestart.Status).To(Equal(metav1.ConditionTrue)) - Expect(t.VM.Status.RestartAwaitingChanges).NotTo(BeNil()) + // Avoid race condition with need restart condition calculation + Eventually(func(g Gomega) { + err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) + g.Expect(err).NotTo(HaveOccurred()) + needRestart, _ := conditions.GetCondition(vmcondition.TypeAwaitingRestartToApplyConfiguration, t.VM.Status.Conditions) + g.Expect(needRestart.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(t.VM.Status.RestartAwaitingChanges).NotTo(BeNil()) + }).WithTimeout(3 * time.Second).WithPolling(time.Second).Should(Succeed()) util.RebootVirtualMachineBySSH(f, t.VM) } @@ -109,7 +111,7 @@ var _ = Describe("VirtualMachineConfiguration", func() { Expect(t.VM.Status.Resources.CPU.Cores).To(Equal(changedCPUCores)) Expect(t.VM.Status.Resources.Memory.Size).To(Equal(resource.MustParse(changedMemorySize))) Expect(t.VM.Status.Resources.CPU.CoreFraction).To(Equal(changedCoreFraction)) - Expect(t.IsVDInVMStatus(t.VDBlank, t.VM)).To(BeTrue()) + Expect(t.IsVDAttached(t.VDBlank, t.VM)).To(BeTrue()) }, Entry("when changes are applied manually", v1alpha2.Manual), Entry("when changes are applied automatically", v1alpha2.Automatic), @@ -163,9 +165,9 @@ func (c *configurationTest) GenerateConfigurationResources(restartApprovalMode v ) } -func (c *configurationTest) IsVDInVMStatus(vd *v1alpha2.VirtualDisk, vm *v1alpha2.VirtualMachine) bool { - for _, bda := range vm.Status.BlockDeviceRefs { - if bda.Kind == v1alpha2.DiskDevice && bda.Name == vd.Name { +func (c *configurationTest) IsVDAttached(vd *v1alpha2.VirtualDisk, vm *v1alpha2.VirtualMachine) bool { + for _, bd := range vm.Status.BlockDeviceRefs { + if bd.Kind == v1alpha2.DiskDevice && bd.Name == vd.Name && bd.Attached { return true } } From e31ea5284cd59ec979db65948c60faadb36f7d1d Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 26 Nov 2025 17:05:05 +0300 Subject: [PATCH 07/10] wrap check Signed-off-by: Valeriy Khorunzhin --- test/e2e/vm/configuration.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/e2e/vm/configuration.go b/test/e2e/vm/configuration.go index 07296eca10..285197720d 100644 --- a/test/e2e/vm/configuration.go +++ b/test/e2e/vm/configuration.go @@ -88,16 +88,9 @@ var _ = Describe("VirtualMachineConfiguration", func() { err = f.Clients.GenericClient().Update(context.Background(), t.VM) Expect(err).NotTo(HaveOccurred()) - if restartApprovalMode == v1alpha2.Manual { - // Avoid race condition with need restart condition calculation - Eventually(func(g Gomega) { - err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) - g.Expect(err).NotTo(HaveOccurred()) - needRestart, _ := conditions.GetCondition(vmcondition.TypeAwaitingRestartToApplyConfiguration, t.VM.Status.Conditions) - g.Expect(needRestart.Status).To(Equal(metav1.ConditionTrue)) - g.Expect(t.VM.Status.RestartAwaitingChanges).NotTo(BeNil()) - }).WithTimeout(3 * time.Second).WithPolling(time.Second).Should(Succeed()) + t.CheckRestartAwaitingChanges(t.VM) + if t.VM.Spec.Disruptions.RestartApprovalMode == v1alpha2.Manual { util.RebootVirtualMachineBySSH(f, t.VM) } @@ -173,3 +166,18 @@ func (c *configurationTest) IsVDAttached(vd *v1alpha2.VirtualDisk, vm *v1alpha2. } return false } + +func (t *configurationTest) CheckRestartAwaitingChanges(vm *v1alpha2.VirtualMachine) { + if vm.Spec.Disruptions.RestartApprovalMode != v1alpha2.Manual { + return + } + + // Avoid race condition with need restart condition calculation + Eventually(func(g Gomega) { + err := t.Framework.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) + g.Expect(err).NotTo(HaveOccurred()) + needRestart, _ := conditions.GetCondition(vmcondition.TypeAwaitingRestartToApplyConfiguration, t.VM.Status.Conditions) + g.Expect(needRestart.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(t.VM.Status.RestartAwaitingChanges).NotTo(BeNil()) + }).WithTimeout(3 * time.Second).WithPolling(time.Second).Should(Succeed()) +} From fcc5c40ebc6555b94a5f82742abfeb43e02367a0 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Wed, 26 Nov 2025 18:07:01 +0300 Subject: [PATCH 08/10] refactoring Signed-off-by: Valeriy Khorunzhin --- test/e2e/internal/util/vm.go | 9 +++++++++ test/e2e/vm/configuration.go | 33 ++++++++++++--------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 47013c196f..4d98e419be 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -139,3 +139,12 @@ func UntilVirtualMachineRebooted(key client.ObjectKey, previousRunningTime time. return fmt.Errorf("virtual machine %s is not rebooted", key.Name) }, timeout, time.Second).Should(Succeed()) } + +func IsVDAttached(vm *v1alpha2.VirtualMachine, vd *v1alpha2.VirtualDisk) bool { + for _, bd := range vm.Status.BlockDeviceRefs { + if bd.Kind == v1alpha2.DiskDevice && bd.Name == vd.Name && bd.Attached { + return true + } + } + return false +} diff --git a/test/e2e/vm/configuration.go b/test/e2e/vm/configuration.go index 285197720d..cdab669b78 100644 --- a/test/e2e/vm/configuration.go +++ b/test/e2e/vm/configuration.go @@ -57,7 +57,7 @@ var _ = Describe("VirtualMachineConfiguration", func() { f.Before() By("Environment preparation") - t.GenerateConfigurationResources(restartApprovalMode) + t.GenerateResources(restartApprovalMode) err := f.CreateWithDeferredDeletion(context.Background(), t.VM, t.VDRoot, t.VDBlank) Expect(err).NotTo(HaveOccurred()) @@ -70,7 +70,7 @@ var _ = Describe("VirtualMachineConfiguration", func() { Expect(t.VM.Status.Resources.CPU.Cores).To(Equal(initialCPUCores)) Expect(t.VM.Status.Resources.Memory.Size).To(Equal(resource.MustParse(initialMemorySize))) Expect(t.VM.Status.Resources.CPU.CoreFraction).To(Equal(initialCoreFraction)) - Expect(t.IsVDAttached(t.VDBlank, t.VM)).To(BeFalse()) + Expect(util.IsVDAttached(t.VM, t.VDBlank)).To(BeFalse()) By("Applying changes") err = f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) @@ -104,7 +104,7 @@ var _ = Describe("VirtualMachineConfiguration", func() { Expect(t.VM.Status.Resources.CPU.Cores).To(Equal(changedCPUCores)) Expect(t.VM.Status.Resources.Memory.Size).To(Equal(resource.MustParse(changedMemorySize))) Expect(t.VM.Status.Resources.CPU.CoreFraction).To(Equal(changedCoreFraction)) - Expect(t.IsVDAttached(t.VDBlank, t.VM)).To(BeTrue()) + Expect(util.IsVDAttached(t.VM, t.VDBlank)).To(BeTrue()) }, Entry("when changes are applied manually", v1alpha2.Manual), Entry("when changes are applied automatically", v1alpha2.Automatic), @@ -125,24 +125,24 @@ func NewConfigurationTest(f *framework.Framework) *configurationTest { } } -func (c *configurationTest) GenerateConfigurationResources(restartApprovalMode v1alpha2.RestartApprovalMode) { - c.VDRoot = vdbuilder.New( +func (t *configurationTest) GenerateResources(restartApprovalMode v1alpha2.RestartApprovalMode) { + t.VDRoot = vdbuilder.New( vdbuilder.WithName("vd-root"), - vdbuilder.WithNamespace(c.Framework.Namespace().Name), + vdbuilder.WithNamespace(t.Framework.Namespace().Name), vdbuilder.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{ URL: object.ImageURLUbuntu, }), ) - c.VDBlank = vdbuilder.New( + t.VDBlank = vdbuilder.New( vdbuilder.WithName("vd-blank"), - vdbuilder.WithNamespace(c.Framework.Namespace().Name), + vdbuilder.WithNamespace(t.Framework.Namespace().Name), vdbuilder.WithSize(ptr.To(resource.MustParse("100Mi"))), ) - c.VM = vmbuilder.New( + t.VM = vmbuilder.New( vmbuilder.WithName("vm"), - vmbuilder.WithNamespace(c.Framework.Namespace().Name), + vmbuilder.WithNamespace(t.Framework.Namespace().Name), vmbuilder.WithCPU(1, ptr.To(initialCoreFraction)), vmbuilder.WithMemory(resource.MustParse(initialMemorySize)), vmbuilder.WithLiveMigrationPolicy(v1alpha2.AlwaysSafeMigrationPolicy), @@ -151,28 +151,19 @@ func (c *configurationTest) GenerateConfigurationResources(restartApprovalMode v vmbuilder.WithBlockDeviceRefs( v1alpha2.BlockDeviceSpecRef{ Kind: v1alpha2.DiskDevice, - Name: c.VDRoot.Name, + Name: t.VDRoot.Name, }, ), vmbuilder.WithRestartApprovalMode(restartApprovalMode), ) } -func (c *configurationTest) IsVDAttached(vd *v1alpha2.VirtualDisk, vm *v1alpha2.VirtualMachine) bool { - for _, bd := range vm.Status.BlockDeviceRefs { - if bd.Kind == v1alpha2.DiskDevice && bd.Name == vd.Name && bd.Attached { - return true - } - } - return false -} - func (t *configurationTest) CheckRestartAwaitingChanges(vm *v1alpha2.VirtualMachine) { if vm.Spec.Disruptions.RestartApprovalMode != v1alpha2.Manual { return } - // Avoid race condition with need restart condition calculation + // Avoid race conditions during the calculation of the "need restart" condition. Eventually(func(g Gomega) { err := t.Framework.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) g.Expect(err).NotTo(HaveOccurred()) From f5f00b0bae7da708c4ed0199f323014c93589005 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Thu, 27 Nov 2025 17:54:24 +0300 Subject: [PATCH 09/10] refactoring Signed-off-by: Valeriy Khorunzhin --- test/e2e/internal/util/vm.go | 19 +++++++++++++++++++ test/e2e/vm/configuration.go | 20 +------------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 4d98e419be..483ae6e0cb 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -26,6 +26,7 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + crclient "sigs.k8s.io/controller-runtime/pkg/client" vmopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmop" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" @@ -148,3 +149,21 @@ func IsVDAttached(vm *v1alpha2.VirtualMachine, vd *v1alpha2.VirtualDisk) bool { } return false } + +func IsRestartRequired(vm *v1alpha2.VirtualMachine, timeout time.Duration) bool { + GinkgoHelper() + + if vm.Spec.Disruptions.RestartApprovalMode != v1alpha2.Manual { + return false + } + + Eventually(func(g Gomega) { + err := framework.GetClients().GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + g.Expect(err).NotTo(HaveOccurred()) + needRestart, _ := conditions.GetCondition(vmcondition.TypeAwaitingRestartToApplyConfiguration, vm.Status.Conditions) + g.Expect(needRestart.Status).To(Equal(metav1.ConditionTrue)) + g.Expect(vm.Status.RestartAwaitingChanges).NotTo(BeNil()) + }).WithTimeout(timeout).WithPolling(time.Second).Should(Succeed()) + + return true +} diff --git a/test/e2e/vm/configuration.go b/test/e2e/vm/configuration.go index cdab669b78..c18bcd2b18 100644 --- a/test/e2e/vm/configuration.go +++ b/test/e2e/vm/configuration.go @@ -25,7 +25,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "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" @@ -88,9 +87,7 @@ var _ = Describe("VirtualMachineConfiguration", func() { err = f.Clients.GenericClient().Update(context.Background(), t.VM) Expect(err).NotTo(HaveOccurred()) - t.CheckRestartAwaitingChanges(t.VM) - - if t.VM.Spec.Disruptions.RestartApprovalMode == v1alpha2.Manual { + if util.IsRestartRequired(t.VM, 3*time.Second) { util.RebootVirtualMachineBySSH(f, t.VM) } @@ -157,18 +154,3 @@ func (t *configurationTest) GenerateResources(restartApprovalMode v1alpha2.Resta vmbuilder.WithRestartApprovalMode(restartApprovalMode), ) } - -func (t *configurationTest) CheckRestartAwaitingChanges(vm *v1alpha2.VirtualMachine) { - if vm.Spec.Disruptions.RestartApprovalMode != v1alpha2.Manual { - return - } - - // Avoid race conditions during the calculation of the "need restart" condition. - Eventually(func(g Gomega) { - err := t.Framework.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) - g.Expect(err).NotTo(HaveOccurred()) - needRestart, _ := conditions.GetCondition(vmcondition.TypeAwaitingRestartToApplyConfiguration, t.VM.Status.Conditions) - g.Expect(needRestart.Status).To(Equal(metav1.ConditionTrue)) - g.Expect(t.VM.Status.RestartAwaitingChanges).NotTo(BeNil()) - }).WithTimeout(3 * time.Second).WithPolling(time.Second).Should(Succeed()) -} From 754ac79f730a7014b7f2f4ae5e4012e741ea0870 Mon Sep 17 00:00:00 2001 From: Valeriy Khorunzhin Date: Thu, 27 Nov 2025 19:15:36 +0300 Subject: [PATCH 10/10] fix linter Signed-off-by: Valeriy Khorunzhin --- test/e2e/internal/util/vm.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 483ae6e0cb..f69780daca 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -26,7 +26,6 @@ import ( . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" - crclient "sigs.k8s.io/controller-runtime/pkg/client" vmopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmop" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" @@ -158,7 +157,7 @@ func IsRestartRequired(vm *v1alpha2.VirtualMachine, timeout time.Duration) bool } Eventually(func(g Gomega) { - err := framework.GetClients().GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(vm), vm) + err := framework.GetClients().GenericClient().Get(context.Background(), client.ObjectKeyFromObject(vm), vm) g.Expect(err).NotTo(HaveOccurred()) needRestart, _ := conditions.GetCondition(vmcondition.TypeAwaitingRestartToApplyConfiguration, vm.Status.Conditions) g.Expect(needRestart.Status).To(Equal(metav1.ConditionTrue))