prepared BackupSession and RestoreSession common code

This commit is contained in:
Jean-Marc ANDRE 2023-03-20 22:13:44 +01:00
parent 19d74cda40
commit 3486ad2efe
9 changed files with 205 additions and 145 deletions

View File

@ -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

View File

@ -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.

View File

@ -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,

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

100
controllers/session.go Normal file
View File

@ -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)
}

12
main.go
View File

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