Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/core/v1alpha2/vdscondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions api/core/v1alpha2/vmscondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,34 @@ package service

import (
"context"
"errors"
"fmt"

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"
)

const (
RequestFSFreeze = "freeze"
RequestFSUnfreeze = "unfreeze"
FSFrozen = "frozen"
)

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
Expand All @@ -49,37 +60,72 @@ func NewSnapshotService(virtClient kubeclient.Client, client Client, protection
}
}

func (s *SnapshotService) IsFrozen(vm *v1alpha2.VirtualMachine) bool {
if vm == nil {
return false
// 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) {
if kvvmi == nil {
return false, nil
}

filesystemFrozen, _ := conditions.GetCondition(vmcondition.TypeFilesystemFrozen, vm.Status.Conditions)
if _, ok := kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest]; ok {
return false, fmt.Errorf("failed to check %s/%s fsFreezeStatus: %w", kvvmi.Namespace, kvvmi.Name, ErrUntrustedFilesystemFrozenCondition)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s add to the error which exact value was found in the annotation

}

return filesystemFrozen.Status == metav1.ConditionTrue && filesystemFrozen.Reason == vmcondition.ReasonFilesystemFrozen.String()
return kvvmi.Status.FSFreezeStatus == FSFrozen, 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, kvvmi *virtv1.VirtualMachineInstance) (bool, error) {
if kvvmi == nil || kvvmi.Status.Phase != virtv1.Running {
return false, nil
}

agentReady, _ := conditions.GetCondition(vmcondition.TypeAgentReady, vm.Status.Conditions)
isFrozen, err := s.IsFrozen(ctx, kvvmi)
if err != nil {
return false, err
}
if isFrozen {
return false, nil
}

return agentReady.Status == metav1.ConditionTrue
for _, c := range kvvmi.Status.Conditions {
if c.Type == virtv1.VirtualMachineInstanceAgentConnected {
return c.Status == corev1.ConditionTrue, nil
}
}

return false, nil
}

func (s *SnapshotService) Freeze(ctx context.Context, name, namespace string) error {
err := s.virtClient.VirtualMachines(namespace).Freeze(ctx, name, subv1alpha2.VirtualMachineFreeze{})
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)
if err != nil {
return fmt.Errorf("failed to freeze virtual machine %s/%s: %w", namespace, name, err)
return fmt.Errorf("failed to annotate internal virtual machine instance with filesystem freeze request: %w", err)
}

err = s.virtClient.VirtualMachines(kvvmi.Namespace).Freeze(ctx, kvvmi.Name, subv1alpha2.VirtualMachineFreeze{})
if err != nil {
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) (bool, error) {
if vm == nil || !s.IsFrozen(vm) {
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
}

Expand All @@ -91,7 +137,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 {
Expand Down Expand Up @@ -126,8 +172,16 @@ func (s *SnapshotService) CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context
return true, nil
}

func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Context, vmSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error) {
if vm == nil || !s.IsFrozen(vm) {
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, kvvmi)
if err != nil {
return false, err
}
if !isFrozen {
return false, nil
}

Expand All @@ -139,7 +193,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 {
Expand Down Expand Up @@ -174,10 +228,19 @@ func (s *SnapshotService) CanUnfreezeWithVirtualMachineSnapshot(ctx context.Cont
return true, nil
}

func (s *SnapshotService) Unfreeze(ctx context.Context, name, namespace string) error {
err := s.virtClient.VirtualMachines(namespace).Unfreeze(ctx, name)
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)
if err != nil {
return fmt.Errorf("unfreeze virtual machine %s/%s: %w", namespace, name, err)
return fmt.Errorf("failed to annotate internal virtual machine instance with filesystem unfreeze request: %w", err)
}

err = s.virtClient.VirtualMachines(kvvmi.Namespace).Unfreeze(ctx, kvvmi.Name)
if err != nil {
return fmt.Errorf("unfreeze virtual machine %s/%s: %w", kvvmi.Namespace, kvvmi.Name, err)
}

return nil
Expand Down Expand Up @@ -243,3 +306,70 @@ 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)
}
kvvmi.Annotations[annotations.AnnVMFilesystemFrozenRequest] = requestType

err := s.client.Update(ctx, kvvmi)
if err != nil {
return err
}

return nil
}

func (s *SnapshotService) removeAnnFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error {
if kvvmi == nil || kvvmi.Annotations == nil {
return nil
}

delete(kvvmi.Annotations, annotations.AnnVMFilesystemFrozenRequest)

err := s.client.Update(ctx, kvvmi)
if err != nil {
return err
}

return nil
}

func (s *SnapshotService) SyncFSFreezeRequest(ctx context.Context, kvvmi *virtv1.VirtualMachineInstance) error {
if kvvmi == nil {
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)
}
}

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ 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"

Expand Down Expand Up @@ -61,6 +64,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 {
Expand All @@ -70,14 +78,20 @@ 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
}
return reconcile.Result{}, err
}

if canUnfreeze {
err = h.snapshotter.Unfreeze(ctx, vm.Name, vm.Namespace)
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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -32,14 +33,16 @@ type VirtualDiskReadySnapshotter interface {
}

type LifeCycleSnapshotter interface {
Freeze(ctx context.Context, name, namespace string) error
IsFrozen(vm *v1alpha2.VirtualMachine) bool
CanFreeze(vm *v1alpha2.VirtualMachine) bool
CanUnfreezeWithVirtualDiskSnapshot(ctx context.Context, vdSnapshotName string, vm *v1alpha2.VirtualMachine) (bool, error)
Unfreeze(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, 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)
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)
}
Loading
Loading