From 1c1dfafdb6ad41a3828e47d843b3a0b8bb06290e Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Mon, 24 Nov 2025 14:02:07 +0200 Subject: [PATCH 1/3] feat(e2e): VMSOP clone tests Signed-off-by: Daniil Antoshin --- test/e2e/e2e_test.go | 1 + test/e2e/snapshot/vmsop.go | 123 +++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 test/e2e/snapshot/vmsop.go diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 9176edfd27..2fe7d7ee34 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -25,6 +25,7 @@ import ( _ "github.com/deckhouse/virtualization/test/e2e/blockdevice" "github.com/deckhouse/virtualization/test/e2e/controller" "github.com/deckhouse/virtualization/test/e2e/legacy" + _ "github.com/deckhouse/virtualization/test/e2e/snapshot" _ "github.com/deckhouse/virtualization/test/e2e/vm" ) diff --git a/test/e2e/snapshot/vmsop.go b/test/e2e/snapshot/vmsop.go new file mode 100644 index 0000000000..2d7ae5799d --- /dev/null +++ b/test/e2e/snapshot/vmsop.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 snapshot + +import ( + "context" + "fmt" + + . "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" + + vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd" + vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm" + vmsbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmsnapshot" + vmsopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmsop" + "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/util" +) + +var _ = Describe("VMSOPCreateVirtualMachine", func() { + var ( + vd *v1alpha2.VirtualDisk + vm *v1alpha2.VirtualMachine + vmsnapshot *v1alpha2.VirtualMachineSnapshot + vmsop *v1alpha2.VirtualMachineSnapshotOperation + + f = framework.NewFramework("vmsop") + ) + + BeforeEach(func() { + DeferCleanup(f.After) + + f.Before() + }) + + It("verifies that vmsop are successful", func() { + By("Environment preparation", func() { + vd = vdbuilder.New( + vdbuilder.WithName("vd-root"), + vdbuilder.WithNamespace(f.Namespace().Name), + vdbuilder.WithSize(ptr.To(resource.MustParse("10Gi"))), + vdbuilder.WithDataSourceHTTP(&v1alpha2.DataSourceHTTP{ + URL: object.ImageURLAlpineBIOS, + }), + ) + vm = object.NewMinimalVM("vmsop-origin-", f.Namespace().Name, + vmbuilder.WithBlockDeviceRefs( + v1alpha2.BlockDeviceSpecRef{ + Kind: v1alpha2.VirtualDiskKind, + Name: vd.Name, + }, + ), + ) + + err := f.CreateWithDeferredDeletion(context.Background(), vd, vm) + Expect(err).NotTo(HaveOccurred()) + + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.LongTimeout) + }) + + By("Create VM Snapshot", func() { + vmsnapshot = vmsbuilder.New( + vmsbuilder.WithName("vmsnapshot"), + vmsbuilder.WithNamespace(f.Namespace().Name), + vmsbuilder.WithVirtualMachineName(vm.Name), + vmsbuilder.WithKeepIPAddress(v1alpha2.KeepIPAddressNever), + vmsbuilder.WithRequiredConsistency(false), + ) + + err := f.CreateWithDeferredDeletion(context.Background(), vmsnapshot) + Expect(err).NotTo(HaveOccurred()) + + util.UntilObjectPhase(string(v1alpha2.VirtualMachineSnapshotPhaseReady), framework.LongTimeout, vmsnapshot) + }) + + By("Create and wait for VMSOP", func() { + vmsop = vmsopbuilder.New( + vmsopbuilder.WithName("vmsop"), + vmsopbuilder.WithNamespace(f.Namespace().Name), + vmsopbuilder.WithVirtualMachineSnapshotName(vmsnapshot.Name), + vmsopbuilder.WithCreateVirtualMachine(&v1alpha2.VMSOPCreateVirtualMachineSpec{ + Mode: v1alpha2.SnapshotOperationModeBestEffort, + Customization: &v1alpha2.VMSOPCreateVirtualMachineCustomization{ + NamePrefix: "created-from-vmsop-", + }, + }), + ) + + err := f.CreateWithDeferredDeletion(context.Background(), vmsop) + Expect(err).NotTo(HaveOccurred()) + + util.UntilObjectPhase(string(v1alpha2.VMSOPPhaseCompleted), framework.LongTimeout, vmsop) + }) + + By("Verify that the created VM is running", func() { + newName := fmt.Sprintf("created-from-vmsop-%s", vm.Name) + createdVM, err := f.Clients.VirtClient().VirtualMachines(f.Namespace().Name).Get(context.Background(), newName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(createdVM), framework.LongTimeout) + }) + }) +}) From 32a8289897fa8aa91fe4b39b57f8696a6c6eec18 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Tue, 25 Nov 2025 16:55:20 +0200 Subject: [PATCH 2/3] add new cases Signed-off-by: Daniil Antoshin --- test/e2e/snapshot/vmsop.go | 147 +++++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 32 deletions(-) diff --git a/test/e2e/snapshot/vmsop.go b/test/e2e/snapshot/vmsop.go index 2d7ae5799d..d15dba2447 100644 --- a/test/e2e/snapshot/vmsop.go +++ b/test/e2e/snapshot/vmsop.go @@ -29,6 +29,7 @@ import ( vdbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vd" vmbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vm" + vmbdabuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmbda" vmsbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmsnapshot" vmsopbuilder "github.com/deckhouse/virtualization-controller/pkg/builder/vmsop" "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -37,24 +38,32 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/util" ) -var _ = Describe("VMSOPCreateVirtualMachine", func() { +const prefix = "vmsop-create-vm-" + +var _ = Describe("VMSOPCreateVirtualMachine", Ordered, func() { var ( vd *v1alpha2.VirtualDisk + vdBlank *v1alpha2.VirtualDisk vm *v1alpha2.VirtualMachine vmsnapshot *v1alpha2.VirtualMachineSnapshot vmsop *v1alpha2.VirtualMachineSnapshotOperation + vmbda *v1alpha2.VirtualMachineBlockDeviceAttachment f = framework.NewFramework("vmsop") ) - BeforeEach(func() { + BeforeAll(func() { DeferCleanup(f.After) f.Before() }) - It("verifies that vmsop are successful", func() { - By("Environment preparation", func() { + AfterAll(func() { + DeferCleanup(f.After) + }) + + It("should prepare environment", func() { + By("create vm", func() { vd = vdbuilder.New( vdbuilder.WithName("vd-root"), vdbuilder.WithNamespace(f.Namespace().Name), @@ -63,6 +72,7 @@ var _ = Describe("VMSOPCreateVirtualMachine", func() { URL: object.ImageURLAlpineBIOS, }), ) + vm = object.NewMinimalVM("vmsop-origin-", f.Namespace().Name, vmbuilder.WithBlockDeviceRefs( v1alpha2.BlockDeviceSpecRef{ @@ -78,7 +88,26 @@ var _ = Describe("VMSOPCreateVirtualMachine", func() { util.UntilVMAgentReady(crclient.ObjectKeyFromObject(vm), framework.LongTimeout) }) - By("Create VM Snapshot", func() { + By("create vmbda", func() { + vdBlank = vdbuilder.New( + vdbuilder.WithName("vd-blank"), + vdbuilder.WithNamespace(f.Namespace().Name), + vdbuilder.WithSize(ptr.To(resource.MustParse("100Mi"))), + ) + + vmbda = vmbdabuilder.New( + vmbdabuilder.WithName("vmbda"), + vmbdabuilder.WithNamespace(f.Namespace().Name), + vmbdabuilder.WithVirtualMachineName(vm.Name), + vmbdabuilder.WithBlockDeviceRef(v1alpha2.VMBDAObjectRefKindVirtualDisk, vdBlank.Name), + ) + err := f.CreateWithDeferredDeletion(context.Background(), vmbda, vdBlank) + Expect(err).NotTo(HaveOccurred()) + + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.LongTimeout, vmbda) + }) + + By("create vmsnapshot", func() { vmsnapshot = vmsbuilder.New( vmsbuilder.WithName("vmsnapshot"), vmsbuilder.WithNamespace(f.Namespace().Name), @@ -92,32 +121,86 @@ var _ = Describe("VMSOPCreateVirtualMachine", func() { util.UntilObjectPhase(string(v1alpha2.VirtualMachineSnapshotPhaseReady), framework.LongTimeout, vmsnapshot) }) - - By("Create and wait for VMSOP", func() { - vmsop = vmsopbuilder.New( - vmsopbuilder.WithName("vmsop"), - vmsopbuilder.WithNamespace(f.Namespace().Name), - vmsopbuilder.WithVirtualMachineSnapshotName(vmsnapshot.Name), - vmsopbuilder.WithCreateVirtualMachine(&v1alpha2.VMSOPCreateVirtualMachineSpec{ - Mode: v1alpha2.SnapshotOperationModeBestEffort, - Customization: &v1alpha2.VMSOPCreateVirtualMachineCustomization{ - NamePrefix: "created-from-vmsop-", - }, - }), - ) - - err := f.CreateWithDeferredDeletion(context.Background(), vmsop) - Expect(err).NotTo(HaveOccurred()) - - util.UntilObjectPhase(string(v1alpha2.VMSOPPhaseCompleted), framework.LongTimeout, vmsop) - }) - - By("Verify that the created VM is running", func() { - newName := fmt.Sprintf("created-from-vmsop-%s", vm.Name) - createdVM, err := f.Clients.VirtClient().VirtualMachines(f.Namespace().Name).Get(context.Background(), newName, metav1.GetOptions{}) - Expect(err).NotTo(HaveOccurred()) - - util.UntilVMAgentReady(crclient.ObjectKeyFromObject(createdVM), framework.LongTimeout) - }) }) + + DescribeTable("VMSOP with different modes", + func(prefix string, mode v1alpha2.SnapshotOperationMode) { + clonedName := func(name string) string { + return fmt.Sprintf("%s%s", prefix, name) + } + + By("Create and wait for VMSOP", func() { + vmsop = vmsopbuilder.New( + vmsopbuilder.WithName(prefix+"vmsop"), + vmsopbuilder.WithNamespace(f.Namespace().Name), + vmsopbuilder.WithVirtualMachineSnapshotName(vmsnapshot.Name), + vmsopbuilder.WithCreateVirtualMachine(&v1alpha2.VMSOPCreateVirtualMachineSpec{ + Mode: mode, + Customization: &v1alpha2.VMSOPCreateVirtualMachineCustomization{ + NamePrefix: prefix, + }, + }), + ) + + err := f.CreateWithDeferredDeletion(context.Background(), vmsop) + Expect(err).NotTo(HaveOccurred()) + + util.UntilObjectPhase(string(v1alpha2.VMSOPPhaseCompleted), framework.LongTimeout, vmsop) + }) + + By("Check that resounsec doesn't exist for DryRun mode", func() { + if mode != v1alpha2.SnapshotOperationModeDryRun { + return + } + + err := f.VirtClient().VirtualMachines(f.Namespace().Name).Delete(context.Background(), clonedName(vm.Name), metav1.DeleteOptions{}) + Expect(err).To(HaveOccurred()) + + err = f.VirtClient().VirtualMachineBlockDeviceAttachments(f.Namespace().Name).Delete(context.Background(), clonedName(vmbda.Name), metav1.DeleteOptions{}) + Expect(err).To(HaveOccurred()) + + err = f.VirtClient().VirtualDisks(f.Namespace().Name).Delete(context.Background(), clonedName(vd.Name), metav1.DeleteOptions{}) + Expect(err).To(HaveOccurred()) + + err = f.VirtClient().VirtualDisks(f.Namespace().Name).Delete(context.Background(), clonedName(vdBlank.Name), metav1.DeleteOptions{}) + Expect(err).To(HaveOccurred()) + }) + + By("Verify that the created VM is running", func() { + if mode == v1alpha2.SnapshotOperationModeDryRun { + return + } + + createdVM, err := f.VirtClient().VirtualMachines(f.Namespace().Name).Get(context.Background(), clonedName(vm.Name), metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + createdVMBDA, err := f.VirtClient().VirtualMachineBlockDeviceAttachments(f.Namespace().Name).Get(context.Background(), clonedName(vmbda.Name), metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + util.UntilVMAgentReady(crclient.ObjectKeyFromObject(createdVM), framework.LongTimeout) + util.UntilObjectPhase(string(v1alpha2.BlockDeviceAttachmentPhaseAttached), framework.LongTimeout, createdVMBDA) + }) + + By("Delete created Resources", func() { + if mode == v1alpha2.SnapshotOperationModeDryRun { + return + } + + err := f.VirtClient().VirtualMachines(f.Namespace().Name).Delete(context.Background(), clonedName(vm.Name), metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + err = f.VirtClient().VirtualMachineBlockDeviceAttachments(f.Namespace().Name).Delete(context.Background(), clonedName(vmbda.Name), metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + err = f.VirtClient().VirtualDisks(f.Namespace().Name).Delete(context.Background(), clonedName(vd.Name), metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + err = f.VirtClient().VirtualDisks(f.Namespace().Name).Delete(context.Background(), clonedName(vdBlank.Name), metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + }, + Entry("VMSOP with BestEffort mode should complete successfully", "best-effort-", v1alpha2.SnapshotOperationModeBestEffort), + Entry("VMSOP with Strict mode should complete successfully", "strict-", v1alpha2.SnapshotOperationModeStrict), + Entry("VMSOP with DryRun mode should complete and do nothing", "dry-run-", v1alpha2.SnapshotOperationModeDryRun), + ) }) From 715c8a2d48d101f4944416e4d60840001d791385 Mon Sep 17 00:00:00 2001 From: Daniil Antoshin Date: Wed, 26 Nov 2025 11:18:33 +0200 Subject: [PATCH 3/3] fix lint Signed-off-by: Daniil Antoshin --- test/e2e/snapshot/vmsop.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e/snapshot/vmsop.go b/test/e2e/snapshot/vmsop.go index d15dba2447..7f67d88c6b 100644 --- a/test/e2e/snapshot/vmsop.go +++ b/test/e2e/snapshot/vmsop.go @@ -38,8 +38,6 @@ import ( "github.com/deckhouse/virtualization/test/e2e/internal/util" ) -const prefix = "vmsop-create-vm-" - var _ = Describe("VMSOPCreateVirtualMachine", Ordered, func() { var ( vd *v1alpha2.VirtualDisk