From d34b12bf00afeabaad85a37c663aade65f32caa2 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Sun, 9 Nov 2025 02:08:49 +0300 Subject: [PATCH 01/23] fix(vdsnapshot): resolve fsfreeze race condition Signed-off-by: Roman Sysoev --- .../pkg/common/annotations/annotations.go | 3 + .../controller/service/snapshot_service.go | 155 ++++++++++++++++-- .../vdsnapshot/internal/deletion.go | 4 + .../vdsnapshot/internal/interfaces.go | 4 +- .../vdsnapshot/internal/life_cycle.go | 32 +++- .../vdsnapshot/internal/life_cycle_test.go | 27 +-- .../controller/vdsnapshot/internal/mock.go | 44 +++-- .../vmsnapshot/internal/interfaces.go | 4 +- .../vmsnapshot/internal/life_cycle.go | 49 +++++- .../vmsnapshot/internal/life_cycle_test.go | 24 +-- .../controller/vmsnapshot/internal/mock.go | 44 +++-- 11 files changed, 305 insertions(+), 85 deletions(-) diff --git a/images/virtualization-artifact/pkg/common/annotations/annotations.go b/images/virtualization-artifact/pkg/common/annotations/annotations.go index ba7453b2a8..38244f7b4e 100644 --- a/images/virtualization-artifact/pkg/common/annotations/annotations.go +++ b/images/virtualization-artifact/pkg/common/annotations/annotations.go @@ -187,6 +187,9 @@ const ( AnnVMOPUID = AnnAPIGroupV + "/vmop-uid" // AnnVMOPSnapshotName is an annotation on vmop that represents name a snapshot created for VMOP. AnnVMOPSnapshotName = AnnAPIGroupV + "/vmop-snapshot-name" + + // AnnVMFilesystemFrozenRequest is an annotation on a virtual machine that indicates a request to freeze or unfreeze the filesystem has been sent. + AnnVMFilesystemFrozenRequest = AnnAPIGroup + "/virtual-machine-filesystem-request" ) // AddAnnotation adds an annotation to an object diff --git a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go index bcc45dce48..87191ece9b 100644 --- a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go +++ b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go @@ -18,6 +18,7 @@ package service import ( "context" + "errors" "fmt" vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" @@ -27,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/deckhouse/virtualization-controller/pkg/common/annotations" "github.com/deckhouse/virtualization-controller/pkg/common/object" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization/api/client/kubeclient" @@ -35,6 +37,16 @@ import ( subv1alpha2 "github.com/deckhouse/virtualization/api/subresources/v1alpha2" ) +const ( + RequestFilesystemFreeze = "freeze" + RequestFilesystemUnfreeze = "unfreeze" +) + +var ( + ErrUntrustedFilesystemFrozenCondition = errors.New("the filesystem status cannot be processed correctly") + ErrUnexpectedFilesystemFrozenRequest = errors.New("found unexpected filesystem frozen request in the virtual machine annotations") +) + type SnapshotService struct { virtClient kubeclient.Client client Client @@ -49,29 +61,78 @@ func NewSnapshotService(virtClient kubeclient.Client, client Client, protection } } -func (s *SnapshotService) IsFrozen(vm *v1alpha2.VirtualMachine) bool { +func (s *SnapshotService) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { if vm == nil { - return false + return false, nil } filesystemFrozen, _ := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, vm.Status.Conditions) - return filesystemFrozen.Status == metav1.ConditionTrue && filesystemFrozen.Reason == vmcondition.ReasonFilesystemFrozen.String() + if vm.Annotations != nil { + if requestType, ok := vm.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + switch { + case requestType == RequestFilesystemFreeze && filesystemFrozen.Status == metav1.ConditionTrue && filesystemFrozen.Reason == vmcondition.ReasonFilesystemFrozen.String(): + err := s.removeAnnFSFreezeRequest(ctx, RequestFilesystemFreeze, vm) + if err != nil { + return false, err + } + return true, nil + case requestType == RequestFilesystemUnfreeze && filesystemFrozen.Status != metav1.ConditionTrue: + err := s.removeAnnFSFreezeRequest(ctx, RequestFilesystemUnfreeze, vm) + if err != nil { + return false, err + } + return false, nil + default: + return false, ErrUntrustedFilesystemFrozenCondition + } + } + } + + return filesystemFrozen.Status == metav1.ConditionTrue && filesystemFrozen.Reason == vmcondition.ReasonFilesystemFrozen.String(), nil } -func (s *SnapshotService) CanFreeze(vm *v1alpha2.VirtualMachine) bool { - if vm == nil || vm.Status.Phase != v1alpha2.MachineRunning || s.IsFrozen(vm) { - return false +func (s *SnapshotService) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { + if vm == nil || vm.Status.Phase != v1alpha2.MachineRunning { + return false, nil + } + + isFrozen, err := s.IsFrozen(ctx, vm) + if err != nil { + return false, err + } + if isFrozen { + return false, nil } agentReady, _ := conditions.GetCondition(vmcondition.TypeAgentReady, vm.Status.Conditions) - return agentReady.Status == metav1.ConditionTrue + return agentReady.Status == metav1.ConditionTrue, nil } func (s *SnapshotService) Freeze(ctx context.Context, name, namespace string) error { - err := s.virtClient.VirtualMachines(namespace).Freeze(ctx, name, subv1alpha2.VirtualMachineFreeze{}) + vm, err := object.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &v1alpha2.VirtualMachine{}, &client.GetOptions{}) if err != nil { + return fmt.Errorf("failed to fetch virtual machine %s/%s: %w", namespace, name, err) + } + + if vm.Annotations != nil { + if _, ok := vm.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + return ErrUnexpectedFilesystemFrozenRequest + } + } + + err = s.annotateWithFSFreezeRequest(ctx, RequestFilesystemFreeze, vm) + if err != nil { + return fmt.Errorf("failed to annotate virtual machine with filesystem freeze request: %w", err) + } + + err = s.virtClient.VirtualMachines(namespace).Freeze(ctx, name, subv1alpha2.VirtualMachineFreeze{}) + if err != nil { + err := s.removeAnnFSFreezeRequest(ctx, RequestFilesystemFreeze, vm) + if err != nil { + return fmt.Errorf("failed to remove virtual machine annotation with filesystem freeze request: %w", err) + } return fmt.Errorf("failed to freeze virtual machine %s/%s: %w", namespace, name, err) } @@ -79,7 +140,16 @@ func (s *SnapshotService) Freeze(ctx context.Context, name, namespace string) er } func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { - if vm == nil || !s.IsFrozen(vm) { + if vm == nil { + return false, nil + } + + isFrozen, err := s.IsFrozen(ctx, vm) + if err != nil { + return false, err + } + + if !isFrozen { return false, nil } @@ -91,7 +161,7 @@ func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context } var vdSnapshots v1alpha2.VirtualDiskSnapshotList - err := s.client.List(ctx, &vdSnapshots, &client.ListOptions{ + err = s.client.List(ctx, &vdSnapshots, &client.ListOptions{ Namespace: vm.Namespace, }) if err != nil { @@ -127,7 +197,15 @@ func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context } func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { - if vm == nil || !s.IsFrozen(vm) { + if vm == nil { + return false, nil + } + + isFrozen, err := s.IsFrozen(ctx, vm) + if err != nil { + return false, err + } + if !isFrozen { return false, nil } @@ -139,7 +217,7 @@ func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Cont } var vdSnapshots v1alpha2.VirtualDiskSnapshotList - err := s.client.List(ctx, &vdSnapshots, &client.ListOptions{ + err = s.client.List(ctx, &vdSnapshots, &client.ListOptions{ Namespace: vm.Namespace, }) if err != nil { @@ -175,8 +253,28 @@ func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Cont } func (s *SnapshotService) Unfreeze(ctx context.Context, name, namespace string) error { - err := s.virtClient.VirtualMachines(namespace).Unfreeze(ctx, name) + vm, err := object.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &v1alpha2.VirtualMachine{}, &client.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to fetch virtual machine %s/%s: %w", namespace, name, err) + } + + if vm.Annotations != nil { + if _, ok := vm.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + return ErrUnexpectedFilesystemFrozenRequest + } + } + + err = s.annotateWithFSFreezeRequest(ctx, RequestFilesystemUnfreeze, vm) + if err != nil { + return fmt.Errorf("failed to annotate virtual machine with filesystem unfreeze request: %w", err) + } + + err = s.virtClient.VirtualMachines(namespace).Unfreeze(ctx, name) if err != nil { + err := s.removeAnnFSFreezeRequest(ctx, RequestFilesystemUnfreeze, vm) + if err != nil { + return fmt.Errorf("failed to remove virtual machine annotation with filesystem unfreeze request: %w", err) + } return fmt.Errorf("unfreeze virtual machine %s/%s: %w", namespace, name, err) } @@ -243,3 +341,34 @@ func (s *SnapshotService) CreateVirtualDiskSnapshot(ctx context.Context, vdSnaps return vdSnapshot, nil } + +func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, requestType string, vm *v1alpha2.VirtualMachine) error { + if vm.Annotations == nil { + vm.Annotations = make(map[string]string) + } + vm.Annotations[annotations.AnnVMFilesystemFrozenRequest] = requestType + + err := s.client.Update(ctx, vm, &client.UpdateOptions{}) + if err != nil { + return err + } + + return nil +} + +func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, requestType string, vm *v1alpha2.VirtualMachine) error { + if vm.Annotations == nil { + return nil + } + + if rt, ok := vm.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok && rt == requestType { + delete(vm.Annotations, annotations.AnnVMFilesystemFrozenRequest) + } + + err := s.client.Update(ctx, vm, &client.UpdateOptions{}) + if err != nil { + return err + } + + return nil +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go index 9731011350..a5203f6f72 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go @@ -18,6 +18,7 @@ package internal import ( "context" + "errors" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -72,6 +73,9 @@ func (h DeletionHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtua var canUnfreeze bool canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm) if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } return reconcile.Result{}, err } diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go index 845deafc10..5adb974a52 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go @@ -33,8 +33,8 @@ type VirtualDiskReadySnapshotter interface { type LifeCycleSnapshotter interface { Freeze(ctx context.Context, name, namespace string) error - IsFrozen(vm *v1alpha2.VirtualMachine) bool - CanFreeze(vm *v1alpha2.VirtualMachine) bool + IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) + CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) Unfreeze(ctx context.Context, name, namespace string) error CreateVolumeSnapshot(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) 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 1d6f6ab8fc..1c8e237a9c 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -19,6 +19,7 @@ package internal import ( "context" "encoding/json" + "errors" "fmt" "strings" @@ -153,8 +154,22 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu switch { case vs == nil: - if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !h.snapshotter.IsFrozen(vm) { - if h.snapshotter.CanFreeze(vm) { + isFrozen, err := h.snapshotter.IsFrozen(ctx, vm) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFrozen { + canFreeze, err := h.snapshotter.CanFreeze(ctx, vm) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + if canFreeze { log.Debug("Freeze the virtual machine to take a snapshot") if vdSnapshot.Status.Phase == v1alpha2.VirtualDiskSnapshotPhasePending { @@ -308,15 +323,26 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu default: log.Debug("The volume snapshot is ready to use") + isFrozen, err := h.snapshotter.IsFrozen(ctx, vm) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + switch { case vm == nil, vm.Status.Phase == v1alpha2.MachineStopped: vdSnapshot.Status.Consistent = ptr.To(true) - case h.snapshotter.IsFrozen(vm): + case isFrozen: vdSnapshot.Status.Consistent = ptr.To(true) var canUnfreeze bool canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm) if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, 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 56200e6019..1c9c9fa506 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 @@ -89,6 +89,9 @@ var _ = Describe("LifeCycle handler", func() { GetVolumeSnapshotFunc: func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { return nil, nil }, + IsFrozenFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return false, nil + }, } }) @@ -179,11 +182,11 @@ var _ = Describe("LifeCycle handler", func() { snapshotter.GetVirtualMachineFunc = func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil } - snapshotter.IsFrozenFunc = func(_ *v1alpha2.VirtualMachine) bool { - return false + snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return false, nil } - snapshotter.CanFreezeFunc = func(_ *v1alpha2.VirtualMachine) bool { - return true + snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return true, nil } snapshotter.FreezeFunc = func(_ context.Context, _, _ string) error { return nil @@ -226,8 +229,8 @@ var _ = Describe("LifeCycle handler", func() { It("Cannot freeze virtual machine: deny potentially inconsistent", func() { vdSnapshot.Spec.RequiredConsistency = true - snapshotter.CanFreezeFunc = func(_ *v1alpha2.VirtualMachine) bool { - return false + snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return false, nil } h := NewLifeCycleHandler(snapshotter) @@ -242,8 +245,8 @@ var _ = Describe("LifeCycle handler", func() { It("Cannot freeze virtual machine: allow potentially inconsistent", func() { vdSnapshot.Spec.RequiredConsistency = false - snapshotter.CanFreezeFunc = func(_ *v1alpha2.VirtualMachine) bool { - return false + snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return false, nil } h := NewLifeCycleHandler(snapshotter) @@ -257,8 +260,8 @@ var _ = Describe("LifeCycle handler", func() { }) It("Unfreeze virtual machine", func() { - snapshotter.IsFrozenFunc = func(_ *v1alpha2.VirtualMachine) bool { - return true + snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return true, nil } snapshotter.GetVolumeSnapshotFunc = func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { vs.Status = &vsv1.VolumeSnapshotStatus{ @@ -280,8 +283,8 @@ var _ = Describe("LifeCycle handler", func() { DescribeTable("Check unfreeze if failed", func(vm *v1alpha2.VirtualMachine, expectUnfreezing bool) { unFreezeCalled := false - snapshotter.IsFrozenFunc = func(_ *v1alpha2.VirtualMachine) bool { - return true + snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return true, nil } snapshotter.GetVolumeSnapshotFunc = func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { vs.Status = &vsv1.VolumeSnapshotStatus{ diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go index a063e0df10..25c673e32a 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go @@ -99,7 +99,7 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // // // make and configure a mocked LifeCycleSnapshotter // mockedLifeCycleSnapshotter := &LifeCycleSnapshotterMock{ -// CanFreezeFunc: func(vm *v1alpha2.VirtualMachine) bool { +// CanFreezeFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { // panic("mock out the CanFreeze method") // }, // CanUnfreezeWithVirtualDiskSnapshotFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { @@ -123,7 +123,7 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // GetVolumeSnapshotFunc: func(ctx context.Context, name string, namespace string) (*vsv1.VolumeSnapshot, error) { // panic("mock out the GetVolumeSnapshot method") // }, -// IsFrozenFunc: func(vm *v1alpha2.VirtualMachine) bool { +// IsFrozenFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { // panic("mock out the IsFrozen method") // }, // UnfreezeFunc: func(ctx context.Context, name string, namespace string) error { @@ -137,7 +137,7 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // } type LifeCycleSnapshotterMock struct { // CanFreezeFunc mocks the CanFreeze method. - CanFreezeFunc func(vm *v1alpha2.VirtualMachine) bool + CanFreezeFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) // CanUnfreezeWithVirtualDiskSnapshotFunc mocks the CanUnfreezeWithVirtualDiskSnapshot method. CanUnfreezeWithVirtualDiskSnapshotFunc func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) @@ -161,7 +161,7 @@ type LifeCycleSnapshotterMock struct { GetVolumeSnapshotFunc func(ctx context.Context, name string, namespace string) (*vsv1.VolumeSnapshot, error) // IsFrozenFunc mocks the IsFrozen method. - IsFrozenFunc func(vm *v1alpha2.VirtualMachine) bool + IsFrozenFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) // UnfreezeFunc mocks the Unfreeze method. UnfreezeFunc func(ctx context.Context, name string, namespace string) error @@ -170,6 +170,8 @@ type LifeCycleSnapshotterMock struct { calls struct { // CanFreeze holds details about calls to the CanFreeze method. CanFreeze []struct { + // Ctx is the ctx argument value. + Ctx context.Context // VM is the vm argument value. VM *v1alpha2.VirtualMachine } @@ -236,6 +238,8 @@ type LifeCycleSnapshotterMock struct { } // IsFrozen holds details about calls to the IsFrozen method. IsFrozen []struct { + // Ctx is the ctx argument value. + Ctx context.Context // VM is the vm argument value. VM *v1alpha2.VirtualMachine } @@ -262,19 +266,21 @@ type LifeCycleSnapshotterMock struct { } // CanFreeze calls CanFreezeFunc. -func (mock *LifeCycleSnapshotterMock) CanFreeze(vm *v1alpha2.VirtualMachine) bool { +func (mock *LifeCycleSnapshotterMock) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { if mock.CanFreezeFunc == nil { panic("LifeCycleSnapshotterMock.CanFreezeFunc: method is nil but LifeCycleSnapshotter.CanFreeze was just called") } callInfo := struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine }{ - VM: vm, + Ctx: ctx, + VM: vm, } mock.lockCanFreeze.Lock() mock.calls.CanFreeze = append(mock.calls.CanFreeze, callInfo) mock.lockCanFreeze.Unlock() - return mock.CanFreezeFunc(vm) + return mock.CanFreezeFunc(ctx, vm) } // CanFreezeCalls gets all the calls that were made to CanFreeze. @@ -282,10 +288,12 @@ func (mock *LifeCycleSnapshotterMock) CanFreeze(vm *v1alpha2.VirtualMachine) boo // // len(mockedLifeCycleSnapshotter.CanFreezeCalls()) func (mock *LifeCycleSnapshotterMock) CanFreezeCalls() []struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine } { var calls []struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine } mock.lockCanFreeze.RLock() calls = mock.calls.CanFreeze @@ -570,19 +578,21 @@ func (mock *LifeCycleSnapshotterMock) GetVolumeSnapshotCalls() []struct { } // IsFrozen calls IsFrozenFunc. -func (mock *LifeCycleSnapshotterMock) IsFrozen(vm *v1alpha2.VirtualMachine) bool { +func (mock *LifeCycleSnapshotterMock) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { if mock.IsFrozenFunc == nil { panic("LifeCycleSnapshotterMock.IsFrozenFunc: method is nil but LifeCycleSnapshotter.IsFrozen was just called") } callInfo := struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine }{ - VM: vm, + Ctx: ctx, + VM: vm, } mock.lockIsFrozen.Lock() mock.calls.IsFrozen = append(mock.calls.IsFrozen, callInfo) mock.lockIsFrozen.Unlock() - return mock.IsFrozenFunc(vm) + return mock.IsFrozenFunc(ctx, vm) } // IsFrozenCalls gets all the calls that were made to IsFrozen. @@ -590,10 +600,12 @@ func (mock *LifeCycleSnapshotterMock) IsFrozen(vm *v1alpha2.VirtualMachine) bool // // len(mockedLifeCycleSnapshotter.IsFrozenCalls()) func (mock *LifeCycleSnapshotterMock) IsFrozenCalls() []struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine } { var calls []struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine } mock.lockIsFrozen.RLock() calls = mock.calls.IsFrozen diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go index 6690332a57..ed2bbe3542 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go @@ -39,7 +39,7 @@ type Snapshotter interface { CreateVirtualDiskSnapshot(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (*v1alpha2.VirtualDiskSnapshot, error) Freeze(ctx context.Context, name, namespace string) error Unfreeze(ctx context.Context, name, namespace string) error - IsFrozen(vm *v1alpha2.VirtualMachine) bool - CanFreeze(vm *v1alpha2.VirtualMachine) bool + IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) + CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, 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 42219dfdbd..d3e892821e 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -82,6 +82,9 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu _, err = h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm) if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } h.setPhaseConditionToFailed(cb, vmSnapshot, err) return reconcile.Result{}, err } @@ -190,9 +193,22 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - needToFreeze := h.needToFreeze(vm, vmSnapshot.Spec.RequiredConsistency) + needToFreeze, err := h.needToFreeze(ctx, vm, vmSnapshot.Spec.RequiredConsistency) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } - isAwaitingConsistency := needToFreeze && !h.snapshotter.CanFreeze(vm) && vmSnapshot.Spec.RequiredConsistency + canFreeze, err := h.snapshotter.CanFreeze(ctx, vm) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + isAwaitingConsistency := needToFreeze && !canFreeze && vmSnapshot.Spec.RequiredConsistency if isAwaitingConsistency { vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhasePending msg := fmt.Sprintf( @@ -335,6 +351,9 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu // 7. Unfreeze VirtualMachine if can. unfrozen, err := h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm) if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } h.setPhaseConditionToFailed(cb, vmSnapshot, err) return reconcile.Result{}, err } @@ -490,20 +509,24 @@ func (h LifeCycleHandler) areVirtualDiskSnapshotsConsistent(vdSnapshots []*v1alp return true } -func (h LifeCycleHandler) needToFreeze(vm *v1alpha2.VirtualMachine, requiredConsistency bool) bool { +func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine, requiredConsistency bool) (bool, error) { if !requiredConsistency { - return false + return false, nil } if vm.Status.Phase == v1alpha2.MachineStopped { - return false + return false, nil } - if h.snapshotter.IsFrozen(vm) { - return false + isFrozen, err := h.snapshotter.IsFrozen(ctx, vm) + if err != nil { + return false, err + } + if isFrozen { + return false, nil } - return true + return true, nil } func (h LifeCycleHandler) freezeVirtualMachine(ctx context.Context, vm *v1alpha2.VirtualMachine, vmSnapshot *v1alpha2.VirtualMachineSnapshot) (bool, error) { @@ -527,7 +550,15 @@ func (h LifeCycleHandler) freezeVirtualMachine(ctx context.Context, vm *v1alpha2 } func (h LifeCycleHandler) unfreezeVirtualMachineIfCan(ctx context.Context, vmSnapshot *v1alpha2.VirtualMachineSnapshot, vm *v1alpha2.VirtualMachine) (bool, error) { - if vm == nil || vm.Status.Phase != v1alpha2.MachineRunning || !h.snapshotter.IsFrozen(vm) { + if vm == nil || vm.Status.Phase != v1alpha2.MachineRunning { + return false, nil + } + + isFrozen, err := h.snapshotter.IsFrozen(ctx, vm) + if err != nil { + return false, err + } + if !isFrozen { return false, nil } 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 66561c384a..5d988bbc96 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 @@ -124,14 +124,14 @@ var _ = Describe("LifeCycle handler", func() { GetVirtualMachineFunc: func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil }, - IsFrozenFunc: func(_ *v1alpha2.VirtualMachine) bool { - return true + IsFrozenFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return true, nil }, CanUnfreezeWithVirtualMachineSnapshotFunc: func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine) (bool, error) { return true, nil }, - CanFreezeFunc: func(_ *v1alpha2.VirtualMachine) bool { - return false + CanFreezeFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return false, nil }, UnfreezeFunc: func(ctx context.Context, _, _ string) error { return nil @@ -248,11 +248,11 @@ var _ = Describe("LifeCycle handler", func() { }) It("The virtual machine is potentially inconsistent", func() { - snapshotter.IsFrozenFunc = func(_ *v1alpha2.VirtualMachine) bool { - return false + snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return false, nil } - snapshotter.CanFreezeFunc = func(_ *v1alpha2.VirtualMachine) bool { - return false + snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return false, nil } h := NewLifeCycleHandler(recorder, snapshotter, storer, fakeClient) @@ -267,11 +267,11 @@ var _ = Describe("LifeCycle handler", func() { }) It("The virtual machine has frozen", func() { - snapshotter.IsFrozenFunc = func(_ *v1alpha2.VirtualMachine) bool { - return false + snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return false, nil } - snapshotter.CanFreezeFunc = func(_ *v1alpha2.VirtualMachine) bool { - return true + snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + return true, nil } snapshotter.FreezeFunc = func(_ context.Context, _, _ string) error { return nil diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go index 134a491adb..2c7724b3f2 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go @@ -98,7 +98,7 @@ var _ Snapshotter = &SnapshotterMock{} // // // make and configure a mocked Snapshotter // mockedSnapshotter := &SnapshotterMock{ -// CanFreezeFunc: func(vm *v1alpha2.VirtualMachine) bool { +// CanFreezeFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { // panic("mock out the CanFreeze method") // }, // CanUnfreezeWithVirtualMachineSnapshotFunc: func(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { @@ -125,7 +125,7 @@ var _ Snapshotter = &SnapshotterMock{} // GetVirtualMachineFunc: func(ctx context.Context, name string, namespace string) (*v1alpha2.VirtualMachine, error) { // panic("mock out the GetVirtualMachine method") // }, -// IsFrozenFunc: func(vm *v1alpha2.VirtualMachine) bool { +// IsFrozenFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { // panic("mock out the IsFrozen method") // }, // UnfreezeFunc: func(ctx context.Context, name string, namespace string) error { @@ -139,7 +139,7 @@ var _ Snapshotter = &SnapshotterMock{} // } type SnapshotterMock struct { // CanFreezeFunc mocks the CanFreeze method. - CanFreezeFunc func(vm *v1alpha2.VirtualMachine) bool + CanFreezeFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) // CanUnfreezeWithVirtualMachineSnapshotFunc mocks the CanUnfreezeWithVirtualMachineSnapshot method. CanUnfreezeWithVirtualMachineSnapshotFunc func(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) @@ -166,7 +166,7 @@ type SnapshotterMock struct { GetVirtualMachineFunc func(ctx context.Context, name string, namespace string) (*v1alpha2.VirtualMachine, error) // IsFrozenFunc mocks the IsFrozen method. - IsFrozenFunc func(vm *v1alpha2.VirtualMachine) bool + IsFrozenFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) // UnfreezeFunc mocks the Unfreeze method. UnfreezeFunc func(ctx context.Context, name string, namespace string) error @@ -175,6 +175,8 @@ type SnapshotterMock struct { calls struct { // CanFreeze holds details about calls to the CanFreeze method. CanFreeze []struct { + // Ctx is the ctx argument value. + Ctx context.Context // VM is the vm argument value. VM *v1alpha2.VirtualMachine } @@ -250,6 +252,8 @@ type SnapshotterMock struct { } // IsFrozen holds details about calls to the IsFrozen method. IsFrozen []struct { + // Ctx is the ctx argument value. + Ctx context.Context // VM is the vm argument value. VM *v1alpha2.VirtualMachine } @@ -277,19 +281,21 @@ type SnapshotterMock struct { } // CanFreeze calls CanFreezeFunc. -func (mock *SnapshotterMock) CanFreeze(vm *v1alpha2.VirtualMachine) bool { +func (mock *SnapshotterMock) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { if mock.CanFreezeFunc == nil { panic("SnapshotterMock.CanFreezeFunc: method is nil but Snapshotter.CanFreeze was just called") } callInfo := struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine }{ - VM: vm, + Ctx: ctx, + VM: vm, } mock.lockCanFreeze.Lock() mock.calls.CanFreeze = append(mock.calls.CanFreeze, callInfo) mock.lockCanFreeze.Unlock() - return mock.CanFreezeFunc(vm) + return mock.CanFreezeFunc(ctx, vm) } // CanFreezeCalls gets all the calls that were made to CanFreeze. @@ -297,10 +303,12 @@ func (mock *SnapshotterMock) CanFreeze(vm *v1alpha2.VirtualMachine) bool { // // len(mockedSnapshotter.CanFreezeCalls()) func (mock *SnapshotterMock) CanFreezeCalls() []struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine } { var calls []struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine } mock.lockCanFreeze.RLock() calls = mock.calls.CanFreeze @@ -625,19 +633,21 @@ func (mock *SnapshotterMock) GetVirtualMachineCalls() []struct { } // IsFrozen calls IsFrozenFunc. -func (mock *SnapshotterMock) IsFrozen(vm *v1alpha2.VirtualMachine) bool { +func (mock *SnapshotterMock) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { if mock.IsFrozenFunc == nil { panic("SnapshotterMock.IsFrozenFunc: method is nil but Snapshotter.IsFrozen was just called") } callInfo := struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine }{ - VM: vm, + Ctx: ctx, + VM: vm, } mock.lockIsFrozen.Lock() mock.calls.IsFrozen = append(mock.calls.IsFrozen, callInfo) mock.lockIsFrozen.Unlock() - return mock.IsFrozenFunc(vm) + return mock.IsFrozenFunc(ctx, vm) } // IsFrozenCalls gets all the calls that were made to IsFrozen. @@ -645,10 +655,12 @@ func (mock *SnapshotterMock) IsFrozen(vm *v1alpha2.VirtualMachine) bool { // // len(mockedSnapshotter.IsFrozenCalls()) func (mock *SnapshotterMock) IsFrozenCalls() []struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine } { var calls []struct { - VM *v1alpha2.VirtualMachine + Ctx context.Context + VM *v1alpha2.VirtualMachine } mock.lockIsFrozen.RLock() calls = mock.calls.IsFrozen From ed538b61374ef347cf92d66a8bd6b1f2d497b23c Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 10 Nov 2025 17:43:40 +0300 Subject: [PATCH 02/23] fix(vdsnapshot): change IsFrozen func Use kvvmi status instead of vm. Signed-off-by: Roman Sysoev --- .../controller/service/snapshot_service.go | 80 +++++++++++-------- .../vdsnapshot/internal/life_cycle.go | 54 +++++++------ 2 files changed, 75 insertions(+), 59 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go index 87191ece9b..af0f622900 100644 --- a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go +++ b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go @@ -26,6 +26,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/deckhouse/virtualization-controller/pkg/common/annotations" @@ -38,8 +39,9 @@ import ( ) const ( - RequestFilesystemFreeze = "freeze" - RequestFilesystemUnfreeze = "unfreeze" + RequestFSFreeze = "freeze" + RequestFSUnfreeze = "unfreeze" + FSFrozen = "frozen" ) var ( @@ -61,24 +63,34 @@ func NewSnapshotService(virtClient kubeclient.Client, client Client, protection } } +// IsFrozen checks if a freeze or unfreeze request has been performed +// and returns the "true" fsFreezeStatus if the internal virtual machine instance is "frozen", +// and "false" otherwise. func (s *SnapshotService) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { if vm == nil { return false, nil } - filesystemFrozen, _ := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, vm.Status.Conditions) + kvvmi, err := object.FetchObject(ctx, client.ObjectKeyFromObject(vm), s.client, &virtv1.VirtualMachineInstance{}) + if err != nil { + return false, fmt.Errorf("failed to fetch internal virtual machine instance %s/%s: %w", vm.Namespace, vm.Name, err) + } + + if kvvmi == nil { + return false, nil + } - if vm.Annotations != nil { - if requestType, ok := vm.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + if kvvmi.Annotations != nil { + if r, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { switch { - case requestType == RequestFilesystemFreeze && filesystemFrozen.Status == metav1.ConditionTrue && filesystemFrozen.Reason == vmcondition.ReasonFilesystemFrozen.String(): - err := s.removeAnnFSFreezeRequest(ctx, RequestFilesystemFreeze, vm) + case r == RequestFSFreeze && kvvmi.Status.FSFreezeStatus == FSFrozen: + err := s.removeAnnFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) if err != nil { return false, err } return true, nil - case requestType == RequestFilesystemUnfreeze && filesystemFrozen.Status != metav1.ConditionTrue: - err := s.removeAnnFSFreezeRequest(ctx, RequestFilesystemUnfreeze, vm) + case r == RequestFSUnfreeze && kvvmi.Status.FSFreezeStatus != FSFrozen: + err := s.removeAnnFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) if err != nil { return false, err } @@ -89,7 +101,7 @@ func (s *SnapshotService) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMach } } - return filesystemFrozen.Status == metav1.ConditionTrue && filesystemFrozen.Reason == vmcondition.ReasonFilesystemFrozen.String(), nil + return kvvmi.Status.FSFreezeStatus == FSFrozen, nil } func (s *SnapshotService) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { @@ -111,25 +123,25 @@ func (s *SnapshotService) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMac } func (s *SnapshotService) Freeze(ctx context.Context, name, namespace string) error { - vm, err := object.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &v1alpha2.VirtualMachine{}, &client.GetOptions{}) + kvvmi, err := object.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &virtv1.VirtualMachineInstance{}) if err != nil { - return fmt.Errorf("failed to fetch virtual machine %s/%s: %w", namespace, name, err) + return fmt.Errorf("failed to fetch internal virtual machine instance %s/%s: %w", namespace, name, err) } - if vm.Annotations != nil { - if _, ok := vm.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + if kvvmi.Annotations != nil { + if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { return ErrUnexpectedFilesystemFrozenRequest } } - err = s.annotateWithFSFreezeRequest(ctx, RequestFilesystemFreeze, vm) + err = s.annotateWithFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) if err != nil { return fmt.Errorf("failed to annotate virtual machine with filesystem freeze request: %w", err) } err = s.virtClient.VirtualMachines(namespace).Freeze(ctx, name, subv1alpha2.VirtualMachineFreeze{}) if err != nil { - err := s.removeAnnFSFreezeRequest(ctx, RequestFilesystemFreeze, vm) + err := s.removeAnnFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) if err != nil { return fmt.Errorf("failed to remove virtual machine annotation with filesystem freeze request: %w", err) } @@ -253,27 +265,27 @@ func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Cont } func (s *SnapshotService) Unfreeze(ctx context.Context, name, namespace string) error { - vm, err := object.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &v1alpha2.VirtualMachine{}, &client.GetOptions{}) + kvvmi, err := object.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &virtv1.VirtualMachineInstance{}) if err != nil { - return fmt.Errorf("failed to fetch virtual machine %s/%s: %w", namespace, name, err) + return fmt.Errorf("failed to fetch internal virtual machine instance %s/%s: %w", namespace, name, err) } - if vm.Annotations != nil { - if _, ok := vm.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + if kvvmi.Annotations != nil { + if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { return ErrUnexpectedFilesystemFrozenRequest } } - err = s.annotateWithFSFreezeRequest(ctx, RequestFilesystemUnfreeze, vm) + err = s.annotateWithFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) if err != nil { - return fmt.Errorf("failed to annotate virtual machine with filesystem unfreeze request: %w", err) + return fmt.Errorf("failed to annotate internal virtual machine instance with filesystem unfreeze request: %w", err) } err = s.virtClient.VirtualMachines(namespace).Unfreeze(ctx, name) if err != nil { - err := s.removeAnnFSFreezeRequest(ctx, RequestFilesystemUnfreeze, vm) + err := s.removeAnnFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) if err != nil { - return fmt.Errorf("failed to remove virtual machine annotation with filesystem unfreeze request: %w", err) + return fmt.Errorf("failed to remove internal virtual machine instance annotation with filesystem unfreeze request: %w", err) } return fmt.Errorf("unfreeze virtual machine %s/%s: %w", namespace, name, err) } @@ -342,13 +354,13 @@ func (s *SnapshotService) CreateVirtualDiskSnapshot(ctx context.Context, vdSnaps return vdSnapshot, nil } -func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, requestType string, vm *v1alpha2.VirtualMachine) error { - if vm.Annotations == nil { - vm.Annotations = make(map[string]string) +func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, requestType string, kvvmi *virtv1.VirtualMachineInstance) error { + if kvvmi.Annotations == nil { + kvvmi.Annotations = make(map[string]string) } - vm.Annotations[annotations.AnnVMFilesystemFrozenRequest] = requestType + kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest] = requestType - err := s.client.Update(ctx, vm, &client.UpdateOptions{}) + err := s.client.Update(ctx, kvvmi) if err != nil { return err } @@ -356,16 +368,16 @@ func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, reque return nil } -func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, requestType string, vm *v1alpha2.VirtualMachine) error { - if vm.Annotations == nil { +func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, requestType string, kvvmi *virtv1.VirtualMachineInstance) error { + if kvvmi.Annotations == nil { return nil } - if rt, ok := vm.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok && rt == requestType { - delete(vm.Annotations, annotations.AnnVMFilesystemFrozenRequest) + if rt, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok && rt == requestType { + delete(kvvmi.Annotations, annotations.AnnVMFilesystemFrozenRequest) } - err := s.client.Update(ctx, vm, &client.UpdateOptions{}) + err := s.client.Update(ctx, kvvmi) if err != nil { return err } 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 1c8e237a9c..ab4a9e7562 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -78,6 +78,21 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } + vm, err := getVirtualMachine(ctx, vd, h.snapshotter) + if err != nil { + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + isFSFrozen, err := h.snapshotter.IsFrozen(ctx, vm) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + if vdSnapshot.DeletionTimestamp != nil { vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseTerminating cb. @@ -146,22 +161,9 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, nil } - vm, err := getVirtualMachine(ctx, vd, h.snapshotter) - if err != nil { - setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) - return reconcile.Result{}, err - } - switch { case vs == nil: - isFrozen, err := h.snapshotter.IsFrozen(ctx, vm) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } - return reconcile.Result{}, err - } - if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFrozen { + if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFSFrozen { canFreeze, err := h.snapshotter.CanFreeze(ctx, vm) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { @@ -380,20 +382,22 @@ func getVirtualMachine(ctx context.Context, vd *v1alpha2.VirtualDisk, snapshotte return nil, nil } - // TODO: ensure vd.Status.AttachedToVirtualMachines is in the actual state. - switch len(vd.Status.AttachedToVirtualMachines) { - case 0: + if vd.Status.AttachedToVirtualMachines == nil { return nil, nil - case 1: - vm, err := snapshotter.GetVirtualMachine(ctx, vd.Status.AttachedToVirtualMachines[0].Name, vd.Namespace) - if err != nil { - return nil, err - } + } - return vm, nil - default: - return nil, fmt.Errorf("the virtual disk %q is attached to multiple virtual machines", vd.Name) + for _, avm := range vd.Status.AttachedToVirtualMachines { + if avm.Mounted { + vm, err := snapshotter.GetVirtualMachine(ctx, avm.Name, vd.Namespace) + if err != nil { + return nil, err + } + + return vm, nil + } } + + return nil, nil } func setPhaseConditionToFailed(cb *conditions.ConditionBuilder, phase *v1alpha2.VirtualDiskSnapshotPhase, err error) { From 23c911b0dcce28a9e77eb6638c8c438901b1f7ce Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 10 Nov 2025 18:29:45 +0300 Subject: [PATCH 03/23] fix(vdsnapshot): fix unit test Signed-off-by: Roman Sysoev --- .../pkg/controller/vdsnapshot/internal/life_cycle_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1c9c9fa506..0d442c7249 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 @@ -177,7 +177,7 @@ var _ = Describe("LifeCycle handler", func() { Phase: v1alpha2.MachineRunning, }, } - vd.Status.AttachedToVirtualMachines = []v1alpha2.AttachedVirtualMachine{{Name: vm.Name}} + vd.Status.AttachedToVirtualMachines = []v1alpha2.AttachedVirtualMachine{{Name: vm.Name, Mounted: true}} snapshotter.GetVirtualMachineFunc = func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil From aab5185857c80af50e4af272073bd725250843d0 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 10 Nov 2025 18:34:48 +0300 Subject: [PATCH 04/23] fix(vdsnapshot): optimize getVirtualMachine func Signed-off-by: Roman Sysoev --- .../pkg/controller/vdsnapshot/internal/life_cycle.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) 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 ab4a9e7562..0c3cb14a55 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -378,11 +378,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu } func getVirtualMachine(ctx context.Context, vd *v1alpha2.VirtualDisk, snapshotter LifeCycleSnapshotter) (*v1alpha2.VirtualMachine, error) { - if vd == nil { - return nil, nil - } - - if vd.Status.AttachedToVirtualMachines == nil { + if vd == nil || vd.Status.AttachedToVirtualMachines == nil { return nil, nil } From 0466c6930948e7633d295e71be47a4225c87f247 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Tue, 11 Nov 2025 02:16:35 +0300 Subject: [PATCH 05/23] fix(vdsnapshot): fetch object once per reconciliation Signed-off-by: Roman Sysoev --- .../controller/service/snapshot_service.go | 99 +++++----- .../vdsnapshot/internal/deletion.go | 7 +- .../vdsnapshot/internal/interfaces.go | 9 +- .../vdsnapshot/internal/life_cycle.go | 58 ++++-- .../vdsnapshot/internal/life_cycle_test.go | 34 +++- .../controller/vdsnapshot/internal/mock.go | 171 ++++++++++++++---- .../vmsnapshot/internal/interfaces.go | 9 +- .../vmsnapshot/internal/life_cycle.go | 25 ++- .../vmsnapshot/internal/life_cycle_test.go | 26 ++- .../controller/vmsnapshot/internal/mock.go | 171 ++++++++++++++---- 10 files changed, 455 insertions(+), 154 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go index af0f622900..a2375b4b80 100644 --- a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go +++ b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go @@ -24,17 +24,14 @@ import ( vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" 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" virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/deckhouse/virtualization-controller/pkg/common/annotations" "github.com/deckhouse/virtualization-controller/pkg/common/object" - "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization/api/client/kubeclient" "github.com/deckhouse/virtualization/api/core/v1alpha2" - "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" subv1alpha2 "github.com/deckhouse/virtualization/api/subresources/v1alpha2" ) @@ -66,50 +63,26 @@ func NewSnapshotService(virtClient kubeclient.Client, client Client, protection // IsFrozen checks if a freeze or unfreeze request has been performed // and returns the "true" fsFreezeStatus if the internal virtual machine instance is "frozen", // and "false" otherwise. -func (s *SnapshotService) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { - if vm == nil { - return false, nil - } - - kvvmi, err := object.FetchObject(ctx, client.ObjectKeyFromObject(vm), s.client, &virtv1.VirtualMachineInstance{}) - if err != nil { - return false, fmt.Errorf("failed to fetch internal virtual machine instance %s/%s: %w", vm.Namespace, vm.Name, err) - } - +func (s *SnapshotService) IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if kvvmi == nil { return false, nil } if kvvmi.Annotations != nil { - if r, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - switch { - case r == RequestFSFreeze && kvvmi.Status.FSFreezeStatus == FSFrozen: - err := s.removeAnnFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) - if err != nil { - return false, err - } - return true, nil - case r == RequestFSUnfreeze && kvvmi.Status.FSFreezeStatus != FSFrozen: - err := s.removeAnnFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) - if err != nil { - return false, err - } - return false, nil - default: - return false, ErrUntrustedFilesystemFrozenCondition - } + if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + return false, ErrUntrustedFilesystemFrozenCondition } } return kvvmi.Status.FSFreezeStatus == FSFrozen, nil } -func (s *SnapshotService) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { - if vm == nil || vm.Status.Phase != v1alpha2.MachineRunning { +func (s *SnapshotService) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + if kvvmi == nil || kvvmi.Status.Phase != virtv1.Running { return false, nil } - isFrozen, err := s.IsFrozen(ctx, vm) + isFrozen, err := s.IsFrozen(ctx, kvvmi) if err != nil { return false, err } @@ -117,9 +90,13 @@ func (s *SnapshotService) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMac return false, nil } - agentReady, _ := conditions.GetCondition(vmcondition.TypeAgentReady, vm.Status.Conditions) + for _, c := range kvvmi.Status.Conditions { + if c.Type == virtv1.VirtualMachineInstanceAgentConnected { + return c.Status == "True", nil + } + } - return agentReady.Status == metav1.ConditionTrue, nil + return false, nil } func (s *SnapshotService) Freeze(ctx context.Context, name, namespace string) error { @@ -151,12 +128,12 @@ func (s *SnapshotService) Freeze(ctx context.Context, name, namespace string) er return nil } -func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { +func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if vm == nil { return false, nil } - isFrozen, err := s.IsFrozen(ctx, vm) + isFrozen, err := s.IsFrozen(ctx, kvvmi) if err != nil { return false, err } @@ -208,12 +185,12 @@ func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context return true, nil } -func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { +func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if vm == nil { return false, nil } - isFrozen, err := s.IsFrozen(ctx, vm) + isFrozen, err := s.IsFrozen(ctx, kvvmi) if err != nil { return false, err } @@ -354,7 +331,18 @@ func (s *SnapshotService) CreateVirtualDiskSnapshot(ctx context.Context, vdSnaps return vdSnapshot, nil } +func (s *SnapshotService) GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + if vm == nil { + return nil, nil + } + return object.FetchObject(ctx, client.ObjectKeyFromObject(vm), s.client, &virtv1.VirtualMachineInstance{}) +} + func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, requestType string, kvvmi *virtv1.VirtualMachineInstance) error { + if kvvmi == nil { + return nil + } + if kvvmi.Annotations == nil { kvvmi.Annotations = make(map[string]string) } @@ -369,11 +357,11 @@ func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, reque } func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, requestType string, kvvmi *virtv1.VirtualMachineInstance) error { - if kvvmi.Annotations == nil { + if kvvmi == nil || kvvmi.Annotations == nil { return nil } - if rt, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok && rt == requestType { + if r, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok && r == requestType { delete(kvvmi.Annotations, annotations.AnnVMFilesystemFrozenRequest) } @@ -384,3 +372,32 @@ func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, requestT return nil } + +func (s *SnapshotService) SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { + if kvvmi == nil { + return nil + } + + if kvvmi.Annotations != nil { + if r, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + switch { + case r == RequestFSFreeze && kvvmi.Status.FSFreezeStatus == FSFrozen: + err := s.removeAnnFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) + if err != nil { + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, err) + } + return nil + case r == RequestFSUnfreeze && kvvmi.Status.FSFreezeStatus != FSFrozen: + err := s.removeAnnFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) + if err != nil { + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, err) + } + return nil + default: + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, ErrUntrustedFilesystemFrozenCondition) + } + } + } + + return nil +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go index a5203f6f72..9978ca173e 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go @@ -62,6 +62,11 @@ func (h DeletionHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtua } } + kvvmi, err := h.snapshotter.GetKubeVirtVirtualMachineInstance(ctx, vm) + if err != nil { + return reconcile.Result{}, err + } + if vs != nil { err = h.snapshotter.DeleteVolumeSnapshot(ctx, vs) if err != nil { @@ -71,7 +76,7 @@ func (h DeletionHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtua if vm != nil { var canUnfreeze bool - canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm) + canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go index 5adb974a52..4e58d7e864 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go @@ -21,6 +21,7 @@ import ( vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" corev1 "k8s.io/api/core/v1" + virtv1 "kubevirt.io/api/core/v1" "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -33,13 +34,15 @@ type VirtualDiskReadySnapshotter interface { type LifeCycleSnapshotter interface { Freeze(ctx context.Context, name, namespace string) error - IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) - CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) - CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) + IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) Unfreeze(ctx context.Context, name, namespace string) error CreateVolumeSnapshot(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) GetPersistentVolumeClaim(ctx context.Context, name, namespace string) (*corev1.PersistentVolumeClaim, error) GetVirtualDisk(ctx context.Context, name, namespace string) (*v1alpha2.VirtualDisk, error) GetVirtualMachine(ctx context.Context, name, namespace string) (*v1alpha2.VirtualMachine, error) GetVolumeSnapshot(ctx context.Context, name, namespace string) (*vsv1.VolumeSnapshot, error) + SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error + GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) } 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 0c3cb14a55..fa35730ade 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -78,21 +78,6 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - vm, err := getVirtualMachine(ctx, vd, h.snapshotter) - if err != nil { - setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) - return reconcile.Result{}, err - } - - isFSFrozen, err := h.snapshotter.IsFrozen(ctx, vm) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } - setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) - return reconcile.Result{}, err - } - if vdSnapshot.DeletionTimestamp != nil { vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseTerminating cb. @@ -161,10 +146,39 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, nil } + vm, err := getVirtualMachine(ctx, vd, h.snapshotter) + if err != nil { + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + kvvmi, err := h.snapshotter.GetKubeVirtVirtualMachineInstance(ctx, vm) + if err != nil { + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + + err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + + isFSFrozen, err := h.snapshotter.IsFrozen(ctx, kvvmi) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + switch { case vs == nil: if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFSFrozen { - canFreeze, err := h.snapshotter.CanFreeze(ctx, vm) + canFreeze, err := h.snapshotter.CanFreeze(ctx, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -325,7 +339,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu default: log.Debug("The volume snapshot is ready to use") - isFrozen, err := h.snapshotter.IsFrozen(ctx, vm) + isFrozen, err := h.snapshotter.IsFrozen(ctx, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -340,7 +354,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu vdSnapshot.Status.Consistent = ptr.To(true) var canUnfreeze bool - canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm) + canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -366,6 +380,14 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu } } + err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + return reconcile.Result{}, err + } + vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseReady vdSnapshot.Status.VolumeSnapshotName = vs.Name cb. 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 0d442c7249..d214ec53d5 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 @@ -25,6 +25,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" + virtv1 "kubevirt.io/api/core/v1" "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization/api/core/v1alpha2" @@ -89,9 +90,15 @@ var _ = Describe("LifeCycle handler", func() { GetVolumeSnapshotFunc: func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { return nil, nil }, - IsFrozenFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + IsFrozenFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil }, + GetKubeVirtVirtualMachineInstanceFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + return nil, nil + }, + SyncFSFreezeRequestFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { + return nil + }, } }) @@ -177,21 +184,32 @@ var _ = Describe("LifeCycle handler", func() { Phase: v1alpha2.MachineRunning, }, } + + kvvmi := &virtv1.VirtualMachineInstance{ + ObjectMeta: metav1.ObjectMeta{Name: "vm"}, + Status: virtv1.VirtualMachineInstanceStatus{ + Phase: virtv1.Running, + }, + } + vd.Status.AttachedToVirtualMachines = []v1alpha2.AttachedVirtualMachine{{Name: vm.Name, Mounted: true}} snapshotter.GetVirtualMachineFunc = func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil } - snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.GetKubeVirtVirtualMachineInstanceFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + return kvvmi, nil + } + snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } - snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } snapshotter.FreezeFunc = func(_ context.Context, _, _ string) error { return nil } - snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } snapshotter.UnfreezeFunc = func(_ context.Context, _, _ string) error { @@ -229,7 +247,7 @@ var _ = Describe("LifeCycle handler", func() { It("Cannot freeze virtual machine: deny potentially inconsistent", func() { vdSnapshot.Spec.RequiredConsistency = true - snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } h := NewLifeCycleHandler(snapshotter) @@ -245,7 +263,7 @@ var _ = Describe("LifeCycle handler", func() { It("Cannot freeze virtual machine: allow potentially inconsistent", func() { vdSnapshot.Spec.RequiredConsistency = false - snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } h := NewLifeCycleHandler(snapshotter) @@ -260,7 +278,7 @@ var _ = Describe("LifeCycle handler", func() { }) It("Unfreeze virtual machine", func() { - snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } snapshotter.GetVolumeSnapshotFunc = func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { @@ -283,7 +301,7 @@ var _ = Describe("LifeCycle handler", func() { DescribeTable("Check unfreeze if failed", func(vm *v1alpha2.VirtualMachine, expectUnfreezing bool) { unFreezeCalled := false - snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } snapshotter.GetVolumeSnapshotFunc = func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go index 25c673e32a..91a580ad5e 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go @@ -8,6 +8,7 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" corev1 "k8s.io/api/core/v1" + virtv1 "kubevirt.io/api/core/v1" "sync" ) @@ -99,10 +100,10 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // // // make and configure a mocked LifeCycleSnapshotter // mockedLifeCycleSnapshotter := &LifeCycleSnapshotterMock{ -// CanFreezeFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { +// CanFreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { // panic("mock out the CanFreeze method") // }, -// CanUnfreezeWithVirtualDiskSnapshotFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { +// CanUnfreezeWithVirtualDiskSnapshotFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { // panic("mock out the CanUnfreezeWithVirtualDiskSnapshot method") // }, // CreateVolumeSnapshotFunc: func(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) { @@ -111,6 +112,9 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // FreezeFunc: func(ctx context.Context, name string, namespace string) error { // panic("mock out the Freeze method") // }, +// GetKubeVirtVirtualMachineInstanceFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { +// panic("mock out the GetKubeVirtVirtualMachineInstance method") +// }, // GetPersistentVolumeClaimFunc: func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { // panic("mock out the GetPersistentVolumeClaim method") // }, @@ -123,9 +127,12 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // GetVolumeSnapshotFunc: func(ctx context.Context, name string, namespace string) (*vsv1.VolumeSnapshot, error) { // panic("mock out the GetVolumeSnapshot method") // }, -// IsFrozenFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { +// IsFrozenFunc: func(ctx context.Context, 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, name string, namespace string) error { // panic("mock out the Unfreeze method") // }, @@ -137,10 +144,10 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // } type LifeCycleSnapshotterMock struct { // CanFreezeFunc mocks the CanFreeze method. - CanFreezeFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) + CanFreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) // CanUnfreezeWithVirtualDiskSnapshotFunc mocks the CanUnfreezeWithVirtualDiskSnapshot method. - CanUnfreezeWithVirtualDiskSnapshotFunc func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) + CanUnfreezeWithVirtualDiskSnapshotFunc func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) // CreateVolumeSnapshotFunc mocks the CreateVolumeSnapshot method. CreateVolumeSnapshotFunc func(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) @@ -148,6 +155,9 @@ type LifeCycleSnapshotterMock struct { // FreezeFunc mocks the Freeze method. FreezeFunc func(ctx context.Context, name string, namespace string) error + // GetKubeVirtVirtualMachineInstanceFunc mocks the GetKubeVirtVirtualMachineInstance method. + GetKubeVirtVirtualMachineInstanceFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) + // GetPersistentVolumeClaimFunc mocks the GetPersistentVolumeClaim method. GetPersistentVolumeClaimFunc func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) @@ -161,7 +171,10 @@ type LifeCycleSnapshotterMock struct { GetVolumeSnapshotFunc func(ctx context.Context, name string, namespace string) (*vsv1.VolumeSnapshot, error) // IsFrozenFunc mocks the IsFrozen method. - IsFrozenFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) + IsFrozenFunc func(ctx context.Context, 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, name string, namespace string) error @@ -172,8 +185,8 @@ type LifeCycleSnapshotterMock struct { CanFreeze []struct { // Ctx is the ctx argument value. Ctx context.Context - // VM is the vm argument value. - VM *v1alpha2.VirtualMachine + // Kvvmi is the kvvmi argument value. + Kvvmi *virtv1.VirtualMachineInstance } // CanUnfreezeWithVirtualDiskSnapshot holds details about calls to the CanUnfreezeWithVirtualDiskSnapshot method. CanUnfreezeWithVirtualDiskSnapshot []struct { @@ -183,6 +196,8 @@ type LifeCycleSnapshotterMock struct { VdSnapshotName string // VM is the vm argument value. VM *v1alpha2.VirtualMachine + // Kvvmi is the kvvmi argument value. + Kvvmi *virtv1.VirtualMachineInstance } // CreateVolumeSnapshot holds details about calls to the CreateVolumeSnapshot method. CreateVolumeSnapshot []struct { @@ -200,6 +215,13 @@ type LifeCycleSnapshotterMock struct { // Namespace is the namespace argument value. Namespace string } + // GetKubeVirtVirtualMachineInstance holds details about calls to the GetKubeVirtVirtualMachineInstance method. + GetKubeVirtVirtualMachineInstance []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // VM is the vm argument value. + VM *v1alpha2.VirtualMachine + } // GetPersistentVolumeClaim holds details about calls to the GetPersistentVolumeClaim method. GetPersistentVolumeClaim []struct { // Ctx is the ctx argument value. @@ -240,8 +262,15 @@ type LifeCycleSnapshotterMock struct { IsFrozen []struct { // Ctx is the ctx argument value. Ctx context.Context - // VM is the vm argument value. - VM *v1alpha2.VirtualMachine + // 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 { @@ -257,30 +286,32 @@ type LifeCycleSnapshotterMock struct { lockCanUnfreezeWithVirtualDiskSnapshot sync.RWMutex lockCreateVolumeSnapshot sync.RWMutex lockFreeze sync.RWMutex + lockGetKubeVirtVirtualMachineInstance sync.RWMutex lockGetPersistentVolumeClaim sync.RWMutex lockGetVirtualDisk sync.RWMutex lockGetVirtualMachine sync.RWMutex lockGetVolumeSnapshot sync.RWMutex lockIsFrozen sync.RWMutex + lockSyncFSFreezeRequest sync.RWMutex lockUnfreeze sync.RWMutex } // CanFreeze calls CanFreezeFunc. -func (mock *LifeCycleSnapshotterMock) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { +func (mock *LifeCycleSnapshotterMock) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if mock.CanFreezeFunc == nil { panic("LifeCycleSnapshotterMock.CanFreezeFunc: method is nil but LifeCycleSnapshotter.CanFreeze was just called") } callInfo := struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, - VM: vm, + Ctx: ctx, + Kvvmi: kvvmi, } mock.lockCanFreeze.Lock() mock.calls.CanFreeze = append(mock.calls.CanFreeze, callInfo) mock.lockCanFreeze.Unlock() - return mock.CanFreezeFunc(ctx, vm) + return mock.CanFreezeFunc(ctx, kvvmi) } // CanFreezeCalls gets all the calls that were made to CanFreeze. @@ -288,12 +319,12 @@ func (mock *LifeCycleSnapshotterMock) CanFreeze(ctx context.Context, vm *v1alpha // // len(mockedLifeCycleSnapshotter.CanFreezeCalls()) func (mock *LifeCycleSnapshotterMock) CanFreezeCalls() []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } mock.lockCanFreeze.RLock() calls = mock.calls.CanFreeze @@ -302,7 +333,7 @@ func (mock *LifeCycleSnapshotterMock) CanFreezeCalls() []struct { } // CanUnfreezeWithVirtualDiskSnapshot calls CanUnfreezeWithVirtualDiskSnapshotFunc. -func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { +func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if mock.CanUnfreezeWithVirtualDiskSnapshotFunc == nil { panic("LifeCycleSnapshotterMock.CanUnfreezeWithVirtualDiskSnapshotFunc: method is nil but LifeCycleSnapshotter.CanUnfreezeWithVirtualDiskSnapshot was just called") } @@ -310,15 +341,17 @@ func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshot(ctx con Ctx context.Context VdSnapshotName string VM *v1alpha2.VirtualMachine + Kvvmi *virtv1.VirtualMachineInstance }{ Ctx: ctx, VdSnapshotName: vdSnapshotName, VM: vm, + Kvvmi: kvvmi, } mock.lockCanUnfreezeWithVirtualDiskSnapshot.Lock() mock.calls.CanUnfreezeWithVirtualDiskSnapshot = append(mock.calls.CanUnfreezeWithVirtualDiskSnapshot, callInfo) mock.lockCanUnfreezeWithVirtualDiskSnapshot.Unlock() - return mock.CanUnfreezeWithVirtualDiskSnapshotFunc(ctx, vdSnapshotName, vm) + return mock.CanUnfreezeWithVirtualDiskSnapshotFunc(ctx, vdSnapshotName, vm, kvvmi) } // CanUnfreezeWithVirtualDiskSnapshotCalls gets all the calls that were made to CanUnfreezeWithVirtualDiskSnapshot. @@ -329,11 +362,13 @@ func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshotCalls() Ctx context.Context VdSnapshotName string VM *v1alpha2.VirtualMachine + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { Ctx context.Context VdSnapshotName string VM *v1alpha2.VirtualMachine + Kvvmi *virtv1.VirtualMachineInstance } mock.lockCanUnfreezeWithVirtualDiskSnapshot.RLock() calls = mock.calls.CanUnfreezeWithVirtualDiskSnapshot @@ -417,6 +452,42 @@ func (mock *LifeCycleSnapshotterMock) FreezeCalls() []struct { return calls } +// GetKubeVirtVirtualMachineInstance calls GetKubeVirtVirtualMachineInstanceFunc. +func (mock *LifeCycleSnapshotterMock) GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + if mock.GetKubeVirtVirtualMachineInstanceFunc == nil { + panic("LifeCycleSnapshotterMock.GetKubeVirtVirtualMachineInstanceFunc: method is nil but LifeCycleSnapshotter.GetKubeVirtVirtualMachineInstance was just called") + } + callInfo := struct { + Ctx context.Context + VM *v1alpha2.VirtualMachine + }{ + Ctx: ctx, + VM: vm, + } + mock.lockGetKubeVirtVirtualMachineInstance.Lock() + mock.calls.GetKubeVirtVirtualMachineInstance = append(mock.calls.GetKubeVirtVirtualMachineInstance, callInfo) + mock.lockGetKubeVirtVirtualMachineInstance.Unlock() + return mock.GetKubeVirtVirtualMachineInstanceFunc(ctx, vm) +} + +// GetKubeVirtVirtualMachineInstanceCalls gets all the calls that were made to GetKubeVirtVirtualMachineInstance. +// Check the length with: +// +// len(mockedLifeCycleSnapshotter.GetKubeVirtVirtualMachineInstanceCalls()) +func (mock *LifeCycleSnapshotterMock) GetKubeVirtVirtualMachineInstanceCalls() []struct { + Ctx context.Context + VM *v1alpha2.VirtualMachine +} { + var calls []struct { + Ctx context.Context + VM *v1alpha2.VirtualMachine + } + mock.lockGetKubeVirtVirtualMachineInstance.RLock() + calls = mock.calls.GetKubeVirtVirtualMachineInstance + mock.lockGetKubeVirtVirtualMachineInstance.RUnlock() + return calls +} + // GetPersistentVolumeClaim calls GetPersistentVolumeClaimFunc. func (mock *LifeCycleSnapshotterMock) GetPersistentVolumeClaim(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { if mock.GetPersistentVolumeClaimFunc == nil { @@ -578,21 +649,21 @@ func (mock *LifeCycleSnapshotterMock) GetVolumeSnapshotCalls() []struct { } // IsFrozen calls IsFrozenFunc. -func (mock *LifeCycleSnapshotterMock) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { +func (mock *LifeCycleSnapshotterMock) IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if mock.IsFrozenFunc == nil { panic("LifeCycleSnapshotterMock.IsFrozenFunc: method is nil but LifeCycleSnapshotter.IsFrozen was just called") } callInfo := struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, - VM: vm, + Ctx: ctx, + Kvvmi: kvvmi, } mock.lockIsFrozen.Lock() mock.calls.IsFrozen = append(mock.calls.IsFrozen, callInfo) mock.lockIsFrozen.Unlock() - return mock.IsFrozenFunc(ctx, vm) + return mock.IsFrozenFunc(ctx, kvvmi) } // IsFrozenCalls gets all the calls that were made to IsFrozen. @@ -600,12 +671,12 @@ func (mock *LifeCycleSnapshotterMock) IsFrozen(ctx context.Context, vm *v1alpha2 // // len(mockedLifeCycleSnapshotter.IsFrozenCalls()) func (mock *LifeCycleSnapshotterMock) IsFrozenCalls() []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } mock.lockIsFrozen.RLock() calls = mock.calls.IsFrozen @@ -613,6 +684,42 @@ func (mock *LifeCycleSnapshotterMock) IsFrozenCalls() []struct { return calls } +// SyncFSFreezeRequest calls SyncFSFreezeRequestFunc. +func (mock *LifeCycleSnapshotterMock) SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { + if mock.SyncFSFreezeRequestFunc == nil { + panic("LifeCycleSnapshotterMock.SyncFSFreezeRequestFunc: method is nil but LifeCycleSnapshotter.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(mockedLifeCycleSnapshotter.SyncFSFreezeRequestCalls()) +func (mock *LifeCycleSnapshotterMock) 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 *LifeCycleSnapshotterMock) Unfreeze(ctx context.Context, name string, namespace string) error { if mock.UnfreezeFunc == nil { diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go index ed2bbe3542..d3bdc132d3 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go @@ -20,6 +20,7 @@ import ( "context" corev1 "k8s.io/api/core/v1" + virtv1 "kubevirt.io/api/core/v1" "github.com/deckhouse/virtualization/api/core/v1alpha2" ) @@ -39,7 +40,9 @@ type Snapshotter interface { CreateVirtualDiskSnapshot(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (*v1alpha2.VirtualDiskSnapshot, error) Freeze(ctx context.Context, name, namespace string) error Unfreeze(ctx context.Context, name, namespace string) error - IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) - CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) - CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) + IsFrozen(ctx context.Context, 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 + GetKubeVirtVirtualMachineInstance(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 d3e892821e..085b085a49 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -26,6 +26,7 @@ import ( 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" @@ -73,6 +74,12 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } + kvvmi, err := h.snapshotter.GetKubeVirtVirtualMachineInstance(ctx, vm) + if err != nil { + h.setPhaseConditionToFailed(cb, vmSnapshot, err) + return reconcile.Result{}, err + } + if vmSnapshot.DeletionTimestamp != nil { vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseTerminating cb. @@ -80,7 +87,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu Reason(conditions.ReasonUnknown). Message("") - _, err = h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm) + _, err = h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -193,7 +200,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - needToFreeze, err := h.needToFreeze(ctx, vm, vmSnapshot.Spec.RequiredConsistency) + needToFreeze, err := h.needToFreeze(ctx, vm, kvvmi, vmSnapshot.Spec.RequiredConsistency) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -201,7 +208,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - canFreeze, err := h.snapshotter.CanFreeze(ctx, vm) + canFreeze, err := h.snapshotter.CanFreeze(ctx, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -349,7 +356,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu } // 7. Unfreeze VirtualMachine if can. - unfrozen, err := h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm) + unfrozen, err := h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -509,7 +516,7 @@ func (h LifeCycleHandler) areVirtualDiskSnapshotsConsistent(vdSnapshots []*v1alp return true } -func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine, requiredConsistency bool) (bool, error) { +func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance, requiredConsistency bool) (bool, error) { if !requiredConsistency { return false, nil } @@ -518,7 +525,7 @@ func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.Virtual return false, nil } - isFrozen, err := h.snapshotter.IsFrozen(ctx, vm) + isFrozen, err := h.snapshotter.IsFrozen(ctx, kvvmi) if err != nil { return false, err } @@ -549,12 +556,12 @@ func (h LifeCycleHandler) freezeVirtualMachine(ctx context.Context, vm *v1alpha2 return true, nil } -func (h LifeCycleHandler) unfreezeVirtualMachineIfCan(ctx context.Context, vmSnapshot *v1alpha2.VirtualMachineSnapshot, vm *v1alpha2.VirtualMachine) (bool, error) { +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(ctx, vm) + isFrozen, err := h.snapshotter.IsFrozen(ctx, kvvmi) if err != nil { return false, err } @@ -562,7 +569,7 @@ func (h LifeCycleHandler) unfreezeVirtualMachineIfCan(ctx context.Context, vmSna return false, nil } - canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualMachineSnapshot(ctx, vmSnapshot.Name, vm) + canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualMachineSnapshot(ctx, vmSnapshot.Name, vm, kvvmi) if err != nil { return false, err } 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 5d988bbc96..ee38a0de9c 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,6 +25,7 @@ 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" @@ -42,6 +43,7 @@ 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 @@ -88,6 +90,13 @@ 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}, } @@ -124,13 +133,13 @@ var _ = Describe("LifeCycle handler", func() { GetVirtualMachineFunc: func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil }, - IsFrozenFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + IsFrozenFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil }, - CanUnfreezeWithVirtualMachineSnapshotFunc: func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine) (bool, error) { + CanUnfreezeWithVirtualMachineSnapshotFunc: func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil }, - CanFreezeFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + CanFreezeFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil }, UnfreezeFunc: func(ctx context.Context, _, _ string) error { @@ -142,6 +151,9 @@ var _ = Describe("LifeCycle handler", func() { GetVirtualDiskSnapshotFunc: func(_ context.Context, _, _ string) (*v1alpha2.VirtualDiskSnapshot, error) { return vdSnapshot, nil }, + GetKubeVirtVirtualMachineInstanceFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + return kvvmi, nil + }, } var err error @@ -248,10 +260,10 @@ var _ = Describe("LifeCycle handler", func() { }) It("The virtual machine is potentially inconsistent", func() { - snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } - snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } @@ -267,10 +279,10 @@ var _ = Describe("LifeCycle handler", func() { }) It("The virtual machine has frozen", func() { - snapshotter.IsFrozenFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } - snapshotter.CanFreezeFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (bool, error) { + snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } snapshotter.FreezeFunc = func(_ context.Context, _, _ string) error { diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go index 2c7724b3f2..665c07f4b6 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go @@ -7,6 +7,7 @@ import ( "context" "github.com/deckhouse/virtualization/api/core/v1alpha2" corev1 "k8s.io/api/core/v1" + virtv1 "kubevirt.io/api/core/v1" "sync" ) @@ -98,10 +99,10 @@ var _ Snapshotter = &SnapshotterMock{} // // // make and configure a mocked Snapshotter // mockedSnapshotter := &SnapshotterMock{ -// CanFreezeFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { +// 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) (bool, error) { +// 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) { @@ -110,6 +111,9 @@ var _ Snapshotter = &SnapshotterMock{} // FreezeFunc: func(ctx context.Context, name string, namespace string) error { // panic("mock out the Freeze method") // }, +// GetKubeVirtVirtualMachineInstanceFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { +// panic("mock out the GetKubeVirtVirtualMachineInstance method") +// }, // GetPersistentVolumeClaimFunc: func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { // panic("mock out the GetPersistentVolumeClaim method") // }, @@ -125,9 +129,12 @@ var _ Snapshotter = &SnapshotterMock{} // GetVirtualMachineFunc: func(ctx context.Context, name string, namespace string) (*v1alpha2.VirtualMachine, error) { // panic("mock out the GetVirtualMachine method") // }, -// IsFrozenFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { +// IsFrozenFunc: func(ctx context.Context, 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, name string, namespace string) error { // panic("mock out the Unfreeze method") // }, @@ -139,10 +146,10 @@ var _ Snapshotter = &SnapshotterMock{} // } type SnapshotterMock struct { // CanFreezeFunc mocks the CanFreeze method. - CanFreezeFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) + 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) (bool, error) + 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) @@ -150,6 +157,9 @@ type SnapshotterMock struct { // FreezeFunc mocks the Freeze method. FreezeFunc func(ctx context.Context, name string, namespace string) error + // GetKubeVirtVirtualMachineInstanceFunc mocks the GetKubeVirtVirtualMachineInstance method. + GetKubeVirtVirtualMachineInstanceFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) + // GetPersistentVolumeClaimFunc mocks the GetPersistentVolumeClaim method. GetPersistentVolumeClaimFunc func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) @@ -166,7 +176,10 @@ type SnapshotterMock struct { GetVirtualMachineFunc func(ctx context.Context, name string, namespace string) (*v1alpha2.VirtualMachine, error) // IsFrozenFunc mocks the IsFrozen method. - IsFrozenFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) + IsFrozenFunc func(ctx context.Context, 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, name string, namespace string) error @@ -177,8 +190,8 @@ type SnapshotterMock struct { CanFreeze []struct { // Ctx is the ctx argument value. Ctx context.Context - // VM is the vm argument value. - VM *v1alpha2.VirtualMachine + // Kvvmi is the kvvmi argument value. + Kvvmi *virtv1.VirtualMachineInstance } // CanUnfreezeWithVirtualMachineSnapshot holds details about calls to the CanUnfreezeWithVirtualMachineSnapshot method. CanUnfreezeWithVirtualMachineSnapshot []struct { @@ -188,6 +201,8 @@ type SnapshotterMock struct { 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 { @@ -205,6 +220,13 @@ type SnapshotterMock struct { // Namespace is the namespace argument value. Namespace string } + // GetKubeVirtVirtualMachineInstance holds details about calls to the GetKubeVirtVirtualMachineInstance method. + GetKubeVirtVirtualMachineInstance []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // VM is the vm argument value. + VM *v1alpha2.VirtualMachine + } // GetPersistentVolumeClaim holds details about calls to the GetPersistentVolumeClaim method. GetPersistentVolumeClaim []struct { // Ctx is the ctx argument value. @@ -254,8 +276,15 @@ type SnapshotterMock struct { IsFrozen []struct { // Ctx is the ctx argument value. Ctx context.Context - // VM is the vm argument value. - VM *v1alpha2.VirtualMachine + // 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 { @@ -271,31 +300,33 @@ type SnapshotterMock struct { lockCanUnfreezeWithVirtualMachineSnapshot sync.RWMutex lockCreateVirtualDiskSnapshot sync.RWMutex lockFreeze sync.RWMutex + lockGetKubeVirtVirtualMachineInstance sync.RWMutex lockGetPersistentVolumeClaim sync.RWMutex lockGetSecret sync.RWMutex lockGetVirtualDisk sync.RWMutex lockGetVirtualDiskSnapshot sync.RWMutex lockGetVirtualMachine sync.RWMutex lockIsFrozen sync.RWMutex + lockSyncFSFreezeRequest sync.RWMutex lockUnfreeze sync.RWMutex } // CanFreeze calls CanFreezeFunc. -func (mock *SnapshotterMock) CanFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { +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 - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, - VM: vm, + Ctx: ctx, + Kvvmi: kvvmi, } mock.lockCanFreeze.Lock() mock.calls.CanFreeze = append(mock.calls.CanFreeze, callInfo) mock.lockCanFreeze.Unlock() - return mock.CanFreezeFunc(ctx, vm) + return mock.CanFreezeFunc(ctx, kvvmi) } // CanFreezeCalls gets all the calls that were made to CanFreeze. @@ -303,12 +334,12 @@ func (mock *SnapshotterMock) CanFreeze(ctx context.Context, vm *v1alpha2.Virtual // // len(mockedSnapshotter.CanFreezeCalls()) func (mock *SnapshotterMock) CanFreezeCalls() []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } mock.lockCanFreeze.RLock() calls = mock.calls.CanFreeze @@ -317,7 +348,7 @@ func (mock *SnapshotterMock) CanFreezeCalls() []struct { } // CanUnfreezeWithVirtualMachineSnapshot calls CanUnfreezeWithVirtualMachineSnapshotFunc. -func (mock *SnapshotterMock) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) { +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") } @@ -325,15 +356,17 @@ func (mock *SnapshotterMock) CanUnfreezeWithVirtualMachineSnapshot(ctx context.C 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) + return mock.CanUnfreezeWithVirtualMachineSnapshotFunc(ctx, vmSnapshotName, vm, kvvmi) } // CanUnfreezeWithVirtualMachineSnapshotCalls gets all the calls that were made to CanUnfreezeWithVirtualMachineSnapshot. @@ -344,11 +377,13 @@ func (mock *SnapshotterMock) CanUnfreezeWithVirtualMachineSnapshotCalls() []stru 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 @@ -432,6 +467,42 @@ func (mock *SnapshotterMock) FreezeCalls() []struct { return calls } +// GetKubeVirtVirtualMachineInstance calls GetKubeVirtVirtualMachineInstanceFunc. +func (mock *SnapshotterMock) GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + if mock.GetKubeVirtVirtualMachineInstanceFunc == nil { + panic("SnapshotterMock.GetKubeVirtVirtualMachineInstanceFunc: method is nil but Snapshotter.GetKubeVirtVirtualMachineInstance was just called") + } + callInfo := struct { + Ctx context.Context + VM *v1alpha2.VirtualMachine + }{ + Ctx: ctx, + VM: vm, + } + mock.lockGetKubeVirtVirtualMachineInstance.Lock() + mock.calls.GetKubeVirtVirtualMachineInstance = append(mock.calls.GetKubeVirtVirtualMachineInstance, callInfo) + mock.lockGetKubeVirtVirtualMachineInstance.Unlock() + return mock.GetKubeVirtVirtualMachineInstanceFunc(ctx, vm) +} + +// GetKubeVirtVirtualMachineInstanceCalls gets all the calls that were made to GetKubeVirtVirtualMachineInstance. +// Check the length with: +// +// len(mockedSnapshotter.GetKubeVirtVirtualMachineInstanceCalls()) +func (mock *SnapshotterMock) GetKubeVirtVirtualMachineInstanceCalls() []struct { + Ctx context.Context + VM *v1alpha2.VirtualMachine +} { + var calls []struct { + Ctx context.Context + VM *v1alpha2.VirtualMachine + } + mock.lockGetKubeVirtVirtualMachineInstance.RLock() + calls = mock.calls.GetKubeVirtVirtualMachineInstance + mock.lockGetKubeVirtVirtualMachineInstance.RUnlock() + return calls +} + // GetPersistentVolumeClaim calls GetPersistentVolumeClaimFunc. func (mock *SnapshotterMock) GetPersistentVolumeClaim(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { if mock.GetPersistentVolumeClaimFunc == nil { @@ -633,21 +704,21 @@ func (mock *SnapshotterMock) GetVirtualMachineCalls() []struct { } // IsFrozen calls IsFrozenFunc. -func (mock *SnapshotterMock) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualMachine) (bool, error) { +func (mock *SnapshotterMock) IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if mock.IsFrozenFunc == nil { panic("SnapshotterMock.IsFrozenFunc: method is nil but Snapshotter.IsFrozen was just called") } callInfo := struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, - VM: vm, + Ctx: ctx, + Kvvmi: kvvmi, } mock.lockIsFrozen.Lock() mock.calls.IsFrozen = append(mock.calls.IsFrozen, callInfo) mock.lockIsFrozen.Unlock() - return mock.IsFrozenFunc(ctx, vm) + return mock.IsFrozenFunc(ctx, kvvmi) } // IsFrozenCalls gets all the calls that were made to IsFrozen. @@ -655,12 +726,12 @@ func (mock *SnapshotterMock) IsFrozen(ctx context.Context, vm *v1alpha2.VirtualM // // len(mockedSnapshotter.IsFrozenCalls()) func (mock *SnapshotterMock) IsFrozenCalls() []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } mock.lockIsFrozen.RLock() calls = mock.calls.IsFrozen @@ -668,6 +739,42 @@ func (mock *SnapshotterMock) IsFrozenCalls() []struct { 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, name string, namespace string) error { if mock.UnfreezeFunc == nil { From 675539f5a9749e180dd71eeff7b0243512f4cd74 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Tue, 11 Nov 2025 17:03:54 +0300 Subject: [PATCH 06/23] fix(vdsnapshot): resolve review comments Signed-off-by: Roman Sysoev --- .../controller/service/snapshot_service.go | 151 ++++-------------- .../vdsnapshot/internal/deletion.go | 4 +- .../vdsnapshot/internal/interfaces.go | 6 +- .../vdsnapshot/internal/life_cycle.go | 78 +++++---- .../vdsnapshot/internal/life_cycle_test.go | 8 +- .../controller/vdsnapshot/internal/mock.go | 132 +++++++-------- .../vmsnapshot/internal/interfaces.go | 6 +- .../vmsnapshot/internal/life_cycle.go | 18 +-- .../vmsnapshot/internal/life_cycle_test.go | 6 +- .../controller/vmsnapshot/internal/mock.go | 146 ++++++++--------- 10 files changed, 227 insertions(+), 328 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go index a2375b4b80..9811b6a573 100644 --- a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go +++ b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go @@ -68,10 +68,8 @@ func (s *SnapshotService) IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMac return false, nil } - if kvvmi.Annotations != nil { - if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - return false, ErrUntrustedFilesystemFrozenCondition - } + if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + return false, fmt.Errorf("failed to check %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, ErrUntrustedFilesystemFrozenCondition) } return kvvmi.Status.FSFreezeStatus == FSFrozen, nil @@ -99,93 +97,25 @@ func (s *SnapshotService) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMa return false, nil } -func (s *SnapshotService) Freeze(ctx context.Context, name, namespace string) error { - kvvmi, err := object.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &virtv1.VirtualMachineInstance{}) - if err != nil { - return fmt.Errorf("failed to fetch internal virtual machine instance %s/%s: %w", namespace, name, err) - } - - if kvvmi.Annotations != nil { - if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - return ErrUnexpectedFilesystemFrozenRequest - } +func (s *SnapshotService) Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { + if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + return fmt.Errorf("failed to freeze %s/%s virtual machine filesystem: %w: request type: %s", kvvmi.Namespace, kvvmi.Name, ErrUnexpectedFilesystemFrozenRequest, request) } - err = s.annotateWithFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) + err := s.annotateWithFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) if err != nil { - return fmt.Errorf("failed to annotate virtual machine with filesystem freeze request: %w", err) + return fmt.Errorf("failed to annotate internal virtual machine instance with filesystem freeze request: %w", err) } - err = s.virtClient.VirtualMachines(namespace).Freeze(ctx, name, subv1alpha2.VirtualMachineFreeze{}) + err = s.virtClient.VirtualMachines(kvvmi.Namespace).Freeze(ctx, kvvmi.Name, subv1alpha2.VirtualMachineFreeze{}) if err != nil { - err := s.removeAnnFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) - if err != nil { - return fmt.Errorf("failed to remove virtual machine annotation with filesystem freeze request: %w", err) - } - return fmt.Errorf("failed to freeze virtual machine %s/%s: %w", namespace, name, err) + return fmt.Errorf("failed to freeze %s/%s virtual machine filesystem: %w", kvvmi.Namespace, kvvmi.Name, err) } return nil } -func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if vm == nil { - return false, nil - } - - isFrozen, err := s.IsFrozen(ctx, kvvmi) - if err != nil { - return false, err - } - - if !isFrozen { - return false, nil - } - - vdByName := make(map[string]struct{}) - for _, bdr := range vm.Status.BlockDeviceRefs { - if bdr.Kind == v1alpha2.DiskDevice { - vdByName[bdr.Name] = struct{}{} - } - } - - var vdSnapshots v1alpha2.VirtualDiskSnapshotList - err = s.client.List(ctx, &vdSnapshots, &client.ListOptions{ - Namespace: vm.Namespace, - }) - if err != nil { - return false, err - } - - for _, vdSnapshot := range vdSnapshots.Items { - if vdSnapshot.Name == vdSnapshotName { - continue - } - - _, ok := vdByName[vdSnapshot.Spec.VirtualDiskName] - if ok && vdSnapshot.Status.Phase == v1alpha2.VirtualDiskSnapshotPhaseInProgress { - return false, nil - } - } - - var vmSnapshots v1alpha2.VirtualMachineSnapshotList - err = s.client.List(ctx, &vmSnapshots, &client.ListOptions{ - Namespace: vm.Namespace, - }) - if err != nil { - return false, err - } - - for _, vmSnapshot := range vmSnapshots.Items { - if vmSnapshot.Spec.VirtualMachineName == vm.Name && vmSnapshot.Status.Phase == v1alpha2.VirtualMachineSnapshotPhaseInProgress { - return false, nil - } - } - - return true, nil -} - -func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +func (s *SnapshotService) CanUnfreeze(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if vm == nil { return false, nil } @@ -241,30 +171,19 @@ func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Cont return true, nil } -func (s *SnapshotService) Unfreeze(ctx context.Context, name, namespace string) error { - kvvmi, err := object.FetchObject(ctx, types.NamespacedName{Name: name, Namespace: namespace}, s.client, &virtv1.VirtualMachineInstance{}) - if err != nil { - return fmt.Errorf("failed to fetch internal virtual machine instance %s/%s: %w", namespace, name, err) - } - - if kvvmi.Annotations != nil { - if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - return ErrUnexpectedFilesystemFrozenRequest - } +func (s *SnapshotService) Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { + if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + return fmt.Errorf("failed to unfreeze %s/%s virtual machine filesystem: %w: request type: %s", kvvmi.Namespace, kvvmi.Name, ErrUnexpectedFilesystemFrozenRequest, request) } - err = s.annotateWithFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) + err := s.annotateWithFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) if err != nil { return fmt.Errorf("failed to annotate internal virtual machine instance with filesystem unfreeze request: %w", err) } - err = s.virtClient.VirtualMachines(namespace).Unfreeze(ctx, name) + err = s.virtClient.VirtualMachines(kvvmi.Namespace).Unfreeze(ctx, kvvmi.Name) if err != nil { - err := s.removeAnnFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) - if err != nil { - return fmt.Errorf("failed to remove internal virtual machine instance annotation with filesystem unfreeze request: %w", err) - } - return fmt.Errorf("unfreeze virtual machine %s/%s: %w", namespace, name, err) + return fmt.Errorf("unfreeze virtual machine %s/%s: %w", kvvmi.Namespace, kvvmi.Name, err) } return nil @@ -356,14 +275,12 @@ func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, reque return nil } -func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, requestType string, kvvmi *virtv1.VirtualMachineInstance) error { +func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { if kvvmi == nil || kvvmi.Annotations == nil { return nil } - if r, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok && r == requestType { - delete(kvvmi.Annotations, annotations.AnnVMFilesystemFrozenRequest) - } + delete(kvvmi.Annotations, annotations.AnnVMFilesystemFrozenRequest) err := s.client.Update(ctx, kvvmi) if err != nil { @@ -378,24 +295,22 @@ func (s *SnapshotService) SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1 return nil } - if kvvmi.Annotations != nil { - if r, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - switch { - case r == RequestFSFreeze && kvvmi.Status.FSFreezeStatus == FSFrozen: - err := s.removeAnnFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) - if err != nil { - return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, err) - } - return nil - case r == RequestFSUnfreeze && kvvmi.Status.FSFreezeStatus != FSFrozen: - err := s.removeAnnFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) - if err != nil { - return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, err) - } - return nil - default: - return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, ErrUntrustedFilesystemFrozenCondition) + if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { + switch { + case request == RequestFSFreeze && kvvmi.Status.FSFreezeStatus == FSFrozen: + err := s.removeAnnFSFreezeRequest(ctx, kvvmi) + if err != nil { + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, err) + } + return nil + case request == RequestFSUnfreeze && kvvmi.Status.FSFreezeStatus != FSFrozen: + err := s.removeAnnFSFreezeRequest(ctx, kvvmi) + if err != nil { + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, err) } + return nil + default: + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, ErrUntrustedFilesystemFrozenCondition) } } diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go index 9978ca173e..812eb7e3d0 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go @@ -76,7 +76,7 @@ func (h DeletionHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtua if vm != nil { var canUnfreeze bool - canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) + canUnfreeze, err = h.snapshotter.CanUnfreeze(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -85,7 +85,7 @@ func (h DeletionHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtua } if canUnfreeze { - err = h.snapshotter.Unfreeze(ctx, vm.Name, vm.Namespace) + err = h.snapshotter.Unfreeze(ctx, kvvmi) if err != nil { return reconcile.Result{}, err } diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go index 4e58d7e864..8ed791bf5a 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go @@ -33,11 +33,11 @@ type VirtualDiskReadySnapshotter interface { } type LifeCycleSnapshotter interface { - Freeze(ctx context.Context, name, namespace string) error + Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - Unfreeze(ctx context.Context, name, namespace string) error + CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error CreateVolumeSnapshot(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) GetPersistentVolumeClaim(ctx context.Context, name, namespace string) (*corev1.PersistentVolumeClaim, error) GetVirtualDisk(ctx context.Context, name, namespace string) (*v1alpha2.VirtualDisk, error) 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 fa35730ade..226dc932d5 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -54,15 +54,16 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu cb := conditions.NewConditionBuilder(vdscondition.VirtualDiskSnapshotReadyType).Generation(vdSnapshot.Generation) defer func() { - err := h.unfreezeFilesystemIfFailed(ctx, vdSnapshot) - if err != nil { - if cb.Condition().Message != "" { - cb.Message(fmt.Sprintf("%s, %s", err.Error(), cb.Condition().Message)) - } else { - cb.Message(err.Error()) + if vdSnapshot.Status.Phase == v1alpha2.VirtualDiskSnapshotPhaseFailed { + err := h.unfreezeFilesystem(ctx, vdSnapshot) + if err != nil { + if cb.Condition().Message != "" { + cb.Message(fmt.Sprintf("%s, %s", err.Error(), cb.Condition().Message)) + } else { + cb.Message(err.Error()) + } } } - conditions.SetCondition(cb, &vdSnapshot.Status.Conditions) }() @@ -72,12 +73,6 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - vd, err := h.snapshotter.GetVirtualDisk(ctx, vdSnapshot.Spec.VirtualDiskName, vdSnapshot.Namespace) - if err != nil { - setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) - return reconcile.Result{}, err - } - if vdSnapshot.DeletionTimestamp != nil { vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseTerminating cb. @@ -118,6 +113,12 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, nil } + vd, err := h.snapshotter.GetVirtualDisk(ctx, vdSnapshot.Spec.VirtualDiskName, vdSnapshot.Namespace) + if err != nil { + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err + } + virtualDiskReadyCondition, _ := conditions.GetCondition(vdscondition.VirtualDiskReadyType, vdSnapshot.Status.Conditions) if vd == nil || virtualDiskReadyCondition.Status != metav1.ConditionTrue { vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhasePending @@ -197,7 +198,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{Requeue: true}, nil } - err = h.snapshotter.Freeze(ctx, vm.Name, vm.Namespace) + err = h.snapshotter.Freeze(ctx, kvvmi) if err != nil { setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, err @@ -354,7 +355,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu vdSnapshot.Status.Consistent = ptr.To(true) var canUnfreeze bool - canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) + canUnfreeze, err = h.snapshotter.CanUnfreeze(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -366,7 +367,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if canUnfreeze { log.Debug("Unfreeze the virtual machine after taking a snapshot") - err = h.snapshotter.Unfreeze(ctx, vm.Name, vm.Namespace) + err = h.snapshotter.Unfreeze(ctx, kvvmi) if err != nil { setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, err @@ -400,22 +401,24 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu } func getVirtualMachine(ctx context.Context, vd *v1alpha2.VirtualDisk, snapshotter LifeCycleSnapshotter) (*v1alpha2.VirtualMachine, error) { - if vd == nil || vd.Status.AttachedToVirtualMachines == nil { + if vd == nil { return nil, nil } - for _, avm := range vd.Status.AttachedToVirtualMachines { - if avm.Mounted { - vm, err := snapshotter.GetVirtualMachine(ctx, avm.Name, vd.Namespace) - if err != nil { - return nil, err - } - - return vm, nil + // TODO: ensure vd.Status.AttachedToVirtualMachines is in the actual state. + switch len(vd.Status.AttachedToVirtualMachines) { + case 0: + return nil, nil + case 1: + vm, err := snapshotter.GetVirtualMachine(ctx, vd.Status.AttachedToVirtualMachines[0].Name, vd.Namespace) + if err != nil { + return nil, err } - } - return nil, nil + return vm, nil + default: + return nil, fmt.Errorf("the virtual disk %q is attached to multiple virtual machines", vd.Name) + } } func setPhaseConditionToFailed(cb *conditions.ConditionBuilder, phase *v1alpha2.VirtualDiskSnapshotPhase, err error) { @@ -426,11 +429,7 @@ func setPhaseConditionToFailed(cb *conditions.ConditionBuilder, phase *v1alpha2. Message(service.CapitalizeFirstLetter(err.Error() + ".")) } -func (h LifeCycleHandler) unfreezeFilesystemIfFailed(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) error { - if vdSnapshot.Status.Phase != v1alpha2.VirtualDiskSnapshotPhaseFailed { - return nil - } - +func (h LifeCycleHandler) unfreezeFilesystem(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) error { vd, err := h.snapshotter.GetVirtualDisk(ctx, vdSnapshot.Spec.VirtualDiskName, vdSnapshot.Namespace) if err != nil { return err @@ -449,9 +448,18 @@ func (h LifeCycleHandler) unfreezeFilesystemIfFailed(ctx context.Context, vdSnap return nil } - frozenCondition, _ := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, vm.Status.Conditions) - if frozenCondition.Status == metav1.ConditionTrue { - err = h.snapshotter.Unfreeze(ctx, vm.Name, vm.Namespace) + kvvmi, err := h.snapshotter.GetKubeVirtVirtualMachineInstance(ctx, vm) + if err != nil { + return err + } + + canUnfreeze, err := h.snapshotter.CanUnfreeze(ctx, vdSnapshot.Name, vm, kvvmi) + if err != nil { + return err + } + + if canUnfreeze { + err = h.snapshotter.Unfreeze(ctx, kvvmi) if err != nil { return 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 d214ec53d5..f8fa85dcc1 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 @@ -206,13 +206,13 @@ var _ = Describe("LifeCycle handler", func() { snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } - snapshotter.FreezeFunc = func(_ context.Context, _, _ string) error { + snapshotter.FreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { return nil } - snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + snapshotter.CanUnfreezeFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } - snapshotter.UnfreezeFunc = func(_ context.Context, _, _ string) error { + snapshotter.UnfreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { return nil } }) @@ -310,7 +310,7 @@ var _ = Describe("LifeCycle handler", func() { } return vs, nil } - snapshotter.UnfreezeFunc = func(_ context.Context, _, _ string) error { + snapshotter.UnfreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { unFreezeCalled = true return nil } diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go index 91a580ad5e..9152cbbb68 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go @@ -103,13 +103,13 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // CanFreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { // panic("mock out the CanFreeze method") // }, -// CanUnfreezeWithVirtualDiskSnapshotFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { -// panic("mock out the CanUnfreezeWithVirtualDiskSnapshot method") +// CanUnfreezeFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +// panic("mock out the CanUnfreeze method") // }, // CreateVolumeSnapshotFunc: func(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) { // panic("mock out the CreateVolumeSnapshot method") // }, -// FreezeFunc: func(ctx context.Context, name string, namespace string) error { +// FreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { // panic("mock out the Freeze method") // }, // GetKubeVirtVirtualMachineInstanceFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { @@ -133,7 +133,7 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // SyncFSFreezeRequestFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { // panic("mock out the SyncFSFreezeRequest method") // }, -// UnfreezeFunc: func(ctx context.Context, name string, namespace string) error { +// UnfreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { // panic("mock out the Unfreeze method") // }, // } @@ -146,14 +146,14 @@ type LifeCycleSnapshotterMock struct { // CanFreezeFunc mocks the CanFreeze method. CanFreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - // CanUnfreezeWithVirtualDiskSnapshotFunc mocks the CanUnfreezeWithVirtualDiskSnapshot method. - CanUnfreezeWithVirtualDiskSnapshotFunc func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + // CanUnfreezeFunc mocks the CanUnfreeze method. + CanUnfreezeFunc func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) // CreateVolumeSnapshotFunc mocks the CreateVolumeSnapshot method. CreateVolumeSnapshotFunc func(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) // FreezeFunc mocks the Freeze method. - FreezeFunc func(ctx context.Context, name string, namespace string) error + FreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error // GetKubeVirtVirtualMachineInstanceFunc mocks the GetKubeVirtVirtualMachineInstance method. GetKubeVirtVirtualMachineInstanceFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) @@ -177,7 +177,7 @@ type LifeCycleSnapshotterMock struct { SyncFSFreezeRequestFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error // UnfreezeFunc mocks the Unfreeze method. - UnfreezeFunc func(ctx context.Context, name string, namespace string) error + UnfreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error // calls tracks calls to the methods. calls struct { @@ -188,8 +188,8 @@ type LifeCycleSnapshotterMock struct { // Kvvmi is the kvvmi argument value. Kvvmi *virtv1.VirtualMachineInstance } - // CanUnfreezeWithVirtualDiskSnapshot holds details about calls to the CanUnfreezeWithVirtualDiskSnapshot method. - CanUnfreezeWithVirtualDiskSnapshot []struct { + // CanUnfreeze holds details about calls to the CanUnfreeze method. + CanUnfreeze []struct { // Ctx is the ctx argument value. Ctx context.Context // VdSnapshotName is the vdSnapshotName argument value. @@ -210,10 +210,8 @@ type LifeCycleSnapshotterMock struct { Freeze []struct { // Ctx is the ctx argument value. Ctx context.Context - // Name is the name argument value. - Name string - // Namespace is the namespace argument value. - Namespace string + // Kvvmi is the kvvmi argument value. + Kvvmi *virtv1.VirtualMachineInstance } // GetKubeVirtVirtualMachineInstance holds details about calls to the GetKubeVirtVirtualMachineInstance method. GetKubeVirtVirtualMachineInstance []struct { @@ -276,24 +274,22 @@ type LifeCycleSnapshotterMock struct { Unfreeze []struct { // Ctx is the ctx argument value. Ctx context.Context - // Name is the name argument value. - Name string - // Namespace is the namespace argument value. - Namespace string + // Kvvmi is the kvvmi argument value. + Kvvmi *virtv1.VirtualMachineInstance } } - lockCanFreeze sync.RWMutex - lockCanUnfreezeWithVirtualDiskSnapshot sync.RWMutex - lockCreateVolumeSnapshot sync.RWMutex - lockFreeze sync.RWMutex - lockGetKubeVirtVirtualMachineInstance sync.RWMutex - lockGetPersistentVolumeClaim sync.RWMutex - lockGetVirtualDisk sync.RWMutex - lockGetVirtualMachine sync.RWMutex - lockGetVolumeSnapshot sync.RWMutex - lockIsFrozen sync.RWMutex - lockSyncFSFreezeRequest sync.RWMutex - lockUnfreeze sync.RWMutex + lockCanFreeze sync.RWMutex + lockCanUnfreeze sync.RWMutex + lockCreateVolumeSnapshot sync.RWMutex + lockFreeze sync.RWMutex + lockGetKubeVirtVirtualMachineInstance sync.RWMutex + lockGetPersistentVolumeClaim sync.RWMutex + lockGetVirtualDisk sync.RWMutex + lockGetVirtualMachine sync.RWMutex + lockGetVolumeSnapshot sync.RWMutex + lockIsFrozen sync.RWMutex + lockSyncFSFreezeRequest sync.RWMutex + lockUnfreeze sync.RWMutex } // CanFreeze calls CanFreezeFunc. @@ -332,10 +328,10 @@ func (mock *LifeCycleSnapshotterMock) CanFreezeCalls() []struct { return calls } -// CanUnfreezeWithVirtualDiskSnapshot calls CanUnfreezeWithVirtualDiskSnapshotFunc. -func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if mock.CanUnfreezeWithVirtualDiskSnapshotFunc == nil { - panic("LifeCycleSnapshotterMock.CanUnfreezeWithVirtualDiskSnapshotFunc: method is nil but LifeCycleSnapshotter.CanUnfreezeWithVirtualDiskSnapshot was just called") +// CanUnfreeze calls CanUnfreezeFunc. +func (mock *LifeCycleSnapshotterMock) CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + if mock.CanUnfreezeFunc == nil { + panic("LifeCycleSnapshotterMock.CanUnfreezeFunc: method is nil but LifeCycleSnapshotter.CanUnfreeze was just called") } callInfo := struct { Ctx context.Context @@ -348,17 +344,17 @@ func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshot(ctx con VM: vm, Kvvmi: kvvmi, } - mock.lockCanUnfreezeWithVirtualDiskSnapshot.Lock() - mock.calls.CanUnfreezeWithVirtualDiskSnapshot = append(mock.calls.CanUnfreezeWithVirtualDiskSnapshot, callInfo) - mock.lockCanUnfreezeWithVirtualDiskSnapshot.Unlock() - return mock.CanUnfreezeWithVirtualDiskSnapshotFunc(ctx, vdSnapshotName, vm, kvvmi) + mock.lockCanUnfreeze.Lock() + mock.calls.CanUnfreeze = append(mock.calls.CanUnfreeze, callInfo) + mock.lockCanUnfreeze.Unlock() + return mock.CanUnfreezeFunc(ctx, vdSnapshotName, vm, kvvmi) } -// CanUnfreezeWithVirtualDiskSnapshotCalls gets all the calls that were made to CanUnfreezeWithVirtualDiskSnapshot. +// CanUnfreezeCalls gets all the calls that were made to CanUnfreeze. // Check the length with: // -// len(mockedLifeCycleSnapshotter.CanUnfreezeWithVirtualDiskSnapshotCalls()) -func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshotCalls() []struct { +// len(mockedLifeCycleSnapshotter.CanUnfreezeCalls()) +func (mock *LifeCycleSnapshotterMock) CanUnfreezeCalls() []struct { Ctx context.Context VdSnapshotName string VM *v1alpha2.VirtualMachine @@ -370,9 +366,9 @@ func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshotCalls() VM *v1alpha2.VirtualMachine Kvvmi *virtv1.VirtualMachineInstance } - mock.lockCanUnfreezeWithVirtualDiskSnapshot.RLock() - calls = mock.calls.CanUnfreezeWithVirtualDiskSnapshot - mock.lockCanUnfreezeWithVirtualDiskSnapshot.RUnlock() + mock.lockCanUnfreeze.RLock() + calls = mock.calls.CanUnfreeze + mock.lockCanUnfreeze.RUnlock() return calls } @@ -413,23 +409,21 @@ func (mock *LifeCycleSnapshotterMock) CreateVolumeSnapshotCalls() []struct { } // Freeze calls FreezeFunc. -func (mock *LifeCycleSnapshotterMock) Freeze(ctx context.Context, name string, namespace string) error { +func (mock *LifeCycleSnapshotterMock) Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { if mock.FreezeFunc == nil { panic("LifeCycleSnapshotterMock.FreezeFunc: method is nil but LifeCycleSnapshotter.Freeze was just called") } callInfo := struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, - Name: name, - Namespace: namespace, + Ctx: ctx, + Kvvmi: kvvmi, } mock.lockFreeze.Lock() mock.calls.Freeze = append(mock.calls.Freeze, callInfo) mock.lockFreeze.Unlock() - return mock.FreezeFunc(ctx, name, namespace) + return mock.FreezeFunc(ctx, kvvmi) } // FreezeCalls gets all the calls that were made to Freeze. @@ -437,14 +431,12 @@ func (mock *LifeCycleSnapshotterMock) Freeze(ctx context.Context, name string, n // // len(mockedLifeCycleSnapshotter.FreezeCalls()) func (mock *LifeCycleSnapshotterMock) FreezeCalls() []struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } mock.lockFreeze.RLock() calls = mock.calls.Freeze @@ -721,23 +713,21 @@ func (mock *LifeCycleSnapshotterMock) SyncFSFreezeRequestCalls() []struct { } // Unfreeze calls UnfreezeFunc. -func (mock *LifeCycleSnapshotterMock) Unfreeze(ctx context.Context, name string, namespace string) error { +func (mock *LifeCycleSnapshotterMock) Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { if mock.UnfreezeFunc == nil { panic("LifeCycleSnapshotterMock.UnfreezeFunc: method is nil but LifeCycleSnapshotter.Unfreeze was just called") } callInfo := struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, - Name: name, - Namespace: namespace, + Ctx: ctx, + Kvvmi: kvvmi, } mock.lockUnfreeze.Lock() mock.calls.Unfreeze = append(mock.calls.Unfreeze, callInfo) mock.lockUnfreeze.Unlock() - return mock.UnfreezeFunc(ctx, name, namespace) + return mock.UnfreezeFunc(ctx, kvvmi) } // UnfreezeCalls gets all the calls that were made to Unfreeze. @@ -745,14 +735,12 @@ func (mock *LifeCycleSnapshotterMock) Unfreeze(ctx context.Context, name string, // // len(mockedLifeCycleSnapshotter.UnfreezeCalls()) func (mock *LifeCycleSnapshotterMock) UnfreezeCalls() []struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } mock.lockUnfreeze.RLock() calls = mock.calls.Unfreeze diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go index d3bdc132d3..17d9bf8327 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go @@ -38,11 +38,11 @@ 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, name, namespace string) error - Unfreeze(ctx context.Context, name, namespace string) error + Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error + Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error IsFrozen(ctx context.Context, 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) + CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error GetKubeVirtVirtualMachineInstance(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 085b085a49..5803e18330 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -266,7 +266,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu // 2. Ensure the virtual machine is consistent for snapshotting. if needToFreeze { - hasFrozen, err = h.freezeVirtualMachine(ctx, vm, vmSnapshot) + hasFrozen, err = h.freezeVirtualMachine(ctx, kvvmi, vmSnapshot) if err != nil { h.setPhaseConditionToFailed(cb, vmSnapshot, err) return reconcile.Result{}, err @@ -536,21 +536,21 @@ func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.Virtual return true, nil } -func (h LifeCycleHandler) freezeVirtualMachine(ctx context.Context, vm *v1alpha2.VirtualMachine, vmSnapshot *v1alpha2.VirtualMachineSnapshot) (bool, error) { - if vm.Status.Phase != v1alpha2.MachineRunning { - return false, errors.New("cannot freeze not Running virtual machine") +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, vm.Name, vm.Namespace) + err := h.snapshotter.Freeze(ctx, kvvmi) if err != nil { - return false, fmt.Errorf("freeze the virtual machine %q: %w", vm.Name, err) + 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.", vm.Name), + fmt.Sprintf("The file system of the virtual machine %q is frozen.", kvvmi.Name), ) return true, nil @@ -569,7 +569,7 @@ func (h LifeCycleHandler) unfreezeVirtualMachineIfCan(ctx context.Context, vmSna return false, nil } - canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualMachineSnapshot(ctx, vmSnapshot.Name, vm, kvvmi) + canUnfreeze, err := h.snapshotter.CanUnfreeze(ctx, vmSnapshot.Name, vm, kvvmi) if err != nil { return false, err } @@ -578,7 +578,7 @@ func (h LifeCycleHandler) unfreezeVirtualMachineIfCan(ctx context.Context, vmSna return false, nil } - err = h.snapshotter.Unfreeze(ctx, vm.Name, vm.Namespace) + err = h.snapshotter.Unfreeze(ctx, kvvmi) if err != nil { return false, fmt.Errorf("unfreeze the virtual machine %q: %w", vm.Name, err) } 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 ee38a0de9c..740b13d64d 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 @@ -136,13 +136,13 @@ var _ = Describe("LifeCycle handler", func() { IsFrozenFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil }, - CanUnfreezeWithVirtualMachineSnapshotFunc: func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { + CanUnfreezeFunc: 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, _, _ string) error { + UnfreezeFunc: func(ctx context.Context, _ *virtv1.VirtualMachineInstance) error { return nil }, GetSecretFunc: func(_ context.Context, _, _ string) (*corev1.Secret, error) { @@ -285,7 +285,7 @@ var _ = Describe("LifeCycle handler", func() { snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } - snapshotter.FreezeFunc = func(_ context.Context, _, _ string) error { + snapshotter.FreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { return nil } diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go index 665c07f4b6..0be2688e5b 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go @@ -102,13 +102,13 @@ var _ Snapshotter = &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") +// CanUnfreezeFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +// panic("mock out the CanUnfreeze method") // }, // CreateVirtualDiskSnapshotFunc: func(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (*v1alpha2.VirtualDiskSnapshot, error) { // panic("mock out the CreateVirtualDiskSnapshot method") // }, -// FreezeFunc: func(ctx context.Context, name string, namespace string) error { +// FreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { // panic("mock out the Freeze method") // }, // GetKubeVirtVirtualMachineInstanceFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { @@ -135,7 +135,7 @@ var _ Snapshotter = &SnapshotterMock{} // SyncFSFreezeRequestFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { // panic("mock out the SyncFSFreezeRequest method") // }, -// UnfreezeFunc: func(ctx context.Context, name string, namespace string) error { +// UnfreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { // panic("mock out the Unfreeze method") // }, // } @@ -148,14 +148,14 @@ 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) + // CanUnfreezeFunc mocks the CanUnfreeze method. + CanUnfreezeFunc func(ctx context.Context, vdSnapshotName 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, name string, namespace string) error + FreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error // GetKubeVirtVirtualMachineInstanceFunc mocks the GetKubeVirtVirtualMachineInstance method. GetKubeVirtVirtualMachineInstanceFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) @@ -182,7 +182,7 @@ type SnapshotterMock struct { SyncFSFreezeRequestFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error // UnfreezeFunc mocks the Unfreeze method. - UnfreezeFunc func(ctx context.Context, name string, namespace string) error + UnfreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error // calls tracks calls to the methods. calls struct { @@ -193,12 +193,12 @@ type SnapshotterMock struct { // Kvvmi is the kvvmi argument value. Kvvmi *virtv1.VirtualMachineInstance } - // CanUnfreezeWithVirtualMachineSnapshot holds details about calls to the CanUnfreezeWithVirtualMachineSnapshot method. - CanUnfreezeWithVirtualMachineSnapshot []struct { + // CanUnfreeze holds details about calls to the CanUnfreeze method. + CanUnfreeze []struct { // Ctx is the ctx argument value. Ctx context.Context - // VmSnapshotName is the vmSnapshotName argument value. - VmSnapshotName string + // VdSnapshotName is the vdSnapshotName argument value. + VdSnapshotName string // VM is the vm argument value. VM *v1alpha2.VirtualMachine // Kvvmi is the kvvmi argument value. @@ -215,10 +215,8 @@ type SnapshotterMock struct { Freeze []struct { // Ctx is the ctx argument value. Ctx context.Context - // Name is the name argument value. - Name string - // Namespace is the namespace argument value. - Namespace string + // Kvvmi is the kvvmi argument value. + Kvvmi *virtv1.VirtualMachineInstance } // GetKubeVirtVirtualMachineInstance holds details about calls to the GetKubeVirtVirtualMachineInstance method. GetKubeVirtVirtualMachineInstance []struct { @@ -290,25 +288,23 @@ type SnapshotterMock struct { Unfreeze []struct { // Ctx is the ctx argument value. Ctx context.Context - // Name is the name argument value. - Name string - // Namespace is the namespace argument value. - Namespace string + // Kvvmi is the kvvmi argument value. + Kvvmi *virtv1.VirtualMachineInstance } } - lockCanFreeze sync.RWMutex - lockCanUnfreezeWithVirtualMachineSnapshot sync.RWMutex - lockCreateVirtualDiskSnapshot sync.RWMutex - lockFreeze sync.RWMutex - lockGetKubeVirtVirtualMachineInstance sync.RWMutex - lockGetPersistentVolumeClaim sync.RWMutex - lockGetSecret sync.RWMutex - lockGetVirtualDisk sync.RWMutex - lockGetVirtualDiskSnapshot sync.RWMutex - lockGetVirtualMachine sync.RWMutex - lockIsFrozen sync.RWMutex - lockSyncFSFreezeRequest sync.RWMutex - lockUnfreeze sync.RWMutex + lockCanFreeze sync.RWMutex + lockCanUnfreeze sync.RWMutex + lockCreateVirtualDiskSnapshot sync.RWMutex + lockFreeze sync.RWMutex + lockGetKubeVirtVirtualMachineInstance sync.RWMutex + lockGetPersistentVolumeClaim sync.RWMutex + lockGetSecret sync.RWMutex + lockGetVirtualDisk sync.RWMutex + lockGetVirtualDiskSnapshot sync.RWMutex + lockGetVirtualMachine sync.RWMutex + lockIsFrozen sync.RWMutex + lockSyncFSFreezeRequest sync.RWMutex + lockUnfreeze sync.RWMutex } // CanFreeze calls CanFreezeFunc. @@ -347,47 +343,47 @@ func (mock *SnapshotterMock) CanFreezeCalls() []struct { 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") +// CanUnfreeze calls CanUnfreezeFunc. +func (mock *SnapshotterMock) CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + if mock.CanUnfreezeFunc == nil { + panic("SnapshotterMock.CanUnfreezeFunc: method is nil but Snapshotter.CanUnfreeze was just called") } callInfo := struct { Ctx context.Context - VmSnapshotName string + VdSnapshotName string VM *v1alpha2.VirtualMachine Kvvmi *virtv1.VirtualMachineInstance }{ Ctx: ctx, - VmSnapshotName: vmSnapshotName, + VdSnapshotName: vdSnapshotName, 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) + mock.lockCanUnfreeze.Lock() + mock.calls.CanUnfreeze = append(mock.calls.CanUnfreeze, callInfo) + mock.lockCanUnfreeze.Unlock() + return mock.CanUnfreezeFunc(ctx, vdSnapshotName, vm, kvvmi) } -// CanUnfreezeWithVirtualMachineSnapshotCalls gets all the calls that were made to CanUnfreezeWithVirtualMachineSnapshot. +// CanUnfreezeCalls gets all the calls that were made to CanUnfreeze. // Check the length with: // -// len(mockedSnapshotter.CanUnfreezeWithVirtualMachineSnapshotCalls()) -func (mock *SnapshotterMock) CanUnfreezeWithVirtualMachineSnapshotCalls() []struct { +// len(mockedSnapshotter.CanUnfreezeCalls()) +func (mock *SnapshotterMock) CanUnfreezeCalls() []struct { Ctx context.Context - VmSnapshotName string + VdSnapshotName string VM *v1alpha2.VirtualMachine Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { Ctx context.Context - VmSnapshotName string + VdSnapshotName string VM *v1alpha2.VirtualMachine Kvvmi *virtv1.VirtualMachineInstance } - mock.lockCanUnfreezeWithVirtualMachineSnapshot.RLock() - calls = mock.calls.CanUnfreezeWithVirtualMachineSnapshot - mock.lockCanUnfreezeWithVirtualMachineSnapshot.RUnlock() + mock.lockCanUnfreeze.RLock() + calls = mock.calls.CanUnfreeze + mock.lockCanUnfreeze.RUnlock() return calls } @@ -428,23 +424,21 @@ func (mock *SnapshotterMock) CreateVirtualDiskSnapshotCalls() []struct { } // Freeze calls FreezeFunc. -func (mock *SnapshotterMock) Freeze(ctx context.Context, name string, namespace string) error { +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 - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, - Name: name, - Namespace: namespace, + Ctx: ctx, + Kvvmi: kvvmi, } mock.lockFreeze.Lock() mock.calls.Freeze = append(mock.calls.Freeze, callInfo) mock.lockFreeze.Unlock() - return mock.FreezeFunc(ctx, name, namespace) + return mock.FreezeFunc(ctx, kvvmi) } // FreezeCalls gets all the calls that were made to Freeze. @@ -452,14 +446,12 @@ func (mock *SnapshotterMock) Freeze(ctx context.Context, name string, namespace // // len(mockedSnapshotter.FreezeCalls()) func (mock *SnapshotterMock) FreezeCalls() []struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } mock.lockFreeze.RLock() calls = mock.calls.Freeze @@ -776,23 +768,21 @@ func (mock *SnapshotterMock) SyncFSFreezeRequestCalls() []struct { } // Unfreeze calls UnfreezeFunc. -func (mock *SnapshotterMock) Unfreeze(ctx context.Context, name string, namespace string) error { +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 - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, - Name: name, - Namespace: namespace, + Ctx: ctx, + Kvvmi: kvvmi, } mock.lockUnfreeze.Lock() mock.calls.Unfreeze = append(mock.calls.Unfreeze, callInfo) mock.lockUnfreeze.Unlock() - return mock.UnfreezeFunc(ctx, name, namespace) + return mock.UnfreezeFunc(ctx, kvvmi) } // UnfreezeCalls gets all the calls that were made to Unfreeze. @@ -800,14 +790,12 @@ func (mock *SnapshotterMock) Unfreeze(ctx context.Context, name string, namespac // // len(mockedSnapshotter.UnfreezeCalls()) func (mock *SnapshotterMock) UnfreezeCalls() []struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context - Name string - Namespace string + Ctx context.Context + Kvvmi *virtv1.VirtualMachineInstance } mock.lockUnfreeze.RLock() calls = mock.calls.Unfreeze From 9a281802fa0fccbb9630f356bd392a408208aaad Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Wed, 12 Nov 2025 02:08:12 +0300 Subject: [PATCH 07/23] fix: review comments Signed-off-by: Roman Sysoev --- .../controller/service/snapshot_service.go | 61 ++++++++++++++- .../vdsnapshot/internal/deletion.go | 2 +- .../vdsnapshot/internal/interfaces.go | 2 +- .../vdsnapshot/internal/life_cycle.go | 4 +- .../vdsnapshot/internal/life_cycle_test.go | 10 +-- .../controller/vdsnapshot/internal/mock.go | 64 +++++++-------- .../vmsnapshot/internal/interfaces.go | 2 +- .../vmsnapshot/internal/life_cycle.go | 2 +- .../vmsnapshot/internal/life_cycle_test.go | 2 +- .../controller/vmsnapshot/internal/mock.go | 78 +++++++++---------- 10 files changed, 142 insertions(+), 85 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go index 9811b6a573..33d775775e 100644 --- a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go +++ b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go @@ -90,7 +90,7 @@ func (s *SnapshotService) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMa for _, c := range kvvmi.Status.Conditions { if c.Type == virtv1.VirtualMachineInstanceAgentConnected { - return c.Status == "True", nil + return c.Status == corev1.ConditionTrue, nil } } @@ -115,7 +115,64 @@ func (s *SnapshotService) Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachi return nil } -func (s *SnapshotService) CanUnfreeze(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + if vm == nil { + return false, nil + } + + isFrozen, err := s.IsFrozen(ctx, kvvmi) + if err != nil { + return false, err + } + + if !isFrozen { + return false, nil + } + + vdByName := make(map[string]struct{}) + for _, bdr := range vm.Status.BlockDeviceRefs { + if bdr.Kind == v1alpha2.DiskDevice { + vdByName[bdr.Name] = struct{}{} + } + } + + var vdSnapshots v1alpha2.VirtualDiskSnapshotList + err = s.client.List(ctx, &vdSnapshots, &client.ListOptions{ + Namespace: vm.Namespace, + }) + if err != nil { + return false, err + } + + for _, vdSnapshot := range vdSnapshots.Items { + if vdSnapshot.Name == vdSnapshotName { + continue + } + + _, ok := vdByName[vdSnapshot.Spec.VirtualDiskName] + if ok && vdSnapshot.Status.Phase == v1alpha2.VirtualDiskSnapshotPhaseInProgress { + return false, nil + } + } + + var vmSnapshots v1alpha2.VirtualMachineSnapshotList + err = s.client.List(ctx, &vmSnapshots, &client.ListOptions{ + Namespace: vm.Namespace, + }) + if err != nil { + return false, err + } + + for _, vmSnapshot := range vmSnapshots.Items { + if vmSnapshot.Spec.VirtualMachineName == vm.Name && vmSnapshot.Status.Phase == v1alpha2.VirtualMachineSnapshotPhaseInProgress { + return false, nil + } + } + + return true, nil +} + +func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if vm == nil { return false, nil } diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go index 812eb7e3d0..2171641e33 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go @@ -76,7 +76,7 @@ func (h DeletionHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtua if vm != nil { var canUnfreeze bool - canUnfreeze, err = h.snapshotter.CanUnfreeze(ctx, vdSnapshot.Name, vm, kvvmi) + canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go index 8ed791bf5a..395205786a 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go @@ -36,7 +36,7 @@ type LifeCycleSnapshotter interface { Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error CreateVolumeSnapshot(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) GetPersistentVolumeClaim(ctx context.Context, name, namespace string) (*corev1.PersistentVolumeClaim, error) 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 226dc932d5..95bbf90a78 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -355,7 +355,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu vdSnapshot.Status.Consistent = ptr.To(true) var canUnfreeze bool - canUnfreeze, err = h.snapshotter.CanUnfreeze(ctx, vdSnapshot.Name, vm, kvvmi) + canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -453,7 +453,7 @@ func (h LifeCycleHandler) unfreezeFilesystem(ctx context.Context, vdSnapshot *v1 return err } - canUnfreeze, err := h.snapshotter.CanUnfreeze(ctx, vdSnapshot.Name, vm, kvvmi) + canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { return 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 f8fa85dcc1..6d780e9fee 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 @@ -192,7 +192,7 @@ var _ = Describe("LifeCycle handler", func() { }, } - vd.Status.AttachedToVirtualMachines = []v1alpha2.AttachedVirtualMachine{{Name: vm.Name, Mounted: true}} + vd.Status.AttachedToVirtualMachines = []v1alpha2.AttachedVirtualMachine{{Name: vm.Name}} snapshotter.GetVirtualMachineFunc = func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil @@ -209,7 +209,7 @@ var _ = Describe("LifeCycle handler", func() { snapshotter.FreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { return nil } - snapshotter.CanUnfreezeFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } snapshotter.UnfreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { @@ -299,7 +299,7 @@ var _ = Describe("LifeCycle handler", func() { }) DescribeTable("Check unfreeze if failed", func(vm *v1alpha2.VirtualMachine, expectUnfreezing bool) { - unFreezeCalled := false + unfreezeCalled := false snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil @@ -311,7 +311,7 @@ var _ = Describe("LifeCycle handler", func() { return vs, nil } snapshotter.UnfreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { - unFreezeCalled = true + unfreezeCalled = true return nil } snapshotter.GetVirtualMachineFunc = func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { @@ -325,7 +325,7 @@ var _ = Describe("LifeCycle handler", func() { Expect(err).To(BeNil()) Expect(vdSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualDiskSnapshotPhaseFailed)) - Expect(unFreezeCalled).To(Equal(expectUnfreezing)) + Expect(unfreezeCalled).To(Equal(expectUnfreezing)) }, Entry("Has VM with frozen filesystem", &v1alpha2.VirtualMachine{ diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go index 9152cbbb68..dc29e0bac9 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go @@ -103,8 +103,8 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // CanFreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { // panic("mock out the CanFreeze method") // }, -// CanUnfreezeFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { -// panic("mock out the CanUnfreeze method") +// CanUnfreezeWithVirtualDiskSnapshotFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +// panic("mock out the CanUnfreezeWithVirtualDiskSnapshot method") // }, // CreateVolumeSnapshotFunc: func(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) { // panic("mock out the CreateVolumeSnapshot method") @@ -146,8 +146,8 @@ type LifeCycleSnapshotterMock struct { // CanFreezeFunc mocks the CanFreeze method. CanFreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - // CanUnfreezeFunc mocks the CanUnfreeze method. - CanUnfreezeFunc func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + // CanUnfreezeWithVirtualDiskSnapshotFunc mocks the CanUnfreezeWithVirtualDiskSnapshot method. + CanUnfreezeWithVirtualDiskSnapshotFunc func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) // CreateVolumeSnapshotFunc mocks the CreateVolumeSnapshot method. CreateVolumeSnapshotFunc func(ctx context.Context, vs *vsv1.VolumeSnapshot) (*vsv1.VolumeSnapshot, error) @@ -188,8 +188,8 @@ type LifeCycleSnapshotterMock struct { // Kvvmi is the kvvmi argument value. Kvvmi *virtv1.VirtualMachineInstance } - // CanUnfreeze holds details about calls to the CanUnfreeze method. - CanUnfreeze []struct { + // CanUnfreezeWithVirtualDiskSnapshot holds details about calls to the CanUnfreezeWithVirtualDiskSnapshot method. + CanUnfreezeWithVirtualDiskSnapshot []struct { // Ctx is the ctx argument value. Ctx context.Context // VdSnapshotName is the vdSnapshotName argument value. @@ -278,18 +278,18 @@ type LifeCycleSnapshotterMock struct { Kvvmi *virtv1.VirtualMachineInstance } } - lockCanFreeze sync.RWMutex - lockCanUnfreeze sync.RWMutex - lockCreateVolumeSnapshot sync.RWMutex - lockFreeze sync.RWMutex - lockGetKubeVirtVirtualMachineInstance sync.RWMutex - lockGetPersistentVolumeClaim sync.RWMutex - lockGetVirtualDisk sync.RWMutex - lockGetVirtualMachine sync.RWMutex - lockGetVolumeSnapshot sync.RWMutex - lockIsFrozen sync.RWMutex - lockSyncFSFreezeRequest sync.RWMutex - lockUnfreeze sync.RWMutex + lockCanFreeze sync.RWMutex + lockCanUnfreezeWithVirtualDiskSnapshot sync.RWMutex + lockCreateVolumeSnapshot sync.RWMutex + lockFreeze sync.RWMutex + lockGetKubeVirtVirtualMachineInstance sync.RWMutex + lockGetPersistentVolumeClaim sync.RWMutex + lockGetVirtualDisk sync.RWMutex + lockGetVirtualMachine sync.RWMutex + lockGetVolumeSnapshot sync.RWMutex + lockIsFrozen sync.RWMutex + lockSyncFSFreezeRequest sync.RWMutex + lockUnfreeze sync.RWMutex } // CanFreeze calls CanFreezeFunc. @@ -328,10 +328,10 @@ func (mock *LifeCycleSnapshotterMock) CanFreezeCalls() []struct { return calls } -// CanUnfreeze calls CanUnfreezeFunc. -func (mock *LifeCycleSnapshotterMock) CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if mock.CanUnfreezeFunc == nil { - panic("LifeCycleSnapshotterMock.CanUnfreezeFunc: method is nil but LifeCycleSnapshotter.CanUnfreeze was just called") +// CanUnfreezeWithVirtualDiskSnapshot calls CanUnfreezeWithVirtualDiskSnapshotFunc. +func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { + if mock.CanUnfreezeWithVirtualDiskSnapshotFunc == nil { + panic("LifeCycleSnapshotterMock.CanUnfreezeWithVirtualDiskSnapshotFunc: method is nil but LifeCycleSnapshotter.CanUnfreezeWithVirtualDiskSnapshot was just called") } callInfo := struct { Ctx context.Context @@ -344,17 +344,17 @@ func (mock *LifeCycleSnapshotterMock) CanUnfreeze(ctx context.Context, vdSnapsho VM: vm, Kvvmi: kvvmi, } - mock.lockCanUnfreeze.Lock() - mock.calls.CanUnfreeze = append(mock.calls.CanUnfreeze, callInfo) - mock.lockCanUnfreeze.Unlock() - return mock.CanUnfreezeFunc(ctx, vdSnapshotName, vm, kvvmi) + mock.lockCanUnfreezeWithVirtualDiskSnapshot.Lock() + mock.calls.CanUnfreezeWithVirtualDiskSnapshot = append(mock.calls.CanUnfreezeWithVirtualDiskSnapshot, callInfo) + mock.lockCanUnfreezeWithVirtualDiskSnapshot.Unlock() + return mock.CanUnfreezeWithVirtualDiskSnapshotFunc(ctx, vdSnapshotName, vm, kvvmi) } -// CanUnfreezeCalls gets all the calls that were made to CanUnfreeze. +// CanUnfreezeWithVirtualDiskSnapshotCalls gets all the calls that were made to CanUnfreezeWithVirtualDiskSnapshot. // Check the length with: // -// len(mockedLifeCycleSnapshotter.CanUnfreezeCalls()) -func (mock *LifeCycleSnapshotterMock) CanUnfreezeCalls() []struct { +// len(mockedLifeCycleSnapshotter.CanUnfreezeWithVirtualDiskSnapshotCalls()) +func (mock *LifeCycleSnapshotterMock) CanUnfreezeWithVirtualDiskSnapshotCalls() []struct { Ctx context.Context VdSnapshotName string VM *v1alpha2.VirtualMachine @@ -366,9 +366,9 @@ func (mock *LifeCycleSnapshotterMock) CanUnfreezeCalls() []struct { VM *v1alpha2.VirtualMachine Kvvmi *virtv1.VirtualMachineInstance } - mock.lockCanUnfreeze.RLock() - calls = mock.calls.CanUnfreeze - mock.lockCanUnfreeze.RUnlock() + mock.lockCanUnfreezeWithVirtualDiskSnapshot.RLock() + calls = mock.calls.CanUnfreezeWithVirtualDiskSnapshot + mock.lockCanUnfreezeWithVirtualDiskSnapshot.RUnlock() return calls } diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go index 17d9bf8327..f25f473d07 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go @@ -42,7 +42,7 @@ type Snapshotter interface { Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, 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 GetKubeVirtVirtualMachineInstance(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 5803e18330..f5fa799c0d 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -569,7 +569,7 @@ func (h LifeCycleHandler) unfreezeVirtualMachineIfCan(ctx context.Context, vmSna return false, nil } - canUnfreeze, err := h.snapshotter.CanUnfreeze(ctx, vmSnapshot.Name, vm, kvvmi) + canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualMachineSnapshot(ctx, vmSnapshot.Name, vm, kvvmi) if err != nil { return false, err } 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 740b13d64d..89ccfd3450 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 @@ -136,7 +136,7 @@ var _ = Describe("LifeCycle handler", func() { IsFrozenFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil }, - CanUnfreezeFunc: func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { + CanUnfreezeWithVirtualMachineSnapshotFunc: func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil }, CanFreezeFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go index 0be2688e5b..2135d9e4b7 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go @@ -102,8 +102,8 @@ var _ Snapshotter = &SnapshotterMock{} // CanFreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { // panic("mock out the CanFreeze method") // }, -// CanUnfreezeFunc: func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { -// panic("mock out the CanUnfreeze 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") @@ -148,8 +148,8 @@ type SnapshotterMock struct { // CanFreezeFunc mocks the CanFreeze method. CanFreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) - // CanUnfreezeFunc mocks the CanUnfreeze method. - CanUnfreezeFunc func(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, 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) @@ -193,12 +193,12 @@ type SnapshotterMock struct { // Kvvmi is the kvvmi argument value. Kvvmi *virtv1.VirtualMachineInstance } - // CanUnfreeze holds details about calls to the CanUnfreeze method. - CanUnfreeze []struct { + // CanUnfreezeWithVirtualMachineSnapshot holds details about calls to the CanUnfreezeWithVirtualMachineSnapshot method. + CanUnfreezeWithVirtualMachineSnapshot []struct { // Ctx is the ctx argument value. Ctx context.Context - // VdSnapshotName is the vdSnapshotName argument value. - VdSnapshotName string + // VmSnapshotName is the vmSnapshotName argument value. + VmSnapshotName string // VM is the vm argument value. VM *v1alpha2.VirtualMachine // Kvvmi is the kvvmi argument value. @@ -292,19 +292,19 @@ type SnapshotterMock struct { Kvvmi *virtv1.VirtualMachineInstance } } - lockCanFreeze sync.RWMutex - lockCanUnfreeze sync.RWMutex - lockCreateVirtualDiskSnapshot sync.RWMutex - lockFreeze sync.RWMutex - lockGetKubeVirtVirtualMachineInstance sync.RWMutex - lockGetPersistentVolumeClaim sync.RWMutex - lockGetSecret sync.RWMutex - lockGetVirtualDisk sync.RWMutex - lockGetVirtualDiskSnapshot sync.RWMutex - lockGetVirtualMachine sync.RWMutex - lockIsFrozen sync.RWMutex - lockSyncFSFreezeRequest sync.RWMutex - lockUnfreeze sync.RWMutex + lockCanFreeze sync.RWMutex + lockCanUnfreezeWithVirtualMachineSnapshot sync.RWMutex + lockCreateVirtualDiskSnapshot sync.RWMutex + lockFreeze sync.RWMutex + lockGetKubeVirtVirtualMachineInstance sync.RWMutex + lockGetPersistentVolumeClaim sync.RWMutex + lockGetSecret sync.RWMutex + lockGetVirtualDisk sync.RWMutex + lockGetVirtualDiskSnapshot sync.RWMutex + lockGetVirtualMachine sync.RWMutex + lockIsFrozen sync.RWMutex + lockSyncFSFreezeRequest sync.RWMutex + lockUnfreeze sync.RWMutex } // CanFreeze calls CanFreezeFunc. @@ -343,47 +343,47 @@ func (mock *SnapshotterMock) CanFreezeCalls() []struct { return calls } -// CanUnfreeze calls CanUnfreezeFunc. -func (mock *SnapshotterMock) CanUnfreeze(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { - if mock.CanUnfreezeFunc == nil { - panic("SnapshotterMock.CanUnfreezeFunc: method is nil but Snapshotter.CanUnfreeze was just called") +// 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 - VdSnapshotName string + VmSnapshotName string VM *v1alpha2.VirtualMachine Kvvmi *virtv1.VirtualMachineInstance }{ Ctx: ctx, - VdSnapshotName: vdSnapshotName, + VmSnapshotName: vmSnapshotName, VM: vm, Kvvmi: kvvmi, } - mock.lockCanUnfreeze.Lock() - mock.calls.CanUnfreeze = append(mock.calls.CanUnfreeze, callInfo) - mock.lockCanUnfreeze.Unlock() - return mock.CanUnfreezeFunc(ctx, vdSnapshotName, vm, kvvmi) + mock.lockCanUnfreezeWithVirtualMachineSnapshot.Lock() + mock.calls.CanUnfreezeWithVirtualMachineSnapshot = append(mock.calls.CanUnfreezeWithVirtualMachineSnapshot, callInfo) + mock.lockCanUnfreezeWithVirtualMachineSnapshot.Unlock() + return mock.CanUnfreezeWithVirtualMachineSnapshotFunc(ctx, vmSnapshotName, vm, kvvmi) } -// CanUnfreezeCalls gets all the calls that were made to CanUnfreeze. +// CanUnfreezeWithVirtualMachineSnapshotCalls gets all the calls that were made to CanUnfreezeWithVirtualMachineSnapshot. // Check the length with: // -// len(mockedSnapshotter.CanUnfreezeCalls()) -func (mock *SnapshotterMock) CanUnfreezeCalls() []struct { +// len(mockedSnapshotter.CanUnfreezeWithVirtualMachineSnapshotCalls()) +func (mock *SnapshotterMock) CanUnfreezeWithVirtualMachineSnapshotCalls() []struct { Ctx context.Context - VdSnapshotName string + VmSnapshotName string VM *v1alpha2.VirtualMachine Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { Ctx context.Context - VdSnapshotName string + VmSnapshotName string VM *v1alpha2.VirtualMachine Kvvmi *virtv1.VirtualMachineInstance } - mock.lockCanUnfreeze.RLock() - calls = mock.calls.CanUnfreeze - mock.lockCanUnfreeze.RUnlock() + mock.lockCanUnfreezeWithVirtualMachineSnapshot.RLock() + calls = mock.calls.CanUnfreezeWithVirtualMachineSnapshot + mock.lockCanUnfreezeWithVirtualMachineSnapshot.RUnlock() return calls } From dc15ab72b9f6697647beefc53236c4d10489e2d6 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Thu, 13 Nov 2025 03:35:13 +0300 Subject: [PATCH 08/23] fix logic Signed-off-by: Roman Sysoev --- .../vdsnapshot/internal/deletion.go | 5 ++ .../vdsnapshot/internal/life_cycle.go | 89 ++++++++++++++----- .../vmsnapshot/internal/life_cycle.go | 11 +++ 3 files changed, 84 insertions(+), 21 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go index 2171641e33..e29b7c337b 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go @@ -19,7 +19,9 @@ package internal import ( "context" "errors" + "time" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -87,6 +89,9 @@ func (h DeletionHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtua if canUnfreeze { err = h.snapshotter.Unfreeze(ctx, kvvmi) if err != nil { + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } return reconcile.Result{}, err } } 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 95bbf90a78..1f25ebff63 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -22,9 +22,11 @@ import ( "errors" "fmt" "strings" + "time" vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -48,7 +50,7 @@ func NewLifeCycleHandler(snapshotter LifeCycleSnapshotter) *LifeCycleHandler { } } -func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (reconcile.Result, error) { +func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (result reconcile.Result, handlerErr error) { log := logger.FromContext(ctx).With(logger.SlogHandler("lifecycle")) cb := conditions.NewConditionBuilder(vdscondition.VirtualDiskSnapshotReadyType).Generation(vdSnapshot.Generation) @@ -57,6 +59,14 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if vdSnapshot.Status.Phase == v1alpha2.VirtualDiskSnapshotPhaseFailed { err := h.unfreezeFilesystem(ctx, vdSnapshot) if err != nil { + if k8serrors.IsConflict(err) { + result = reconcile.Result{RequeueAfter: 5 * time.Second} + handlerErr = err + } + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + result = reconcile.Result{} + handlerErr = nil + } if cb.Condition().Message != "" { cb.Message(fmt.Sprintf("%s, %s", err.Error(), cb.Condition().Message)) } else { @@ -164,6 +174,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil } + if k8serrors.IsConflict(err) { + result = reconcile.Result{RequeueAfter: 5 * time.Second} + handlerErr = err + } return reconcile.Result{}, err } @@ -178,7 +192,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu switch { case vs == nil: - if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFSFrozen { + if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFSFrozen && vdSnapshot.Spec.RequiredConsistency { canFreeze, err := h.snapshotter.CanFreeze(ctx, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { @@ -200,6 +214,9 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu err = h.snapshotter.Freeze(ctx, kvvmi) if err != nil { + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, err } @@ -348,37 +365,47 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - switch { - case vm == nil, vm.Status.Phase == v1alpha2.MachineStopped: - vdSnapshot.Status.Consistent = ptr.To(true) - case isFrozen: - vdSnapshot.Status.Consistent = ptr.To(true) - - var canUnfreeze bool - canUnfreeze, err = h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) + if h.isConsistent(vdSnapshot) { + err := h.unfreezeFilesystem(ctx, vdSnapshot) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil } - setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } return reconcile.Result{}, err } + } else { + switch { + case vm == nil, vm.Status.Phase == v1alpha2.MachineStopped: + if vdSnapshot.Spec.RequiredConsistency && vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { + vdSnapshot.Status.Consistent = ptr.To(true) + return reconcile.Result{RequeueAfter: 2 * time.Second}, nil + } + case isFrozen && vdSnapshot.Spec.RequiredConsistency: + if vdSnapshot.Spec.RequiredConsistency && vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { + vdSnapshot.Status.Consistent = ptr.To(true) + return reconcile.Result{RequeueAfter: 2 * time.Second}, nil + } - if canUnfreeze { - log.Debug("Unfreeze the virtual machine after taking a snapshot") - - err = h.snapshotter.Unfreeze(ctx, kvvmi) + err := h.unfreezeFilesystem(ctx, vdSnapshot) if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } + return reconcile.Result{}, err + } + default: + if vdSnapshot.Spec.RequiredConsistency && vdSnapshot.Status.Consistent == nil { + err := fmt.Errorf("virtual disk snapshot is not consistent because the virtual machine %s has not been stopped or its filesystem has not been frozen", vm.Name) setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, err } } - default: - if vdSnapshot.Spec.RequiredConsistency { - err := fmt.Errorf("virtual disk snapshot is not consistent because the virtual machine %s has not been stopped or its filesystem has not been frozen", vm.Name) - setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) - return reconcile.Result{}, err - } } err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) @@ -386,6 +413,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil } + if k8serrors.IsConflict(err) { + result = reconcile.Result{RequeueAfter: 5 * time.Second} + handlerErr = err + } return reconcile.Result{}, err } @@ -467,3 +498,19 @@ func (h LifeCycleHandler) unfreezeFilesystem(ctx context.Context, vdSnapshot *v1 return nil } + +func (h LifeCycleHandler) isConsistent(vdSnapshot *v1alpha2.VirtualDiskSnapshot) bool { + if !vdSnapshot.Spec.RequiredConsistency { + return false + } + + if vdSnapshot.Status.Consistent == nil { + return false + } + + if !*vdSnapshot.Status.Consistent { + return false + } + + return true +} 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 f5fa799c0d..ed2513b173 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -21,8 +21,10 @@ 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" @@ -92,6 +94,9 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil } + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } h.setPhaseConditionToFailed(cb, vmSnapshot, err) return reconcile.Result{}, err } @@ -268,6 +273,9 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu if needToFreeze { hasFrozen, err = h.freezeVirtualMachine(ctx, kvvmi, vmSnapshot) if err != nil { + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } h.setPhaseConditionToFailed(cb, vmSnapshot, err) return reconcile.Result{}, err } @@ -361,6 +369,9 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil } + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil + } h.setPhaseConditionToFailed(cb, vmSnapshot, err) return reconcile.Result{}, err } From 6c4a827a4293e2e1cdce016a0f1531ebee001af4 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Thu, 13 Nov 2025 15:04:22 +0300 Subject: [PATCH 09/23] fix: mark snapshot consistent even requiredConsistency is false Signed-off-by: Roman Sysoev --- .../pkg/controller/vdsnapshot/internal/life_cycle.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) 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 1f25ebff63..15b6d6bbd1 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -192,7 +192,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu switch { case vs == nil: - if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFSFrozen && vdSnapshot.Spec.RequiredConsistency { + if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFSFrozen { canFreeze, err := h.snapshotter.CanFreeze(ctx, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { @@ -379,12 +379,12 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu } else { switch { case vm == nil, vm.Status.Phase == v1alpha2.MachineStopped: - if vdSnapshot.Spec.RequiredConsistency && vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { + if vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { vdSnapshot.Status.Consistent = ptr.To(true) return reconcile.Result{RequeueAfter: 2 * time.Second}, nil } case isFrozen && vdSnapshot.Spec.RequiredConsistency: - if vdSnapshot.Spec.RequiredConsistency && vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { + if vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { vdSnapshot.Status.Consistent = ptr.To(true) return reconcile.Result{RequeueAfter: 2 * time.Second}, nil } @@ -500,10 +500,6 @@ func (h LifeCycleHandler) unfreezeFilesystem(ctx context.Context, vdSnapshot *v1 } func (h LifeCycleHandler) isConsistent(vdSnapshot *v1alpha2.VirtualDiskSnapshot) bool { - if !vdSnapshot.Spec.RequiredConsistency { - return false - } - if vdSnapshot.Status.Consistent == nil { return false } From 9a216e12273110b3462ef38ff5979c49c2a326ba Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Thu, 13 Nov 2025 17:06:48 +0300 Subject: [PATCH 10/23] fix unit tests Signed-off-by: Roman Sysoev --- .../vdsnapshot/internal/life_cycle.go | 2 +- .../vdsnapshot/internal/life_cycle_test.go | 28 +++++++------------ test/e2e/legacy/vd_snapshots.go | 27 ++++++++++-------- 3 files changed, 26 insertions(+), 31 deletions(-) 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 15b6d6bbd1..d1cb4d3ceb 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -383,7 +383,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu vdSnapshot.Status.Consistent = ptr.To(true) return reconcile.Result{RequeueAfter: 2 * time.Second}, nil } - case isFrozen && vdSnapshot.Spec.RequiredConsistency: + case isFrozen: if vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { vdSnapshot.Status.Consistent = ptr.To(true) return reconcile.Result{RequeueAfter: 2 * time.Second}, nil 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 6d780e9fee..33480995f8 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 @@ -30,7 +30,6 @@ import ( "github.com/deckhouse/virtualization-controller/pkg/controller/conditions" "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vdscondition" - "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" ) var _ = Describe("LifeCycle handler", func() { @@ -163,6 +162,7 @@ var _ = Describe("LifeCycle handler", func() { h := NewLifeCycleHandler(snapshotter) _, err := h.Handle(testContext(), vdSnapshot) + _, err = h.Handle(testContext(), vdSnapshot) Expect(err).To(BeNil()) Expect(vdSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualDiskSnapshotPhaseReady)) ready, _ := conditions.GetCondition(vdscondition.VirtualDiskSnapshotReadyType, vdSnapshot.Status.Conditions) @@ -290,6 +290,7 @@ var _ = Describe("LifeCycle handler", func() { h := NewLifeCycleHandler(snapshotter) _, err := h.Handle(testContext(), vdSnapshot) + _, err = h.Handle(testContext(), vdSnapshot) Expect(err).To(BeNil()) Expect(vdSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualDiskSnapshotPhaseReady)) ready, _ := conditions.GetCondition(vdscondition.VirtualDiskSnapshotReadyType, vdSnapshot.Status.Conditions) @@ -298,11 +299,14 @@ var _ = Describe("LifeCycle handler", func() { Expect(ready.Message).To(BeEmpty()) }) - DescribeTable("Check unfreeze if failed", func(vm *v1alpha2.VirtualMachine, expectUnfreezing bool) { + DescribeTable("Check unfreeze if failed", func(vm *v1alpha2.VirtualMachine, isFrozen, canUnfreeze, expectUnfreezing bool) { unfreezeCalled := false snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { - return true, nil + return isFrozen, nil + } + snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { + return canUnfreeze, nil } snapshotter.GetVolumeSnapshotFunc = func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { vs.Status = &vsv1.VolumeSnapshotStatus{ @@ -327,21 +331,9 @@ var _ = Describe("LifeCycle handler", func() { Expect(vdSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualDiskSnapshotPhaseFailed)) Expect(unfreezeCalled).To(Equal(expectUnfreezing)) }, - Entry("Has VM with frozen filesystem", - &v1alpha2.VirtualMachine{ - Status: v1alpha2.VirtualMachineStatus{ - Conditions: []metav1.Condition{ - { - Type: vmcondition.TypeFilesystemFrozen.String(), - Status: metav1.ConditionTrue, - }, - }, - }, - }, - true, - ), - Entry("Has VM with unfrozen filesystem", &v1alpha2.VirtualMachine{}, false), - Entry("Has no VM", nil, false), + Entry("Has VM with frozen filesystem", &v1alpha2.VirtualMachine{}, true, true, true), + Entry("Has VM with unfrozen filesystem", &v1alpha2.VirtualMachine{}, false, false, false), + Entry("Has no VM", nil, false, false, false), ) }) }) diff --git a/test/e2e/legacy/vd_snapshots.go b/test/e2e/legacy/vd_snapshots.go index 4095812880..4a622bf97e 100644 --- a/test/e2e/legacy/vd_snapshots.go +++ b/test/e2e/legacy/vd_snapshots.go @@ -30,6 +30,7 @@ import ( "github.com/deckhouse/virtualization/api/core/v1alpha2" "github.com/deckhouse/virtualization/api/core/v1alpha2/vmcondition" + "github.com/deckhouse/virtualization/test/e2e/internal/config" kc "github.com/deckhouse/virtualization/test/e2e/internal/kubectl" "github.com/deckhouse/virtualization/test/e2e/internal/util" ) @@ -360,19 +361,21 @@ var _ = Describe("VirtualDiskSnapshots", Ordered, func() { Context("When test is completed", func() { It("deletes test case resources", func() { - DeleteTestCaseResources(ns, ResourcesToDelete{ - KustomizationDir: conf.TestData.VdSnapshots, - AdditionalResources: []AdditionalResource{ - { - Resource: kc.ResourceVDSnapshot, - Labels: hasNoConsumerLabel, + if config.IsCleanUpNeeded() { + DeleteTestCaseResources(ns, ResourcesToDelete{ + KustomizationDir: conf.TestData.VdSnapshots, + AdditionalResources: []AdditionalResource{ + { + Resource: kc.ResourceVDSnapshot, + Labels: hasNoConsumerLabel, + }, + { + Resource: kc.ResourceVDSnapshot, + Labels: attachedVirtualDiskLabel, + }, }, - { - Resource: kc.ResourceVDSnapshot, - Labels: attachedVirtualDiskLabel, - }, - }, - }) + }) + } }) }) }) From 05b2028c07ca9a8402b8964a742249b585d7a305 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Thu, 13 Nov 2025 17:16:51 +0300 Subject: [PATCH 11/23] fix unit test Signed-off-by: Roman Sysoev --- .../pkg/controller/vdsnapshot/internal/life_cycle_test.go | 2 ++ 1 file changed, 2 insertions(+) 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 33480995f8..089d1ec8b5 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 @@ -162,6 +162,7 @@ var _ = Describe("LifeCycle handler", func() { h := NewLifeCycleHandler(snapshotter) _, err := h.Handle(testContext(), vdSnapshot) + Expect(err).To(BeNil()) _, err = h.Handle(testContext(), vdSnapshot) Expect(err).To(BeNil()) Expect(vdSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualDiskSnapshotPhaseReady)) @@ -290,6 +291,7 @@ var _ = Describe("LifeCycle handler", func() { h := NewLifeCycleHandler(snapshotter) _, err := h.Handle(testContext(), vdSnapshot) + Expect(err).To(BeNil()) _, err = h.Handle(testContext(), vdSnapshot) Expect(err).To(BeNil()) Expect(vdSnapshot.Status.Phase).To(Equal(v1alpha2.VirtualDiskSnapshotPhaseReady)) From 6216b4107e445f638394b51b867a1cffc2efdaae Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Fri, 14 Nov 2025 02:31:33 +0300 Subject: [PATCH 12/23] fix ready sync unfreeze request Signed-off-by: Roman Sysoev --- .../vdsnapshot/internal/life_cycle.go | 25 +++++-- .../internal/watcher/kvvmi_watcher.go | 69 +++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go 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 d1cb4d3ceb..aa60f59217 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -29,6 +29,7 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/ptr" + virtv1 "kubevirt.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/reconcile" "github.com/deckhouse/virtualization-controller/pkg/common/annotations" @@ -57,7 +58,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu defer func() { if vdSnapshot.Status.Phase == v1alpha2.VirtualDiskSnapshotPhaseFailed { - err := h.unfreezeFilesystem(ctx, vdSnapshot) + err := h.unfreezeFilesystemIfFailed(ctx, vdSnapshot) if err != nil { if k8serrors.IsConflict(err) { result = reconcile.Result{RequeueAfter: 5 * time.Second} @@ -366,7 +367,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu } if h.isConsistent(vdSnapshot) { - err := h.unfreezeFilesystem(ctx, vdSnapshot) + err := h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -389,7 +390,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{RequeueAfter: 2 * time.Second}, nil } - err := h.unfreezeFilesystem(ctx, vdSnapshot) + err := h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -460,7 +461,7 @@ func setPhaseConditionToFailed(cb *conditions.ConditionBuilder, phase *v1alpha2. Message(service.CapitalizeFirstLetter(err.Error() + ".")) } -func (h LifeCycleHandler) unfreezeFilesystem(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) error { +func (h LifeCycleHandler) unfreezeFilesystemIfFailed(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) error { vd, err := h.snapshotter.GetVirtualDisk(ctx, vdSnapshot.Spec.VirtualDiskName, vdSnapshot.Namespace) if err != nil { return err @@ -499,6 +500,22 @@ func (h LifeCycleHandler) unfreezeFilesystem(ctx context.Context, vdSnapshot *v1 return nil } +func (h LifeCycleHandler) unfreezeFilesystem(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) error { + canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshotName, vm, kvvmi) + if err != nil { + return err + } + + if canUnfreeze { + err = h.snapshotter.Unfreeze(ctx, kvvmi) + if err != nil { + return err + } + } + + return nil +} + func (h LifeCycleHandler) isConsistent(vdSnapshot *v1alpha2.VirtualDiskSnapshot) bool { if vdSnapshot.Status.Consistent == nil { return false diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go new file mode 100644 index 0000000000..a18167e1c1 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go @@ -0,0 +1,69 @@ +/* +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 watcher + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/types" + virtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" +) + +func NewKVVMIWatcher() *KVVMIWatcher { + return &KVVMIWatcher{} +} + +type KVVMIWatcher struct{} + +func (w *KVVMIWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + if err := ctr.Watch( + source.Kind( + mgr.GetCache(), + &virtv1.VirtualMachineInstance{}, + handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, vmi *virtv1.VirtualMachineInstance) []reconcile.Request { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: vmi.GetName(), + Namespace: vmi.GetNamespace(), + }, + }, + } + }), + predicate.TypedFuncs[*virtv1.VirtualMachineInstance]{ + UpdateFunc: func(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstance]) bool { + if !equality.Semantic.DeepEqual(e.ObjectOld.Annotations, e.ObjectNew.Annotations) { + return true + } + return !equality.Semantic.DeepEqual(e.ObjectOld.Status, e.ObjectNew.Status) + }, + }, + ), + ); err != nil { + return fmt.Errorf("error setting watch on VirtualMachine: %w", err) + } + return nil +} From 4b457fbba58c0b7f25c3474d004afc7798c3c943 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Fri, 14 Nov 2025 03:47:37 +0300 Subject: [PATCH 13/23] fix kvvmi watcher and add new condition reason Signed-off-by: Roman Sysoev --- api/core/v1alpha2/vdscondition/condition.go | 2 + .../vdsnapshot/internal/life_cycle.go | 9 +- .../internal/watcher/kvvmi_watcher.go | 93 ++++++++++++++----- .../vdsnapshot/vdsnapshot_reconciler.go | 1 + 4 files changed, 79 insertions(+), 26 deletions(-) diff --git a/api/core/v1alpha2/vdscondition/condition.go b/api/core/v1alpha2/vdscondition/condition.go index 26506bd3f9..20937d6104 100644 --- a/api/core/v1alpha2/vdscondition/condition.go +++ b/api/core/v1alpha2/vdscondition/condition.go @@ -63,6 +63,8 @@ const ( VolumeSnapshotLost VirtualDiskSnapshotReadyReason = "Lost" // FileSystemFreezing signifies that the `VirtualDiskSnapshot` resource is in the process of freezing the filesystem of the virtual machine associated with the source virtual disk. FileSystemFreezing VirtualDiskSnapshotReadyReason = "FileSystemFreezing" + // FileSystemUnfreezing signifies that the `VirtualDiskSnapshot` resource is in the process of unfreezing the filesystem of the virtual machine associated with the source virtual disk. + FileSystemUnfreezing VirtualDiskSnapshotReadyReason = "FileSystemUnfreezing" // Snapshotting signifies that the `VirtualDiskSnapshot` resource is in the process of taking a snapshot of the virtual disk. Snapshotting VirtualDiskSnapshotReadyReason = "Snapshotting" // VirtualDiskSnapshotReady signifies that the snapshot process is complete and the `VirtualDiskSnapshot` is ready for use. 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 aa60f59217..730422bb47 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -218,7 +218,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if k8serrors.IsConflict(err) { return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } - setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + cb. + Status(metav1.ConditionFalse). + Reason(vdscondition.FileSystemFreezing). + Message(service.CapitalizeFirstLetter(err.Error() + ".")) return reconcile.Result{}, err } @@ -398,6 +401,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if k8serrors.IsConflict(err) { return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } + cb. + Status(metav1.ConditionFalse). + Reason(vdscondition.FileSystemUnfreezing). + Message(service.CapitalizeFirstLetter(err.Error() + ".")) return reconcile.Result{}, err } default: diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go index a18167e1c1..f912474eee 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go @@ -19,10 +19,11 @@ package watcher import ( "context" "fmt" + "log/slog" - "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/types" virtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" @@ -30,40 +31,82 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/deckhouse/virtualization-controller/pkg/common/annotations" + "github.com/deckhouse/virtualization/api/core/v1alpha2" ) -func NewKVVMIWatcher() *KVVMIWatcher { - return &KVVMIWatcher{} +type KVVMIWatcher struct { + client client.Client } -type KVVMIWatcher struct{} +func NewKVVMIWatcher(client client.Client) *KVVMIWatcher { + return &KVVMIWatcher{ + client: client, + } +} -func (w *KVVMIWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { +func (w KVVMIWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { if err := ctr.Watch( - source.Kind( - mgr.GetCache(), - &virtv1.VirtualMachineInstance{}, - handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, vmi *virtv1.VirtualMachineInstance) []reconcile.Request { - return []reconcile.Request{ - { - NamespacedName: types.NamespacedName{ - Name: vmi.GetName(), - Namespace: vmi.GetNamespace(), - }, - }, - } - }), + source.Kind(mgr.GetCache(), &virtv1.VirtualMachineInstance{}, + handler.TypedEnqueueRequestsFromMapFunc(w.enqueueRequests), predicate.TypedFuncs[*virtv1.VirtualMachineInstance]{ - UpdateFunc: func(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstance]) bool { - if !equality.Semantic.DeepEqual(e.ObjectOld.Annotations, e.ObjectNew.Annotations) { - return true - } - return !equality.Semantic.DeepEqual(e.ObjectOld.Status, e.ObjectNew.Status) - }, + UpdateFunc: w.filterUpdateEvents, }, ), ); err != nil { - return fmt.Errorf("error setting watch on VirtualMachine: %w", err) + return fmt.Errorf("error setting watch on KVVMI: %w", err) } return nil } + +func (w KVVMIWatcher) enqueueRequests(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (requests []reconcile.Request) { + volumeByName := make(map[string]struct{}) + for _, v := range kvvmi.Status.VolumeStatus { + if v.PersistentVolumeClaimInfo == nil { + continue + } + + volumeByName[v.Name] = struct{}{} + } + + if len(volumeByName) == 0 { + return + } + + var vdSnapshots v1alpha2.VirtualDiskSnapshotList + err := w.client.List(ctx, &vdSnapshots, &client.ListOptions{ + Namespace: kvvmi.GetNamespace(), + }) + if err != nil { + slog.Default().Error(fmt.Sprintf("failed to list virtual disk snapshots: %s", err)) + return + } + + for _, vdSnapshot := range vdSnapshots.Items { + _, ok := volumeByName[vdSnapshot.Spec.VirtualDiskName] + if !ok { + continue + } + + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vdSnapshot.Name, + Namespace: vdSnapshot.Namespace, + }, + }) + } + + return +} + +func (w KVVMIWatcher) filterUpdateEvents(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstance]) bool { + oldRequest, oldOk := e.ObjectOld.Annotations[annotations.AnnVMFilesystemFrozenRequest] + newRequest, newOk := e.ObjectNew.Annotations[annotations.AnnVMFilesystemFrozenRequest] + + if oldOk && newOk { + return oldRequest != newRequest + } + + return oldOk != newOk +} diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_reconciler.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_reconciler.go index 1c0b3264c7..9e5a9703c5 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/vdsnapshot_reconciler.go @@ -82,6 +82,7 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr watcher.NewVirtualDiskWatcher(mgr.GetClient()), watcher.NewVolumeSnapshotWatcher(), watcher.NewVirtualMachineWatcher(mgr.GetClient()), + watcher.NewKVVMIWatcher(mgr.GetClient()), } { err := w.Watch(mgr, ctr) if err != nil { From 5b48e872a974b757140023c3947534135baba991 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Fri, 14 Nov 2025 03:56:21 +0300 Subject: [PATCH 14/23] fix unit test Signed-off-by: Roman Sysoev --- .../pkg/controller/vdsnapshot/internal/life_cycle_test.go | 3 +++ 1 file changed, 3 insertions(+) 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 089d1ec8b5..1b5c478116 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 @@ -158,6 +158,9 @@ var _ = Describe("LifeCycle handler", func() { } return vs, nil } + snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { + return false, nil + } h := NewLifeCycleHandler(snapshotter) From a2075ccd0016f4640b470d72c64b02ad25773092 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Fri, 14 Nov 2025 15:27:31 +0300 Subject: [PATCH 15/23] fix vmsnapshot status sync and add kvvmi watcher Signed-off-by: Roman Sysoev --- api/core/v1alpha2/vmscondition/condition.go | 2 + .../vdsnapshot/internal/life_cycle.go | 3 +- .../vmsnapshot/internal/life_cycle.go | 43 ++++++-- .../internal/watcher/kvvmi_watcher.go | 99 +++++++++++++++++++ .../vmsnapshot/vmsnapshot_reconciler.go | 1 + 5 files changed, 140 insertions(+), 8 deletions(-) create mode 100644 images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go diff --git a/api/core/v1alpha2/vmscondition/condition.go b/api/core/v1alpha2/vmscondition/condition.go index afcfb2c684..d28ef154e2 100644 --- a/api/core/v1alpha2/vmscondition/condition.go +++ b/api/core/v1alpha2/vmscondition/condition.go @@ -49,6 +49,8 @@ const ( VirtualDiskSnapshotLost VirtualMachineSnapshotReadyReason = "VirtualDiskSnapshotLost" // FileSystemFreezing signifies that the `VirtualMachineSnapshot` resource is in the process of freezing the filesystem of the virtual machine. FileSystemFreezing VirtualMachineSnapshotReadyReason = "FileSystemFreezing" + // FileSystemUnfreezing signifies that the `VirtualMachineSnapshot` resource is in the process of unfreezing the filesystem of the virtual machine. + FileSystemUnfreezing VirtualMachineSnapshotReadyReason = "FileSystemUnfreezing" // Snapshotting signifies that the `VirtualMachineSnapshot` resource is in the process of taking a snapshot of the virtual machine. Snapshotting VirtualMachineSnapshotReadyReason = "Snapshotting" // VirtualMachineSnapshotReady signifies that the snapshot process is complete and the `VirtualMachineSnapshot` is ready for use. 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 730422bb47..0b47ba19eb 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -176,8 +176,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, nil } if k8serrors.IsConflict(err) { - result = reconcile.Result{RequeueAfter: 5 * time.Second} - handlerErr = err + return reconcile.Result{RequeueAfter: 5 * time.Second}, err } return reconcile.Result{}, err } 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 ed2513b173..c9068ca337 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -60,7 +60,7 @@ func NewLifeCycleHandler(recorder eventrecord.EventRecorderLogger, snapshotter S } } -func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.VirtualMachineSnapshot) (reconcile.Result, error) { +func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.VirtualMachineSnapshot) (result reconcile.Result, handlerErr error) { log := logger.FromContext(ctx).With(logger.SlogHandler("lifecycle")) cb := conditions.NewConditionBuilder(vmscondition.VirtualMachineSnapshotReadyType) @@ -82,6 +82,17 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } + err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, err + } + return reconcile.Result{}, err + } + if vmSnapshot.DeletionTimestamp != nil { vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseTerminating cb. @@ -205,7 +216,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - needToFreeze, err := h.needToFreeze(ctx, vm, kvvmi, vmSnapshot.Spec.RequiredConsistency) + needToFreeze, err := h.needToFreeze(ctx, vm, kvvmi, vmSnapshot) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -372,7 +383,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu if k8serrors.IsConflict(err) { return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } - h.setPhaseConditionToFailed(cb, vmSnapshot, err) + cb. + Status(metav1.ConditionFalse). + Reason(vmscondition.FileSystemUnfreezing). + Message(service.CapitalizeFirstLetter(err.Error() + ".")) return reconcile.Result{}, err } @@ -383,7 +397,20 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - // 9. Move to Ready phase. + // 9. Synchronize FSFreezeRequest with KVVMI status. + err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) + if err != nil { + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + if k8serrors.IsConflict(err) { + result = reconcile.Result{RequeueAfter: 5 * time.Second} + handlerErr = 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) vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseReady @@ -527,8 +554,12 @@ func (h LifeCycleHandler) areVirtualDiskSnapshotsConsistent(vdSnapshots []*v1alp return true } -func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance, requiredConsistency bool) (bool, error) { - if !requiredConsistency { +func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance, vmsnapshot *v1alpha2.VirtualMachineSnapshot) (bool, error) { + if vmsnapshot.Status.Consistent != nil && *vmsnapshot.Status.Consistent == true { + return false, nil + } + + if !vmsnapshot.Spec.RequiredConsistency { return false, nil } diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go new file mode 100644 index 0000000000..c847b60914 --- /dev/null +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go @@ -0,0 +1,99 @@ +/* +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 watcher + +import ( + "context" + "fmt" + "log/slog" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + virtv1 "kubevirt.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" + + "github.com/deckhouse/virtualization-controller/pkg/common/annotations" + "github.com/deckhouse/virtualization-controller/pkg/controller/indexer" + "github.com/deckhouse/virtualization/api/core/v1alpha2" +) + +type KVVMIWatcher struct { + client client.Client +} + +func NewKVVMIWatcher(client client.Client) *KVVMIWatcher { + return &KVVMIWatcher{ + client: client, + } +} + +func (w KVVMIWatcher) Watch(mgr manager.Manager, ctr controller.Controller) error { + if err := ctr.Watch( + source.Kind(mgr.GetCache(), &virtv1.VirtualMachineInstance{}, + handler.TypedEnqueueRequestsFromMapFunc(w.enqueueRequests), + predicate.TypedFuncs[*virtv1.VirtualMachineInstance]{ + UpdateFunc: w.filterUpdateEvents, + }, + ), + ); err != nil { + return fmt.Errorf("error setting watch on KVVMI: %w", err) + } + return nil +} + +func (w KVVMIWatcher) enqueueRequests(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (requests []reconcile.Request) { + var vmSnapshots v1alpha2.VirtualMachineSnapshotList + err := w.client.List(ctx, &vmSnapshots, &client.ListOptions{ + Namespace: kvvmi.GetNamespace(), + FieldSelector: fields.OneTermEqualSelector(indexer.IndexFieldVMSnapshotByVM, kvvmi.GetName()), + }) + if err != nil { + slog.Default().Error(fmt.Sprintf("failed to list virtual machine snapshots: %s", err)) + return + } + + for _, vmSnapshot := range vmSnapshots.Items { + if vmSnapshot.Spec.VirtualMachineName == kvvmi.GetName() { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: vmSnapshot.Name, + Namespace: vmSnapshot.Namespace, + }, + }) + } + } + + return +} + +func (w KVVMIWatcher) filterUpdateEvents(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstance]) bool { + oldRequest, oldOk := e.ObjectOld.Annotations[annotations.AnnVMFilesystemFrozenRequest] + newRequest, newOk := e.ObjectNew.Annotations[annotations.AnnVMFilesystemFrozenRequest] + + if oldOk && newOk { + return oldRequest != newRequest + } + + return oldOk != newOk +} diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_reconciler.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_reconciler.go index 750a70a2b9..c9a1513b18 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_reconciler.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/vmsnapshot_reconciler.go @@ -82,6 +82,7 @@ func (r *Reconciler) SetupController(_ context.Context, mgr manager.Manager, ctr watcher.NewVirtualDiskSnapshotWatcher(mgr.GetClient()), watcher.NewVirtualMachineWatcher(mgr.GetClient()), watcher.NewVirtualDiskWatcher(mgr.GetClient()), + watcher.NewKVVMIWatcher(mgr.GetClient()), } { err := w.Watch(mgr, ctr) if err != nil { From c61eb82f7d8706f25fbb9bbf9354e2f164401067 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Fri, 14 Nov 2025 15:49:34 +0300 Subject: [PATCH 16/23] fix unit tests Signed-off-by: Roman Sysoev --- .../pkg/controller/vmsnapshot/internal/life_cycle.go | 7 +++---- .../pkg/controller/vmsnapshot/internal/life_cycle_test.go | 3 +++ 2 files changed, 6 insertions(+), 4 deletions(-) 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 c9068ca337..1e1265206c 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -60,7 +60,7 @@ func NewLifeCycleHandler(recorder eventrecord.EventRecorderLogger, snapshotter S } } -func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.VirtualMachineSnapshot) (result reconcile.Result, handlerErr error) { +func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.VirtualMachineSnapshot) (reconcile.Result, error) { log := logger.FromContext(ctx).With(logger.SlogHandler("lifecycle")) cb := conditions.NewConditionBuilder(vmscondition.VirtualMachineSnapshotReadyType) @@ -404,8 +404,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, nil } if k8serrors.IsConflict(err) { - result = reconcile.Result{RequeueAfter: 5 * time.Second} - handlerErr = err + return reconcile.Result{RequeueAfter: 5 * time.Second}, err } return reconcile.Result{}, err } @@ -555,7 +554,7 @@ func (h LifeCycleHandler) areVirtualDiskSnapshotsConsistent(vdSnapshots []*v1alp } func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance, vmsnapshot *v1alpha2.VirtualMachineSnapshot) (bool, error) { - if vmsnapshot.Status.Consistent != nil && *vmsnapshot.Status.Consistent == true { + if vmsnapshot.Status.Consistent != nil && *vmsnapshot.Status.Consistent { return false, nil } 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 89ccfd3450..9402bceffa 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 @@ -154,6 +154,9 @@ var _ = Describe("LifeCycle handler", func() { GetKubeVirtVirtualMachineInstanceFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { return kvvmi, nil }, + SyncFSFreezeRequestFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { + return nil + }, } var err error From 6970c1d906f397373e753a8776472501477e1315 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 17 Nov 2025 15:57:39 +0300 Subject: [PATCH 17/23] intermediate commit Signed-off-by: Roman Sysoev --- .../pkg/common/annotations/annotations.go | 4 +- .../controller/service/snapshot_service.go | 73 +++++---- .../vdsnapshot/internal/deletion.go | 2 +- .../vdsnapshot/internal/interfaces.go | 4 +- .../vdsnapshot/internal/life_cycle.go | 152 +++++++----------- .../vdsnapshot/internal/life_cycle_test.go | 12 +- .../controller/vdsnapshot/internal/mock.go | 114 +++++++------ .../internal/watcher/kvvmi_watcher.go | 4 +- .../vmsnapshot/internal/interfaces.go | 4 +- .../vmsnapshot/internal/life_cycle.go | 40 ++--- .../vmsnapshot/internal/life_cycle_test.go | 8 +- .../controller/vmsnapshot/internal/mock.go | 114 +++++++------ .../internal/watcher/kvvmi_watcher.go | 4 +- 13 files changed, 245 insertions(+), 290 deletions(-) diff --git a/images/virtualization-artifact/pkg/common/annotations/annotations.go b/images/virtualization-artifact/pkg/common/annotations/annotations.go index 38244f7b4e..26fcb8229a 100644 --- a/images/virtualization-artifact/pkg/common/annotations/annotations.go +++ b/images/virtualization-artifact/pkg/common/annotations/annotations.go @@ -188,8 +188,8 @@ const ( // AnnVMOPSnapshotName is an annotation on vmop that represents name a snapshot created for VMOP. AnnVMOPSnapshotName = AnnAPIGroupV + "/vmop-snapshot-name" - // AnnVMFilesystemFrozenRequest is an annotation on a virtual machine that indicates a request to freeze or unfreeze the filesystem has been sent. - AnnVMFilesystemFrozenRequest = AnnAPIGroup + "/virtual-machine-filesystem-request" + // AnnVMFilesystemRequest is an annotation on a virtual machine that indicates a request to freeze or unfreeze the filesystem has been sent. + AnnVMFilesystemRequest = AnnAPIGroup + "/virtual-machine-filesystem-request" ) // AddAnnotation adds an annotation to an object diff --git a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go index 33d775775e..684b0773f3 100644 --- a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go +++ b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go @@ -43,7 +43,7 @@ const ( var ( ErrUntrustedFilesystemFrozenCondition = errors.New("the filesystem status cannot be processed correctly") - ErrUnexpectedFilesystemFrozenRequest = errors.New("found unexpected filesystem frozen request in the virtual machine annotations") + ErrUnexpectedFilesystemRequest = errors.New("found unexpected filesystem request in the virtual machine instance annotations") ) type SnapshotService struct { @@ -63,13 +63,13 @@ func NewSnapshotService(virtClient kubeclient.Client, client Client, protection // IsFrozen checks if a freeze or unfreeze request has been performed // and returns the "true" fsFreezeStatus if the internal virtual machine instance is "frozen", // and "false" otherwise. -func (s *SnapshotService) IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +func (s *SnapshotService) IsFrozen(kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if kvvmi == nil { return false, nil } - if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - return false, fmt.Errorf("failed to check %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, ErrUntrustedFilesystemFrozenCondition) + if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemRequest]; ok { + return false, fmt.Errorf("failed to check %s/%s fsFreezeStatus: request: %s: %w", kvvmi.Namespace, kvvmi.Name, request, ErrUntrustedFilesystemFrozenCondition) } return kvvmi.Status.FSFreezeStatus == FSFrozen, nil @@ -80,7 +80,7 @@ func (s *SnapshotService) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMa return false, nil } - isFrozen, err := s.IsFrozen(ctx, kvvmi) + isFrozen, err := s.IsFrozen(kvvmi) if err != nil { return false, err } @@ -98,8 +98,8 @@ func (s *SnapshotService) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMa } func (s *SnapshotService) Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { - if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - return fmt.Errorf("failed to freeze %s/%s virtual machine filesystem: %w: request type: %s", kvvmi.Namespace, kvvmi.Name, ErrUnexpectedFilesystemFrozenRequest, request) + if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemRequest]; ok { + return fmt.Errorf("failed to freeze %s/%s virtual machine filesystem: %w: request type: %s", kvvmi.Namespace, kvvmi.Name, ErrUnexpectedFilesystemRequest, request) } err := s.annotateWithFSFreezeRequest(ctx, RequestFSFreeze, kvvmi) @@ -120,7 +120,7 @@ func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context return false, nil } - isFrozen, err := s.IsFrozen(ctx, kvvmi) + isFrozen, err := s.IsFrozen(kvvmi) if err != nil { return false, err } @@ -177,7 +177,7 @@ func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Cont return false, nil } - isFrozen, err := s.IsFrozen(ctx, kvvmi) + isFrozen, err := s.IsFrozen(kvvmi) if err != nil { return false, err } @@ -229,8 +229,8 @@ func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Cont } func (s *SnapshotService) Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { - if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - return fmt.Errorf("failed to unfreeze %s/%s virtual machine filesystem: %w: request type: %s", kvvmi.Namespace, kvvmi.Name, ErrUnexpectedFilesystemFrozenRequest, request) + if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemRequest]; ok { + return fmt.Errorf("failed to unfreeze %s/%s virtual machine filesystem: %w: request type: %s", kvvmi.Namespace, kvvmi.Name, ErrUnexpectedFilesystemRequest, request) } err := s.annotateWithFSFreezeRequest(ctx, RequestFSUnfreeze, kvvmi) @@ -307,7 +307,7 @@ func (s *SnapshotService) CreateVirtualDiskSnapshot(ctx context.Context, vdSnaps return vdSnapshot, nil } -func (s *SnapshotService) GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { +func (s *SnapshotService) GetVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { if vm == nil { return nil, nil } @@ -316,13 +316,13 @@ func (s *SnapshotService) GetKubeVirtVirtualMachineInstance(ctx context.Context, func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, requestType string, kvvmi *virtv1.VirtualMachineInstance) error { if kvvmi == nil { - return nil + return fmt.Errorf("failed to annotate virtual machine instance; virtual machine instance cannot be nil") } if kvvmi.Annotations == nil { kvvmi.Annotations = make(map[string]string) } - kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest] = requestType + kvvmi.Annotations[annotations.AnnVMFilesystemRequest] = requestType err := s.client.Update(ctx, kvvmi) if err != nil { @@ -333,11 +333,15 @@ func (s *SnapshotService) annotateWithFSFreezeRequest(ctx context.Context, reque } func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { - if kvvmi == nil || kvvmi.Annotations == nil { + if kvvmi == nil { + return fmt.Errorf("failed to annotate virtual machine instance; virtual machine instance cannot be nil") + } + + if kvvmi.Annotations == nil { return nil } - delete(kvvmi.Annotations, annotations.AnnVMFilesystemFrozenRequest) + delete(kvvmi.Annotations, annotations.AnnVMFilesystemRequest) err := s.client.Update(ctx, kvvmi) if err != nil { @@ -352,24 +356,25 @@ func (s *SnapshotService) SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1 return nil } - if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok { - switch { - case request == RequestFSFreeze && kvvmi.Status.FSFreezeStatus == FSFrozen: - err := s.removeAnnFSFreezeRequest(ctx, kvvmi) - if err != nil { - return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, err) - } - return nil - case request == RequestFSUnfreeze && kvvmi.Status.FSFreezeStatus != FSFrozen: - err := s.removeAnnFSFreezeRequest(ctx, kvvmi) - if err != nil { - return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, err) - } - return nil - default: - return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, ErrUntrustedFilesystemFrozenCondition) - } + request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemRequest] + if !ok { + return nil } - return nil + switch { + case request == RequestFSFreeze && kvvmi.Status.FSFreezeStatus == FSFrozen: + err := s.removeAnnFSFreezeRequest(ctx, kvvmi) + if err != nil { + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: request: %s: %w", kvvmi.Namespace, kvvmi.Name, request, err) + } + return nil + case request == RequestFSUnfreeze && kvvmi.Status.FSFreezeStatus != FSFrozen: + err := s.removeAnnFSFreezeRequest(ctx, kvvmi) + if err != nil { + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: request: %s: %w", kvvmi.Namespace, kvvmi.Name, request, err) + } + return nil + default: + return fmt.Errorf("failed to sync kvvmi %s/%s fsFreezeStatus: request: %s: %w", kvvmi.Namespace, kvvmi.Name, request, ErrUntrustedFilesystemFrozenCondition) + } } diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go index e29b7c337b..b0eb8264ae 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/deletion.go @@ -64,7 +64,7 @@ func (h DeletionHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtua } } - kvvmi, err := h.snapshotter.GetKubeVirtVirtualMachineInstance(ctx, vm) + kvvmi, err := h.snapshotter.GetVirtualMachineInstance(ctx, vm) if err != nil { return reconcile.Result{}, err } diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go index 395205786a..c792c96684 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/interfaces.go @@ -34,7 +34,7 @@ type VirtualDiskReadySnapshotter interface { type LifeCycleSnapshotter interface { Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + IsFrozen(kvvmi *virtv1.VirtualMachineInstance) (bool, error) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance) (bool, error) Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error @@ -44,5 +44,5 @@ type LifeCycleSnapshotter interface { GetVirtualMachine(ctx context.Context, name, namespace string) (*v1alpha2.VirtualMachine, error) GetVolumeSnapshot(ctx context.Context, name, namespace string) (*vsv1.VolumeSnapshot, error) SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) + GetVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) } 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 0b47ba19eb..e42763661f 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -51,32 +51,12 @@ func NewLifeCycleHandler(snapshotter LifeCycleSnapshotter) *LifeCycleHandler { } } -func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (result reconcile.Result, handlerErr error) { +func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.VirtualDiskSnapshot) (reconcile.Result, error) { log := logger.FromContext(ctx).With(logger.SlogHandler("lifecycle")) cb := conditions.NewConditionBuilder(vdscondition.VirtualDiskSnapshotReadyType).Generation(vdSnapshot.Generation) - defer func() { - if vdSnapshot.Status.Phase == v1alpha2.VirtualDiskSnapshotPhaseFailed { - err := h.unfreezeFilesystemIfFailed(ctx, vdSnapshot) - if err != nil { - if k8serrors.IsConflict(err) { - result = reconcile.Result{RequeueAfter: 5 * time.Second} - handlerErr = err - } - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - result = reconcile.Result{} - handlerErr = nil - } - if cb.Condition().Message != "" { - cb.Message(fmt.Sprintf("%s, %s", err.Error(), cb.Condition().Message)) - } else { - cb.Message(err.Error()) - } - } - } - conditions.SetCondition(cb, &vdSnapshot.Status.Conditions) - }() + defer func() { conditions.SetCondition(cb, &vdSnapshot.Status.Conditions) }() vs, err := h.snapshotter.GetVolumeSnapshot(ctx, vdSnapshot.Name, vdSnapshot.Namespace) if err != nil { @@ -98,6 +78,22 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu case "": vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhasePending case v1alpha2.VirtualDiskSnapshotPhaseFailed: + err := h.unfreezeFilesystemIfFailed(ctx, vdSnapshot) + if err != nil { + if k8serrors.IsConflict(err) { + return reconcile.Result{RequeueAfter: 5 * time.Second}, err + } + // Who changes the state? Sync? + if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + return reconcile.Result{}, nil + } + if cb.Condition().Message != "" { + cb.Message(fmt.Sprintf("%s, %s", err.Error(), cb.Condition().Message)) + } else { + cb.Message(err.Error()) + } + } + readyCondition, _ := conditions.GetCondition(vdscondition.VirtualDiskSnapshotReadyType, vdSnapshot.Status.Conditions) cb. Status(metav1.ConditionFalse). @@ -164,28 +160,26 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - kvvmi, err := h.snapshotter.GetKubeVirtVirtualMachineInstance(ctx, vm) + kvvmi, err := h.snapshotter.GetVirtualMachineInstance(ctx, vm) if err != nil { setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, err } err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } - if k8serrors.IsConflict(err) { - return reconcile.Result{RequeueAfter: 5 * time.Second}, err - } + switch { + case err == nil: + // OK. + case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + return reconcile.Result{}, nil + case k8serrors.IsConflict(err): + return reconcile.Result{RequeueAfter: 5 * time.Second}, err + default: return reconcile.Result{}, err } - isFSFrozen, err := h.snapshotter.IsFrozen(ctx, kvvmi) + isFSFrozen, err := h.snapshotter.IsFrozen(kvvmi) if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, err } @@ -195,9 +189,6 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFSFrozen { canFreeze, err := h.snapshotter.CanFreeze(ctx, kvvmi) if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } return reconcile.Result{}, err } if canFreeze { @@ -224,7 +215,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress + vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress // TODO: add it to freeze/unfreeze block if required cb. Status(metav1.ConditionFalse). Reason(vdscondition.FileSystemFreezing). @@ -360,73 +351,49 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu default: log.Debug("The volume snapshot is ready to use") - isFrozen, err := h.snapshotter.IsFrozen(ctx, kvvmi) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } - return reconcile.Result{}, err - } - if h.isConsistent(vdSnapshot) { err := h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } if k8serrors.IsConflict(err) { return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } return reconcile.Result{}, err } - } else { - switch { - case vm == nil, vm.Status.Phase == v1alpha2.MachineStopped: - if vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { - vdSnapshot.Status.Consistent = ptr.To(true) - return reconcile.Result{RequeueAfter: 2 * time.Second}, nil - } - case isFrozen: - if vdSnapshot.Status.Consistent == nil || !*vdSnapshot.Status.Consistent { - vdSnapshot.Status.Consistent = ptr.To(true) - return reconcile.Result{RequeueAfter: 2 * time.Second}, nil - } + } - err := h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } - if k8serrors.IsConflict(err) { - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - cb. - Status(metav1.ConditionFalse). - Reason(vdscondition.FileSystemUnfreezing). - Message(service.CapitalizeFirstLetter(err.Error() + ".")) - return reconcile.Result{}, err - } - default: - if vdSnapshot.Spec.RequiredConsistency && vdSnapshot.Status.Consistent == nil { - err := fmt.Errorf("virtual disk snapshot is not consistent because the virtual machine %s has not been stopped or its filesystem has not been frozen", vm.Name) - setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) - return reconcile.Result{}, err - } + if vdSnapshot.Status.Consistent == nil { + if vm == nil || vm.Status.Phase == v1alpha2.MachineStopped || isFSFrozen { + vdSnapshot.Status.Consistent = ptr.To(true) + return reconcile.Result{RequeueAfter: 2 * time.Second}, nil + } + + if vdSnapshot.Spec.RequiredConsistency { + err = fmt.Errorf("virtual disk snapshot is not consistent because the virtual machine %s has not been stopped or its filesystem has not been frozen", vm.Name) + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err } } - err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) + err = h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } if k8serrors.IsConflict(err) { - result = reconcile.Result{RequeueAfter: 5 * time.Second} - handlerErr = err + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } return reconcile.Result{}, err } + err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) + switch { + case err == nil: + // OK. + case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + return reconcile.Result{}, nil + case k8serrors.IsConflict(err): + return reconcile.Result{RequeueAfter: 5 * time.Second}, err + default: + return reconcile.Result{}, err + } + vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseReady vdSnapshot.Status.VolumeSnapshotName = vs.Name cb. @@ -486,23 +453,16 @@ func (h LifeCycleHandler) unfreezeFilesystemIfFailed(ctx context.Context, vdSnap return nil } - kvvmi, err := h.snapshotter.GetKubeVirtVirtualMachineInstance(ctx, vm) + kvvmi, err := h.snapshotter.GetVirtualMachineInstance(ctx, vm) if err != nil { return err } - canUnfreeze, err := h.snapshotter.CanUnfreezeWithVirtualDiskSnapshot(ctx, vdSnapshot.Name, vm, kvvmi) + err = h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) if err != nil { return err } - if canUnfreeze { - err = h.snapshotter.Unfreeze(ctx, kvvmi) - if err != nil { - return err - } - } - return nil } 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 1b5c478116..a9ea15a8c2 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 @@ -89,10 +89,10 @@ var _ = Describe("LifeCycle handler", func() { GetVolumeSnapshotFunc: func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { return nil, nil }, - IsFrozenFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { + IsFrozenFunc: func(_ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil }, - GetKubeVirtVirtualMachineInstanceFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + GetVirtualMachineInstanceFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { return nil, nil }, SyncFSFreezeRequestFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { @@ -201,10 +201,10 @@ var _ = Describe("LifeCycle handler", func() { snapshotter.GetVirtualMachineFunc = func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil } - snapshotter.GetKubeVirtVirtualMachineInstanceFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + snapshotter.GetVirtualMachineInstanceFunc = func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { return kvvmi, nil } - snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { + snapshotter.IsFrozenFunc = func(_ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { @@ -282,7 +282,7 @@ var _ = Describe("LifeCycle handler", func() { }) It("Unfreeze virtual machine", func() { - snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { + snapshotter.IsFrozenFunc = func(_ *virtv1.VirtualMachineInstance) (bool, error) { return true, nil } snapshotter.GetVolumeSnapshotFunc = func(_ context.Context, _, _ string) (*vsv1.VolumeSnapshot, error) { @@ -307,7 +307,7 @@ var _ = Describe("LifeCycle handler", func() { DescribeTable("Check unfreeze if failed", func(vm *v1alpha2.VirtualMachine, isFrozen, canUnfreeze, expectUnfreezing bool) { unfreezeCalled := false - snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { + snapshotter.IsFrozenFunc = func(_ *virtv1.VirtualMachineInstance) (bool, error) { return isFrozen, nil } snapshotter.CanUnfreezeWithVirtualDiskSnapshotFunc = func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go index dc29e0bac9..c5adf99af9 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/mock.go @@ -112,9 +112,6 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // FreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { // panic("mock out the Freeze method") // }, -// GetKubeVirtVirtualMachineInstanceFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { -// panic("mock out the GetKubeVirtVirtualMachineInstance method") -// }, // GetPersistentVolumeClaimFunc: func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { // panic("mock out the GetPersistentVolumeClaim method") // }, @@ -124,10 +121,13 @@ var _ LifeCycleSnapshotter = &LifeCycleSnapshotterMock{} // 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") +// }, // GetVolumeSnapshotFunc: func(ctx context.Context, name string, namespace string) (*vsv1.VolumeSnapshot, error) { // panic("mock out the GetVolumeSnapshot method") // }, -// IsFrozenFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +// IsFrozenFunc: func(kvvmi *virtv1.VirtualMachineInstance) (bool, error) { // panic("mock out the IsFrozen method") // }, // SyncFSFreezeRequestFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { @@ -155,9 +155,6 @@ type LifeCycleSnapshotterMock struct { // FreezeFunc mocks the Freeze method. FreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - // GetKubeVirtVirtualMachineInstanceFunc mocks the GetKubeVirtVirtualMachineInstance method. - GetKubeVirtVirtualMachineInstanceFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) - // GetPersistentVolumeClaimFunc mocks the GetPersistentVolumeClaim method. GetPersistentVolumeClaimFunc func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) @@ -167,11 +164,14 @@ type LifeCycleSnapshotterMock 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) + // GetVolumeSnapshotFunc mocks the GetVolumeSnapshot method. GetVolumeSnapshotFunc func(ctx context.Context, name string, namespace string) (*vsv1.VolumeSnapshot, error) // IsFrozenFunc mocks the IsFrozen method. - IsFrozenFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + IsFrozenFunc func(kvvmi *virtv1.VirtualMachineInstance) (bool, error) // SyncFSFreezeRequestFunc mocks the SyncFSFreezeRequest method. SyncFSFreezeRequestFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error @@ -213,13 +213,6 @@ type LifeCycleSnapshotterMock struct { // Kvvmi is the kvvmi argument value. Kvvmi *virtv1.VirtualMachineInstance } - // GetKubeVirtVirtualMachineInstance holds details about calls to the GetKubeVirtVirtualMachineInstance method. - GetKubeVirtVirtualMachineInstance []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // VM is the vm argument value. - VM *v1alpha2.VirtualMachine - } // GetPersistentVolumeClaim holds details about calls to the GetPersistentVolumeClaim method. GetPersistentVolumeClaim []struct { // Ctx is the ctx argument value. @@ -247,6 +240,13 @@ type LifeCycleSnapshotterMock 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 + } // GetVolumeSnapshot holds details about calls to the GetVolumeSnapshot method. GetVolumeSnapshot []struct { // Ctx is the ctx argument value. @@ -258,8 +258,6 @@ type LifeCycleSnapshotterMock struct { } // IsFrozen holds details about calls to the IsFrozen method. IsFrozen []struct { - // Ctx is the ctx argument value. - Ctx context.Context // Kvvmi is the kvvmi argument value. Kvvmi *virtv1.VirtualMachineInstance } @@ -282,10 +280,10 @@ type LifeCycleSnapshotterMock struct { lockCanUnfreezeWithVirtualDiskSnapshot sync.RWMutex lockCreateVolumeSnapshot sync.RWMutex lockFreeze sync.RWMutex - lockGetKubeVirtVirtualMachineInstance sync.RWMutex lockGetPersistentVolumeClaim sync.RWMutex lockGetVirtualDisk sync.RWMutex lockGetVirtualMachine sync.RWMutex + lockGetVirtualMachineInstance sync.RWMutex lockGetVolumeSnapshot sync.RWMutex lockIsFrozen sync.RWMutex lockSyncFSFreezeRequest sync.RWMutex @@ -444,42 +442,6 @@ func (mock *LifeCycleSnapshotterMock) FreezeCalls() []struct { return calls } -// GetKubeVirtVirtualMachineInstance calls GetKubeVirtVirtualMachineInstanceFunc. -func (mock *LifeCycleSnapshotterMock) GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { - if mock.GetKubeVirtVirtualMachineInstanceFunc == nil { - panic("LifeCycleSnapshotterMock.GetKubeVirtVirtualMachineInstanceFunc: method is nil but LifeCycleSnapshotter.GetKubeVirtVirtualMachineInstance was just called") - } - callInfo := struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine - }{ - Ctx: ctx, - VM: vm, - } - mock.lockGetKubeVirtVirtualMachineInstance.Lock() - mock.calls.GetKubeVirtVirtualMachineInstance = append(mock.calls.GetKubeVirtVirtualMachineInstance, callInfo) - mock.lockGetKubeVirtVirtualMachineInstance.Unlock() - return mock.GetKubeVirtVirtualMachineInstanceFunc(ctx, vm) -} - -// GetKubeVirtVirtualMachineInstanceCalls gets all the calls that were made to GetKubeVirtVirtualMachineInstance. -// Check the length with: -// -// len(mockedLifeCycleSnapshotter.GetKubeVirtVirtualMachineInstanceCalls()) -func (mock *LifeCycleSnapshotterMock) GetKubeVirtVirtualMachineInstanceCalls() []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine -} { - var calls []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine - } - mock.lockGetKubeVirtVirtualMachineInstance.RLock() - calls = mock.calls.GetKubeVirtVirtualMachineInstance - mock.lockGetKubeVirtVirtualMachineInstance.RUnlock() - return calls -} - // GetPersistentVolumeClaim calls GetPersistentVolumeClaimFunc. func (mock *LifeCycleSnapshotterMock) GetPersistentVolumeClaim(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { if mock.GetPersistentVolumeClaimFunc == nil { @@ -600,6 +562,42 @@ func (mock *LifeCycleSnapshotterMock) GetVirtualMachineCalls() []struct { return calls } +// GetVirtualMachineInstance calls GetVirtualMachineInstanceFunc. +func (mock *LifeCycleSnapshotterMock) GetVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + if mock.GetVirtualMachineInstanceFunc == nil { + panic("LifeCycleSnapshotterMock.GetVirtualMachineInstanceFunc: method is nil but LifeCycleSnapshotter.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(mockedLifeCycleSnapshotter.GetVirtualMachineInstanceCalls()) +func (mock *LifeCycleSnapshotterMock) 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 +} + // GetVolumeSnapshot calls GetVolumeSnapshotFunc. func (mock *LifeCycleSnapshotterMock) GetVolumeSnapshot(ctx context.Context, name string, namespace string) (*vsv1.VolumeSnapshot, error) { if mock.GetVolumeSnapshotFunc == nil { @@ -641,21 +639,19 @@ func (mock *LifeCycleSnapshotterMock) GetVolumeSnapshotCalls() []struct { } // IsFrozen calls IsFrozenFunc. -func (mock *LifeCycleSnapshotterMock) IsFrozen(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +func (mock *LifeCycleSnapshotterMock) IsFrozen(kvvmi *virtv1.VirtualMachineInstance) (bool, error) { if mock.IsFrozenFunc == nil { panic("LifeCycleSnapshotterMock.IsFrozenFunc: method is nil but LifeCycleSnapshotter.IsFrozen was just called") } callInfo := struct { - Ctx context.Context Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, Kvvmi: kvvmi, } mock.lockIsFrozen.Lock() mock.calls.IsFrozen = append(mock.calls.IsFrozen, callInfo) mock.lockIsFrozen.Unlock() - return mock.IsFrozenFunc(ctx, kvvmi) + return mock.IsFrozenFunc(kvvmi) } // IsFrozenCalls gets all the calls that were made to IsFrozen. @@ -663,11 +659,9 @@ func (mock *LifeCycleSnapshotterMock) IsFrozen(ctx context.Context, kvvmi *virtv // // len(mockedLifeCycleSnapshotter.IsFrozenCalls()) func (mock *LifeCycleSnapshotterMock) IsFrozenCalls() []struct { - Ctx context.Context Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context Kvvmi *virtv1.VirtualMachineInstance } mock.lockIsFrozen.RLock() diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go index f912474eee..2d1fbf2657 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go @@ -101,8 +101,8 @@ func (w KVVMIWatcher) enqueueRequests(ctx context.Context, kvvmi *virtv1.Virtual } func (w KVVMIWatcher) filterUpdateEvents(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstance]) bool { - oldRequest, oldOk := e.ObjectOld.Annotations[annotations.AnnVMFilesystemFrozenRequest] - newRequest, newOk := e.ObjectNew.Annotations[annotations.AnnVMFilesystemFrozenRequest] + oldRequest, oldOk := e.ObjectOld.Annotations[annotations.AnnVMFilesystemRequest] + newRequest, newOk := e.ObjectNew.Annotations[annotations.AnnVMFilesystemRequest] if oldOk && newOk { return oldRequest != newRequest diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go index f25f473d07..4744a2b801 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/interfaces.go @@ -40,9 +40,9 @@ type Snapshotter interface { 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(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, 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 - GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*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 1e1265206c..90db488588 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -76,20 +76,21 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - kvvmi, err := h.snapshotter.GetKubeVirtVirtualMachineInstance(ctx, vm) + 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) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } - if k8serrors.IsConflict(err) { - return reconcile.Result{RequeueAfter: 5 * time.Second}, err - } + switch { + case err == nil: + // OK. + case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + return reconcile.Result{}, nil + case k8serrors.IsConflict(err): + return reconcile.Result{RequeueAfter: 5 * time.Second}, err + default: return reconcile.Result{}, err } @@ -216,7 +217,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - needToFreeze, err := h.needToFreeze(ctx, vm, kvvmi, vmSnapshot) + needToFreeze, err := h.needToFreeze(vm, kvvmi, vmSnapshot) if err != nil { if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { return reconcile.Result{}, nil @@ -399,13 +400,14 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu // 9. Synchronize FSFreezeRequest with KVVMI status. err = h.snapshotter.SyncFSFreezeRequest(ctx, kvvmi) - if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } - if k8serrors.IsConflict(err) { - return reconcile.Result{RequeueAfter: 5 * time.Second}, err - } + switch { + case err == nil: + // OK. + case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + return reconcile.Result{}, nil + case k8serrors.IsConflict(err): + return reconcile.Result{RequeueAfter: 5 * time.Second}, err + default: return reconcile.Result{}, err } @@ -553,7 +555,7 @@ func (h LifeCycleHandler) areVirtualDiskSnapshotsConsistent(vdSnapshots []*v1alp return true } -func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.VirtualMachine, kvvmi *virtv1.VirtualMachineInstance, vmsnapshot *v1alpha2.VirtualMachineSnapshot) (bool, error) { +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 } @@ -566,7 +568,7 @@ func (h LifeCycleHandler) needToFreeze(ctx context.Context, vm *v1alpha2.Virtual return false, nil } - isFrozen, err := h.snapshotter.IsFrozen(ctx, kvvmi) + isFrozen, err := h.snapshotter.IsFrozen(kvvmi) if err != nil { return false, err } @@ -602,7 +604,7 @@ func (h LifeCycleHandler) unfreezeVirtualMachineIfCan(ctx context.Context, vmSna return false, nil } - isFrozen, err := h.snapshotter.IsFrozen(ctx, kvvmi) + isFrozen, err := h.snapshotter.IsFrozen(kvvmi) if err != nil { return false, err } 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 9402bceffa..78752c91b4 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 @@ -133,7 +133,7 @@ var _ = Describe("LifeCycle handler", func() { GetVirtualMachineFunc: func(_ context.Context, _, _ string) (*v1alpha2.VirtualMachine, error) { return vm, nil }, - IsFrozenFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { + IsFrozenFunc: func(*virtv1.VirtualMachineInstance) (bool, error) { return true, nil }, CanUnfreezeWithVirtualMachineSnapshotFunc: func(_ context.Context, _ string, _ *v1alpha2.VirtualMachine, _ *virtv1.VirtualMachineInstance) (bool, error) { @@ -151,7 +151,7 @@ var _ = Describe("LifeCycle handler", func() { GetVirtualDiskSnapshotFunc: func(_ context.Context, _, _ string) (*v1alpha2.VirtualDiskSnapshot, error) { return vdSnapshot, nil }, - GetKubeVirtVirtualMachineInstanceFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { + GetVirtualMachineInstanceFunc: func(_ context.Context, _ *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { return kvvmi, nil }, SyncFSFreezeRequestFunc: func(_ context.Context, _ *virtv1.VirtualMachineInstance) error { @@ -263,7 +263,7 @@ var _ = Describe("LifeCycle handler", func() { }) It("The virtual machine is potentially inconsistent", func() { - snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { + snapshotter.IsFrozenFunc = func(_ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { @@ -282,7 +282,7 @@ var _ = Describe("LifeCycle handler", func() { }) It("The virtual machine has frozen", func() { - snapshotter.IsFrozenFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { + snapshotter.IsFrozenFunc = func(_ *virtv1.VirtualMachineInstance) (bool, error) { return false, nil } snapshotter.CanFreezeFunc = func(_ context.Context, _ *virtv1.VirtualMachineInstance) (bool, error) { diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go index 2135d9e4b7..67e5c82199 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/mock.go @@ -111,9 +111,6 @@ var _ Snapshotter = &SnapshotterMock{} // FreezeFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { // panic("mock out the Freeze method") // }, -// GetKubeVirtVirtualMachineInstanceFunc: func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { -// panic("mock out the GetKubeVirtVirtualMachineInstance method") -// }, // GetPersistentVolumeClaimFunc: func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { // panic("mock out the GetPersistentVolumeClaim method") // }, @@ -129,7 +126,10 @@ var _ Snapshotter = &SnapshotterMock{} // GetVirtualMachineFunc: func(ctx context.Context, name string, namespace string) (*v1alpha2.VirtualMachine, error) { // panic("mock out the GetVirtualMachine method") // }, -// IsFrozenFunc: func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +// 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 { @@ -157,9 +157,6 @@ type SnapshotterMock struct { // FreezeFunc mocks the Freeze method. FreezeFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error - // GetKubeVirtVirtualMachineInstanceFunc mocks the GetKubeVirtVirtualMachineInstance method. - GetKubeVirtVirtualMachineInstanceFunc func(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) - // GetPersistentVolumeClaimFunc mocks the GetPersistentVolumeClaim method. GetPersistentVolumeClaimFunc func(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) @@ -175,8 +172,11 @@ 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(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) + IsFrozenFunc func(kvvmi *virtv1.VirtualMachineInstance) (bool, error) // SyncFSFreezeRequestFunc mocks the SyncFSFreezeRequest method. SyncFSFreezeRequestFunc func(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error @@ -218,13 +218,6 @@ type SnapshotterMock struct { // Kvvmi is the kvvmi argument value. Kvvmi *virtv1.VirtualMachineInstance } - // GetKubeVirtVirtualMachineInstance holds details about calls to the GetKubeVirtVirtualMachineInstance method. - GetKubeVirtVirtualMachineInstance []struct { - // Ctx is the ctx argument value. - Ctx context.Context - // VM is the vm argument value. - VM *v1alpha2.VirtualMachine - } // GetPersistentVolumeClaim holds details about calls to the GetPersistentVolumeClaim method. GetPersistentVolumeClaim []struct { // Ctx is the ctx argument value. @@ -270,10 +263,15 @@ type SnapshotterMock struct { // Namespace is the namespace argument value. Namespace string } - // IsFrozen holds details about calls to the IsFrozen method. - IsFrozen []struct { + // 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 } @@ -296,12 +294,12 @@ type SnapshotterMock struct { lockCanUnfreezeWithVirtualMachineSnapshot sync.RWMutex lockCreateVirtualDiskSnapshot sync.RWMutex lockFreeze sync.RWMutex - lockGetKubeVirtVirtualMachineInstance 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 @@ -459,42 +457,6 @@ func (mock *SnapshotterMock) FreezeCalls() []struct { return calls } -// GetKubeVirtVirtualMachineInstance calls GetKubeVirtVirtualMachineInstanceFunc. -func (mock *SnapshotterMock) GetKubeVirtVirtualMachineInstance(ctx context.Context, vm *v1alpha2.VirtualMachine) (*virtv1.VirtualMachineInstance, error) { - if mock.GetKubeVirtVirtualMachineInstanceFunc == nil { - panic("SnapshotterMock.GetKubeVirtVirtualMachineInstanceFunc: method is nil but Snapshotter.GetKubeVirtVirtualMachineInstance was just called") - } - callInfo := struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine - }{ - Ctx: ctx, - VM: vm, - } - mock.lockGetKubeVirtVirtualMachineInstance.Lock() - mock.calls.GetKubeVirtVirtualMachineInstance = append(mock.calls.GetKubeVirtVirtualMachineInstance, callInfo) - mock.lockGetKubeVirtVirtualMachineInstance.Unlock() - return mock.GetKubeVirtVirtualMachineInstanceFunc(ctx, vm) -} - -// GetKubeVirtVirtualMachineInstanceCalls gets all the calls that were made to GetKubeVirtVirtualMachineInstance. -// Check the length with: -// -// len(mockedSnapshotter.GetKubeVirtVirtualMachineInstanceCalls()) -func (mock *SnapshotterMock) GetKubeVirtVirtualMachineInstanceCalls() []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine -} { - var calls []struct { - Ctx context.Context - VM *v1alpha2.VirtualMachine - } - mock.lockGetKubeVirtVirtualMachineInstance.RLock() - calls = mock.calls.GetKubeVirtVirtualMachineInstance - mock.lockGetKubeVirtVirtualMachineInstance.RUnlock() - return calls -} - // GetPersistentVolumeClaim calls GetPersistentVolumeClaimFunc. func (mock *SnapshotterMock) GetPersistentVolumeClaim(ctx context.Context, name string, namespace string) (*corev1.PersistentVolumeClaim, error) { if mock.GetPersistentVolumeClaimFunc == nil { @@ -695,22 +657,56 @@ func (mock *SnapshotterMock) GetVirtualMachineCalls() []struct { 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(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) (bool, error) { +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 { - Ctx context.Context Kvvmi *virtv1.VirtualMachineInstance }{ - Ctx: ctx, Kvvmi: kvvmi, } mock.lockIsFrozen.Lock() mock.calls.IsFrozen = append(mock.calls.IsFrozen, callInfo) mock.lockIsFrozen.Unlock() - return mock.IsFrozenFunc(ctx, kvvmi) + return mock.IsFrozenFunc(kvvmi) } // IsFrozenCalls gets all the calls that were made to IsFrozen. @@ -718,11 +714,9 @@ func (mock *SnapshotterMock) IsFrozen(ctx context.Context, kvvmi *virtv1.Virtual // // len(mockedSnapshotter.IsFrozenCalls()) func (mock *SnapshotterMock) IsFrozenCalls() []struct { - Ctx context.Context Kvvmi *virtv1.VirtualMachineInstance } { var calls []struct { - Ctx context.Context Kvvmi *virtv1.VirtualMachineInstance } mock.lockIsFrozen.RLock() diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go index c847b60914..850d0184d1 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go @@ -88,8 +88,8 @@ func (w KVVMIWatcher) enqueueRequests(ctx context.Context, kvvmi *virtv1.Virtual } func (w KVVMIWatcher) filterUpdateEvents(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstance]) bool { - oldRequest, oldOk := e.ObjectOld.Annotations[annotations.AnnVMFilesystemFrozenRequest] - newRequest, newOk := e.ObjectNew.Annotations[annotations.AnnVMFilesystemFrozenRequest] + oldRequest, oldOk := e.ObjectOld.Annotations[annotations.AnnVMFilesystemRequest] + newRequest, newOk := e.ObjectNew.Annotations[annotations.AnnVMFilesystemRequest] if oldOk && newOk { return oldRequest != newRequest From fbeaa2a154abeafc60ce661f3da2b254e8bffe60 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 17 Nov 2025 17:18:55 +0300 Subject: [PATCH 18/23] add debug msg and todo about not atomic methods Signed-off-by: Roman Sysoev --- .../pkg/controller/service/snapshot_service.go | 6 ++++++ .../controller/vdsnapshot/internal/life_cycle.go | 9 ++++++++- .../controller/vmsnapshot/internal/life_cycle.go | 13 ++++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go index 684b0773f3..2e392eebd7 100644 --- a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go +++ b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go @@ -97,6 +97,9 @@ func (s *SnapshotService) CanFreeze(ctx context.Context, kvvmi *virtv1.VirtualMa return false, nil } +// TODO: The Freeze method should be atomic because there is a chance of encountering +// a dead-end with the `ErrUntrustedFilesystemFrozenCondition` error in the SyncFSFreezeRequest method +// when the API returns an error on an freeze request. func (s *SnapshotService) Freeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemRequest]; ok { return fmt.Errorf("failed to freeze %s/%s virtual machine filesystem: %w: request type: %s", kvvmi.Namespace, kvvmi.Name, ErrUnexpectedFilesystemRequest, request) @@ -228,6 +231,9 @@ func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Cont return true, nil } +// TODO: The Unfreeze method should be atomic because there is a chance of encountering +// a dead-end with the `ErrUntrustedFilesystemFrozenCondition` error in the SyncFSFreezeRequest method +// when the API returns an error on an unfreeze request. func (s *SnapshotService) Unfreeze(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error { if request, ok := kvvmi.Annotations[annotations.AnnVMFilesystemRequest]; ok { return fmt.Errorf("failed to unfreeze %s/%s virtual machine filesystem: %w: request type: %s", kvvmi.Namespace, kvvmi.Name, ErrUnexpectedFilesystemRequest, request) 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 e42763661f..cb371d7138 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -83,8 +83,8 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if k8serrors.IsConflict(err) { return reconcile.Result{RequeueAfter: 5 * time.Second}, err } - // Who changes the state? Sync? if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { + log.Debug(err.Error()) return reconcile.Result{}, nil } if cb.Condition().Message != "" { @@ -171,6 +171,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu case err == nil: // OK. case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + log.Debug(err.Error()) return reconcile.Result{}, nil case k8serrors.IsConflict(err): return reconcile.Result{RequeueAfter: 5 * time.Second}, err @@ -379,6 +380,11 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if k8serrors.IsConflict(err) { return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } + vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress + cb. + Status(metav1.ConditionFalse). + Reason(vdscondition.FileSystemUnfreezing). + Message(service.CapitalizeFirstLetter(err.Error() + ".")) return reconcile.Result{}, err } @@ -387,6 +393,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu case err == nil: // OK. case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + log.Debug(err.Error()) return reconcile.Result{}, nil case k8serrors.IsConflict(err): return reconcile.Result{RequeueAfter: 5 * time.Second}, err 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 90db488588..e79030e701 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -87,6 +87,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu case err == nil: // OK. case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + log.Debug(err.Error()) return reconcile.Result{}, nil case k8serrors.IsConflict(err): return reconcile.Result{RequeueAfter: 5 * time.Second}, err @@ -104,6 +105,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu _, 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) { @@ -219,17 +221,11 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu needToFreeze, err := h.needToFreeze(vm, kvvmi, vmSnapshot) if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } return reconcile.Result{}, err } canFreeze, err := h.snapshotter.CanFreeze(ctx, kvvmi) if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } return reconcile.Result{}, err } isAwaitingConsistency := needToFreeze && !canFreeze && vmSnapshot.Spec.RequiredConsistency @@ -378,12 +374,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu // 7. Unfreeze VirtualMachine if can. unfrozen, err := h.unfreezeVirtualMachineIfCan(ctx, vmSnapshot, vm, kvvmi) if err != nil { - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - return reconcile.Result{}, nil - } if k8serrors.IsConflict(err) { return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } + vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseInProgress cb. Status(metav1.ConditionFalse). Reason(vmscondition.FileSystemUnfreezing). @@ -404,6 +398,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu case err == nil: // OK. case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + log.Debug(err.Error()) return reconcile.Result{}, nil case k8serrors.IsConflict(err): return reconcile.Result{RequeueAfter: 5 * time.Second}, err From e699af03c4d53a33841238491148b41799222eda Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Mon, 17 Nov 2025 19:35:57 +0300 Subject: [PATCH 19/23] fix vdsnapshot unfreeze final operation Signed-off-by: Roman Sysoev --- .../vdsnapshot/internal/life_cycle.go | 24 +------------------ 1 file changed, 1 insertion(+), 23 deletions(-) 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 cb371d7138..397fc22b43 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -216,7 +216,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu return reconcile.Result{}, err } - vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress // TODO: add it to freeze/unfreeze block if required + vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress cb. Status(metav1.ConditionFalse). Reason(vdscondition.FileSystemFreezing). @@ -352,16 +352,6 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu default: log.Debug("The volume snapshot is ready to use") - if h.isConsistent(vdSnapshot) { - err := h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) - if err != nil { - if k8serrors.IsConflict(err) { - return reconcile.Result{RequeueAfter: 5 * time.Second}, nil - } - return reconcile.Result{}, err - } - } - if vdSnapshot.Status.Consistent == nil { if vm == nil || vm.Status.Phase == v1alpha2.MachineStopped || isFSFrozen { vdSnapshot.Status.Consistent = ptr.To(true) @@ -488,15 +478,3 @@ func (h LifeCycleHandler) unfreezeFilesystem(ctx context.Context, vdSnapshotName return nil } - -func (h LifeCycleHandler) isConsistent(vdSnapshot *v1alpha2.VirtualDiskSnapshot) bool { - if vdSnapshot.Status.Consistent == nil { - return false - } - - if !*vdSnapshot.Status.Consistent { - return false - } - - return true -} From f12d0a58191d0f9711a0316c2bb2f855e1241209 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Wed, 19 Nov 2025 14:55:26 +0300 Subject: [PATCH 20/23] resolve review comments - add context to errors - watch kvvmi fsFreezeStatus instead of vm fsFrozenCondition - change freeze request annotation api group Signed-off-by: Roman Sysoev --- .../pkg/common/annotations/annotations.go | 2 +- .../vdsnapshot/internal/life_cycle.go | 46 ++++++++++--------- .../internal/watcher/kvvmi_watcher.go | 7 +++ .../vdsnapshot/internal/watcher/vm_watcher.go | 7 --- .../vmsnapshot/internal/life_cycle.go | 11 +++-- .../internal/watcher/kvvmi_watcher.go | 7 +++ .../vmsnapshot/internal/watcher/vm_watcher.go | 7 --- 7 files changed, 46 insertions(+), 41 deletions(-) diff --git a/images/virtualization-artifact/pkg/common/annotations/annotations.go b/images/virtualization-artifact/pkg/common/annotations/annotations.go index 26fcb8229a..a6864b5dfb 100644 --- a/images/virtualization-artifact/pkg/common/annotations/annotations.go +++ b/images/virtualization-artifact/pkg/common/annotations/annotations.go @@ -189,7 +189,7 @@ const ( AnnVMOPSnapshotName = AnnAPIGroupV + "/vmop-snapshot-name" // AnnVMFilesystemRequest is an annotation on a virtual machine that indicates a request to freeze or unfreeze the filesystem has been sent. - AnnVMFilesystemRequest = AnnAPIGroup + "/virtual-machine-filesystem-request" + AnnVMFilesystemRequest = AnnAPIGroupV + "/virtual-machine-filesystem-request" ) // AddAnnotation adds an annotation to an object 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 397fc22b43..99bc5e6678 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -78,28 +78,26 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu case "": vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhasePending case v1alpha2.VirtualDiskSnapshotPhaseFailed: - err := h.unfreezeFilesystemIfFailed(ctx, vdSnapshot) - if err != nil { - if k8serrors.IsConflict(err) { - return reconcile.Result{RequeueAfter: 5 * time.Second}, err - } - if errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition) { - log.Debug(err.Error()) - return reconcile.Result{}, nil - } - if cb.Condition().Message != "" { - cb.Message(fmt.Sprintf("%s, %s", err.Error(), cb.Condition().Message)) - } else { - cb.Message(err.Error()) - } - } - readyCondition, _ := conditions.GetCondition(vdscondition.VirtualDiskSnapshotReadyType, vdSnapshot.Status.Conditions) cb. Status(metav1.ConditionFalse). Reason(conditions.CommonReason(readyCondition.Reason)). Message(readyCondition.Message) - return reconcile.Result{}, nil + + err = h.unfreezeFilesystemIfFailed(ctx, vdSnapshot) + switch { + case err == nil: + return reconcile.Result{}, nil + case 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 + case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): + log.Debug(err.Error()) + return reconcile.Result{}, nil + default: + cb.Message(fmt.Sprintf("%s, %s", err.Error(), cb.Condition().Message)) + return reconcile.Result{}, fmt.Errorf("failed to unfreeze filesystem: %w", err) + } case v1alpha2.VirtualDiskSnapshotPhaseReady: if vs == nil || vs.Status == nil || vs.Status.ReadyToUse == nil || !*vs.Status.ReadyToUse { vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseFailed @@ -174,15 +172,16 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu log.Debug(err.Error()) return reconcile.Result{}, nil case k8serrors.IsConflict(err): - return reconcile.Result{RequeueAfter: 5 * time.Second}, err + log.Debug(fmt.Sprintf("failed to sync filesystem status; resource update conflict error: %s", err)) + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil default: - return reconcile.Result{}, err + return reconcile.Result{}, fmt.Errorf("failed to sync filesystem status: %w", err) } isFSFrozen, err := h.snapshotter.IsFrozen(kvvmi) if err != nil { setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) - return reconcile.Result{}, err + return reconcile.Result{}, fmt.Errorf("failed to check filesystem status: %w", err) } switch { @@ -207,6 +206,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu err = h.snapshotter.Freeze(ctx, kvvmi) if err != nil { if k8serrors.IsConflict(err) { + log.Debug(fmt.Sprintf("failed to freeze filesystem; resource update conflict error: %s", err)) return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } cb. @@ -368,6 +368,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu err = h.unfreezeFilesystem(ctx, vdSnapshot.Name, vm, kvvmi) if err != 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 } vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress @@ -386,9 +387,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu log.Debug(err.Error()) return reconcile.Result{}, nil case k8serrors.IsConflict(err): - return reconcile.Result{RequeueAfter: 5 * time.Second}, err + log.Debug(fmt.Sprintf("failed to sync filesystem status; resource update conflict error: %s", err)) + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil default: - return reconcile.Result{}, err + return reconcile.Result{}, fmt.Errorf("failed to sync filesystem status: %w", err) } vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseReady diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go index 2d1fbf2657..1a8a9275c6 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go @@ -101,6 +101,13 @@ func (w KVVMIWatcher) enqueueRequests(ctx context.Context, kvvmi *virtv1.Virtual } func (w KVVMIWatcher) filterUpdateEvents(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstance]) bool { + oldFSFrozen := e.ObjectOld.Status.FSFreezeStatus + newFSFrozen := e.ObjectNew.Status.FSFreezeStatus + + if oldFSFrozen != newFSFrozen { + return true + } + oldRequest, oldOk := e.ObjectOld.Annotations[annotations.AnnVMFilesystemRequest] newRequest, newOk := e.ObjectNew.Annotations[annotations.AnnVMFilesystemRequest] diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vm_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vm_watcher.go index fd717873a6..4f7eed7d9a 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vm_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/vm_watcher.go @@ -105,13 +105,6 @@ func (w VirtualMachineWatcher) filterUpdateEvents(e event.TypedUpdateEvent[*v1al return true } - oldFSFrozen, _ := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, e.ObjectOld.Status.Conditions) - newFSFrozen, _ := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, e.ObjectNew.Status.Conditions) - - if oldFSFrozen.Status != newFSFrozen.Status { - return true - } - oldAgentReady, _ := conditions.GetCondition(vmcondition.TypeAgentReady, e.ObjectOld.Status.Conditions) newAgentReady, _ := conditions.GetCondition(vmcondition.TypeAgentReady, e.ObjectNew.Status.Conditions) 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 e79030e701..0ef00d2ae5 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -90,9 +90,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu log.Debug(err.Error()) return reconcile.Result{}, nil case k8serrors.IsConflict(err): - return reconcile.Result{RequeueAfter: 5 * time.Second}, err + log.Debug(fmt.Sprintf("failed to sync filesystem status; resource update conflict error: %s", err)) + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil default: - return reconcile.Result{}, err + return reconcile.Result{}, fmt.Errorf("failed to sync filesystem status: %w", err) } if vmSnapshot.DeletionTimestamp != nil { @@ -375,6 +376,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu 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)) return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } vmSnapshot.Status.Phase = v1alpha2.VirtualMachineSnapshotPhaseInProgress @@ -401,9 +403,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu log.Debug(err.Error()) return reconcile.Result{}, nil case k8serrors.IsConflict(err): - return reconcile.Result{RequeueAfter: 5 * time.Second}, err + log.Debug(fmt.Sprintf("failed to sync filesystem status; resource update conflict error: %s", err)) + return reconcile.Result{RequeueAfter: 5 * time.Second}, nil default: - return reconcile.Result{}, err + return reconcile.Result{}, fmt.Errorf("failed to sync filesystem status: %w", err) } // 10. Move to Ready phase. diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go index 850d0184d1..b63c0063a2 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/kvvmi_watcher.go @@ -88,6 +88,13 @@ func (w KVVMIWatcher) enqueueRequests(ctx context.Context, kvvmi *virtv1.Virtual } func (w KVVMIWatcher) filterUpdateEvents(e event.TypedUpdateEvent[*virtv1.VirtualMachineInstance]) bool { + oldFSFrozen := e.ObjectOld.Status.FSFreezeStatus + newFSFrozen := e.ObjectNew.Status.FSFreezeStatus + + if oldFSFrozen != newFSFrozen { + return true + } + oldRequest, oldOk := e.ObjectOld.Annotations[annotations.AnnVMFilesystemRequest] newRequest, newOk := e.ObjectNew.Annotations[annotations.AnnVMFilesystemRequest] diff --git a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vm_watcher.go b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vm_watcher.go index 03f03c1aec..67e13804af 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vm_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/watcher/vm_watcher.go @@ -96,13 +96,6 @@ func (w VirtualMachineWatcher) filterUpdateEvents(e event.TypedUpdateEvent[*v1al return true } - oldFSFrozen, _ := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, e.ObjectOld.Status.Conditions) - newFSFrozen, _ := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, e.ObjectNew.Status.Conditions) - - if oldFSFrozen.Reason != newFSFrozen.Reason { - return true - } - oldSnapshotting, _ := conditions.GetCondition(vmcondition.TypeSnapshotting, e.ObjectOld.Status.Conditions) newSnapshotting, _ := conditions.GetCondition(vmcondition.TypeSnapshotting, e.ObjectNew.Status.Conditions) From 3628c19421ba9a4bf90e36ffce594d4bd810c512 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Thu, 20 Nov 2025 12:42:55 +0300 Subject: [PATCH 21/23] fix kvvmi watcher Signed-off-by: Roman Sysoev --- .../controller/vdsnapshot/internal/watcher/kvvmi_watcher.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go index 1a8a9275c6..45c182dad8 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/watcher/kvvmi_watcher.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "log/slog" + "strings" "k8s.io/apimachinery/pkg/types" virtv1 "kubevirt.io/api/core/v1" @@ -67,7 +68,9 @@ func (w KVVMIWatcher) enqueueRequests(ctx context.Context, kvvmi *virtv1.Virtual continue } - volumeByName[v.Name] = struct{}{} + if originalName, ok := strings.CutPrefix(v.Name, "vd-"); ok { + volumeByName[originalName] = struct{}{} + } } if len(volumeByName) == 0 { From af7c059e254c6b9be7e169c4b6a000d5fc7bc88c Mon Sep 17 00:00:00 2001 From: Isteb4k Date: Thu, 20 Nov 2025 15:30:43 +0100 Subject: [PATCH 22/23] fix(vdsnapshot): improve condition messages Signed-off-by: Isteb4k --- .../controller/service/snapshot_service.go | 2 +- .../vdsnapshot/internal/life_cycle.go | 37 ++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go index 2e392eebd7..9253fed996 100644 --- a/images/virtualization-artifact/pkg/controller/service/snapshot_service.go +++ b/images/virtualization-artifact/pkg/controller/service/snapshot_service.go @@ -42,7 +42,7 @@ const ( ) var ( - ErrUntrustedFilesystemFrozenCondition = errors.New("the filesystem status cannot be processed correctly") + ErrUntrustedFilesystemFrozenCondition = errors.New("the filesystem status is not synced") ErrUnexpectedFilesystemRequest = errors.New("found unexpected filesystem request in the virtual machine instance annotations") ) 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 99bc5e6678..8846dec42f 100644 --- a/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vdsnapshot/internal/life_cycle.go @@ -170,11 +170,20 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu // OK. case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): log.Debug(err.Error()) + cb. + Status(metav1.ConditionFalse). + Reason(vdscondition.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(vdscondition.Snapshotting). + Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) return reconcile.Result{RequeueAfter: 5 * time.Second}, nil default: + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, fmt.Errorf("failed to sync filesystem status: %w", err) } @@ -187,8 +196,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu switch { case vs == nil: if vm != nil && vm.Status.Phase != v1alpha2.MachineStopped && !isFSFrozen { - canFreeze, err := h.snapshotter.CanFreeze(ctx, kvvmi) + var canFreeze bool + canFreeze, err = h.snapshotter.CanFreeze(ctx, kvvmi) if err != nil { + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) return reconcile.Result{}, err } if canFreeze { @@ -207,6 +218,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu 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(vdscondition.Snapshotting). + Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } cb. @@ -355,6 +370,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu if vdSnapshot.Status.Consistent == nil { if vm == nil || vm.Status.Phase == v1alpha2.MachineStopped || isFSFrozen { vdSnapshot.Status.Consistent = ptr.To(true) + cb. + Status(metav1.ConditionFalse). + Reason(vdscondition.Snapshotting). + Message(service.CapitalizeFirstLetter("The consistent snapshot has been taken.")) return reconcile.Result{RequeueAfter: 2 * time.Second}, nil } @@ -369,6 +388,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu 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(vdscondition.Snapshotting). + Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) return reconcile.Result{RequeueAfter: 5 * time.Second}, nil } vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseInProgress @@ -385,12 +408,22 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vdSnapshot *v1alpha2.Virtu // OK. case errors.Is(err, service.ErrUntrustedFilesystemFrozenCondition): log.Debug(err.Error()) + cb. + Status(metav1.ConditionFalse). + Reason(vdscondition.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(vdscondition.Snapshotting). + Message(service.CapitalizeFirstLetter("Waiting for the filesystem of the virtual machine to be synced.")) return reconcile.Result{RequeueAfter: 5 * time.Second}, nil default: - return reconcile.Result{}, fmt.Errorf("failed to sync filesystem status: %w", err) + err = fmt.Errorf("failed to sync filesystem status: %w", err) + setPhaseConditionToFailed(cb, &vdSnapshot.Status.Phase, err) + return reconcile.Result{}, err } vdSnapshot.Status.Phase = v1alpha2.VirtualDiskSnapshotPhaseReady From a470a047bd2efbd8f94d980c0502f8d3e235a960 Mon Sep 17 00:00:00 2001 From: Roman Sysoev Date: Thu, 20 Nov 2025 19:51:46 +0300 Subject: [PATCH 23/23] fix(vmsnapshot): improve condition messages Signed-off-by: Roman Sysoev --- .../vmsnapshot/internal/life_cycle.go | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) 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 0ef00d2ae5..6be254afc9 100644 --- a/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go +++ b/images/virtualization-artifact/pkg/controller/vmsnapshot/internal/life_cycle.go @@ -88,12 +88,22 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu // 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: - return reconcile.Result{}, fmt.Errorf("failed to sync filesystem status: %w", err) + err = fmt.Errorf("failed to sync filesystem status: %w", err) + h.setPhaseConditionToFailed(cb, vmSnapshot, err) + return reconcile.Result{}, err } if vmSnapshot.DeletionTimestamp != nil { @@ -107,9 +117,18 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu 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) @@ -227,6 +246,7 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu 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 @@ -283,6 +303,11 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu 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) @@ -377,6 +402,10 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu 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 @@ -401,12 +430,22 @@ func (h LifeCycleHandler) Handle(ctx context.Context, vmSnapshot *v1alpha2.Virtu // 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: - return reconcile.Result{}, fmt.Errorf("failed to sync filesystem status: %w", err) + err = fmt.Errorf("failed to sync filesystem status: %w", err) + h.setPhaseConditionToFailed(cb, vmSnapshot, err) + return reconcile.Result{}, err } // 10. Move to Ready phase.