From b1ea232691fbe28da40b5b64b391db21ec5a174e Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Sat, 17 Apr 2021 16:22:41 +0200 Subject: [PATCH] Refactor and added the restoresession controller --- pkg/controllers/backupsession_controller.go | 132 +++++++++++++------ pkg/controllers/restoresession_controller.go | 57 ++++++++ pkg/restore/root.go | 4 +- pkg/server/root.go | 39 ++++-- pkg/session/root.go | 15 +-- pkg/utils/root.go | 12 +- 6 files changed, 180 insertions(+), 79 deletions(-) create mode 100644 pkg/controllers/restoresession_controller.go diff --git a/pkg/controllers/backupsession_controller.go b/pkg/controllers/backupsession_controller.go index 8413f22..5dc7718 100644 --- a/pkg/controllers/backupsession_controller.go +++ b/pkg/controllers/backupsession_controller.go @@ -38,58 +38,104 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro backupConf := &formolv1alpha1.BackupConfiguration{} if err := r.Get(ctx, client.ObjectKey{ Namespace: backupSession.Namespace, - Name: backupSession.Spec.Ref.Name, + Name: backupSession.Spec.Ref, }, backupConf); err != nil { log.Error(err, "unable to get backupConfiguration") return ctrl.Result{}, client.IgnoreNotFound(err) } - deploymentName := os.Getenv("POD_DEPLOYMENT") + deploymentName := os.Getenv(formolv1alpha1.TARGET_NAME) for _, target := range backupConf.Spec.Targets { switch target.Kind { - case "Deployment": + case formolv1alpha1.SidecarKind: if target.Name == deploymentName { - for i, status := range backupSession.Status.Targets { - if status.Name == target.Name { - log.V(0).Info("It's for us!", "target", target) - switch status.SessionState { - case formolv1alpha1.New: - // TODO: Run beforeBackup - log.V(0).Info("New session, run the beforeBackup hooks if any") - result := formolv1alpha1.Running - if err := formolcliutils.RunBeforeBackup(target); err != nil { - result = formolv1alpha1.Failure - } - backupSession.Status.Targets[i].SessionState = result - log.V(1).Info("current backupSession status", "status", backupSession.Status) - if err := r.Status().Update(ctx, backupSession); err != nil { - log.Error(err, "unable to update backupsession status") - return ctrl.Result{}, err - } - case formolv1alpha1.Running: - log.V(0).Info("Running session. Do the backup") - result := formolv1alpha1.Success - status.StartTime = &metav1.Time{Time: time.Now()} - output, err := restic.BackupPaths(backupSession.Name, target.Paths) - if err != nil { - log.Error(err, "unable to backup deployment", "output", string(output)) - result = formolv1alpha1.Failure - } else { - snapshotId := restic.GetBackupResults(output) - backupSession.Status.Targets[i].SnapshotId = snapshotId - backupSession.Status.Targets[i].Duration = &metav1.Duration{Duration: time.Now().Sub(backupSession.Status.Targets[i].StartTime.Time)} - } - backupSession.Status.Targets[i].SessionState = result - log.V(1).Info("current backupSession status", "status", backupSession.Status) - if err := r.Status().Update(ctx, backupSession); err != nil { - log.Error(err, "unable to update backupsession status") - return ctrl.Result{}, err - } - case formolv1alpha1.Success, formolv1alpha1.Failure: - // I decided not to flag the backup as a failure if the AfterBackup command fail. But maybe I'm wrong - log.V(0).Info("Backup is over, run the afterBackup hooks if any") - formolcliutils.RunAfterBackup(target) + // We are involved in that Backup, let's see if it's our turn + status := &(backupSession.Status.Targets[len(backupSession.Status.Targets)-1]) + if status.Name == deploymentName { + log.V(0).Info("It's for us!", "target", target) + switch status.SessionState { + case formolv1alpha1.New: + log.V(0).Info("New session, move to Initializing state") + status.SessionState = formolv1alpha1.Init + if err := r.Status().Update(ctx, backupSession); err != nil { + log.Error(err, "unable to update backupsession status") + return ctrl.Result{}, err } + case formolv1alpha1.Init: + log.V(0).Info("Start to run the backup initializing steps if any") + result := formolv1alpha1.Running + for _, step := range target.Steps { + if step.Finalize != nil && *step.Finalize == false { + continue + } + function := &formolv1alpha1.Function{} + if err := r.Get(ctx, client.ObjectKey{ + Name: step.Name, + Namespace: backupConf.Namespace, + }, function); err != nil { + log.Error(err, "unable to get function", "function", step.Name) + return ctrl.Result{}, err + } + if err := formolcliutils.RunChroot(function.Spec.Command[0], function.Spec.Command[1:]...); err != nil { + log.Error(err, "unable to run function command", "command", function.Spec.Command) + result = formolv1alpha1.Failure + break + } + } + status.SessionState = result + + if err := r.Status().Update(ctx, backupSession); err != nil { + log.Error(err, "unable to update backupsession status") + return ctrl.Result{}, err + } + case formolv1alpha1.Running: + log.V(0).Info("Running session. Do the backup") + result := formolv1alpha1.Finalize + status.StartTime = &metav1.Time{Time: time.Now()} + output, err := restic.BackupPaths(backupSession.Name, target.Paths) + if err != nil { + log.Error(err, "unable to backup deployment", "output", string(output)) + result = formolv1alpha1.Failure + } else { + snapshotId := restic.GetBackupResults(output) + status.SnapshotId = snapshotId + status.Duration = &metav1.Duration{Duration: time.Now().Sub(status.StartTime.Time)} + } + status.SessionState = result + log.V(1).Info("current backupSession status", "status", backupSession.Status) + if err := r.Status().Update(ctx, backupSession); err != nil { + log.Error(err, "unable to update backupsession status") + return ctrl.Result{}, err + } + case formolv1alpha1.Finalize: + log.V(0).Info("Start to run the backup finalizing steps if any") + result := formolv1alpha1.Success + for _, step := range target.Steps { + if step.Finalize != nil && *step.Finalize == false { + function := &formolv1alpha1.Function{} + if err := r.Get(ctx, client.ObjectKey{ + Name: step.Name, + Namespace: backupConf.Namespace, + }, function); err != nil { + log.Error(err, "unable to get function", "function", step.Name) + return ctrl.Result{}, err + } + if err := formolcliutils.RunChroot(function.Spec.Command[0], function.Spec.Command[1:]...); err != nil { + log.Error(err, "unable to run function command", "command", function.Spec.Command) + result = formolv1alpha1.Failure + break + } + } + } + status.SessionState = result + + if err := r.Status().Update(ctx, backupSession); err != nil { + log.Error(err, "unable to update backupsession status") + return ctrl.Result{}, err + } + + case formolv1alpha1.Success, formolv1alpha1.Failure: + log.V(0).Info("Backup is over") } } } diff --git a/pkg/controllers/restoresession_controller.go b/pkg/controllers/restoresession_controller.go new file mode 100644 index 0000000..8c7836f --- /dev/null +++ b/pkg/controllers/restoresession_controller.go @@ -0,0 +1,57 @@ +package controllers + +import ( + "context" + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/predicate" +) + +type RestoreSessionReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + log := r.Log.WithValues("backupsession", req.NamespacedName) + + restoreSession := &formolv1alpha1.RestoreSession{} + if err := r.Get(ctx, req.NamespacedName, restoreSession); err != nil { + log.Error(err, "unable to get restoresession") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + backupSession := &formolv1alpha1.BackupSession{} + if err := r.Get(ctx, client.ObjectKey{ + Namespace: restoreSession.Namespace, + Name: restoreSession.Spec.Ref, + }, backupSession); err != nil { + log.Error(err, "unable to get backupsession") + return ctrl.Result{}, err + } + backupConf := &formolv1alpha1.BackupConfiguration{} + if err := r.Get(ctx, client.ObjectKey{ + Namespace: backupSession.Namespace, + Name: backupSession.Spec.Ref, + }, backupConf); err != nil { + log.Error(err, "unable to get backupConfiguration") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + return ctrl.Result{}, nil +} + +func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&formolv1alpha1.RestoreSession{}). + WithEventFilter(predicate.Funcs{ + CreateFunc: func(e event.CreateEvent) bool { return false }, + DeleteFunc: func(e event.DeleteEvent) bool { return false }, + }). + Complete(r) +} diff --git a/pkg/restore/root.go b/pkg/restore/root.go index fa27b0c..0fcc696 100644 --- a/pkg/restore/root.go +++ b/pkg/restore/root.go @@ -26,10 +26,10 @@ func init() { func RestoreVolume(snapshotId string) error { log := logger.WithName("restore-volume") - if err := session.RestoreSessionUpdateTargetStatus(formolv1alpha1.Running); err != nil { + if err := session.RestoreSessionUpdateTargetStatus(formolv1alpha1.Init); err != nil { return err } - state := formolv1alpha1.Success + state := formolv1alpha1.Finalize output, err := restic.RestorePaths(snapshotId) if err != nil { log.Error(err, "unable to restore volume", "output", string(output)) diff --git a/pkg/server/root.go b/pkg/server/root.go index 1635e2f..e181019 100644 --- a/pkg/server/root.go +++ b/pkg/server/root.go @@ -1,7 +1,7 @@ package server import ( - "flag" + // "flag" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "github.com/desmo999r/formolcli/pkg/controllers" "k8s.io/apimachinery/pkg/runtime" @@ -22,30 +22,31 @@ func init() { } func Server() { - var metricsAddr string - var enableLeaderElection bool - flag.StringVar(&metricsAddr, "metrics-addr", ":8082", "The address the metric endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - flag.Parse() + // var metricsAddr string + // var enableLeaderElection bool + // flag.StringVar(&metricsAddr, "metrics-addr", ":8082", "The address the metric endpoint binds to.") + // flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, + // "Enable leader election for controller manager. "+ + // "Enabling this will ensure there is only one active controller manager.") + // flag.Parse() ctrl.SetLogger(zap.New(zap.UseDevMode(true))) config, err := ctrl.GetConfig() mgr, err := ctrl.NewManager(config, ctrl.Options{ - Scheme: scheme, - MetricsBindAddress: metricsAddr, - Port: 9443, - LeaderElection: enableLeaderElection, - LeaderElectionID: "12345.desmojim.fr", - Namespace: os.Getenv("POD_NAMESPACE"), + Scheme: scheme, + // MetricsBindAddress: metricsAddr, + // Port: 9443, + // LeaderElection: enableLeaderElection, + // LeaderElectionID: "12345.desmojim.fr", + Namespace: os.Getenv("POD_NAMESPACE"), }) if err != nil { setupLog.Error(err, "unable to create manager") os.Exit(1) } + // BackupSession controller if err = (&controllers.BackupSessionReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("BackupSession"), @@ -55,6 +56,16 @@ func Server() { os.Exit(1) } + // RestoreSession controller + if err = (&controllers.RestoreSessionReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("RestoreSession"), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "RestoreSession") + os.Exit(1) + } + setupLog.Info("starting manager") if err = mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running the manager") diff --git a/pkg/session/root.go b/pkg/session/root.go index 7be9e15..d567fde 100644 --- a/pkg/session/root.go +++ b/pkg/session/root.go @@ -61,12 +61,11 @@ func BackupSessionUpdateTargetStatus(state formolv1alpha1.SessionState, snapshot log.Error(err, "unable to get backupsession", "BACKUPSESSION_NAME", os.Getenv("BACKUPSESSION_NAME"), "BACKUPSESSION_NAMESPACE", os.Getenv("BACKUPSESSION_NAMESPACE")) return err } - for i, target := range backupSession.Status.Targets { - if target.Name == targetName { - backupSession.Status.Targets[i].SessionState = state - backupSession.Status.Targets[i].SnapshotId = snapshotId - backupSession.Status.Targets[i].Duration = &metav1.Duration{Duration: time.Now().Sub(backupSession.Status.Targets[i].StartTime.Time)} - } + target := &(backupSession.Status.Targets[len(backupSession.Status.Targets)-1]) + if target.Name == targetName { + target.SessionState = state + target.SnapshotId = snapshotId + target.Duration = &metav1.Duration{Duration: time.Now().Sub(target.StartTime.Time)} } if err := cl.Status().Update(context.Background(), backupSession); err != nil { @@ -128,9 +127,7 @@ func CreateBackupSession(name string, namespace string) { Namespace: namespace, }, Spec: formolv1alpha1.BackupSessionSpec{ - Ref: formolv1alpha1.Ref{ - Name: name, - }, + Ref: name, }, } log.V(1).Info("create backupsession", "backupSession", backupSession) diff --git a/pkg/utils/root.go b/pkg/utils/root.go index d7b730e..dd60417 100644 --- a/pkg/utils/root.go +++ b/pkg/utils/root.go @@ -19,9 +19,7 @@ func init() { logger = zapr.NewLogger(zapLog) } -func runHook(hooks []formolv1alpha1.Hook, label string) error { - log := logger.WithName(label) - log.V(0).Info("Run commands") +func RunHooks(hooks []formolv1alpha1.Hook) error { for _, hook := range hooks { err := RunChroot(hook.Cmd, hook.Args...) if err != nil { @@ -78,11 +76,3 @@ func RunChroot(runCmd string, args ...string) error { } return nil } - -func RunBeforeBackup(target formolv1alpha1.Target) error { - return runHook(target.BeforeBackup, "runBeforeBackup") -} - -func RunAfterBackup(target formolv1alpha1.Target) error { - return runHook(target.AfterBackup, "runAfterBackup") -}