From 52bd494d0bb45f3d604107be050b24624529806d Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 18 May 2026 02:50:48 +0300 Subject: [PATCH 1/2] refactor(vmsnapshot): remove freeze/unfreeze logic from VMSnapshot controller VMSnapshot controller was incorrectly handling filesystem freeze/unfreeze operations, which is the responsibility of VDSnapshot. This change: - Removes freeze/unfreeze methods from VMSnapshot lifecycle handler - Simplifies Snapshotter interface to only necessary methods - Removes freeze/unfreeze related code from unit tests - Updates mock to match new interface Now VMSnapshot only coordinates VDSnapshot creation, while VDSnapshot is fully responsible for freeze/unfreeze operations. Signed-off-by: Roman Sysoev --- .../vmsnapshot/internal/interfaces.go | 8 - .../vmsnapshot/internal/life_cycle.go | 293 +------------- .../vmsnapshot/internal/life_cycle_test.go | 70 +--- .../controller/vmsnapshot/internal/mock.go | 369 +----------------- 4 files changed, 11 insertions(+), 729 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go index 4744a2b801..be50acb95a 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go @@ -20,7 +20,6 @@ import ( "context" corev1 "k8s.io/api/core/v1" - virtv1 "kubevirt.io/api/core/v1" "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -38,11 +37,4 @@ type Snapshotter interface { GetPersistentVolumeClaim(ctx context.Context, name, namespace string) (*corev1.PersistentVolumeClaim, error) GetVirtualDiskSnapshot(ctx context.Context, name, namespace string) (*v1alpha2.VirtualDiskSnapshot, error) CreateVirtualDiskSnapshot(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (*v1alpha2.VirtualDiskSnapshot, error) - Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - IsFrozen(kvvmi *virtv1.VirtualMachineInstance) (bool, error) - CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - GetVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) } diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go index abb7d775f6..fed7ae1d12 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -21,14 +21,11 @@ import ( "errors" "fmt" "strings" - "time" corev1 "k8s.io/api/core/v1" - k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" - virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -76,81 +73,12 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - var frozen bool - if vm != nil { - frozenCondition, ok := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, vm.Status.Conditions) - if ok && frozenCondition.Status == metav1.ConditionTrue { - frozen = true - } - } - - kvvmi, err := h.snapshotter.GetVirtualMachineInstance(ctx, vm) - if err != nil { - h.setPhaseConditionToFailed(cb, vmSnapshot, err) - return reconcile.Result{}, err - } - - err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) - switch { - case err == nil: - // OK. - case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): - log.Debug(err.Error()) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.Snapshotting). - Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) - return reconcile.Result{}, nil - case k8serrors.IsConflict(err): - log.Debug(fmt.Sprintf("failed to sync filesystem status; resource update conflict error: %s", err)) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.Snapshotting). - Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - default: - err = fmt.Errorf("failed to sync filesystem status: %w", err) - h.setPhaseConditionToFailed(cb, vmSnapshot, err) - return reconcile.Result{}, err - } - if vmSnapshot.DeletionTimestamp != nil { vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseTerminating cb. Status(metav1.ConditionUnknown). Reason(conditions.ReasonUnknown). Message("") - - if !frozen { - return reconcile.Result{}, nil - } - - canUnfreeze, err := h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm, kvvmi) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - log.Debug(err.Error()) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.Snapshotting). - Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) - return reconcile.Result{}, nil - } - if k8serrors.IsConflict(err) { - log.Debug(fmt.Sprintf("failed to freeze filesystem; resource update conflict error: %s", err)) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.Snapshotting). - Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - h.setPhaseConditionToFailed(cb, vmSnapshot, err) - return reconcile.Result{}, err - } - - if !canUnfreeze { - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - return reconcile.Result{}, nil } @@ -163,27 +91,6 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu Status(readyCondition.Status). Reason(conditions.CommonReason(readyCondition.Reason)). Message(readyCondition.Message) - - if !frozen { - return reconcile.Result{}, nil - } - - canUnfreeze, err := h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm, kvvmi) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - log.Debug(err.Error()) - return reconcile.Result{}, nil - } - if k8serrors.IsConflict(err) { - log.Debug(fmt.Sprintf("failed to unfreeze filesystem; resource update conflict error: %s", err)) - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - cb.Message(fmt.Sprintf("%s, %s", err.Error(), cb.Condition().Message)) - return reconcile.Result{}, fmt.Errorf("failed to unfreeze filesystem: %w", err) - } - if !canUnfreeze { - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } return reconcile.Result{}, nil case v1alpha2.VirtualMachineSnapshotPhaseReady: // Ensure vd snapshots aren't lost. @@ -291,45 +198,6 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - needToFreeze, err := h.needToFreeze(vm, kvvmi, vmSnapshot) - if err != nil { - return reconcile.Result{}, err - } - - canFreeze, err := h.snapshotter.CanFreeze(ctx, kvvmi) - if err != nil { - h.setPhaseConditionToFailed(cb, vmSnapshot, err) - return reconcile.Result{}, err - } - isAwaitingConsistency := needToFreeze && !canFreeze && vmSnapshot.Spec.RequiredConsistency - if isAwaitingConsistency { - vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseFailed - msg := fmt.Sprintf( - "Cannot take a consistent snapshot of virtual machine %q.", - vm.Name, - ) - - agentReadyCondition, _ := conditions.GetCondition(vmcondition.TypeAgentReady, vm.Status.Conditions) - if agentReadyCondition.Status != metav1.ConditionTrue { - msg = fmt.Sprintf( - "Cannot take a consistent snapshot of virtual machine %q: virtual machine agent is not ready and the virtual machine cannot be frozen.", - vm.Name, - ) - } - - h.recorder.Event( - vmSnapshot, - corev1.EventTypeWarning, - v1alpha2.ReasonVMSnapshottingFailed, - msg, - ) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.PotentiallyInconsistent). - Message(msg) - return reconcile.Result{}, nil - } - if vmSnapshot.Status.Phase == v1alpha2.VirtualMachineSnapshotPhasePending { vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseInProgress h.recorder.Event( @@ -340,39 +208,11 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu ) cb. Status(metav1.ConditionFalse). - Reason(vmscondition.FileSystemFreezing). + Reason(vmscondition.Snapshotting). Message("The snapshotting process has started.") return reconcile.Result{Requeue: true}, nil } - var hasFrozen bool - - // 2. Ensure the virtual machine is consistent for snapshotting. - if needToFreeze { - hasFrozen, err = h.freezeVirtualMachine(ctx, kvvmi, vmSnapshot) - if err != nil { - if k8serrors.IsConflict(err) { - log.Debug(fmt.Sprintf("failed to freeze filesystem; resource update conflict error: %s", err)) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.Snapshotting). - Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - h.setPhaseConditionToFailed(cb, vmSnapshot, err) - return reconcile.Result{}, err - } - } - - if hasFrozen { - vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseInProgress - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.FileSystemFreezing). - Message(fmt.Sprintf("The virtual machine %q is in the process of being frozen for taking a snapshot.", vm.Name)) - return reconcile.Result{}, nil - } - // 3. Create secret. err = h.ensureSecret(ctx, vm, vmSnapshot) if err != nil { @@ -446,59 +286,15 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu vmSnapshot.Status.Consistent = nil } - // 7. Unfreeze VirtualMachine if can. - unfrozen, err := h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm, kvvmi) - if err != nil { - if k8serrors.IsConflict(err) { - log.Debug(fmt.Sprintf("failed to unfreeze filesystem; resource update conflict error: %s", err)) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.Snapshotting). - Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseInProgress - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.FileSystemUnfreezing). - Message(service.CapitalizeFirstLetter(err.Error() + ".")) - return reconcile.Result{}, err - } - - // 8. Fill status resources. + // 7. Fill status resources. err = h.fillStatusResources(ctx, vmSnapshot, vm) if err != nil { h.setPhaseConditionToFailed(cb, vmSnapshot, err) return reconcile.Result{}, err } - // 9. Synchronize FSFreezeRequest with KVVMI status. - err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) - switch { - case err == nil: - // OK. - case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): - log.Debug(err.Error()) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.Snapshotting). - Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) - return reconcile.Result{}, nil - case k8serrors.IsConflict(err): - log.Debug(fmt.Sprintf("failed to sync filesystem status; resource update conflict error: %s", err)) - cb. - Status(metav1.ConditionFalse). - Reason(vmscondition.Snapshotting). - Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - default: - err = fmt.Errorf("failed to sync filesystem status: %w", err) - h.setPhaseConditionToFailed(cb, vmSnapshot, err) - return reconcile.Result{}, err - } - - // 10. Move to Ready phase. - log.Debug("The virtual disk snapshots are taken: the virtual machine snapshot is Ready now", "unfrozen", unfrozen) + // 8. Move to Ready phase. + log.Debug("The virtual disk snapshots are taken: the virtual machine snapshot is Ready now") vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseReady h.recorder.Event( @@ -641,87 +437,6 @@ func (h LifeCycleHandler) areVirtualDiskSnapshotsConsistent(vdSnapshots []*v1alp return true } -func (h LifeCycleHandler) needToFreeze(vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance, vmsnapshot *v1alpha2.VirtualMachineSnapshot) (bool, error) { - if vmsnapshot.Status.Consistent != nil && *vmsnapshot.Status.Consistent { - return false, nil - } - - if !vmsnapshot.Spec.RequiredConsistency { - return false, nil - } - - if vm.Status.Phase == v1alpha2.MachineStopped { - return false, nil - } - - isFrozen, err := h.snapshotter.IsFrozen(kvvmi) - if err != nil { - return false, err - } - if isFrozen { - return false, nil - } - - return true, nil -} - -func (h LifeCycleHandler) freezeVirtualMachine(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance, vmSnapshot *v1alpha2.VirtualMachineSnapshot) (bool, error) { - if kvvmi.Status.Phase != virtv1.Running { - return false, fmt.Errorf("cannot freeze not Running %s/%s virtual machine", kvvmi.Namespace, kvvmi.Name) - } - - err := h.snapshotter.Freeze(ctx, kvvmi) - if err != nil { - return false, fmt.Errorf("freeze the virtual machine %s/%s: %w", kvvmi.Namespace, kvvmi.Name, err) - } - - h.recorder.Event( - vmSnapshot, - corev1.EventTypeNormal, - v1alpha2.ReasonVMSnapshottingFrozen, - fmt.Sprintf("The file system of the virtual machine %q is frozen.", kvvmi.Name), - ) - - return true, nil -} - -func (h LifeCycleHandler) unfreezeVirtualMachineIfCan(ctx context.Context, vmSnapshot *v1alpha2.VirtualMachineSnapshot, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if vm == nil || vm.Status.Phase != v1alpha2.MachineRunning { - return false, nil - } - - isFrozen, err := h.snapshotter.IsFrozen(kvvmi) - if err != nil { - return false, err - } - if !isFrozen { - return false, nil - } - - canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualMachineSnapshot(ctx, vmSnapshot.Name, vm, kvvmi) - if err != nil { - return false, err - } - - if !canUnfreeze { - return false, nil - } - - err = h.snapshotter.Unfreeze(ctx, kvvmi) - if err != nil { - return false, fmt.Errorf("unfreeze the virtual machine %q: %w", vm.Name, err) - } - - h.recorder.Event( - vmSnapshot, - corev1.EventTypeNormal, - v1alpha2.ReasonVMSnapshottingThawed, - fmt.Sprintf("The file system of the virtual machine %q is thawed.", vm.Name), - ) - - return true, nil -} - var ( ErrBlockDevicesNotReady = errors.New("block devices not ready") ErrVirtualDiskNotReady = errors.New("virtual disk not ready") diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle_test.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle_test.go index ae8fc6d345..2b93af3ea5 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle_test.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle_test.go @@ -25,7 +25,6 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" - virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/deckhouse/virtualization-controller/pkg/common/testutil" @@ -43,7 +42,6 @@ var _ = Describe("LifeCycle handler", func() { var storer *StorerMock var vd *v1alpha2.VirtualDisk var vm *v1alpha2.VirtualMachine - var kvvmi *virtv1.VirtualMachineInstance var secret *corev1.Secret var vdSnapshot *v1alpha2.VirtualDiskSnapshot var vmSnapshot *v1alpha2.VirtualMachineSnapshot @@ -90,13 +88,6 @@ var _ = Describe("LifeCycle handler", func() { }, } - kvvmi = &virtv1.VirtualMachineInstance{ - ObjectMeta: metav1.ObjectMeta{Name: "vm"}, - Status: virtv1.VirtualMachineInstanceStatus{ - Phase: virtv1.Running, - }, - } - secret = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: vm.Name}, } @@ -133,30 +124,12 @@ var _ = Describe("LifeCycle handler", func() { GetVirtualMachineFunc: func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil }, - IsFrozenFunc: func(*virtv1.VirtualMachineInstance) (bool, error) { - return true, nil - }, - CanUnfreezeWithVirtualMachineSnapshotFunc: func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { - return true, nil - }, - CanFreezeFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { - return false, nil - }, - UnfreezeFunc: func(ctx context.Context, _ *virtv1.VirtualMachineInstance) error { - return nil - }, GetSecretFunc: func(_ context.Context, _, _ string) (*corev1.Secret, error) { return secret, nil }, GetVirtualDiskSnapshotFunc: func(_ context.Context, _, _ string) (*v1alpha2.VirtualDiskSnapshot, error) { return vdSnapshot, nil }, - GetVirtualMachineInstanceFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { - return kvvmi, nil - }, - SyncFSFreezeRequestFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { - return nil - }, } var err error @@ -293,50 +266,9 @@ var _ = Describe("LifeCycle handler", func() { Expect(vmSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualMachineSnapshotPhaseInProgress)) ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.FileSystemFreezing.String())) + Expect(ready.Reason).To(Equal(vmscondition.Snapshotting.String())) Expect(ready.Message).To(Equal("The snapshotting process has started.")) }) - - It("The virtual machine is potentially inconsistent", func() { - snapshotter.IsFrozenFunc = func(_ *virtv1.VirtualMachineInstance) (bool, error) { - return false, nil - } - snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { - return false, nil - } - - h := NewLifeCycleHandler(recorder, snapshotter, storer, fakeClient) - - _, err := h.Handle(testContext(), vmSnapshot) - Expect(err).To(BeNil()) - Expect(vmSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualMachineSnapshotPhaseFailed)) - ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) - Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.PotentiallyInconsistent.String())) - Expect(ready.Message).ToNot(BeEmpty()) - }) - - It("The virtual machine has frozen", func() { - snapshotter.IsFrozenFunc = func(_ *virtv1.VirtualMachineInstance) (bool, error) { - return false, nil - } - snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { - return true, nil - } - snapshotter.FreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { - return nil - } - - h := NewLifeCycleHandler(recorder, snapshotter, storer, fakeClient) - - _, err := h.Handle(testContext(), vmSnapshot) - Expect(err).To(BeNil()) - Expect(vmSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualMachineSnapshotPhaseInProgress)) - ready, _ := conditions.GetCondition(vmscondition.VirtualMachineSnapshotReadyType, vmSnapshot.Status.Conditions) - Expect(ready.Status).To(Equal(metav1.ConditionFalse)) - Expect(ready.Reason).To(Equal(vmscondition.FileSystemFreezing.String())) - Expect(ready.Message).ToNot(BeEmpty()) - }) }) Context("The virtual machine snapshot is Ready", func() { diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go index 67e5c82199..6dd0424961 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go @@ -7,7 +7,6 @@ import ( "context" "github.com/deckhouse/virtualization/api/core/v1alpha2" corev1 "k8s.io/api/core/v1" - virtv1 "kubevirt.io/api/core/v1" "sync" ) @@ -99,18 +98,9 @@ var _ Snapshotter = &SnapshotterMock{} // // // make and configure a mocked Snapshotter // mockedSnapshotter := &SnapshotterMock{ -// CanFreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { -// panic("mock out the CanFreeze method") -// }, -// CanUnfreezeWithVirtualMachineSnapshotFunc: func(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { -// panic("mock out the CanUnfreezeWithVirtualMachineSnapshot method") -// }, // CreateVirtualDiskSnapshotFunc: func(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (*v1alpha2.VirtualDiskSnapshot, error) { // panic("mock out the CreateVirtualDiskSnapshot method") // }, -// FreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { -// panic("mock out the Freeze method") -// }, // GetPersistentVolumeClaimFunc: func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { // panic("mock out the GetPersistentVolumeClaim method") // }, @@ -126,18 +116,6 @@ var _ Snapshotter = &SnapshotterMock{} // GetVirtualMachineFunc: func(ctx context.Context, name string, namespace string) (*v1alpha2.VirtualMachine, error) { // panic("mock out the GetVirtualMachine method") // }, -// GetVirtualMachineInstanceFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { -// panic("mock out the GetVirtualMachineInstance method") -// }, -// IsFrozenFunc: func(kvvmi *virtv1.VirtualMachineInstance) (bool, error) { -// panic("mock out the IsFrozen method") -// }, -// SyncFSFreezeRequestFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { -// panic("mock out the SyncFSFreezeRequest method") -// }, -// UnfreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { -// panic("mock out the Unfreeze method") -// }, // } // // // use mockedSnapshotter in code that requires Snapshotter @@ -145,18 +123,9 @@ var _ Snapshotter = &SnapshotterMock{} // // } type SnapshotterMock struct { - // CanFreezeFunc mocks the CanFreeze method. - CanFreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - - // CanUnfreezeWithVirtualMachineSnapshotFunc mocks the CanUnfreezeWithVirtualMachineSnapshot method. - CanUnfreezeWithVirtualMachineSnapshotFunc func(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - // CreateVirtualDiskSnapshotFunc mocks the CreateVirtualDiskSnapshot method. CreateVirtualDiskSnapshotFunc func(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (*v1alpha2.VirtualDiskSnapshot, error) - // FreezeFunc mocks the Freeze method. - FreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - // GetPersistentVolumeClaimFunc mocks the GetPersistentVolumeClaim method. GetPersistentVolumeClaimFunc func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) @@ -172,38 +141,8 @@ type SnapshotterMock struct { // GetVirtualMachineFunc mocks the GetVirtualMachine method. GetVirtualMachineFunc func(ctx context.Context, name string, namespace string) (*v1alpha2.VirtualMachine, error) - // GetVirtualMachineInstanceFunc mocks the GetVirtualMachineInstance method. - GetVirtualMachineInstanceFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) - - // IsFrozenFunc mocks the IsFrozen method. - IsFrozenFunc func(kvvmi *virtv1.VirtualMachineInstance) (bool, error) - - // SyncFSFreezeRequestFunc mocks the SyncFSFreezeRequest method. - SyncFSFreezeRequestFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - - // UnfreezeFunc mocks the Unfreeze method. - UnfreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - // calls tracks calls to the methods. calls struct { - // CanFreeze holds details about calls to the CanFreeze method. - CanFreeze []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // Kvvmi is the kvvmi argument value. - Kvvmi *virtv1.VirtualMachineInstance - } - // CanUnfreezeWithVirtualMachineSnapshot holds details about calls to the CanUnfreezeWithVirtualMachineSnapshot method. - CanUnfreezeWithVirtualMachineSnapshot []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // VmSnapshotName is the vmSnapshotName argument value. - VmSnapshotName string - // VM is the vm argument value. - VM *v1alpha2.VirtualMachine - // Kvvmi is the kvvmi argument value. - Kvvmi *virtv1.VirtualMachineInstance - } // CreateVirtualDiskSnapshot holds details about calls to the CreateVirtualDiskSnapshot method. CreateVirtualDiskSnapshot []struct { // Ctx is the ctx argument value. @@ -211,13 +150,6 @@ type SnapshotterMock struct { // VdSnapshot is the vdSnapshot argument value. VdSnapshot *v1alpha2.VirtualDiskSnapshot } - // Freeze holds details about calls to the Freeze method. - Freeze []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // Kvvmi is the kvvmi argument value. - Kvvmi *virtv1.VirtualMachineInstance - } // GetPersistentVolumeClaim holds details about calls to the GetPersistentVolumeClaim method. GetPersistentVolumeClaim []struct { // Ctx is the ctx argument value. @@ -263,126 +195,13 @@ type SnapshotterMock struct { // Namespace is the namespace argument value. Namespace string } - // GetVirtualMachineInstance holds details about calls to the GetVirtualMachineInstance method. - GetVirtualMachineInstance []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // VM is the vm argument value. - VM *v1alpha2.VirtualMachine - } - // IsFrozen holds details about calls to the IsFrozen method. - IsFrozen []struct { - // Kvvmi is the kvvmi argument value. - Kvvmi *virtv1.VirtualMachineInstance - } - // SyncFSFreezeRequest holds details about calls to the SyncFSFreezeRequest method. - SyncFSFreezeRequest []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // Kvvmi is the kvvmi argument value. - Kvvmi *virtv1.VirtualMachineInstance - } - // Unfreeze holds details about calls to the Unfreeze method. - Unfreeze []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // Kvvmi is the kvvmi argument value. - Kvvmi *virtv1.VirtualMachineInstance - } - } - lockCanFreeze sync.RWMutex - lockCanUnfreezeWithVirtualMachineSnapshot sync.RWMutex - lockCreateVirtualDiskSnapshot sync.RWMutex - lockFreeze sync.RWMutex - lockGetPersistentVolumeClaim sync.RWMutex - lockGetSecret sync.RWMutex - lockGetVirtualDisk sync.RWMutex - lockGetVirtualDiskSnapshot sync.RWMutex - lockGetVirtualMachine sync.RWMutex - lockGetVirtualMachineInstance sync.RWMutex - lockIsFrozen sync.RWMutex - lockSyncFSFreezeRequest sync.RWMutex - lockUnfreeze sync.RWMutex -} - -// CanFreeze calls CanFreezeFunc. -func (mock *SnapshotterMock) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if mock.CanFreezeFunc == nil { - panic("SnapshotterMock.CanFreezeFunc: method is nil but Snapshotter.CanFreeze was just called") - } - callInfo := struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance - }{ - Ctx: ctx, - Kvvmi: kvvmi, - } - mock.lockCanFreeze.Lock() - mock.calls.CanFreeze = append(mock.calls.CanFreeze, callInfo) - mock.lockCanFreeze.Unlock() - return mock.CanFreezeFunc(ctx, kvvmi) -} - -// CanFreezeCalls gets all the calls that were made to CanFreeze. -// Check the length with: -// -// len(mockedSnapshotter.CanFreezeCalls()) -func (mock *SnapshotterMock) CanFreezeCalls() []struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance -} { - var calls []struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance - } - mock.lockCanFreeze.RLock() - calls = mock.calls.CanFreeze - mock.lockCanFreeze.RUnlock() - return calls -} - -// CanUnfreezeWithVirtualMachineSnapshot calls CanUnfreezeWithVirtualMachineSnapshotFunc. -func (mock *SnapshotterMock) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if mock.CanUnfreezeWithVirtualMachineSnapshotFunc == nil { - panic("SnapshotterMock.CanUnfreezeWithVirtualMachineSnapshotFunc: method is nil but Snapshotter.CanUnfreezeWithVirtualMachineSnapshot was just called") } - callInfo := struct { - Ctx context.Context - VmSnapshotName string - VM *v1alpha2.VirtualMachine - Kvvmi *virtv1.VirtualMachineInstance - }{ - Ctx: ctx, - VmSnapshotName: vmSnapshotName, - VM: vm, - Kvvmi: kvvmi, - } - mock.lockCanUnfreezeWithVirtualMachineSnapshot.Lock() - mock.calls.CanUnfreezeWithVirtualMachineSnapshot = append(mock.calls.CanUnfreezeWithVirtualMachineSnapshot, callInfo) - mock.lockCanUnfreezeWithVirtualMachineSnapshot.Unlock() - return mock.CanUnfreezeWithVirtualMachineSnapshotFunc(ctx, vmSnapshotName, vm, kvvmi) -} - -// CanUnfreezeWithVirtualMachineSnapshotCalls gets all the calls that were made to CanUnfreezeWithVirtualMachineSnapshot. -// Check the length with: -// -// len(mockedSnapshotter.CanUnfreezeWithVirtualMachineSnapshotCalls()) -func (mock *SnapshotterMock) CanUnfreezeWithVirtualMachineSnapshotCalls() []struct { - Ctx context.Context - VmSnapshotName string - VM *v1alpha2.VirtualMachine - Kvvmi *virtv1.VirtualMachineInstance -} { - var calls []struct { - Ctx context.Context - VmSnapshotName string - VM *v1alpha2.VirtualMachine - Kvvmi *virtv1.VirtualMachineInstance - } - mock.lockCanUnfreezeWithVirtualMachineSnapshot.RLock() - calls = mock.calls.CanUnfreezeWithVirtualMachineSnapshot - mock.lockCanUnfreezeWithVirtualMachineSnapshot.RUnlock() - return calls + lockCreateVirtualDiskSnapshot sync.RWMutex + lockGetPersistentVolumeClaim sync.RWMutex + lockGetSecret sync.RWMutex + lockGetVirtualDisk sync.RWMutex + lockGetVirtualDiskSnapshot sync.RWMutex + lockGetVirtualMachine sync.RWMutex } // CreateVirtualDiskSnapshot calls CreateVirtualDiskSnapshotFunc. @@ -421,42 +240,6 @@ func (mock *SnapshotterMock) CreateVirtualDiskSnapshotCalls() []struct { return calls } -// Freeze calls FreezeFunc. -func (mock *SnapshotterMock) Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { - if mock.FreezeFunc == nil { - panic("SnapshotterMock.FreezeFunc: method is nil but Snapshotter.Freeze was just called") - } - callInfo := struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance - }{ - Ctx: ctx, - Kvvmi: kvvmi, - } - mock.lockFreeze.Lock() - mock.calls.Freeze = append(mock.calls.Freeze, callInfo) - mock.lockFreeze.Unlock() - return mock.FreezeFunc(ctx, kvvmi) -} - -// FreezeCalls gets all the calls that were made to Freeze. -// Check the length with: -// -// len(mockedSnapshotter.FreezeCalls()) -func (mock *SnapshotterMock) FreezeCalls() []struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance -} { - var calls []struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance - } - mock.lockFreeze.RLock() - calls = mock.calls.Freeze - mock.lockFreeze.RUnlock() - return calls -} - // GetPersistentVolumeClaim calls GetPersistentVolumeClaimFunc. func (mock *SnapshotterMock) GetPersistentVolumeClaim(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { if mock.GetPersistentVolumeClaimFunc == nil { @@ -656,143 +439,3 @@ func (mock *SnapshotterMock) GetVirtualMachineCalls() []struct { mock.lockGetVirtualMachine.RUnlock() return calls } - -// GetVirtualMachineInstance calls GetVirtualMachineInstanceFunc. -func (mock *SnapshotterMock) GetVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { - if mock.GetVirtualMachineInstanceFunc == nil { - panic("SnapshotterMock.GetVirtualMachineInstanceFunc: method is nil but Snapshotter.GetVirtualMachineInstance was just called") - } - callInfo := struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine - }{ - Ctx: ctx, - VM: vm, - } - mock.lockGetVirtualMachineInstance.Lock() - mock.calls.GetVirtualMachineInstance = append(mock.calls.GetVirtualMachineInstance, callInfo) - mock.lockGetVirtualMachineInstance.Unlock() - return mock.GetVirtualMachineInstanceFunc(ctx, vm) -} - -// GetVirtualMachineInstanceCalls gets all the calls that were made to GetVirtualMachineInstance. -// Check the length with: -// -// len(mockedSnapshotter.GetVirtualMachineInstanceCalls()) -func (mock *SnapshotterMock) GetVirtualMachineInstanceCalls() []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine -} { - var calls []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine - } - mock.lockGetVirtualMachineInstance.RLock() - calls = mock.calls.GetVirtualMachineInstance - mock.lockGetVirtualMachineInstance.RUnlock() - return calls -} - -// IsFrozen calls IsFrozenFunc. -func (mock *SnapshotterMock) IsFrozen(kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if mock.IsFrozenFunc == nil { - panic("SnapshotterMock.IsFrozenFunc: method is nil but Snapshotter.IsFrozen was just called") - } - callInfo := struct { - Kvvmi *virtv1.VirtualMachineInstance - }{ - Kvvmi: kvvmi, - } - mock.lockIsFrozen.Lock() - mock.calls.IsFrozen = append(mock.calls.IsFrozen, callInfo) - mock.lockIsFrozen.Unlock() - return mock.IsFrozenFunc(kvvmi) -} - -// IsFrozenCalls gets all the calls that were made to IsFrozen. -// Check the length with: -// -// len(mockedSnapshotter.IsFrozenCalls()) -func (mock *SnapshotterMock) IsFrozenCalls() []struct { - Kvvmi *virtv1.VirtualMachineInstance -} { - var calls []struct { - Kvvmi *virtv1.VirtualMachineInstance - } - mock.lockIsFrozen.RLock() - calls = mock.calls.IsFrozen - mock.lockIsFrozen.RUnlock() - return calls -} - -// SyncFSFreezeRequest calls SyncFSFreezeRequestFunc. -func (mock *SnapshotterMock) SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { - if mock.SyncFSFreezeRequestFunc == nil { - panic("SnapshotterMock.SyncFSFreezeRequestFunc: method is nil but Snapshotter.SyncFSFreezeRequest was just called") - } - callInfo := struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance - }{ - Ctx: ctx, - Kvvmi: kvvmi, - } - mock.lockSyncFSFreezeRequest.Lock() - mock.calls.SyncFSFreezeRequest = append(mock.calls.SyncFSFreezeRequest, callInfo) - mock.lockSyncFSFreezeRequest.Unlock() - return mock.SyncFSFreezeRequestFunc(ctx, kvvmi) -} - -// SyncFSFreezeRequestCalls gets all the calls that were made to SyncFSFreezeRequest. -// Check the length with: -// -// len(mockedSnapshotter.SyncFSFreezeRequestCalls()) -func (mock *SnapshotterMock) SyncFSFreezeRequestCalls() []struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance -} { - var calls []struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance - } - mock.lockSyncFSFreezeRequest.RLock() - calls = mock.calls.SyncFSFreezeRequest - mock.lockSyncFSFreezeRequest.RUnlock() - return calls -} - -// Unfreeze calls UnfreezeFunc. -func (mock *SnapshotterMock) Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { - if mock.UnfreezeFunc == nil { - panic("SnapshotterMock.UnfreezeFunc: method is nil but Snapshotter.Unfreeze was just called") - } - callInfo := struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance - }{ - Ctx: ctx, - Kvvmi: kvvmi, - } - mock.lockUnfreeze.Lock() - mock.calls.Unfreeze = append(mock.calls.Unfreeze, callInfo) - mock.lockUnfreeze.Unlock() - return mock.UnfreezeFunc(ctx, kvvmi) -} - -// UnfreezeCalls gets all the calls that were made to Unfreeze. -// Check the length with: -// -// len(mockedSnapshotter.UnfreezeCalls()) -func (mock *SnapshotterMock) UnfreezeCalls() []struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance -} { - var calls []struct { - Ctx context.Context - Kvvmi *virtv1.VirtualMachineInstance - } - mock.lockUnfreeze.RLock() - calls = mock.calls.Unfreeze - mock.lockUnfreeze.RUnlock() - return calls -} From ab9013e8e1517da68e98330aabd6580459fe9f46 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 18 May 2026 09:41:05 +0300 Subject: [PATCH 2/2] fix(vdsnapshot): wait for other snapshots before unfreezing filesystem VDSnapshot controller now checks if other VDSnapshots are still in progress before attempting to unfreeze. If other snapshots are active, it will requeue and retry until all snapshots are ready. This fixes a regression where multiple VDSnapshots created by VMSnapshot would race each other - the first one to complete would block the others from unfreezing. Additionally: - Added unit test case for waiting when other VDSnapshots are active - Updated existing test to expect canUnfreeze=true for Ready phase - Added UnfreezeFunc to mock in vdsnapshot tests Signed-off-by: Roman Sysoev --- .../vdsnapshot/internal/life_cycle.go | 16 +++++++++ .../vdsnapshot/internal/life_cycle_test.go | 33 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go index 04f9863892..53bd353254 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -414,6 +414,22 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu } } + // Check if we can unfreeze now or need to wait for other VDSnapshots. + canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) + if err != nil { + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + if !canUnfreeze { + log.Debug("waiting for other virtual disk snapshots to be ready before unfreezing filesystem") + vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress + cb. + Status(metav1.ConditionFalse). + Reason(vdscondition.Snapshotting). + Message(service.CapitalizeFirstLetter("Waiting for other virtual disk snapshots to be ready before unfreezing filesystem.")) + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } + err = h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if k8serrors.IsConflict(err) { diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle_test.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle_test.go index bf74a51a36..7dbffb4a25 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle_test.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle_test.go @@ -100,6 +100,9 @@ var _ = Describe("LifeCycle handler", func() { SyncFSFreezeRequestFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { return nil }, + UnfreezeFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { + return nil + }, } }) @@ -187,7 +190,7 @@ var _ = Describe("LifeCycle handler", func() { return vs, nil } snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { - return false, nil + return true, nil } h := NewLifeCycleHandler(snapshotter) @@ -203,6 +206,34 @@ var _ = Describe("LifeCycle handler", func() { Expect(ready.Message).To(BeEmpty()) }) + It("waits for other VDSnapshots before unfreezing", func() { + vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress + // Set Consistent to skip the early branch that sets consistent=true + vdSnapshot.Status.Consistent = ptr.To(true) + + snapshotter.GetVolumeSnapshotFunc = func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { + vs.Status = &vsv1.VolumeSnapshotStatus{ + ReadyToUse: ptr.To(true), + } + return vs, nil + } + snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { + // Simulate other VDSnapshots are still in progress + return false, nil + } + + h := NewLifeCycleHandler(snapshotter) + + _, err := h.Handle(testContext(), vdSnapshot) + Expect(err).To(BeNil()) + // Should stay in InProgress when other snapshots are active + Expect(vdSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualDiskSnapshotPhaseInProgress)) + ready, _ := conditions.GetCondition(vdscondition.VirtualDiskSnapshotReadyType, vdSnapshot.Status.Conditions) + Expect(ready.Status).To(Equal(metav1.ConditionFalse)) + Expect(ready.Reason).To(Equal(vdscondition.Snapshotting.String())) + Expect(ready.Message).To(ContainSubstring("other virtual disk snapshots")) + }) + It("fails when the virtual disk is missing", func() { snapshotter.GetVirtualDiskFunc = func(_ context.Context, _, _ string) (*v1alpha2.VirtualDisk, error) { return nil, nil