From 3486ad2efe7bcf40990212299a87aaa70f88965f Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Mon, 20 Mar 2023 22:13:44 +0100 Subject: [PATCH] prepared BackupSession and RestoreSession common code --- api/v1alpha1/restoresession_types.go | 17 ++- api/v1alpha1/zz_generated.deepcopy.go | 33 +++++- .../backupconfiguration_controller_helpers.go | 2 +- controllers/backupsession_controller.go | 16 +-- .../backupsession_controller_helpers.go | 104 ------------------ controllers/restoresession_controller.go | 57 +++++++--- .../restoresession_controller_helper.go | 9 ++ controllers/session.go | 100 +++++++++++++++++ main.go | 12 +- 9 files changed, 205 insertions(+), 145 deletions(-) delete mode 100644 controllers/backupsession_controller_helpers.go create mode 100644 controllers/restoresession_controller_helper.go create mode 100644 controllers/session.go diff --git a/api/v1alpha1/restoresession_types.go b/api/v1alpha1/restoresession_types.go index 462bd3c..289dacb 100644 --- a/api/v1alpha1/restoresession_types.go +++ b/api/v1alpha1/restoresession_types.go @@ -20,22 +20,21 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +type BackupSessionRef struct { + Spec BackupSessionSpec `json:"spec"` + Status BackupSessionStatus `json:"status"` +} // RestoreSessionSpec defines the desired state of RestoreSession type RestoreSessionSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // Foo is an example field of RestoreSession. Edit restoresession_types.go to remove/update - Foo string `json:"foo,omitempty"` + BackupSessionRef `json:"backupSessionRef"` } // RestoreSessionStatus defines the observed state of RestoreSession type RestoreSessionStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file + SessionState `json:"state,omitempty"` + StartTime *metav1.Time `json:"startTime,omitempty"` + Targets []TargetStatus `json:"targets,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4f7eaf8..a4b093e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -216,6 +216,23 @@ func (in *BackupSessionList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupSessionRef) DeepCopyInto(out *BackupSessionRef) { + *out = *in + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSessionRef. +func (in *BackupSessionRef) DeepCopy() *BackupSessionRef { + if in == nil { + return nil + } + out := new(BackupSessionRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *BackupSessionSpec) DeepCopyInto(out *BackupSessionSpec) { *out = *in @@ -458,8 +475,8 @@ func (in *RestoreSession) DeepCopyInto(out *RestoreSession) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSession. @@ -515,6 +532,7 @@ func (in *RestoreSessionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RestoreSessionSpec) DeepCopyInto(out *RestoreSessionSpec) { *out = *in + in.BackupSessionRef.DeepCopyInto(&out.BackupSessionRef) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSessionSpec. @@ -530,6 +548,17 @@ func (in *RestoreSessionSpec) DeepCopy() *RestoreSessionSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RestoreSessionStatus) DeepCopyInto(out *RestoreSessionStatus) { *out = *in + if in.StartTime != nil { + in, out := &in.StartTime, &out.StartTime + *out = (*in).DeepCopy() + } + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]TargetStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSessionStatus. diff --git a/controllers/backupconfiguration_controller_helpers.go b/controllers/backupconfiguration_controller_helpers.go index a6828ad..c9dcb96 100644 --- a/controllers/backupconfiguration_controller_helpers.go +++ b/controllers/backupconfiguration_controller_helpers.go @@ -256,7 +256,7 @@ func (r *BackupConfigurationReconciler) addSidecar(backupConf formolv1alpha1.Bac sidecar := corev1.Container{ Name: formolv1alpha1.SIDECARCONTAINER_NAME, Image: backupConf.Spec.Image, - Args: []string{"backupsession", "server"}, + Args: []string{"server"}, Env: []corev1.EnvVar{ corev1.EnvVar{ Name: formolv1alpha1.TARGET_NAME, diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index e86b662..6bd3210 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -20,10 +20,8 @@ import ( "context" "time" - "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -39,10 +37,7 @@ const ( // BackupSessionReconciler reconciles a BackupSession object type BackupSessionReconciler struct { - client.Client - Scheme *runtime.Scheme - Log logr.Logger - context.Context + Session } //+kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions,verbs=get;list;watch;create;update;patch;delete @@ -100,22 +95,23 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques RequeueAfter: 30 * time.Second, }, nil } - newSessionState = r.initBackup(&backupSession, backupConf) + backupSession.Status.Targets = r.initSession(backupConf) + newSessionState = formolv1alpha1.Initializing case formolv1alpha1.Initializing: // Wait for all the Targets to be in the Initialized state then move them to Running and move to Running myself. // if one of the Target fails to initialize, move it back to New state and decrement Try. // if try reaches 0, move all the Targets to Finalize and move myself to Failure. - newSessionState = r.checkInitialized(&backupSession, backupConf) + newSessionState = r.checkInitialized(backupSession.Status.Targets, backupConf) case formolv1alpha1.Running: // Wait for all the target to be in Waiting state then move them to the Finalize state. Move myself to Finalize. // if one of the Target fails the backup, move it back to Running state and decrement Try. // if try reaches 0, move all the Targets to Finalize and move myself to Failure. - newSessionState = r.checkWaiting(&backupSession, backupConf) + newSessionState = r.checkWaiting(backupSession.Status.Targets, backupConf) case formolv1alpha1.Finalize: // Check the TargetStatus of all the Targets. If they are all Success then move myself to Success. // if one of the Target fails to Finalize, move it back to Finalize state and decrement Try. // if try reaches 0, move myself to Success because the backup was a Success even if the Finalize failed. - if newSessionState = r.checkSuccess(&backupSession, backupConf); newSessionState == formolv1alpha1.Failure { + if newSessionState = r.checkSuccess(backupSession.Status.Targets, backupConf); newSessionState == formolv1alpha1.Failure { r.Log.V(0).Info("One of the target did not manage to Finalize but the backup is still a Success") newSessionState = formolv1alpha1.Success } diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go deleted file mode 100644 index 0ef20db..0000000 --- a/controllers/backupsession_controller_helpers.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -Copyright 2023. - -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 controllers - -import ( - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "sigs.k8s.io/controller-runtime/pkg/client" - "time" -) - -func (r *BackupSessionReconciler) isBackupOngoing(backupConf formolv1alpha1.BackupConfiguration) bool { - backupSessionList := &formolv1alpha1.BackupSessionList{} - if err := r.List(r.Context, backupSessionList, - client.InNamespace(backupConf.Namespace), client.MatchingFieldsSelector{ - Selector: fields.SelectorFromSet(fields.Set{ - sessionState: "Running", - }), - }); err != nil { - r.Log.Error(err, "unable to get backupsessionlist") - return true - } - return len(backupSessionList.Items) > 0 -} - -func (r *BackupSessionReconciler) initBackup(backupSession *formolv1alpha1.BackupSession, backupConf formolv1alpha1.BackupConfiguration) formolv1alpha1.SessionState { - for _, target := range backupConf.Spec.Targets { - r.Log.V(0).Info("Creating new target", "target", target.TargetName) - backupSession.Status.Targets = append(backupSession.Status.Targets, formolv1alpha1.TargetStatus{ - BackupType: target.BackupType, - TargetName: target.TargetName, - TargetKind: target.TargetKind, - SessionState: "", - StartTime: &metav1.Time{Time: time.Now()}, - Try: 1, - }) - } - return formolv1alpha1.Initializing -} - -func (r *BackupSessionReconciler) checkSessionState( - backupSession *formolv1alpha1.BackupSession, - backupConf formolv1alpha1.BackupConfiguration, - currentState formolv1alpha1.SessionState, - waitState formolv1alpha1.SessionState, - nextState formolv1alpha1.SessionState) formolv1alpha1.SessionState { - for i, targetStatus := range backupSession.Status.Targets { - r.Log.V(0).Info("Target status", "target", targetStatus.TargetName, "session state", targetStatus.SessionState) - switch targetStatus.SessionState { - case currentState: - r.Log.V(0).Info("Move target to waitState", "target", targetStatus.TargetName, "waitState", waitState) - backupSession.Status.Targets[i].SessionState = waitState - return waitState - case formolv1alpha1.Failure: - if targetStatus.Try < backupConf.Spec.Targets[i].Retry { - r.Log.V(0).Info("Target failed. Try one more time", "target", targetStatus.TargetName, "waitState", waitState) - backupSession.Status.Targets[i].SessionState = waitState - backupSession.Status.Targets[i].Try++ - backupSession.Status.Targets[i].StartTime = &metav1.Time{Time: time.Now()} - return waitState - } else { - r.Log.V(0).Info("Target failed for the last time", "target", targetStatus.TargetName) - return formolv1alpha1.Failure - } - case waitState: - // target is still busy with its current state. Wait until it is done. - r.Log.V(0).Info("Waiting for one target to finish", "waitState", waitState) - return "" - default: - if i == len(backupSession.Status.Targets)-1 { - r.Log.V(0).Info("Moving to next state", "nextState", nextState) - return nextState - } - } - } - return "" -} - -func (r *BackupSessionReconciler) checkInitialized(backupSession *formolv1alpha1.BackupSession, backupConf formolv1alpha1.BackupConfiguration) formolv1alpha1.SessionState { - return r.checkSessionState(backupSession, backupConf, "", formolv1alpha1.Initializing, formolv1alpha1.Running) -} - -func (r *BackupSessionReconciler) checkWaiting(backupSession *formolv1alpha1.BackupSession, backupConf formolv1alpha1.BackupConfiguration) formolv1alpha1.SessionState { - return r.checkSessionState(backupSession, backupConf, formolv1alpha1.Initialized, formolv1alpha1.Running, formolv1alpha1.Finalize) -} - -func (r *BackupSessionReconciler) checkSuccess(backupSession *formolv1alpha1.BackupSession, backupConf formolv1alpha1.BackupConfiguration) formolv1alpha1.SessionState { - return r.checkSessionState(backupSession, backupConf, formolv1alpha1.Waiting, formolv1alpha1.Finalize, formolv1alpha1.Success) -} diff --git a/controllers/restoresession_controller.go b/controllers/restoresession_controller.go index b9d8da1..fdf9b0b 100644 --- a/controllers/restoresession_controller.go +++ b/controllers/restoresession_controller.go @@ -18,8 +18,10 @@ package controllers import ( "context" + "time" - "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -29,28 +31,53 @@ import ( // RestoreSessionReconciler reconciles a RestoreSession object type RestoreSessionReconciler struct { - client.Client - Scheme *runtime.Scheme + Session } //+kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions/status,verbs=get;update;patch //+kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions/finalizers,verbs=update -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the RestoreSession object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) - - // TODO(user): your logic here + r.Log = log.FromContext(ctx) + r.Context = ctx + restoreSession := formolv1alpha1.RestoreSession{} + err := r.Get(r.Context, req.NamespacedName, &restoreSession) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + backupSession := formolv1alpha1.BackupSession{ + Spec: restoreSession.Spec.BackupSessionRef.Spec, + Status: restoreSession.Spec.BackupSessionRef.Status, + } + backupConf := formolv1alpha1.BackupConfiguration{} + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: backupSession.Spec.Ref.Namespace, + Name: backupSession.Spec.Ref.Name, + }, &restoreSession); err != nil { + r.Log.Error(err, "unable to get BackupConfiguration") + return ctrl.Result{}, err + } + var newSessionState formolv1alpha1.SessionState + switch restoreSession.Status.SessionState { + case formolv1alpha1.New: + newSessionState = r.initRestore(&restoreSession, backupConf) + case "": + newSessionState = formolv1alpha1.New + restoreSession.Status.StartTime = &metav1.Time{Time: time.Now()} + } + if newSessionState != "" { + r.Log.V(0).Info("Restore session needs a status update", "newSessionState", newSessionState, "RestoreSession", restoreSession) + restoreSession.Status.SessionState = newSessionState + if err := r.Status().Update(r.Context, &restoreSession); err != nil { + r.Log.Error(err, "unable to update RestoreSession.Status") + return ctrl.Result{}, err + } + } return ctrl.Result{}, nil } diff --git a/controllers/restoresession_controller_helper.go b/controllers/restoresession_controller_helper.go new file mode 100644 index 0000000..7013fb7 --- /dev/null +++ b/controllers/restoresession_controller_helper.go @@ -0,0 +1,9 @@ +package controllers + +import ( + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" +) + +func (r *RestoreSessionReconciler) initRestore(restoreSession *formolv1alpha1.RestoreSession, backupConf formolv1alpha1.BackupConfiguration) formolv1alpha1.SessionState { + return formolv1alpha1.Running +} diff --git a/controllers/session.go b/controllers/session.go new file mode 100644 index 0000000..afb5cbc --- /dev/null +++ b/controllers/session.go @@ -0,0 +1,100 @@ +package controllers + +import ( + "context" + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "time" +) + +type Session struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + context.Context + Namespace string +} + +func (s Session) isBackupOngoing(backupConf formolv1alpha1.BackupConfiguration) bool { + backupSessionList := &formolv1alpha1.BackupSessionList{} + if err := s.List(s.Context, backupSessionList, + client.InNamespace(backupConf.Namespace), client.MatchingFieldsSelector{ + Selector: fields.SelectorFromSet(fields.Set{ + sessionState: "Running", + }), + }); err != nil { + s.Log.Error(err, "unable to get backupsessionlist") + return true + } + return len(backupSessionList.Items) > 0 +} + +func (s Session) initSession(backupConf formolv1alpha1.BackupConfiguration) []formolv1alpha1.TargetStatus { + tss := []formolv1alpha1.TargetStatus{} + for _, target := range backupConf.Spec.Targets { + s.Log.V(0).Info("Creating new target", "target", target.TargetName) + tss = append(tss, formolv1alpha1.TargetStatus{ + BackupType: target.BackupType, + TargetName: target.TargetName, + TargetKind: target.TargetKind, + SessionState: "", + StartTime: &metav1.Time{Time: time.Now()}, + Try: 1, + }) + } + return tss +} + +func (s Session) checkSessionState( + tss []formolv1alpha1.TargetStatus, + backupConf formolv1alpha1.BackupConfiguration, + currentState formolv1alpha1.SessionState, + waitState formolv1alpha1.SessionState, + nextState formolv1alpha1.SessionState) formolv1alpha1.SessionState { + for i, targetStatus := range tss { + s.Log.V(0).Info("Target status", "target", targetStatus.TargetName, "session state", targetStatus.SessionState) + switch targetStatus.SessionState { + case currentState: + s.Log.V(0).Info("Move target to waitState", "target", targetStatus.TargetName, "waitState", waitState) + tss[i].SessionState = waitState + return waitState + case formolv1alpha1.Failure: + if targetStatus.Try < backupConf.Spec.Targets[i].Retry { + s.Log.V(0).Info("Target failed. Try one more time", "target", targetStatus.TargetName, "waitState", waitState) + tss[i].SessionState = waitState + tss[i].Try++ + tss[i].StartTime = &metav1.Time{Time: time.Now()} + return waitState + } else { + s.Log.V(0).Info("Target failed for the last time", "target", targetStatus.TargetName) + return formolv1alpha1.Failure + } + case waitState: + // target is still busy with its current state. Wait until it is done. + s.Log.V(0).Info("Waiting for one target to finish", "waitState", waitState) + return "" + default: + if i == len(tss)-1 { + s.Log.V(0).Info("Moving to next state", "nextState", nextState) + return nextState + } + } + } + return "" +} + +func (s Session) checkInitialized(tss []formolv1alpha1.TargetStatus, backupConf formolv1alpha1.BackupConfiguration) formolv1alpha1.SessionState { + return s.checkSessionState(tss, backupConf, "", formolv1alpha1.Initializing, formolv1alpha1.Running) +} + +func (s Session) checkWaiting(tss []formolv1alpha1.TargetStatus, backupConf formolv1alpha1.BackupConfiguration) formolv1alpha1.SessionState { + return s.checkSessionState(tss, backupConf, formolv1alpha1.Initialized, formolv1alpha1.Running, formolv1alpha1.Finalize) +} + +func (s Session) checkSuccess(tss []formolv1alpha1.TargetStatus, backupConf formolv1alpha1.BackupConfiguration) formolv1alpha1.SessionState { + return s.checkSessionState(tss, backupConf, formolv1alpha1.Waiting, formolv1alpha1.Finalize, formolv1alpha1.Success) +} diff --git a/main.go b/main.go index 0ae8782..6d7c5f7 100644 --- a/main.go +++ b/main.go @@ -97,15 +97,19 @@ func main() { os.Exit(1) } if err = (&controllers.BackupSessionReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Session: controllers.Session{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "BackupSession") os.Exit(1) } if err = (&controllers.RestoreSessionReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Session: controllers.Session{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "RestoreSession") os.Exit(1)