diff --git a/test/e2e/internal/object/const.go b/test/e2e/internal/object/const.go index 4e537eaad2..b6ccf48c60 100644 --- a/test/e2e/internal/object/const.go +++ b/test/e2e/internal/object/const.go @@ -21,6 +21,8 @@ const ( ImageURLUbuntu = "https://89d64382-20df-4581-8cc7-80df331f67fa.selstorage.ru/ubuntu/ubuntu-24.04-minimal-cloudimg-amd64.qcow2" ImageURLAlpineBIOS = "https://89d64382-20df-4581-8cc7-80df331f67fa.selstorage.ru/alpine/alpine-3-21-bios-base.qcow2" ImageURLContainerImage = "cr.yandex/crpvs5j3nh1mi2tpithr/e2e/alpine/alpine-image:latest" + ImageURLMinimalQCOW = "https://89d64382-20df-4581-8cc7-80df331f67fa.selstorage.ru/test/test.qcow2" + ImageURLMinimalISO = "https://89d64382-20df-4581-8cc7-80df331f67fa.selstorage.ru/test/test.iso" Mi256 = 256 * 1024 * 1024 DefaultVMClass = "generic" DefaultCloudInit = `#cloud-config diff --git a/test/e2e/internal/util/vm.go b/test/e2e/internal/util/vm.go index 29a3b1520e..7055b0b918 100644 --- a/test/e2e/internal/util/vm.go +++ b/test/e2e/internal/util/vm.go @@ -19,12 +19,13 @@ package util import ( "context" "fmt" - "strings" "time" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" vmopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmop" @@ -34,6 +35,8 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/framework" ) +const VmopE2ePrefix = "vmop-e2e" + func UntilVMAgentReady(key client.ObjectKey, timeout time.Duration) { GinkgoHelper() @@ -52,6 +55,16 @@ func UntilVMAgentReady(key client.ObjectKey, timeout time.Duration) { }).WithTimeout(timeout).WithPolling(time.Second).Should(Succeed()) } +func UntilSSHReady(f *framework.Framework, vm *v1alpha2.VirtualMachine, timeout time.Duration) { + GinkgoHelper() + + Eventually(func(g Gomega) { + result, err := f.SSHCommand(vm.Name, vm.Namespace, "echo 'test'") + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(result).To(ContainSubstring("test")) + }).WithTimeout(timeout).WithPolling(time.Second).Should(Succeed()) +} + func UntilVMMigrationSucceeded(key client.ObjectKey, timeout time.Duration) { GinkgoHelper() @@ -84,7 +97,7 @@ func MigrateVirtualMachine(f *framework.Framework, vm *v1alpha2.VirtualMachine, GinkgoHelper() opts := []vmopbuilder.Option{ - vmopbuilder.WithGenerateName("vmop-e2e-"), + vmopbuilder.WithGenerateName(fmt.Sprintf("%s-evict-", VmopE2ePrefix)), vmopbuilder.WithNamespace(vm.Namespace), vmopbuilder.WithType(v1alpha2.VMOPTypeEvict), vmopbuilder.WithVirtualMachine(vm.Name), @@ -100,7 +113,7 @@ func StartVirtualMachine(f *framework.Framework, vm *v1alpha2.VirtualMachine, op GinkgoHelper() opts := []vmopbuilder.Option{ - vmopbuilder.WithGenerateName("vmop-e2e-"), + vmopbuilder.WithGenerateName(fmt.Sprintf("%s-start-", VmopE2ePrefix)), vmopbuilder.WithNamespace(vm.Namespace), vmopbuilder.WithType(v1alpha2.VMOPTypeStart), vmopbuilder.WithVirtualMachine(vm.Name), @@ -112,18 +125,17 @@ func StartVirtualMachine(f *framework.Framework, vm *v1alpha2.VirtualMachine, op Expect(err).NotTo(HaveOccurred()) } -func StopVirtualMachineFromOS(f *framework.Framework, vm *v1alpha2.VirtualMachine) error { - _, err := f.SSHCommand(vm.Name, vm.Namespace, "sudo init 0") - if err != nil && strings.Contains(err.Error(), "unexpected EOF") { - return nil - } - return err +func StopVirtualMachineFromOS(f *framework.Framework, vm *v1alpha2.VirtualMachine) { + GinkgoHelper() + + _, err := f.SSHCommand(vm.Name, vm.Namespace, "nohup sh -c \"sleep 5 && sudo poweroff\" > /dev/null 2>&1 &") + Expect(err).NotTo(HaveOccurred()) } func RebootVirtualMachineBySSH(f *framework.Framework, vm *v1alpha2.VirtualMachine) { GinkgoHelper() - _, err := f.SSHCommand(vm.Name, vm.Namespace, "sudo reboot") + _, err := f.SSHCommand(vm.Name, vm.Namespace, "nohup sh -c \"sleep 5 && sudo reboot\" > /dev/null 2>&1 &") Expect(err).NotTo(HaveOccurred()) } @@ -131,7 +143,7 @@ func RebootVirtualMachineByVMOP(f *framework.Framework, vm *v1alpha2.VirtualMach GinkgoHelper() vmop := vmopbuilder.New( - vmopbuilder.WithGenerateName("vmop-e2e-reboot-"), + vmopbuilder.WithGenerateName(fmt.Sprintf("%s-reboot-", VmopE2ePrefix)), vmopbuilder.WithNamespace(vm.Namespace), vmopbuilder.WithType(v1alpha2.VMOPTypeRestart), vmopbuilder.WithVirtualMachine(vm.Name), @@ -140,7 +152,37 @@ func RebootVirtualMachineByVMOP(f *framework.Framework, vm *v1alpha2.VirtualMach Expect(err).NotTo(HaveOccurred()) } +func RebootVirtualMachineByPodDeletion(f *framework.Framework, vm *v1alpha2.VirtualMachine) { + GinkgoHelper() + + activePodName, err := getActivePodName(vm) + Expect(err).NotTo(HaveOccurred()) + Expect(activePodName).NotTo(BeEmpty()) + + var pod corev1.Pod + err = framework.GetClients().GenericClient().Get(context.Background(), types.NamespacedName{ + Namespace: vm.Namespace, + Name: activePodName, + }, &pod) + Expect(err).NotTo(HaveOccurred()) + + err = framework.GetClients().GenericClient().Delete(context.Background(), &pod) + Expect(err).NotTo(HaveOccurred()) +} + +func getActivePodName(vm *v1alpha2.VirtualMachine) (string, error) { + for _, pod := range vm.Status.VirtualMachinePods { + if pod.Active { + return pod.Name, nil + } + } + + return "", fmt.Errorf("no active pod found for virtual machine %s", vm.Name) +} + func UntilVirtualMachineRebooted(key client.ObjectKey, previousRunningTime time.Time, timeout time.Duration) { + GinkgoHelper() + Eventually(func() error { vm := &v1alpha2.VirtualMachine{} err := framework.GetClients().GenericClient().Get(context.Background(), key, vm) diff --git a/test/e2e/legacy/complex.go b/test/e2e/legacy/complex.go index 9bdb11b4c7..5c031fa5a3 100644 --- a/test/e2e/legacy/complex.go +++ b/test/e2e/legacy/complex.go @@ -19,7 +19,6 @@ package legacy import ( "fmt" "strings" - "sync" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -36,8 +35,6 @@ var _ = Describe("ComplexTest", Ordered, func() { var ( testCaseLabel = map[string]string{"testcase": "complex-test"} hasNoConsumerLabel = map[string]string{"hasNoConsumer": "complex-test"} - alwaysOnLabel = map[string]string{"alwaysOn": "complex-test"} - notAlwaysOnLabel = map[string]string{"notAlwaysOn": "complex-test"} ns string phaseByVolumeBindingMode = GetPhaseByVolumeBindingModeForTemplateSc() f = framework.NewFramework("") @@ -196,291 +193,6 @@ var _ = Describe("ComplexTest", Ordered, func() { }) }) - Describe("Power state checks", func() { - var ( - alwaysOnVMs []string - notAlwaysOnVMs []string - alwaysOnVMStopVMOPs []string - notAlwaysOnVMStopVMs []string - ) - - Context("Verify that the virtual machines are stopping by VMOPs", func() { - It("stops VMs by VMOPs", func() { - var vmList v1alpha2.VirtualMachineList - err := GetObjects(kc.ResourceVM, &vmList, kc.GetOptions{ - Labels: testCaseLabel, - Namespace: ns, - }) - Expect(err).ShouldNot(HaveOccurred()) - Expect(len(vmList.Items)).To(Equal(VirtualMachineCount)) - - for _, vmObj := range vmList.Items { - if vmObj.Spec.RunPolicy == v1alpha2.AlwaysOnPolicy { - alwaysOnVMs = append(alwaysOnVMs, vmObj.Name) - alwaysOnVMStopVMOPs = append(alwaysOnVMStopVMOPs, fmt.Sprintf("%s-%s", vmObj.Name, strings.ToLower(string(v1alpha2.VMOPTypeStop)))) - } else { - notAlwaysOnVMs = append(notAlwaysOnVMs, vmObj.Name) - notAlwaysOnVMStopVMs = append(notAlwaysOnVMStopVMs, fmt.Sprintf("%s-%s", vmObj.Name, strings.ToLower(string(v1alpha2.VMOPTypeStop)))) - } - } - - By("Trying to stop AlwaysOn VMs") - StopVirtualMachinesByVMOP(alwaysOnLabel, ns, alwaysOnVMs...) - By("Trying to stop not AlwaysOn VMs") - StopVirtualMachinesByVMOP(notAlwaysOnLabel, ns, notAlwaysOnVMs...) - }) - - It("checks VMOPs and VMs phases", func() { - By(fmt.Sprintf("AlwaysOn VM VMOPs should be in %s phases", v1alpha2.VMOPPhaseFailed)) - WaitResourcesByPhase(alwaysOnVMStopVMOPs, kc.ResourceVMOP, string(v1alpha2.VMOPPhaseFailed), kc.WaitOptions{ - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - By(fmt.Sprintf("Not AlwaysOn VM VMOPs should be in %s phases", v1alpha2.VMOPPhaseCompleted)) - WaitResourcesByPhase(notAlwaysOnVMStopVMs, kc.ResourceVMOP, string(v1alpha2.VMOPPhaseCompleted), kc.WaitOptions{ - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - By(fmt.Sprintf("AlwaysOn VMs should be in %s phases", v1alpha2.MachineRunning)) - WaitResourcesByPhase(alwaysOnVMs, kc.ResourceVM, string(v1alpha2.MachineRunning), kc.WaitOptions{ - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - By(fmt.Sprintf("Not AlwaysOn VMs should be in %s phases", v1alpha2.MachineStopped)) - WaitResourcesByPhase(notAlwaysOnVMs, kc.ResourceVM, string(v1alpha2.MachineStopped), kc.WaitOptions{ - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - - It("cleanup AlwaysOn VM VMOPs", func() { - res := kubectl.Delete(kc.DeleteOptions{ - Namespace: ns, - Labels: alwaysOnLabel, - IgnoreNotFound: true, - Resource: kc.ResourceVMOP, - }) - Expect(res.Error()).NotTo(HaveOccurred(), "%v", res.StdErr()) - }) - }) - - Context("Verify that the virtual machines are starting", func() { - It("starts VMs by VMOP", func() { - var vms v1alpha2.VirtualMachineList - err := GetObjects(kc.ResourceVM, &vms, kc.GetOptions{ - Namespace: ns, - Labels: testCaseLabel, - }) - Expect(err).NotTo(HaveOccurred()) - Expect(len(vms.Items)).To(Equal(VirtualMachineCount)) - - var notAlwaysOnVMs []string - for _, vm := range vms.Items { - if vm.Spec.RunPolicy != v1alpha2.AlwaysOnPolicy { - notAlwaysOnVMs = append(notAlwaysOnVMs, vm.Name) - } - } - - StartVirtualMachinesByVMOP(testCaseLabel, ns, notAlwaysOnVMs...) - }) - - It("checks VMs and VMOPs phases", func() { - By(fmt.Sprintf("VMOPs should be in %s phases", v1alpha2.VMOPPhaseCompleted)) - WaitPhaseByLabel(kc.ResourceVMOP, string(v1alpha2.VMOPPhaseCompleted), kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - By("Virtual machine agents should be ready") - WaitVMAgentReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - }) - - Context("Verify that the virtual machines are stopping by ssh", func() { - It("stops VMs by ssh", func() { - var vmList v1alpha2.VirtualMachineList - err := GetObjects(kc.ResourceVM, &vmList, kc.GetOptions{ - Labels: testCaseLabel, - Namespace: ns, - }) - Expect(err).ShouldNot(HaveOccurred()) - Expect(len(vmList.Items)).To(Equal(VirtualMachineCount)) - - alwaysOnVMs = []string{} - notAlwaysOnVMs = []string{} - for _, vmObj := range vmList.Items { - if vmObj.Spec.RunPolicy == v1alpha2.AlwaysOnPolicy { - alwaysOnVMs = append(alwaysOnVMs, vmObj.Name) - } else { - notAlwaysOnVMs = append(notAlwaysOnVMs, vmObj.Name) - } - } - var vms []string - vms = append(vms, alwaysOnVMs...) - vms = append(vms, notAlwaysOnVMs...) - - StopVirtualMachinesBySSH(ns, vms...) - }) - - It("checks VMs phases", func() { - By(fmt.Sprintf("Not AlwaysOn VMs should be in %s phases", v1alpha2.MachineStopped)) - WaitResourcesByPhase(notAlwaysOnVMs, kc.ResourceVM, string(v1alpha2.MachineStopped), kc.WaitOptions{ - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - By(fmt.Sprintf("AlwaysOn VMs should be in %s phases", v1alpha2.MachineRunning)) - WaitResourcesByPhase(alwaysOnVMs, kc.ResourceVM, string(v1alpha2.MachineRunning), kc.WaitOptions{ - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - - It("start not AlwaysOn VMs", func() { - CreateAndApplyVMOPsWithSuffix(testCaseLabel, "-after-ssh-stopping", v1alpha2.VMOPTypeStart, ns, notAlwaysOnVMs...) - }) - - It("checks VMs and VMOPs phases", func() { - By(fmt.Sprintf("VMOPs should be in %s phases", v1alpha2.VMOPPhaseCompleted)) - WaitPhaseByLabel(kc.ResourceVMOP, string(v1alpha2.VMOPPhaseCompleted), kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - By("Virtual machine agents should be ready") - WaitVMAgentReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - }) - - Context("Verify that the virtual machines are restarting by VMOP", func() { - It("reboot VMs by VMOP", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: testCaseLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) - - vms := strings.Split(res.StdOut(), " ") - - RebootVirtualMachinesByVMOP(testCaseLabel, ns, vms...) - }) - - It("checks VMs and VMOPs phases", func() { - By(fmt.Sprintf("VMOPs should be in %s phases", v1alpha2.VMOPPhaseCompleted)) - WaitPhaseByLabel(kc.ResourceVMOP, string(v1alpha2.VMOPPhaseCompleted), kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - By("Virtual machine agents should be ready") - WaitVMAgentReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - }) - - Context("Verify that the virtual machines are restarting by ssh", func() { - It("reboot VMs by ssh", func() { - wg := &sync.WaitGroup{} - - By("Virtual machines should be stopped", func() { - wg.Add(1) - go func() { - defer GinkgoRecover() - defer wg.Done() - WaitPhaseByLabel(kc.ResourceVM, string(v1alpha2.MachineStopped), kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }() - }) - - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: testCaseLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) - - vms := strings.Split(res.StdOut(), " ") - - RebootVirtualMachinesBySSH(ns, vms...) - - By("Virtual machines agent should be ready", func() { - WaitVMAgentReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - wg.Wait() - }) - }) - - Context("Verify that the virtual machines are restarting after deleting their pods", func() { - It("reboots the VMs by deleting their pods", func() { - wg := &sync.WaitGroup{} - - By("Virtual machines should be stopped", func() { - wg.Add(1) - go func() { - defer GinkgoRecover() - defer wg.Done() - WaitPhaseByLabel(kc.ResourceVM, string(v1alpha2.MachineStopped), kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }() - }) - - res := kubectl.Delete(kc.DeleteOptions{ - IgnoreNotFound: true, - Labels: testCaseLabel, - Namespace: ns, - Resource: kc.ResourcePod, - }) - Expect(res.Error()).NotTo(HaveOccurred()) - - By("Virtual machines agent should be ready", func() { - WaitVMAgentReady(kc.WaitOptions{ - Labels: testCaseLabel, - Namespace: ns, - Timeout: MaxWaitTimeout, - }) - }) - - wg.Wait() - }) - - It("checks VMs external connection after reboot", func() { - res := kubectl.List(kc.ResourceVM, kc.GetOptions{ - Labels: testCaseLabel, - Namespace: ns, - Output: "jsonpath='{.items[*].metadata.name}'", - }) - Expect(res.Error()).NotTo(HaveOccurred(), res.StdErr()) - - vms := strings.Split(res.StdOut(), " ") - - // Skip this check until the issue with cilium-agents is fixed. - // CheckCiliumAgents(kubectl, ns, vms...) - CheckExternalConnection(externalHost, httpStatusOk, ns, vms...) - }) - }) - }) - Describe("Migrations", func() { Context("When Virtual machine agents are ready", func() { It("starts migrations", func() { diff --git a/test/e2e/vm/power_state.go b/test/e2e/vm/power_state.go new file mode 100644 index 0000000000..6dcc47d8ed --- /dev/null +++ b/test/e2e/vm/power_state.go @@ -0,0 +1,291 @@ +/* +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" + + . "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" + + cvibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/cvi" + vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd" + vibuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vi" + vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm" + vmbdabuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmbda" + vmopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmop" + "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/network" + "github.com/deckhouse/virtualization/test/e2e/internal/object" + "github.com/deckhouse/virtualization/test/e2e/internal/util" +) + +var _ = Describe("PowerState", func() { + DescribeTable("manages power state of a virtual machine", func(runPolicy v1alpha2.RunPolicy) { + var namespaceSuffix string + switch runPolicy { + case v1alpha2.AlwaysOnPolicy: + namespaceSuffix = "always-on" + case v1alpha2.AlwaysOnUnlessStoppedManually: + namespaceSuffix = "stopped-manually" + case v1alpha2.ManualPolicy: + namespaceSuffix = "manual" + } + f := framework.NewFramework(fmt.Sprintf("power-state-%s", namespaceSuffix)) + DeferCleanup(f.After) + f.Before() + + t := newPowerStateTest(f) + + By("Environment preparation", func() { + t.GenerateResources(runPolicy) + err := f.CreateWithDeferredDeletion( + context.Background(), t.CVI, t.VI, t.VDRoot, t.VDBlank, t.VM, t.VMBDA, + ) + Expect(err).NotTo(HaveOccurred()) + + if t.VM.Spec.RunPolicy == v1alpha2.ManualPolicy { + util.UntilObjectPhase(string(v1alpha2.MachineStopped), framework.LongTimeout, t.VM) + util.StartVirtualMachine(f, t.VM) + } + + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.LongTimeout, t.VM) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.MiddleTimeout, t.VMBDA) + util.UntilSSHReady(f, t.VM, framework.ShortTimeout) + }) + + By("Shutdown VM by VMOP", func() { + vmopStop := vmopbuilder.New( + vmopbuilder.WithGenerateName(fmt.Sprintf("%s-stop-", util.VmopE2ePrefix)), + vmopbuilder.WithNamespace(t.VM.Namespace), + vmopbuilder.WithType(v1alpha2.VMOPTypeStop), + vmopbuilder.WithVirtualMachine(t.VM.Name), + ) + err := f.CreateWithDeferredDeletion(context.Background(), vmopStop) + Expect(err).NotTo(HaveOccurred()) + + switch t.VM.Spec.RunPolicy { + case v1alpha2.AlwaysOnPolicy: + util.UntilObjectPhase(string(v1alpha2.VMOPPhaseFailed), framework.ShortTimeout, vmopStop) + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.ShortTimeout, t.VM) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.ShortTimeout, t.VMBDA) + case v1alpha2.AlwaysOnUnlessStoppedManually, v1alpha2.ManualPolicy: + util.UntilObjectPhase(string(v1alpha2.VMOPPhaseCompleted), framework.LongTimeout, vmopStop) + util.UntilObjectPhase(string(v1alpha2.MachineStopped), framework.ShortTimeout, t.VM) + } + }) + + By("Start VM", func() { + if t.VM.Spec.RunPolicy != v1alpha2.AlwaysOnPolicy { + util.StartVirtualMachine(f, t.VM) + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.MiddleTimeout, t.VM) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.ShortTimeout, t.VMBDA) + util.UntilSSHReady(f, t.VM, framework.ShortTimeout) + } + }) + + By("Shutdown VM by SSH", func() { + err := f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) + Expect(err).NotTo(HaveOccurred()) + runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, t.VM.Status.Conditions) + runningLastTransitionTime := runningCondition.LastTransitionTime.Time + + util.StopVirtualMachineFromOS(f, t.VM) + + switch t.VM.Spec.RunPolicy { + case v1alpha2.AlwaysOnPolicy: + util.UntilVirtualMachineRebooted(crclient.ObjectKeyFromObject(t.VM), runningLastTransitionTime, framework.LongTimeout) + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.ShortTimeout, t.VM) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.ShortTimeout, t.VMBDA) + util.UntilSSHReady(f, t.VM, framework.ShortTimeout) + case v1alpha2.AlwaysOnUnlessStoppedManually, v1alpha2.ManualPolicy: + util.UntilObjectPhase(string(v1alpha2.MachineStopped), framework.LongTimeout, t.VM) + } + }) + + By("Start VM", func() { + if t.VM.Spec.RunPolicy != v1alpha2.AlwaysOnPolicy { + util.StartVirtualMachine(f, t.VM) + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.MiddleTimeout, t.VM) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.ShortTimeout, t.VMBDA) + util.UntilSSHReady(f, t.VM, framework.ShortTimeout) + } + }) + + By("Reboot VM by VMOP", func() { + err := f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) + Expect(err).NotTo(HaveOccurred()) + + runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, t.VM.Status.Conditions) + runningLastTransitionTime := runningCondition.LastTransitionTime.Time + + vmopRestart := vmopbuilder.New( + vmopbuilder.WithGenerateName(fmt.Sprintf("%s-restart-", util.VmopE2ePrefix)), + vmopbuilder.WithNamespace(t.VM.Namespace), + vmopbuilder.WithType(v1alpha2.VMOPTypeRestart), + vmopbuilder.WithVirtualMachine(t.VM.Name), + ) + err = f.CreateWithDeferredDeletion(context.Background(), vmopRestart) + Expect(err).NotTo(HaveOccurred()) + + util.UntilObjectPhase(string(v1alpha2.VMOPPhaseCompleted), framework.LongTimeout, vmopRestart) + util.UntilVirtualMachineRebooted(crclient.ObjectKeyFromObject(t.VM), runningLastTransitionTime, framework.MiddleTimeout) + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.ShortTimeout, t.VM) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.ShortTimeout, t.VMBDA) + util.UntilSSHReady(f, t.VM, framework.ShortTimeout) + }) + + By("Reboot VM by SSH", func() { + err := f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) + Expect(err).NotTo(HaveOccurred()) + + runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, t.VM.Status.Conditions) + runningLastTransitionTime := runningCondition.LastTransitionTime.Time + + util.RebootVirtualMachineBySSH(f, t.VM) + + util.UntilVirtualMachineRebooted(crclient.ObjectKeyFromObject(t.VM), runningLastTransitionTime, framework.LongTimeout) + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.ShortTimeout, t.VM) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.ShortTimeout, t.VMBDA) + util.UntilSSHReady(f, t.VM, framework.ShortTimeout) + }) + + By("Reboot VM by Pod Deletion", func() { + err := f.Clients.GenericClient().Get(context.Background(), crclient.ObjectKeyFromObject(t.VM), t.VM) + Expect(err).NotTo(HaveOccurred()) + + runningCondition, _ := conditions.GetCondition(vmcondition.TypeRunning, t.VM.Status.Conditions) + runningLastTransitionTime := runningCondition.LastTransitionTime.Time + + util.RebootVirtualMachineByPodDeletion(f, t.VM) + + if t.VM.Spec.RunPolicy != v1alpha2.AlwaysOnPolicy { + util.UntilObjectPhase(string(v1alpha2.MachineStopped), framework.MiddleTimeout, t.VM) + util.StartVirtualMachine(f, t.VM) + } + + util.UntilVirtualMachineRebooted(crclient.ObjectKeyFromObject(t.VM), runningLastTransitionTime, framework.LongTimeout) + util.UntilObjectPhase(string(v1alpha2.MachineRunning), framework.ShortTimeout, t.VM) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.ShortTimeout, t.VMBDA) + util.UntilSSHReady(f, t.VM, framework.ShortTimeout) + }) + + By("Check VM can reach external network", func() { + err := network.CheckCiliumAgents(context.Background(), f.Clients.Kubectl(), t.VM.Name, f.Namespace().Name) + Expect(err).NotTo(HaveOccurred(), "Cilium agents check should succeed for VM %s", t.VM.Name) + network.CheckExternalConnectivity(f, t.VM.Name, network.ExternalHost, network.HTTPStatusOk) + }) + }, + Entry( + "AlwaysOn run policy", + v1alpha2.AlwaysOnPolicy, + ), + Entry( + "Manual run policy", + v1alpha2.ManualPolicy, + ), + Entry( + "AlwaysOnUnlessStoppedManually run policy", + v1alpha2.AlwaysOnUnlessStoppedManually, + ), + ) +}) + +type powerStateTest struct { + Framework *framework.Framework + + CVI *v1alpha2.ClusterVirtualImage + VI *v1alpha2.VirtualImage + VM *v1alpha2.VirtualMachine + VDRoot *v1alpha2.VirtualDisk + VDBlank *v1alpha2.VirtualDisk + VMBDA *v1alpha2.VirtualMachineBlockDeviceAttachment +} + +func newPowerStateTest(f *framework.Framework) *powerStateTest { + return &powerStateTest{ + Framework: f, + } +} + +func (t *powerStateTest) GenerateResources(runPolicy v1alpha2.RunPolicy) { + t.CVI = cvibuilder.New( + cvibuilder.WithName(fmt.Sprintf("%s-cvi", t.Framework.Namespace().Name)), + cvibuilder.WithDataSourceHTTP(object.ImageURLMinimalISO, nil, nil), + ) + + t.VI = vibuilder.New( + vibuilder.WithName("vi"), + vibuilder.WithNamespace(t.Framework.Namespace().Name), + vibuilder.WithDataSourceHTTP(object.ImageURLMinimalQCOW, nil, nil), + vibuilder.WithStorage(v1alpha2.StorageContainerRegistry), + ) + + t.VDRoot = vdbuilder.New( + vdbuilder.WithName("vd-root"), + vdbuilder.WithNamespace(t.Framework.Namespace().Name), + vdbuilder.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{ + URL: object.ImageURLUbuntu, + }), + ) + + t.VDBlank = vdbuilder.New( + vdbuilder.WithName("vd-blank"), + vdbuilder.WithNamespace(t.Framework.Namespace().Name), + vdbuilder.WithPersistentVolumeClaim(nil, ptr.To(resource.MustParse("51Mi"))), + ) + + t.VM = vmbuilder.New( + vmbuilder.WithName("vm"), + vmbuilder.WithNamespace(t.Framework.Namespace().Name), + vmbuilder.WithCPU(1, ptr.To("10%")), + vmbuilder.WithMemory(resource.MustParse("512Mi")), + vmbuilder.WithLiveMigrationPolicy(v1alpha2.AlwaysSafeMigrationPolicy), + vmbuilder.WithVirtualMachineClass(object.DefaultVMClass), + vmbuilder.WithBlockDeviceRefs( + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.DiskDevice, + Name: t.VDRoot.Name, + }, + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.ClusterImageDevice, + Name: t.CVI.Name, + }, + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.ImageDevice, + Name: t.VI.Name, + }, + ), + vmbuilder.WithRestartApprovalMode(v1alpha2.Manual), + vmbuilder.WithRunPolicy(runPolicy), + vmbuilder.WithProvisioningUserData(object.DefaultCloudInit), + ) + + t.VMBDA = vmbdabuilder.New( + vmbdabuilder.WithName("vmbda"), + vmbdabuilder.WithNamespace(t.VDBlank.Namespace), + vmbdabuilder.WithVirtualMachineName(t.VM.Name), + vmbdabuilder.WithBlockDeviceRef(v1alpha2.VMBDAObjectRefKindVirtualDisk, t.VDBlank.Name), + ) +} diff --git a/test/e2e/vm/volume_migration_local_disks.go b/test/e2e/vm/volume_migration_local_disks.go index 4d0739d31e..9ba03b2602 100644 --- a/test/e2e/vm/volume_migration_local_disks.go +++ b/test/e2e/vm/volume_migration_local_disks.go @@ -333,7 +333,8 @@ var _ = Describe("LocalVirtualDiskMigration", decoratorsForVolumeMigrations(), f }), Entry("when virtual machine stopped from OS", func(vm *v1alpha2.VirtualMachine) error { By(fmt.Sprintf("Exec shutdown command for virtualmachine %s/%s", vm.Namespace, vm.Name)) - return util.StopVirtualMachineFromOS(f, vm) + util.StopVirtualMachineFromOS(f, vm) + return nil }), ) diff --git a/test/e2e/vmop/restore.go b/test/e2e/vmop/restore.go index 8108db435d..bce10bc23f 100644 --- a/test/e2e/vmop/restore.go +++ b/test/e2e/vmop/restore.go @@ -50,8 +50,6 @@ import ( ) const ( - minimalVIURL = "https://89d64382-20df-4581-8cc7-80df331f67fa.selstorage.ru/test/test.qcow2" - minimalCVIURL = "https://89d64382-20df-4581-8cc7-80df331f67fa.selstorage.ru/test/test.iso" vmAnnotationName = "vmAnnotationName" vmAnnotationOriginalValue = "vmAnnotationOriginalValue" vmAnnotationChangedValue = "vmAnnotationChangedValue" @@ -270,13 +268,13 @@ func newRestoreTest(f *framework.Framework) *restoreModeTest { func (t *restoreModeTest) GenerateResources(restoreMode v1alpha2.SnapshotOperationMode, restartApprovalMode v1alpha2.RestartApprovalMode, runPolicy v1alpha2.RunPolicy) { t.CVI = cvibuilder.New( cvibuilder.WithName(fmt.Sprintf("%s-cvi", t.Framework.Namespace().Name)), - cvibuilder.WithDataSourceHTTP(minimalCVIURL, nil, nil), + cvibuilder.WithDataSourceHTTP(object.ImageURLMinimalISO, nil, nil), ) t.VI = vibuilder.New( vibuilder.WithName("vi"), vibuilder.WithNamespace(t.Framework.Namespace().Name), - vibuilder.WithDataSourceHTTP(minimalVIURL, nil, nil), + vibuilder.WithDataSourceHTTP(object.ImageURLMinimalQCOW, nil, nil), vibuilder.WithStorage(v1alpha2.StorageContainerRegistry), ) @@ -404,11 +402,10 @@ runcmd: func (t *restoreModeTest) RemoveRecoverableResources() { GinkgoHelper() - err := util.StopVirtualMachineFromOS(t.Framework, t.VM) - Expect(err).NotTo(HaveOccurred()) + util.StopVirtualMachineFromOS(t.Framework, t.VM) util.UntilObjectPhase(string(v1alpha2.MachineStopped), framework.ShortTimeout, t.VM) - err = t.Framework.Delete(context.Background(), t.VDRoot, t.VDBlank, t.VMBDA, t.VDBlankWithNoFstabEntry, t.VMBDAWithNoFstabEntry) + err := t.Framework.Delete(context.Background(), t.VDRoot, t.VDBlank, t.VMBDA, t.VDBlankWithNoFstabEntry, t.VMBDAWithNoFstabEntry) Expect(err).NotTo(HaveOccurred()) // Wait for resources to be deleted before proceeding.