Skip to content

Commit

Permalink
snapshot: Restore instancetype controllerrevisions
Browse files Browse the repository at this point in the history
Building on previous changes that essentially cloned any referenced
instancetype or preference ControllerRevisions during the creation of a
VirtualMachineSnapshot we can now restore these into the existing or new
VirtualMachine.

When restoring for use by a new VirtualMachine we need to ensure that
the newly created ControllerRevisions are claimed by the new
VirtualMachine once it has been created.

Signed-off-by: Lee Yarwood <lyarwood@redhat.com>
  • Loading branch information
lyarwood committed Nov 16, 2022
1 parent 9459e5e commit 5cc62c0
Show file tree
Hide file tree
Showing 6 changed files with 389 additions and 3 deletions.
2 changes: 2 additions & 0 deletions pkg/storage/snapshot/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ go_test(
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/api/storage/v1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
"//vendor/k8s.io/client-go/testing:go_default_library",
Expand Down
121 changes: 121 additions & 0 deletions pkg/storage/snapshot/restore.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import (

jsonpatch "github.com/evanphx/json-patch"
vsv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
Expand Down Expand Up @@ -561,6 +563,10 @@ func (t *vmRestoreTarget) reconcileSpec() (bool, error) {
return false, fmt.Errorf("error patching VM %s: %v", newVM.Name, err)
}

if err = t.restoreInstancetypeControllerRevisions(newVM); err != nil {
return false, err
}

if !t.doesTargetVMExist() {
newVM, err = t.controller.Client.VirtualMachine(t.vmRestore.Namespace).Create(newVM)
} else {
Expand All @@ -571,6 +577,10 @@ func (t *vmRestoreTarget) reconcileSpec() (bool, error) {
}
t.vm = newVM

if err = t.claimInstancetypeControllerRevisionsOwnership(t.vm); err != nil {
return false, err
}

return true, nil
}

Expand All @@ -596,6 +606,117 @@ func (t *vmRestoreTarget) reconcileDataVolumes() (bool, error) {
return createdDV || waitingDV, nil
}

func (t *vmRestoreTarget) getControllerRevision(namespace, name string) (*appsv1.ControllerRevision, error) {
revisionKey := cacheKeyFunc(namespace, name)
obj, exists, err := t.controller.CRInformer.GetStore().GetByKey(revisionKey)
if err != nil {
return nil, err
}
if !exists {
return nil, fmt.Errorf("Unable to find ControllerRevision %s", revisionKey)
}
return obj.(*appsv1.ControllerRevision), nil
}

func (t *vmRestoreTarget) getVirtualMachineSnapshot(namespace, name string) (*snapshotv1.VirtualMachineSnapshot, error) {
vmSnapshotKey := cacheKeyFunc(namespace, name)
obj, exists, err := t.controller.VMSnapshotInformer.GetStore().GetByKey(vmSnapshotKey)
if err != nil {
return nil, err
}
if !exists {
return nil, fmt.Errorf("Unable to find VirtualMachineSnapshot %s", vmSnapshotKey)
}
return obj.(*snapshotv1.VirtualMachineSnapshot), nil
}

func (t *vmRestoreTarget) restoreInstancetypeControllerRevision(vmSnapshotRevisionName, vmSnapshotName string, vm *kubevirtv1.VirtualMachine) (*appsv1.ControllerRevision, error) {
// Switch the snapshot and vm names for the restored CR name
restoredCRName := strings.Replace(vmSnapshotRevisionName, vmSnapshotName, vm.Name, 1)

// If the target VirtualMachine already exists it's likely that the original ControllerRevision is already present.
// Check that here by attempting to lookup the CR using the generated restoredCRName.
// Ignore any NotFound errors raised allowing the CR to be restored below.
if t.doesTargetVMExist() {
existingCR, err := t.getControllerRevision(vm.Namespace, restoredCRName)
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
if existingCR != nil {
// TODO - Check the contents of the existing CR here against that of the snapshot CR
return existingCR, nil
}
}

snapshotCR, err := t.getControllerRevision(vm.Namespace, vmSnapshotRevisionName)
if err != nil {
return nil, err
}

restoredCR := snapshotCR.DeepCopy()
restoredCR.ObjectMeta.Reset()
restoredCR.Name = restoredCRName

restoredCR, err = t.controller.Client.AppsV1().ControllerRevisions(vm.Namespace).Create(context.Background(), restoredCR, metav1.CreateOptions{})
if err != nil {
return nil, err
}

return restoredCR, nil
}

func (t *vmRestoreTarget) restoreInstancetypeControllerRevisions(vm *kubevirtv1.VirtualMachine) error {
if vm.Spec.Instancetype != nil && vm.Spec.Instancetype.RevisionName != "" {
restoredCR, err := t.restoreInstancetypeControllerRevision(vm.Spec.Instancetype.RevisionName, t.vmRestore.Spec.VirtualMachineSnapshotName, vm)
if err != nil {
return err
}
vm.Spec.Instancetype.RevisionName = restoredCR.Name
}

if vm.Spec.Preference != nil && vm.Spec.Preference.RevisionName != "" {
restoredCR, err := t.restoreInstancetypeControllerRevision(vm.Spec.Preference.RevisionName, t.vmRestore.Spec.VirtualMachineSnapshotName, vm)
if err != nil {
return err
}
vm.Spec.Preference.RevisionName = restoredCR.Name
}

return nil
}

func (t *vmRestoreTarget) claimInstancetypeControllerRevisionOwnership(revisionName string, vm *kubevirtv1.VirtualMachine) error {
cr, err := t.getControllerRevision(vm.Namespace, revisionName)
if err != nil {
return err
}

if !metav1.IsControlledBy(cr, vm) {
cr.OwnerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(vm, kubevirtv1.VirtualMachineGroupVersionKind)}
_, err = t.controller.Client.AppsV1().ControllerRevisions(vm.Namespace).Update(context.Background(), cr, v1.UpdateOptions{})
if err != nil {
return err
}
}
return nil
}

func (t *vmRestoreTarget) claimInstancetypeControllerRevisionsOwnership(vm *kubevirtv1.VirtualMachine) error {
if vm.Spec.Instancetype != nil && vm.Spec.Instancetype.RevisionName != "" {
if err := t.claimInstancetypeControllerRevisionOwnership(vm.Spec.Instancetype.RevisionName, vm); err != nil {
return err
}
}

if vm.Spec.Preference != nil && vm.Spec.Preference.RevisionName != "" {
if err := t.claimInstancetypeControllerRevisionOwnership(vm.Spec.Preference.RevisionName, vm); err != nil {
return err
}
}

return nil
}

func (t *vmRestoreTarget) createDataVolume(dvt kubevirtv1.DataVolumeTemplateSpec) (bool, error) {
pvc, err := t.controller.getPVC(t.vm.Namespace, dvt.Name)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/storage/snapshot/restore_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type VMRestoreController struct {
DataVolumeInformer cache.SharedIndexInformer
PVCInformer cache.SharedIndexInformer
StorageClassInformer cache.SharedIndexInformer
CRInformer cache.SharedIndexInformer

VolumeSnapshotProvider VolumeSnapshotProvider

Expand Down
Loading

0 comments on commit 5cc62c0

Please sign in to comment.