Skip to content

Commit 481ec17

Browse files
authored
Add support for restoring volumes from snapshots (#3207)
* Create dummy restore request * Create restore request * Restore volumes from snapshots * Remove config that does not exist anymore
1 parent 62cdfcd commit 481ec17

File tree

10 files changed

+1128
-189
lines changed

10 files changed

+1128
-189
lines changed

pkg/controllers/register.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,15 @@ func RegisterControllers(ctx *synccontext.ControllerContext, syncers []syncertyp
4444
}
4545

4646
if !ctx.Config.ControlPlane.Standalone.Enabled {
47-
// register vcluster snapshot controller only for non-standalone
47+
// register vcluster snapshot & restore controllers only for non-standalone
4848
err = registerSnapshotController(registerContext)
4949
if err != nil {
5050
return err
5151
}
52+
err = registerRestoreController(registerContext)
53+
if err != nil {
54+
return err
55+
}
5256
}
5357

5458
// skip if we run in dedicated mode
@@ -278,3 +282,16 @@ func registerSnapshotController(registerContext *synccontext.RegisterContext) er
278282

279283
return nil
280284
}
285+
286+
func registerRestoreController(registerContext *synccontext.RegisterContext) error {
287+
controller, err := snapshot.NewRestoreController(registerContext)
288+
if err != nil {
289+
return fmt.Errorf("unable to create vcluster snapshot controller: %w", err)
290+
}
291+
err = controller.Register()
292+
if err != nil {
293+
return fmt.Errorf("unable to register vcluster snapshot controller: %w", err)
294+
}
295+
296+
return nil
297+
}

pkg/snapshot/controller.go

Lines changed: 16 additions & 185 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"fmt"
88
"time"
99

10-
snapshotsv1 "github.com/kubernetes-csi/external-snapshotter/client/v8/clientset/versioned"
1110
"github.com/loft-sh/vcluster/pkg/config"
1211
"github.com/loft-sh/vcluster/pkg/constants"
1312
snapshotMeta "github.com/loft-sh/vcluster/pkg/snapshot/meta"
@@ -19,14 +18,11 @@ import (
1918
kerrors "k8s.io/apimachinery/pkg/api/errors"
2019
"k8s.io/apimachinery/pkg/labels"
2120
"k8s.io/apimachinery/pkg/types"
22-
"k8s.io/client-go/kubernetes"
23-
"k8s.io/client-go/rest"
2421
"k8s.io/client-go/tools/record"
2522
ctrl "sigs.k8s.io/controller-runtime"
2623
"sigs.k8s.io/controller-runtime/pkg/builder"
2724
"sigs.k8s.io/controller-runtime/pkg/client"
2825
"sigs.k8s.io/controller-runtime/pkg/controller"
29-
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3026
"sigs.k8s.io/controller-runtime/pkg/predicate"
3127
)
3228

@@ -36,6 +32,7 @@ const (
3632
)
3733

3834
type Reconciler struct {
35+
reconcilerBase
3936
vConfig *config.VirtualClusterConfig
4037
snapshotRequestsKubeClient client.Client
4138
snapshotRequestsManager ctrl.Manager
@@ -85,7 +82,19 @@ func NewController(registerContext *synccontext.RegisterContext) (*Reconciler, e
8582
return nil, fmt.Errorf("failed to create volume snapshotter: %w", err)
8683
}
8784

85+
reconciler := reconcilerBase{
86+
vConfig: registerContext.Config,
87+
requestsKubeClient: snapshotRequestsManager.GetClient(),
88+
requestsManager: snapshotRequestsManager,
89+
logger: logger,
90+
eventRecorder: snapshotRequestsManager.GetEventRecorderFor(controllerName),
91+
isHostMode: isHostMode,
92+
kind: snapshotReconciler,
93+
finalizer: ControllerFinalizer,
94+
requestKey: RequestKey,
95+
}
8896
return &Reconciler{
97+
reconcilerBase: reconciler,
8998
vConfig: registerContext.Config,
9099
snapshotRequestsKubeClient: snapshotRequestsManager.GetClient(),
91100
snapshotRequestsManager: snapshotRequestsManager,
@@ -96,24 +105,6 @@ func NewController(registerContext *synccontext.RegisterContext) (*Reconciler, e
96105
}, nil
97106
}
98107

99-
func createClients(restConfig *rest.Config) (*kubernetes.Clientset, *snapshotsv1.Clientset, error) {
100-
if restConfig == nil {
101-
return nil, nil, errors.New("rest config is nil")
102-
}
103-
104-
kubeClient, err := kubernetes.NewForConfig(restConfig)
105-
if err != nil {
106-
return nil, nil, fmt.Errorf("could not create kube client: %w", err)
107-
}
108-
109-
snapshotClient, err := snapshotsv1.NewForConfig(restConfig)
110-
if err != nil {
111-
return nil, nil, fmt.Errorf("could not create snapshot client: %w", err)
112-
}
113-
114-
return kubeClient, snapshotClient, nil
115-
}
116-
117108
func (c *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, retErr error) {
118109
c.logger.Infof("Reconciling snapshot request ConfigMap %s", req.NamespacedName)
119110

@@ -242,15 +233,15 @@ func (c *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
242233
return ctrl.Result{}, fmt.Errorf("failed to reconcile failed snapshot request %s/%s: %w", configMap.Namespace, configMap.Name, err)
243234
}
244235
default:
245-
return ctrl.Result{}, fmt.Errorf("unknown snapshot request phase %s", snapshotRequest.Status.Phase)
236+
return ctrl.Result{}, fmt.Errorf("invalid snapshot request phase %s", snapshotRequest.Status.Phase)
246237
}
247238

248239
return ctrl.Result{}, nil
249240
}
250241

251242
func (c *Reconciler) Register() error {
252243
isVolumeSnapshotsConfig := predicate.NewPredicateFuncs(func(obj client.Object) bool {
253-
if obj.GetNamespace() != c.getSnapshotRequestNamespace() {
244+
if obj.GetNamespace() != c.getRequestNamespace() {
254245
return false
255246
}
256247

@@ -337,166 +328,6 @@ func (c *Reconciler) reconcileCreatingEtcdBackup(ctx context.Context, configMap
337328
return false, nil
338329
}
339330

340-
// reconcileCompletedRequest cleans up the completed snapshot request resources.
341-
func (c *Reconciler) reconcileCompletedRequest(ctx context.Context, configMap *corev1.ConfigMap) error {
342-
c.logger.Infof("Snapshot request from ConfigMap %s/%s has been completed", configMap.Namespace, configMap.Name)
343-
err := c.reconcileDoneRequest(ctx, configMap)
344-
if err != nil {
345-
return fmt.Errorf("failed to delete snapshot request Secret %s/%s: %w", configMap.Namespace, configMap.Name, err)
346-
}
347-
return nil
348-
}
349-
350-
// reconcileFailedRequest cleans up the failed snapshot request resources.
351-
func (c *Reconciler) reconcileFailedRequest(ctx context.Context, configMap *corev1.ConfigMap) error {
352-
c.logger.Errorf("Snapshot request from ConfigMap %s/%s has failed", configMap.Namespace, configMap.Name)
353-
err := c.reconcileDoneRequest(ctx, configMap)
354-
if err != nil {
355-
return fmt.Errorf("failed to delete snapshot request Secret %s/%s: %w", configMap.Namespace, configMap.Name, err)
356-
}
357-
return nil
358-
}
359-
360-
// reconcileDeletedRequest deletes the snapshot request Secret and removes the finalizer from the
361-
// snapshot request ConfigMap.
362-
func (c *Reconciler) reconcileDeletedRequest(ctx context.Context, configMap *corev1.ConfigMap) (retErr error) {
363-
// snapshot request ConfigMap deleted, so delete Secret as well
364-
c.logger.Infof("Snapshot request ConfigMap %s/%s deleted", configMap.Namespace, configMap.Name)
365-
366-
err := c.reconcileDoneRequest(ctx, configMap)
367-
if err != nil {
368-
return fmt.Errorf("failed to delete snapshot request Secret %s/%s: %w", configMap.Namespace, configMap.Name, err)
369-
}
370-
return nil
371-
}
372-
373-
// reconcileDoneRequest deletes the snapshot request Secret and removes the finalizer from the
374-
// snapshot request ConfigMap.
375-
func (c *Reconciler) reconcileDoneRequest(ctx context.Context, configMap *corev1.ConfigMap) (retErr error) {
376-
defer func() {
377-
if retErr != nil {
378-
// an error occurred, don't remove the finalizer
379-
return
380-
}
381-
err := c.removeFinalizer(ctx, configMap)
382-
if err != nil {
383-
retErr = fmt.Errorf("failed to remove vCluster snapshot controller finalizer from the snapshot request ConfigMap %s/%s: %w", configMap.Namespace, configMap.Name, err)
384-
}
385-
}()
386-
387-
err := c.deleteSnapshotRequestSecret(ctx, configMap)
388-
if err != nil {
389-
return fmt.Errorf("failed to delete snapshot request Secret %s/%s: %w", configMap.Namespace, configMap.Name, err)
390-
}
391-
return nil
392-
}
393-
394-
func (c *Reconciler) addFinalizer(ctx context.Context, configMap *corev1.ConfigMap) (bool, error) {
395-
if controllerutil.ContainsFinalizer(configMap, ControllerFinalizer) {
396-
return false, nil
397-
}
398-
399-
c.logger.Infof(
400-
"Adding vCluster snapshot controller finalizer %s to the snapshot request ConfigMap %s/%s",
401-
ControllerFinalizer,
402-
configMap.Namespace,
403-
configMap.Name)
404-
405-
// before the change
406-
oldConfigMap := client.MergeFrom(configMap.DeepCopy())
407-
408-
// add the snapshot controller finalizer to the snapshot request ConfigMap
409-
controllerutil.AddFinalizer(configMap, ControllerFinalizer)
410-
411-
// patch the object
412-
err := c.client().Patch(ctx, configMap, oldConfigMap)
413-
if err != nil {
414-
return false, fmt.Errorf("failed to patch snapshot request ConfigMap %s/%s finalizers: %w", configMap.Namespace, configMap.Name, err)
415-
}
416-
417-
c.logger.Infof(
418-
"Added vCluster snapshot controller finalizer %s to the snapshot request ConfigMap %s/%s",
419-
ControllerFinalizer,
420-
configMap.Namespace,
421-
configMap.Name)
422-
return true, nil
423-
}
424-
425-
func (c *Reconciler) removeFinalizer(ctx context.Context, configMap *corev1.ConfigMap) error {
426-
if !controllerutil.ContainsFinalizer(configMap, ControllerFinalizer) {
427-
return nil
428-
}
429-
430-
c.logger.Infof(
431-
"Removing vCluster snapshot controller finalizer %s from the snapshot request ConfigMap %s/%s",
432-
ControllerFinalizer,
433-
configMap.Namespace,
434-
configMap.Name)
435-
436-
// before the change
437-
oldConfigMap := client.MergeFrom(configMap.DeepCopy())
438-
439-
// add the snapshot controller finalizer to the snapshot request ConfigMap
440-
controllerutil.RemoveFinalizer(configMap, ControllerFinalizer)
441-
442-
// patch the object
443-
err := c.client().Patch(ctx, configMap, oldConfigMap)
444-
if err != nil {
445-
return fmt.Errorf("failed to patch snapshot request ConfigMap %s/%s finalizers: %w", configMap.Namespace, configMap.Name, err)
446-
}
447-
448-
c.logger.Infof(
449-
"Removed vCluster snapshot controller finalizer %s from the snapshot request ConfigMap %s/%s",
450-
ControllerFinalizer,
451-
configMap.Namespace,
452-
configMap.Name)
453-
return nil
454-
}
455-
456-
func (c *Reconciler) deleteSnapshotRequestSecret(ctx context.Context, configMap *corev1.ConfigMap) error {
457-
namespace := configMap.Namespace
458-
name := configMap.Name
459-
c.logger.Debugf("Deleting snapshot request Secret %s/%s", namespace, name)
460-
461-
// find snapshot request secret
462-
var secret corev1.Secret
463-
secretObjectKey := client.ObjectKey{
464-
Namespace: namespace,
465-
Name: name,
466-
}
467-
err := c.client().Get(ctx, secretObjectKey, &secret)
468-
if kerrors.IsNotFound(err) {
469-
c.logger.Debugf("Snapshot request Secret %s/%s aleady deleted", namespace, name)
470-
return nil
471-
} else if err != nil {
472-
return fmt.Errorf("failed to get snapshot request Secret %s/%s: %w", namespace, name, err)
473-
}
474-
475-
// delete snapshot request secret
476-
err = c.client().Delete(ctx, &secret)
477-
if kerrors.IsNotFound(err) {
478-
c.logger.Debugf("Snapshot request Secret %s/%s aleady deleted", namespace, name)
479-
return nil
480-
} else if err != nil {
481-
return fmt.Errorf("failed to delete snapshot request Secret %s/%s: %w", namespace, name, err)
482-
}
483-
484-
c.logger.Debugf("Deleted snapshot request Secret %s/%s", namespace, name)
485-
c.eventRecorder.Eventf(configMap, corev1.EventTypeNormal, "SnapshotRequestCleanup", "Snapshot request Secret %s/%s has been deleted", configMap.Namespace, configMap.Name)
486-
return nil
487-
}
488-
489-
func (c *Reconciler) client() client.Client {
490-
return c.snapshotRequestsKubeClient
491-
}
492-
493-
func (c *Reconciler) getSnapshotRequestNamespace() string {
494-
if c.isHostMode {
495-
return c.vConfig.HostNamespace
496-
}
497-
return "kube-system"
498-
}
499-
500331
func (c *Reconciler) updateRequest(ctx context.Context, previousConfigMapState client.Patch, configMap *corev1.ConfigMap, snapshotRequest Request) error {
501332
snapshotRequestJSON, err := json.Marshal(snapshotRequest)
502333
if err != nil {
@@ -517,7 +348,7 @@ func (c *Reconciler) getOngoingSnapshotRequestsResourceNames(ctx context.Context
517348
// list options with label selector
518349
var configMaps corev1.ConfigMapList
519350
listOptions := &client.ListOptions{
520-
Namespace: c.getSnapshotRequestNamespace(),
351+
Namespace: c.getRequestNamespace(),
521352
LabelSelector: labels.SelectorFromSet(map[string]string{
522353
snapshotMeta.RequestLabel: "",
523354
}),

0 commit comments

Comments
 (0)