Refactored since reconciler are not reentrant
This commit is contained in:
parent
393465300a
commit
2901f9aa1a
3
Makefile
3
Makefile
@ -2,7 +2,8 @@
|
|||||||
# Image URL to use all building/pushing image targets
|
# Image URL to use all building/pushing image targets
|
||||||
IMG ?= desmo999r/formolcontroller:latest
|
IMG ?= desmo999r/formolcontroller:latest
|
||||||
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
|
||||||
CRD_OPTIONS ?= "crd:trivialVersions=true"
|
#CRD_OPTIONS ?= "crd:trivialVersions=true"
|
||||||
|
CRD_OPTIONS ?= "crd:trivialVersions=true,crdVersions=v1"
|
||||||
|
|
||||||
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||||
ifeq (,$(shell go env GOBIN))
|
ifeq (,$(shell go env GOBIN))
|
||||||
|
|||||||
@ -21,10 +21,18 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SidecarKind string = "Sidecar"
|
||||||
|
JobKind string = "Job"
|
||||||
|
BackupVolumes string = "Volumes"
|
||||||
|
)
|
||||||
|
|
||||||
type Step struct {
|
type Step struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Namespace string `json:"namespace"`
|
// +optional
|
||||||
Env []corev1.EnvVar `json:"env"`
|
Env []corev1.EnvVar `json:"env,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Finalize *bool `json:"finalize,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
@ -34,14 +42,10 @@ type Hook struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Target struct {
|
type Target struct {
|
||||||
// +kubebuilder:validation:Enum=Deployment;Task
|
// +kubebuilder:validation:Enum=Sidecar;Job
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
// +optional
|
// +optional
|
||||||
BeforeBackup []Hook `json:"beforeBackup,omitempty"`
|
|
||||||
// +optional
|
|
||||||
AfterBackup []Hook `json:"afterBackup,omitempty"`
|
|
||||||
// +optional
|
|
||||||
ApiVersion string `json:"apiVersion,omitempty"`
|
ApiVersion string `json:"apiVersion,omitempty"`
|
||||||
// +optional
|
// +optional
|
||||||
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
|
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
|
||||||
@ -50,6 +54,8 @@ type Target struct {
|
|||||||
// +optional
|
// +optional
|
||||||
// +kubebuilder:validation:MinItems=1
|
// +kubebuilder:validation:MinItems=1
|
||||||
Steps []Step `json:"steps,omitempty"`
|
Steps []Step `json:"steps,omitempty"`
|
||||||
|
// +kubebuilder:default:=2
|
||||||
|
Retry int `json:"retry,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Keep struct {
|
type Keep struct {
|
||||||
|
|||||||
@ -23,17 +23,9 @@ import (
|
|||||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
// 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.
|
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
|
||||||
|
|
||||||
type Ref struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackupSessionSpec defines the desired state of BackupSession
|
// BackupSessionSpec defines the desired state of BackupSession
|
||||||
type BackupSessionSpec struct {
|
type BackupSessionSpec struct {
|
||||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
Ref string `json:"ref"`
|
||||||
// Important: Run "make" to regenerate code after modifying this file
|
|
||||||
|
|
||||||
// Foo is an example field of BackupSession. Edit BackupSession_types.go to remove/update
|
|
||||||
Ref `json:"ref"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackupSessionStatus defines the observed state of BackupSession
|
// BackupSessionStatus defines the observed state of BackupSession
|
||||||
@ -41,8 +33,6 @@ type BackupSessionStatus struct {
|
|||||||
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
||||||
// Important: Run "make" to regenerate code after modifying this file
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
// +optional
|
// +optional
|
||||||
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
|
|
||||||
// +optional
|
|
||||||
SessionState `json:"state,omitempty"`
|
SessionState `json:"state,omitempty"`
|
||||||
// +optional
|
// +optional
|
||||||
StartTime *metav1.Time `json:"startTime,omitempty"`
|
StartTime *metav1.Time `json:"startTime,omitempty"`
|
||||||
|
|||||||
@ -8,13 +8,23 @@ type SessionState string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
New SessionState = "New"
|
New SessionState = "New"
|
||||||
|
Init SessionState = "Initializing"
|
||||||
Running SessionState = "Running"
|
Running SessionState = "Running"
|
||||||
|
Finalize SessionState = "Finalizing"
|
||||||
Success SessionState = "Success"
|
Success SessionState = "Success"
|
||||||
Failure SessionState = "Failure"
|
Failure SessionState = "Failure"
|
||||||
Deleted SessionState = "Deleted"
|
Deleted SessionState = "Deleted"
|
||||||
|
// Environment variables used by the sidecar container
|
||||||
|
// the name of the sidecar container
|
||||||
|
SIDECARCONTAINER_NAME string = "formol"
|
||||||
|
// Used by both the backupsession and restoresession controllers to identified the target deployment
|
||||||
TARGET_NAME string = "TARGET_NAME"
|
TARGET_NAME string = "TARGET_NAME"
|
||||||
|
// Used by restoresession controller
|
||||||
RESTORESESSION_NAMESPACE string = "RESTORESESSION_NAMESPACE"
|
RESTORESESSION_NAMESPACE string = "RESTORESESSION_NAMESPACE"
|
||||||
RESTORESESSION_NAME string = "RESTORESESSION_NAME"
|
RESTORESESSION_NAME string = "RESTORESESSION_NAME"
|
||||||
|
// Used by the backupsession controller
|
||||||
|
POD_NAME string = "POD_NAME"
|
||||||
|
POD_NAMESPACE string = "POD_NAMESPACE"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TargetStatus struct {
|
type TargetStatus struct {
|
||||||
@ -28,4 +38,6 @@ type TargetStatus struct {
|
|||||||
StartTime *metav1.Time `json:"startTime,omitempty"`
|
StartTime *metav1.Time `json:"startTime,omitempty"`
|
||||||
// +optional
|
// +optional
|
||||||
Duration *metav1.Duration `json:"duration,omitempty"`
|
Duration *metav1.Duration `json:"duration,omitempty"`
|
||||||
|
// +optional
|
||||||
|
Try int `json:"try,omitemmpty"`
|
||||||
}
|
}
|
||||||
|
|||||||
76
api/v1alpha1/function_webhook.go
Normal file
76
api/v1alpha1/function_webhook.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
|
||||||
|
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 v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/webhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
// log is for logging in this package.
|
||||||
|
var functionlog = logf.Log.WithName("function-resource")
|
||||||
|
|
||||||
|
func (r *Function) SetupWebhookWithManager(mgr ctrl.Manager) error {
|
||||||
|
return ctrl.NewWebhookManagedBy(mgr).
|
||||||
|
For(r).
|
||||||
|
Complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||||
|
|
||||||
|
// +kubebuilder:webhook:path=/mutate-formol-desmojim-fr-v1alpha1-function,mutating=true,failurePolicy=fail,groups=formol.desmojim.fr,resources=functions,verbs=create;update,versions=v1alpha1,name=mfunction.kb.io
|
||||||
|
|
||||||
|
var _ webhook.Defaulter = &Function{}
|
||||||
|
|
||||||
|
// Default implements webhook.Defaulter so a webhook will be registered for the type
|
||||||
|
func (r *Function) Default() {
|
||||||
|
functionlog.Info("default", "name", r.Name)
|
||||||
|
|
||||||
|
// TODO(user): fill in your defaulting logic.
|
||||||
|
r.Spec.Name = r.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
|
||||||
|
// +kubebuilder:webhook:verbs=create;update,path=/validate-formol-desmojim-fr-v1alpha1-function,mutating=false,failurePolicy=fail,groups=formol.desmojim.fr,resources=functions,versions=v1alpha1,name=vfunction.kb.io
|
||||||
|
|
||||||
|
var _ webhook.Validator = &Function{}
|
||||||
|
|
||||||
|
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
|
||||||
|
func (r *Function) ValidateCreate() error {
|
||||||
|
functionlog.Info("validate create", "name", r.Name)
|
||||||
|
|
||||||
|
// TODO(user): fill in your validation logic upon object creation.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
|
||||||
|
func (r *Function) ValidateUpdate(old runtime.Object) error {
|
||||||
|
functionlog.Info("validate update", "name", r.Name)
|
||||||
|
|
||||||
|
// TODO(user): fill in your validation logic upon object update.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
|
||||||
|
func (r *Function) ValidateDelete() error {
|
||||||
|
functionlog.Info("validate delete", "name", r.Name)
|
||||||
|
|
||||||
|
// TODO(user): fill in your validation logic upon object deletion.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ package v1alpha1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
//"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
|
||||||
@ -28,7 +29,7 @@ type RestoreSessionSpec struct {
|
|||||||
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
|
||||||
// Important: Run "make" to regenerate code after modifying this file
|
// Important: Run "make" to regenerate code after modifying this file
|
||||||
|
|
||||||
BackupSessionRef metav1.ObjectMeta `json:"backupSessionRef"`
|
Ref string `json:"backupSessionRef"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestoreSessionStatus defines the observed state of RestoreSession
|
// RestoreSessionStatus defines the observed state of RestoreSession
|
||||||
|
|||||||
@ -210,7 +210,6 @@ func (in *BackupSessionList) DeepCopyObject() runtime.Object {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *BackupSessionSpec) DeepCopyInto(out *BackupSessionSpec) {
|
func (in *BackupSessionSpec) DeepCopyInto(out *BackupSessionSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
out.Ref = in.Ref
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSessionSpec.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSessionSpec.
|
||||||
@ -342,21 +341,6 @@ func (in *Keep) DeepCopy() *Keep {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *Ref) DeepCopyInto(out *Ref) {
|
|
||||||
*out = *in
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ref.
|
|
||||||
func (in *Ref) DeepCopy() *Ref {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(Ref)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Repo) DeepCopyInto(out *Repo) {
|
func (in *Repo) DeepCopyInto(out *Repo) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@ -452,7 +436,7 @@ func (in *RestoreSession) DeepCopyInto(out *RestoreSession) {
|
|||||||
*out = *in
|
*out = *in
|
||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
in.Spec.DeepCopyInto(&out.Spec)
|
out.Spec = in.Spec
|
||||||
in.Status.DeepCopyInto(&out.Status)
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,7 +493,6 @@ func (in *RestoreSessionList) DeepCopyObject() runtime.Object {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *RestoreSessionSpec) DeepCopyInto(out *RestoreSessionSpec) {
|
func (in *RestoreSessionSpec) DeepCopyInto(out *RestoreSessionSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
in.BackupSessionRef.DeepCopyInto(&out.BackupSessionRef)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSessionSpec.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSessionSpec.
|
||||||
@ -573,6 +556,11 @@ func (in *Step) DeepCopyInto(out *Step) {
|
|||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if in.Finalize != nil {
|
||||||
|
in, out := &in.Finalize, &out.Finalize
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Step.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Step.
|
||||||
@ -588,20 +576,6 @@ func (in *Step) DeepCopy() *Step {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Target) DeepCopyInto(out *Target) {
|
func (in *Target) DeepCopyInto(out *Target) {
|
||||||
*out = *in
|
*out = *in
|
||||||
if in.BeforeBackup != nil {
|
|
||||||
in, out := &in.BeforeBackup, &out.BeforeBackup
|
|
||||||
*out = make([]Hook, len(*in))
|
|
||||||
for i := range *in {
|
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if in.AfterBackup != nil {
|
|
||||||
in, out := &in.AfterBackup, &out.AfterBackup
|
|
||||||
*out = make([]Hook, len(*in))
|
|
||||||
for i := range *in {
|
|
||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if in.VolumeMounts != nil {
|
if in.VolumeMounts != nil {
|
||||||
in, out := &in.VolumeMounts, &out.VolumeMounts
|
in, out := &in.VolumeMounts, &out.VolumeMounts
|
||||||
*out = make([]v1.VolumeMount, len(*in))
|
*out = make([]v1.VolumeMount, len(*in))
|
||||||
|
|||||||
@ -14,17 +14,17 @@ patchesStrategicMerge:
|
|||||||
# patches here are for enabling the conversion webhook for each CRD
|
# patches here are for enabling the conversion webhook for each CRD
|
||||||
#- patches/webhook_in_tasks.yaml
|
#- patches/webhook_in_tasks.yaml
|
||||||
#- patches/webhook_in_functions.yaml
|
#- patches/webhook_in_functions.yaml
|
||||||
#- patches/webhook_in_backupconfigurations.yaml
|
- patches/webhook_in_backupconfigurations.yaml
|
||||||
- patches/webhook_in_backupsessions.yaml
|
#- patches/webhook_in_backupsessions.yaml
|
||||||
#- patches/webhook_in_repoes.yaml
|
#- patches/webhook_in_repoes.yaml
|
||||||
#- patches/webhook_in_restoresessions.yaml
|
#- patches/webhook_in_restoresessions.yaml
|
||||||
# +kubebuilder:scaffold:crdkustomizewebhookpatch
|
# +kubebuilder:scaffold:crdkustomizewebhookpatch
|
||||||
|
|
||||||
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
|
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
|
||||||
# patches here are for enabling the CA injection for each CRD
|
# patches here are for enabling the CA injection for each CRD
|
||||||
- patches/cainjection_in_functions.yaml
|
#- patches/cainjection_in_functions.yaml
|
||||||
- patches/cainjection_in_backupconfigurations.yaml
|
#- patches/cainjection_in_backupconfigurations.yaml
|
||||||
- patches/cainjection_in_backupsessions.yaml
|
#- patches/cainjection_in_backupsessions.yaml
|
||||||
#- patches/cainjection_in_repoes.yaml
|
#- patches/cainjection_in_repoes.yaml
|
||||||
#- patches/cainjection_in_restoresessions.yaml
|
#- patches/cainjection_in_restoresessions.yaml
|
||||||
# +kubebuilder:scaffold:crdkustomizecainjectionpatch
|
# +kubebuilder:scaffold:crdkustomizecainjectionpatch
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# The following patch adds a directive for certmanager to inject CA into the CRD
|
# The following patch adds a directive for certmanager to inject CA into the CRD
|
||||||
# CRD conversion requires k8s 1.13 or later.
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# The following patch adds a directive for certmanager to inject CA into the CRD
|
# The following patch adds a directive for certmanager to inject CA into the CRD
|
||||||
# CRD conversion requires k8s 1.13 or later.
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# The following patch adds a directive for certmanager to inject CA into the CRD
|
# The following patch adds a directive for certmanager to inject CA into the CRD
|
||||||
# CRD conversion requires k8s 1.13 or later.
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# The following patch adds a directive for certmanager to inject CA into the CRD
|
# The following patch adds a directive for certmanager to inject CA into the CRD
|
||||||
# CRD conversion requires k8s 1.13 or later.
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
# The following patch enables conversion webhook for CRD
|
# The following patch enables conversion webhook for CRD
|
||||||
# CRD conversion requires k8s 1.13 or later.
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: backupconfigurations.formol.desmojim.fr
|
name: backupconfigurations.formol.desmojim.fr
|
||||||
spec:
|
spec:
|
||||||
|
preserveUnknownFields: false
|
||||||
conversion:
|
conversion:
|
||||||
strategy: Webhook
|
strategy: Webhook
|
||||||
webhookClientConfig:
|
webhook:
|
||||||
|
conversionReviewVersions: ["v1", "v1beta1", "v1alpha1"]
|
||||||
|
clientConfig:
|
||||||
# this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
|
# this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
|
||||||
# but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
|
# but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
|
||||||
caBundle: Cg==
|
caBundle: Cg==
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# The following patch enables conversion webhook for CRD
|
# The following patch enables conversion webhook for CRD
|
||||||
# CRD conversion requires k8s 1.13 or later.
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: backupsessions.formol.desmojim.fr
|
name: backupsessions.formol.desmojim.fr
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# The following patch enables conversion webhook for CRD
|
# The following patch enables conversion webhook for CRD
|
||||||
# CRD conversion requires k8s 1.13 or later.
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: functions.formol.desmojim.fr
|
name: functions.formol.desmojim.fr
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
# The following patch enables conversion webhook for CRD
|
# The following patch enables conversion webhook for CRD
|
||||||
# CRD conversion requires k8s 1.13 or later.
|
# CRD conversion requires k8s 1.13 or later.
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: restoresessions.formol.desmojim.fr.desmojim.fr
|
name: restoresessions.formol.desmojim.fr.desmojim.fr
|
||||||
|
|||||||
@ -18,7 +18,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
//"time"
|
||||||
|
|
||||||
formolrbac "github.com/desmo999r/formol/pkg/rbac"
|
formolrbac "github.com/desmo999r/formol/pkg/rbac"
|
||||||
formolutils "github.com/desmo999r/formol/pkg/utils"
|
formolutils "github.com/desmo999r/formol/pkg/utils"
|
||||||
@ -33,7 +33,7 @@ import (
|
|||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
//"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||||
|
|
||||||
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
|
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
|
||||||
)
|
)
|
||||||
@ -45,16 +45,6 @@ type BackupConfigurationReconciler struct {
|
|||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BackupConfigurationReconciler) getDeployment(namespace string, name string) (*appsv1.Deployment, error) {
|
|
||||||
|
|
||||||
deployment := &appsv1.Deployment{}
|
|
||||||
err := r.Get(context.Background(), client.ObjectKey{
|
|
||||||
Namespace: namespace,
|
|
||||||
Name: name,
|
|
||||||
}, deployment)
|
|
||||||
return deployment, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=*,verbs=*
|
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=*,verbs=*
|
||||||
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
|
||||||
// +kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;create;update;patch;delete
|
||||||
@ -67,183 +57,28 @@ func (r *BackupConfigurationReconciler) getDeployment(namespace string, name str
|
|||||||
// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
|
||||||
// +kubebuilder:rbac:groups=batch,resources=cronjobs/status,verbs=get
|
// +kubebuilder:rbac:groups=batch,resources=cronjobs/status,verbs=get
|
||||||
|
|
||||||
func (r *BackupConfigurationReconciler) deleteSidecarContainer(backupConf *formolv1alpha1.BackupConfiguration, target formolv1alpha1.Target) error {
|
func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||||
deployment, err := r.getDeployment(backupConf.Namespace, target.Name)
|
ctx := context.Background()
|
||||||
if err != nil {
|
log := r.Log.WithValues("backupconfiguration", req.NamespacedName)
|
||||||
return err
|
//time.Sleep(300 * time.Millisecond)
|
||||||
}
|
|
||||||
restorecontainers := []corev1.Container{}
|
log.V(1).Info("Enter Reconcile with req", "req", req, "reconciler", r)
|
||||||
for _, container := range deployment.Spec.Template.Spec.Containers {
|
|
||||||
if container.Name == "backup" {
|
backupConf := &formolv1alpha1.BackupConfiguration{}
|
||||||
continue
|
if err := r.Get(ctx, req.NamespacedName, backupConf); err != nil {
|
||||||
}
|
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||||
restorecontainers = append(restorecontainers, container)
|
|
||||||
}
|
|
||||||
deployment.Spec.Template.Spec.Containers = restorecontainers
|
|
||||||
if err := r.Update(context.Background(), deployment); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := formolrbac.DeleteFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
selector, err := metav1.LabelSelectorAsMap(deployment.Spec.Selector)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
pods := &corev1.PodList{}
|
|
||||||
err = r.List(context.Background(), pods, client.MatchingLabels(selector))
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
replicasToDelete := []appsv1.ReplicaSet{}
|
|
||||||
for _, pod := range pods.Items {
|
|
||||||
for _, podRef := range pod.OwnerReferences {
|
|
||||||
rs := &appsv1.ReplicaSet{}
|
|
||||||
if err := r.Get(context.Background(), client.ObjectKey{
|
|
||||||
Name: podRef.Name,
|
|
||||||
Namespace: pod.Namespace,
|
|
||||||
}, rs); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, rsRef := range rs.OwnerReferences {
|
|
||||||
if rsRef.Kind == deployment.Kind && rsRef.Name == deployment.Name {
|
|
||||||
replicasToDelete = append(replicasToDelete, *rs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, replica := range replicasToDelete {
|
getDeployment := func(namespace string, name string) (*appsv1.Deployment, error) {
|
||||||
if err := r.Delete(context.TODO(), &replica); err != nil {
|
deployment := &appsv1.Deployment{}
|
||||||
return nil
|
err := r.Get(context.Background(), client.ObjectKey{
|
||||||
}
|
Namespace: namespace,
|
||||||
}
|
Name: name,
|
||||||
return nil
|
}, deployment)
|
||||||
|
return deployment, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1alpha1.BackupConfiguration, target formolv1alpha1.Target) error {
|
deleteCronJob := func() error {
|
||||||
log := r.Log.WithValues("backupconf", backupConf.Name)
|
|
||||||
deployment, err := r.getDeployment(backupConf.Namespace, target.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err, "unable to get Deployment")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.V(1).Info("got deployment", "Deployment", deployment)
|
|
||||||
for _, container := range deployment.Spec.Template.Spec.Containers {
|
|
||||||
if container.Name == "backup" {
|
|
||||||
log.V(0).Info("There is already a backup sidecar container. Skipping", "container", container)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sidecar := corev1.Container{
|
|
||||||
Name: "backup",
|
|
||||||
Image: "desmo999r/formolcli:latest",
|
|
||||||
Args: []string{"backupsession", "server"},
|
|
||||||
//Image: "busybox",
|
|
||||||
//Command: []string{
|
|
||||||
// "sh",
|
|
||||||
// "-c",
|
|
||||||
// "sleep 3600; echo done",
|
|
||||||
//},
|
|
||||||
Env: []corev1.EnvVar{
|
|
||||||
corev1.EnvVar{
|
|
||||||
Name: "POD_NAME",
|
|
||||||
ValueFrom: &corev1.EnvVarSource{
|
|
||||||
FieldRef: &corev1.ObjectFieldSelector{
|
|
||||||
FieldPath: "metadata.name",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
corev1.EnvVar{
|
|
||||||
Name: "POD_NAMESPACE",
|
|
||||||
ValueFrom: &corev1.EnvVarSource{
|
|
||||||
FieldRef: &corev1.ObjectFieldSelector{
|
|
||||||
FieldPath: "metadata.namespace",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
corev1.EnvVar{
|
|
||||||
Name: "POD_DEPLOYMENT",
|
|
||||||
Value: target.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
VolumeMounts: []corev1.VolumeMount{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather information from the repo
|
|
||||||
repo := &formolv1alpha1.Repo{}
|
|
||||||
if err := r.Get(context.Background(), client.ObjectKey{
|
|
||||||
Namespace: backupConf.Namespace,
|
|
||||||
Name: backupConf.Spec.Repository,
|
|
||||||
}, repo); err != nil {
|
|
||||||
log.Error(err, "unable to get Repo from BackupConfiguration")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sidecar.Env = append(sidecar.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...)
|
|
||||||
|
|
||||||
for _, volumemount := range target.VolumeMounts {
|
|
||||||
log.V(1).Info("mounts", "volumemount", volumemount)
|
|
||||||
volumemount.ReadOnly = true
|
|
||||||
sidecar.VolumeMounts = append(sidecar.VolumeMounts, *volumemount.DeepCopy())
|
|
||||||
}
|
|
||||||
selector, err := metav1.LabelSelectorAsMap(deployment.Spec.Selector)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err, "unable to get LableSelector for deployment", "label", deployment.Spec.Selector)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.V(1).Info("getting pods matching label", "label", selector)
|
|
||||||
pods := &corev1.PodList{}
|
|
||||||
err = r.List(context.Background(), pods, client.InNamespace(backupConf.Namespace), client.MatchingLabels(selector))
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err, "unable to get deployment pods")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
replicasToDelete := []appsv1.ReplicaSet{}
|
|
||||||
log.V(1).Info("got that list of pods", "pods", len(pods.Items))
|
|
||||||
for _, pod := range pods.Items {
|
|
||||||
log.V(1).Info("checking pod", "pod", pod)
|
|
||||||
for _, podRef := range pod.OwnerReferences {
|
|
||||||
rs := &appsv1.ReplicaSet{}
|
|
||||||
if err := r.Get(context.Background(), client.ObjectKey{
|
|
||||||
Name: podRef.Name,
|
|
||||||
Namespace: pod.Namespace,
|
|
||||||
}, rs); err != nil {
|
|
||||||
log.Error(err, "unable to get replicaset", "replicaset", podRef.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.V(1).Info("got a replicaset", "rs", rs.Name)
|
|
||||||
for _, rsRef := range rs.OwnerReferences {
|
|
||||||
if rsRef.Kind == deployment.Kind && rsRef.Name == deployment.Name {
|
|
||||||
log.V(0).Info("Adding pod to the list of pods to be restarted", "pod", pod.Name)
|
|
||||||
replicasToDelete = append(replicasToDelete, *rs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, sidecar)
|
|
||||||
deployment.Spec.Template.Spec.ShareProcessNamespace = func() *bool { b := true; return &b }()
|
|
||||||
|
|
||||||
if err := formolrbac.CreateFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
|
|
||||||
log.Error(err, "unable to create backupsessionlistener RBAC")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.V(0).Info("Adding a sicar container")
|
|
||||||
if err := r.Update(context.Background(), deployment); err != nil {
|
|
||||||
log.Error(err, "unable to update the Deployment")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, replica := range replicasToDelete {
|
|
||||||
if err := r.Delete(context.TODO(), &replica); err != nil {
|
|
||||||
log.Error(err, "unable to delete replica", "replica", replica.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *BackupConfigurationReconciler) deleteCronJob(backupConf *formolv1alpha1.BackupConfiguration) error {
|
|
||||||
log := r.Log.WithValues("deleteCronJob", backupConf.Name)
|
|
||||||
_ = formolrbac.DeleteFormolRBAC(r.Client, "default", backupConf.Namespace)
|
_ = formolrbac.DeleteFormolRBAC(r.Client, "default", backupConf.Namespace)
|
||||||
_ = formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace)
|
_ = formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace)
|
||||||
cronjob := &kbatch_beta1.CronJob{}
|
cronjob := &kbatch_beta1.CronJob{}
|
||||||
@ -257,9 +92,8 @@ func (r *BackupConfigurationReconciler) deleteCronJob(backupConf *formolv1alpha1
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.BackupConfiguration) error {
|
|
||||||
log := r.Log.WithValues("addCronJob", backupConf.Name)
|
|
||||||
|
|
||||||
|
addCronJob := func() error {
|
||||||
if err := formolrbac.CreateFormolRBAC(r.Client, "default", backupConf.Namespace); err != nil {
|
if err := formolrbac.CreateFormolRBAC(r.Client, "default", backupConf.Namespace); err != nil {
|
||||||
log.Error(err, "unable to create backupsessionlistener RBAC")
|
log.Error(err, "unable to create backupsessionlistener RBAC")
|
||||||
return nil
|
return nil
|
||||||
@ -345,32 +179,127 @@ func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.Ba
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
deleteSidecarContainer := func(target formolv1alpha1.Target) error {
|
||||||
ctx := context.Background()
|
deployment, err := getDeployment(backupConf.Namespace, target.Name)
|
||||||
log := r.Log.WithValues("backupconfiguration", req.NamespacedName)
|
if err != nil {
|
||||||
time.Sleep(300 * time.Millisecond)
|
return err
|
||||||
|
}
|
||||||
|
restorecontainers := []corev1.Container{}
|
||||||
|
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||||||
|
if container.Name == formolv1alpha1.SIDECARCONTAINER_NAME {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
restorecontainers = append(restorecontainers, container)
|
||||||
|
}
|
||||||
|
deployment.Spec.Template.Spec.Containers = restorecontainers
|
||||||
|
if err := r.Update(context.Background(), deployment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := formolrbac.DeleteFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
log.V(1).Info("Enter Reconcile with req", "req", req)
|
addSidecarContainer := func(target formolv1alpha1.Target) error {
|
||||||
|
deployment, err := getDeployment(backupConf.Namespace, target.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "unable to get Deployment")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.V(1).Info("got deployment", "Deployment", deployment)
|
||||||
|
for _, container := range deployment.Spec.Template.Spec.Containers {
|
||||||
|
if container.Name == formolv1alpha1.SIDECARCONTAINER_NAME {
|
||||||
|
log.V(0).Info("There is already a backup sidecar container. Skipping", "container", container)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sidecar := corev1.Container{
|
||||||
|
Name: formolv1alpha1.SIDECARCONTAINER_NAME,
|
||||||
|
// TODO: Put the image in the BackupConfiguration YAML file
|
||||||
|
Image: "desmo999r/formolcli:latest",
|
||||||
|
Args: []string{"backupsession", "server"},
|
||||||
|
//Image: "busybox",
|
||||||
|
//Command: []string{
|
||||||
|
// "sh",
|
||||||
|
// "-c",
|
||||||
|
// "sleep 3600; echo done",
|
||||||
|
//},
|
||||||
|
Env: []corev1.EnvVar{
|
||||||
|
corev1.EnvVar{
|
||||||
|
Name: formolv1alpha1.POD_NAME,
|
||||||
|
ValueFrom: &corev1.EnvVarSource{
|
||||||
|
FieldRef: &corev1.ObjectFieldSelector{
|
||||||
|
FieldPath: "metadata.name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
corev1.EnvVar{
|
||||||
|
Name: formolv1alpha1.POD_NAMESPACE,
|
||||||
|
ValueFrom: &corev1.EnvVarSource{
|
||||||
|
FieldRef: &corev1.ObjectFieldSelector{
|
||||||
|
FieldPath: "metadata.namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
corev1.EnvVar{
|
||||||
|
Name: formolv1alpha1.TARGET_NAME,
|
||||||
|
Value: target.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
VolumeMounts: []corev1.VolumeMount{},
|
||||||
|
}
|
||||||
|
|
||||||
backupConf := &formolv1alpha1.BackupConfiguration{}
|
// Gather information from the repo
|
||||||
if err := r.Get(ctx, req.NamespacedName, backupConf); err != nil {
|
repo := &formolv1alpha1.Repo{}
|
||||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
if err := r.Get(context.Background(), client.ObjectKey{
|
||||||
|
Namespace: backupConf.Namespace,
|
||||||
|
Name: backupConf.Spec.Repository,
|
||||||
|
}, repo); err != nil {
|
||||||
|
log.Error(err, "unable to get Repo from BackupConfiguration")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sidecar.Env = append(sidecar.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...)
|
||||||
|
|
||||||
|
for _, volumemount := range target.VolumeMounts {
|
||||||
|
log.V(1).Info("mounts", "volumemount", volumemount)
|
||||||
|
volumemount.ReadOnly = true
|
||||||
|
sidecar.VolumeMounts = append(sidecar.VolumeMounts, *volumemount.DeepCopy())
|
||||||
|
}
|
||||||
|
deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, sidecar)
|
||||||
|
deployment.Spec.Template.Spec.ShareProcessNamespace = func() *bool { b := true; return &b }()
|
||||||
|
|
||||||
|
if err := formolrbac.CreateFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
|
||||||
|
log.Error(err, "unable to create backupsessionlistener RBAC")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.V(0).Info("Adding a sicar container")
|
||||||
|
if err := r.Update(context.Background(), deployment); err != nil {
|
||||||
|
log.Error(err, "unable to update the Deployment")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteExternalResources := func() error {
|
||||||
|
for _, target := range backupConf.Spec.Targets {
|
||||||
|
switch target.Kind {
|
||||||
|
case formolv1alpha1.SidecarKind:
|
||||||
|
_ = deleteSidecarContainer(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: remove the hardcoded "default"
|
||||||
|
_ = deleteCronJob()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
finalizerName := "finalizer.backupconfiguration.formol.desmojim.fr"
|
finalizerName := "finalizer.backupconfiguration.formol.desmojim.fr"
|
||||||
|
|
||||||
if backupConf.ObjectMeta.DeletionTimestamp.IsZero() {
|
if !backupConf.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||||
if !formolutils.ContainsString(backupConf.ObjectMeta.Finalizers, finalizerName) {
|
|
||||||
backupConf.ObjectMeta.Finalizers = append(backupConf.ObjectMeta.Finalizers, finalizerName)
|
|
||||||
if err := r.Update(context.Background(), backupConf); err != nil {
|
|
||||||
log.Error(err, "unable to append finalizer")
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.V(0).Info("backupconf being deleted", "backupconf", backupConf.Name)
|
log.V(0).Info("backupconf being deleted", "backupconf", backupConf.Name)
|
||||||
if formolutils.ContainsString(backupConf.ObjectMeta.Finalizers, finalizerName) {
|
if formolutils.ContainsString(backupConf.ObjectMeta.Finalizers, finalizerName) {
|
||||||
_ = r.deleteExternalResources(backupConf)
|
_ = deleteExternalResources()
|
||||||
}
|
}
|
||||||
backupConf.ObjectMeta.Finalizers = formolutils.RemoveString(backupConf.ObjectMeta.Finalizers, finalizerName)
|
backupConf.ObjectMeta.Finalizers = formolutils.RemoveString(backupConf.ObjectMeta.Finalizers, finalizerName)
|
||||||
if err := r.Update(context.Background(), backupConf); err != nil {
|
if err := r.Update(context.Background(), backupConf); err != nil {
|
||||||
@ -382,21 +311,27 @@ func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := r.addCronJob(backupConf); err != nil {
|
if !formolutils.ContainsString(backupConf.ObjectMeta.Finalizers, finalizerName) {
|
||||||
|
backupConf.ObjectMeta.Finalizers = append(backupConf.ObjectMeta.Finalizers, finalizerName)
|
||||||
|
err := r.Update(context.Background(), backupConf)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err, "unable to append finalizer")
|
||||||
|
}
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := addCronJob(); err != nil {
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
backupConf.Status.ActiveCronJob = true
|
backupConf.Status.ActiveCronJob = true
|
||||||
|
|
||||||
for _, target := range backupConf.Spec.Targets {
|
for _, target := range backupConf.Spec.Targets {
|
||||||
switch target.Kind {
|
switch target.Kind {
|
||||||
case "Deployment":
|
case formolv1alpha1.SidecarKind:
|
||||||
if err := r.addSidecarContainer(backupConf, target); err != nil {
|
if err := addSidecarContainer(target); err != nil {
|
||||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||||
}
|
}
|
||||||
backupConf.Status.ActiveSidecar = true
|
backupConf.Status.ActiveSidecar = true
|
||||||
case "PersistentVolumeClaim":
|
|
||||||
log.V(0).Info("TODO backup PVC")
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,23 +345,11 @@ func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BackupConfigurationReconciler) deleteExternalResources(backupConf *formolv1alpha1.BackupConfiguration) error {
|
|
||||||
for _, target := range backupConf.Spec.Targets {
|
|
||||||
switch target.Kind {
|
|
||||||
case "Deployment":
|
|
||||||
_ = r.deleteSidecarContainer(backupConf, target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: remove the hardcoded "default"
|
|
||||||
_ = r.deleteCronJob(backupConf)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *BackupConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *BackupConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
For(&formolv1alpha1.BackupConfiguration{}).
|
For(&formolv1alpha1.BackupConfiguration{}).
|
||||||
WithOptions(controller.Options{MaxConcurrentReconciles: 3}).
|
WithOptions(controller.Options{MaxConcurrentReconciles: 3}).
|
||||||
WithEventFilter(predicate.GenerationChangedPredicate{}). // Don't reconcile when status gets updated
|
//WithEventFilter(predicate.GenerationChangedPredicate{}). // Don't reconcile when status gets updated
|
||||||
//Owns(&formolv1alpha1.BackupSession{}).
|
//Owns(&formolv1alpha1.BackupSession{}).
|
||||||
Owns(&kbatch_beta1.CronJob{}).
|
Owns(&kbatch_beta1.CronJob{}).
|
||||||
Complete(r)
|
Complete(r)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import (
|
|||||||
//"k8s.io/apimachinery/pkg/types"
|
//"k8s.io/apimachinery/pkg/types"
|
||||||
//"reflect"
|
//"reflect"
|
||||||
//"fmt"
|
//"fmt"
|
||||||
//"time"
|
"time"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@ -21,11 +21,11 @@ import (
|
|||||||
|
|
||||||
var _ = Describe("Testing BackupConf controller", func() {
|
var _ = Describe("Testing BackupConf controller", func() {
|
||||||
const (
|
const (
|
||||||
BackupConfName = "test-backupconf"
|
BCBackupConfName = "test-backupconf-controller"
|
||||||
)
|
)
|
||||||
var (
|
var (
|
||||||
key = types.NamespacedName{
|
key = types.NamespacedName{
|
||||||
Name: BackupConfName,
|
Name: BCBackupConfName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
}
|
}
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
@ -35,24 +35,32 @@ var _ = Describe("Testing BackupConf controller", func() {
|
|||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
backupConf = &formolv1alpha1.BackupConfiguration{
|
backupConf = &formolv1alpha1.BackupConfiguration{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: BackupConfName,
|
Name: BCBackupConfName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
},
|
},
|
||||||
Spec: formolv1alpha1.BackupConfigurationSpec{
|
Spec: formolv1alpha1.BackupConfigurationSpec{
|
||||||
Repository: RepoName,
|
Repository: TestRepoName,
|
||||||
Schedule: "1 * * * *",
|
Schedule: "1 * * * *",
|
||||||
Targets: []formolv1alpha1.Target{
|
Targets: []formolv1alpha1.Target{
|
||||||
formolv1alpha1.Target{
|
formolv1alpha1.Target{
|
||||||
Kind: "Deployment",
|
Kind: formolv1alpha1.SidecarKind,
|
||||||
Name: DeploymentName,
|
Name: TestDeploymentName,
|
||||||
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
|
corev1.VolumeMount{
|
||||||
|
Name: TestDataVolume,
|
||||||
|
MountPath: TestDataMountPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Paths: []string{
|
||||||
|
TestDataMountPath,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
formolv1alpha1.Target{
|
formolv1alpha1.Target{
|
||||||
Kind: "Task",
|
Kind: formolv1alpha1.JobKind,
|
||||||
Name: BackupFuncName,
|
Name: TestBackupFuncName,
|
||||||
Steps: []formolv1alpha1.Step{
|
Steps: []formolv1alpha1.Step{
|
||||||
formolv1alpha1.Step{
|
formolv1alpha1.Step{
|
||||||
Name: BackupFuncName,
|
Name: TestBackupFuncName,
|
||||||
Namespace: TestNamespace,
|
|
||||||
Env: []corev1.EnvVar{
|
Env: []corev1.EnvVar{
|
||||||
corev1.EnvVar{
|
corev1.EnvVar{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
@ -66,7 +74,7 @@ var _ = Describe("Testing BackupConf controller", func() {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Context("There is a backupconf", func() {
|
Context("Creating a backupconf", func() {
|
||||||
JustBeforeEach(func() {
|
JustBeforeEach(func() {
|
||||||
Eventually(func() error {
|
Eventually(func() error {
|
||||||
return k8sClient.Create(ctx, backupConf)
|
return k8sClient.Create(ctx, backupConf)
|
||||||
@ -85,18 +93,16 @@ var _ = Describe("Testing BackupConf controller", func() {
|
|||||||
return true
|
return true
|
||||||
}, timeout, interval).Should(BeTrue())
|
}, timeout, interval).Should(BeTrue())
|
||||||
Expect(realBackupConf.Spec.Schedule).Should(Equal("1 * * * *"))
|
Expect(realBackupConf.Spec.Schedule).Should(Equal("1 * * * *"))
|
||||||
|
Expect(realBackupConf.Spec.Targets[0].Retry).Should(Equal(2))
|
||||||
})
|
})
|
||||||
It("Should also create a CronJob", func() {
|
It("Should also create a CronJob", func() {
|
||||||
cronJob := &batchv1beta1.CronJob{}
|
cronJob := &batchv1beta1.CronJob{}
|
||||||
Eventually(func() bool {
|
Eventually(func() bool {
|
||||||
err := k8sClient.Get(ctx, types.NamespacedName{
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
||||||
Name: "backup-" + BackupConfName,
|
Name: "backup-" + BCBackupConfName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
}, cronJob)
|
}, cronJob)
|
||||||
if err != nil {
|
return err == nil
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}, timeout, interval).Should(BeTrue())
|
}, timeout, interval).Should(BeTrue())
|
||||||
Expect(cronJob.Spec.Schedule).Should(Equal("1 * * * *"))
|
Expect(cronJob.Spec.Schedule).Should(Equal("1 * * * *"))
|
||||||
})
|
})
|
||||||
@ -104,7 +110,7 @@ var _ = Describe("Testing BackupConf controller", func() {
|
|||||||
realDeployment := &appsv1.Deployment{}
|
realDeployment := &appsv1.Deployment{}
|
||||||
Eventually(func() (int, error) {
|
Eventually(func() (int, error) {
|
||||||
err := k8sClient.Get(ctx, types.NamespacedName{
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
||||||
Name: DeploymentName,
|
Name: TestDeploymentName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
}, realDeployment)
|
}, realDeployment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,6 +121,7 @@ var _ = Describe("Testing BackupConf controller", func() {
|
|||||||
})
|
})
|
||||||
It("Should also update the CronJob", func() {
|
It("Should also update the CronJob", func() {
|
||||||
realBackupConf := &formolv1alpha1.BackupConfiguration{}
|
realBackupConf := &formolv1alpha1.BackupConfiguration{}
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
Eventually(func() bool {
|
Eventually(func() bool {
|
||||||
err := k8sClient.Get(ctx, key, realBackupConf)
|
err := k8sClient.Get(ctx, key, realBackupConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -129,7 +136,7 @@ var _ = Describe("Testing BackupConf controller", func() {
|
|||||||
cronJob := &batchv1beta1.CronJob{}
|
cronJob := &batchv1beta1.CronJob{}
|
||||||
Eventually(func() (string, error) {
|
Eventually(func() (string, error) {
|
||||||
err := k8sClient.Get(ctx, types.NamespacedName{
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
||||||
Name: "backup-" + BackupConfName,
|
Name: "backup-" + BCBackupConfName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
}, cronJob)
|
}, cronJob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -139,7 +146,7 @@ var _ = Describe("Testing BackupConf controller", func() {
|
|||||||
}, timeout, interval).Should(Equal("1 0 * * *"))
|
}, timeout, interval).Should(Equal("1 0 * * *"))
|
||||||
Eventually(func() (bool, error) {
|
Eventually(func() (bool, error) {
|
||||||
err := k8sClient.Get(ctx, types.NamespacedName{
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
||||||
Name: "backup-" + BackupConfName,
|
Name: "backup-" + BCBackupConfName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
}, cronJob)
|
}, cronJob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -160,7 +167,7 @@ var _ = Describe("Testing BackupConf controller", func() {
|
|||||||
realDeployment := &appsv1.Deployment{}
|
realDeployment := &appsv1.Deployment{}
|
||||||
Eventually(func() (int, error) {
|
Eventually(func() (int, error) {
|
||||||
err := k8sClient.Get(ctx, types.NamespacedName{
|
err := k8sClient.Get(ctx, types.NamespacedName{
|
||||||
Name: DeploymentName,
|
Name: TestDeploymentName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
}, realDeployment)
|
}, realDeployment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -48,36 +48,199 @@ type BackupSessionReconciler struct {
|
|||||||
client.Client
|
client.Client
|
||||||
Log logr.Logger
|
Log logr.Logger
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
BackupSession *formolv1alpha1.BackupSession
|
|
||||||
BackupConf *formolv1alpha1.BackupConfiguration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BackupSessionReconciler) StatusUpdate() error {
|
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions,verbs=get;list;watch;create;update;patch;delete
|
||||||
log := r.Log.WithValues("backupsession-statusupdate", r.BackupSession)
|
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions/status,verbs=get;update;patch;create;delete
|
||||||
|
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=functions,verbs=get;list;watch
|
||||||
|
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;create;update;patch;delete;watch
|
||||||
|
|
||||||
|
func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||||
|
log := r.Log.WithValues("backupsession", req.NamespacedName)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
r.BackupConf = &formolv1alpha1.BackupConfiguration{}
|
|
||||||
|
backupSession := &formolv1alpha1.BackupSession{}
|
||||||
|
if err := r.Get(ctx, req.NamespacedName, backupSession); err != nil {
|
||||||
|
log.Error(err, "unable to get backupsession")
|
||||||
|
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||||
|
}
|
||||||
|
backupConf := &formolv1alpha1.BackupConfiguration{}
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
if err := r.Get(ctx, client.ObjectKey{
|
||||||
Namespace: r.BackupSession.Namespace,
|
Namespace: backupSession.Namespace,
|
||||||
Name: r.BackupSession.Spec.Ref.Name}, r.BackupConf); err != nil {
|
Name: backupSession.Spec.Ref,
|
||||||
|
}, backupConf); err != nil {
|
||||||
log.Error(err, "unable to get backupConfiguration")
|
log.Error(err, "unable to get backupConfiguration")
|
||||||
return client.IgnoreNotFound(err)
|
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper functions
|
||||||
|
// is there a backup operation ongoing
|
||||||
|
isBackupOngoing := func() bool {
|
||||||
|
backupSessionList := &formolv1alpha1.BackupSessionList{}
|
||||||
|
if err := r.List(ctx, backupSessionList, client.InNamespace(backupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: "Running"})}); err != nil {
|
||||||
|
log.Error(err, "unable to get backupsessionlist")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return len(backupSessionList.Items) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete session specific backup resources
|
||||||
|
deleteExternalResources := func() error {
|
||||||
|
log := r.Log.WithValues("deleteExternalResources", backupSession.Name)
|
||||||
|
// Gather information from the repo
|
||||||
|
repo := &formolv1alpha1.Repo{}
|
||||||
|
if err := r.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: backupConf.Namespace,
|
||||||
|
Name: backupConf.Spec.Repository,
|
||||||
|
}, repo); err != nil {
|
||||||
|
log.Error(err, "unable to get Repo from BackupConfiguration")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env := formolutils.ConfigureResticEnvVar(backupConf, repo)
|
||||||
|
// container that will delete the restic snapshot(s) matching the backupsession
|
||||||
|
deleteSnapshots := []corev1.Container{}
|
||||||
|
for _, target := range backupSession.Status.Targets {
|
||||||
|
if target.SessionState == formolv1alpha1.Success {
|
||||||
|
deleteSnapshots = append(deleteSnapshots, corev1.Container{
|
||||||
|
Name: target.Name,
|
||||||
|
Image: "desmo999r/formolcli:latest",
|
||||||
|
Args: []string{"snapshot", "delete", "--snapshot-id", target.SnapshotId},
|
||||||
|
Env: env,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// create a job to delete the restic snapshot(s) with the backupsession name tag
|
||||||
|
if len(deleteSnapshots) > 0 {
|
||||||
|
jobTtl := JOBTTL
|
||||||
|
job := &batchv1.Job{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: fmt.Sprintf("delete-%s-", backupSession.Name),
|
||||||
|
Namespace: backupSession.Namespace,
|
||||||
|
},
|
||||||
|
Spec: batchv1.JobSpec{
|
||||||
|
TTLSecondsAfterFinished: &jobTtl,
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
InitContainers: []corev1.Container{},
|
||||||
|
Containers: deleteSnapshots,
|
||||||
|
RestartPolicy: corev1.RestartPolicyOnFailure,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
log.V(0).Info("creating a job to delete restic snapshots")
|
||||||
|
if err := r.Create(ctx, job); err != nil {
|
||||||
|
log.Error(err, "unable to delete job", "job", job)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a backup job
|
||||||
|
createBackupJob := func(target formolv1alpha1.Target) error {
|
||||||
|
log := r.Log.WithValues("createbackupjob", target.Name)
|
||||||
|
ctx := context.Background()
|
||||||
|
backupSessionEnv := []corev1.EnvVar{
|
||||||
|
corev1.EnvVar{
|
||||||
|
Name: "TARGET_NAME",
|
||||||
|
Value: target.Name,
|
||||||
|
},
|
||||||
|
corev1.EnvVar{
|
||||||
|
Name: "BACKUPSESSION_NAME",
|
||||||
|
Value: backupSession.Name,
|
||||||
|
},
|
||||||
|
corev1.EnvVar{
|
||||||
|
Name: "BACKUPSESSION_NAMESPACE",
|
||||||
|
Value: backupSession.Namespace,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
output := corev1.VolumeMount{
|
||||||
|
Name: "output",
|
||||||
|
MountPath: "/output",
|
||||||
|
}
|
||||||
|
restic := corev1.Container{
|
||||||
|
Name: "restic",
|
||||||
|
Image: "desmo999r/formolcli:latest",
|
||||||
|
Args: []string{"volume", "backup", "--tag", backupSession.Name, "--path", "/output"},
|
||||||
|
VolumeMounts: []corev1.VolumeMount{output},
|
||||||
|
Env: backupSessionEnv,
|
||||||
|
}
|
||||||
|
log.V(1).Info("creating a tagged backup job", "container", restic)
|
||||||
|
// Gather information from the repo
|
||||||
|
repo := &formolv1alpha1.Repo{}
|
||||||
|
if err := r.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: backupConf.Namespace,
|
||||||
|
Name: backupConf.Spec.Repository,
|
||||||
|
}, repo); err != nil {
|
||||||
|
log.Error(err, "unable to get Repo from BackupConfiguration")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// S3 backing storage
|
||||||
|
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...)
|
||||||
|
jobTtl := JOBTTL
|
||||||
|
job := &batchv1.Job{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: fmt.Sprintf("%s-%s-", backupSession.Name, target.Name),
|
||||||
|
Namespace: backupConf.Namespace,
|
||||||
|
},
|
||||||
|
Spec: batchv1.JobSpec{
|
||||||
|
TTLSecondsAfterFinished: &jobTtl,
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
InitContainers: []corev1.Container{},
|
||||||
|
Containers: []corev1.Container{restic},
|
||||||
|
Volumes: []corev1.Volume{
|
||||||
|
corev1.Volume{Name: "output"},
|
||||||
|
},
|
||||||
|
RestartPolicy: corev1.RestartPolicyOnFailure,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, step := range target.Steps {
|
||||||
|
function := &formolv1alpha1.Function{}
|
||||||
|
if err := r.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: backupConf.Namespace,
|
||||||
|
Name: step.Name,
|
||||||
|
}, function); err != nil {
|
||||||
|
log.Error(err, "unable to get function", "Function", step)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
function.Spec.Name = function.Name
|
||||||
|
function.Spec.Env = append(step.Env, backupSessionEnv...)
|
||||||
|
function.Spec.VolumeMounts = append(function.Spec.VolumeMounts, output)
|
||||||
|
job.Spec.Template.Spec.InitContainers = append(job.Spec.Template.Spec.InitContainers, function.Spec)
|
||||||
|
}
|
||||||
|
if err := ctrl.SetControllerReference(backupConf, job, r.Scheme); err != nil {
|
||||||
|
log.Error(err, "unable to set controller on job", "job", job, "backupconf", backupConf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.V(0).Info("creating a backup job", "target", target)
|
||||||
|
if err := r.Create(ctx, job); err != nil {
|
||||||
|
log.Error(err, "unable to create job", "job", job)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// start the next task
|
// start the next task
|
||||||
startNextTask := func() (*formolv1alpha1.TargetStatus, error) {
|
startNextTask := func() (*formolv1alpha1.TargetStatus, error) {
|
||||||
nextTarget := len(r.BackupSession.Status.Targets)
|
nextTarget := len(backupSession.Status.Targets)
|
||||||
if nextTarget < len(r.BackupConf.Spec.Targets) {
|
if nextTarget < len(backupConf.Spec.Targets) {
|
||||||
target := r.BackupConf.Spec.Targets[nextTarget]
|
target := backupConf.Spec.Targets[nextTarget]
|
||||||
targetStatus := formolv1alpha1.TargetStatus{
|
targetStatus := formolv1alpha1.TargetStatus{
|
||||||
Name: target.Name,
|
Name: target.Name,
|
||||||
Kind: target.Kind,
|
Kind: target.Kind,
|
||||||
SessionState: formolv1alpha1.New,
|
SessionState: formolv1alpha1.New,
|
||||||
StartTime: &metav1.Time{Time: time.Now()},
|
StartTime: &metav1.Time{Time: time.Now()},
|
||||||
|
Try: 1,
|
||||||
}
|
}
|
||||||
r.BackupSession.Status.Targets = append(r.BackupSession.Status.Targets, targetStatus)
|
backupSession.Status.Targets = append(backupSession.Status.Targets, targetStatus)
|
||||||
switch target.Kind {
|
switch target.Kind {
|
||||||
case "Task":
|
case formolv1alpha1.JobKind:
|
||||||
if err := r.CreateBackupJob(target); err != nil {
|
if err := createBackupJob(target); err != nil {
|
||||||
log.V(0).Info("unable to create task", "task", target)
|
log.V(0).Info("unable to create task", "task", target)
|
||||||
targetStatus.SessionState = formolv1alpha1.Failure
|
targetStatus.SessionState = formolv1alpha1.Failure
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -88,56 +251,18 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Test the backupsession backupstate to decide what to do
|
|
||||||
switch r.BackupSession.Status.SessionState {
|
// cleanup existing backupsessions
|
||||||
case formolv1alpha1.New:
|
cleanupSessions := func() {
|
||||||
// Brand new backupsession; start the first task
|
|
||||||
r.BackupSession.Status.SessionState = formolv1alpha1.Running
|
|
||||||
targetStatus, err := startNextTask()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.V(0).Info("New backup. Start the first task", "task", targetStatus)
|
|
||||||
if err := r.Status().Update(ctx, r.BackupSession); err != nil {
|
|
||||||
log.Error(err, "unable to update BackupSession status")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case formolv1alpha1.Running:
|
|
||||||
// Backup ongoing. Check the status of the last task to decide what to do
|
|
||||||
currentTargetStatus := r.BackupSession.Status.Targets[len(r.BackupSession.Status.Targets)-1]
|
|
||||||
switch currentTargetStatus.SessionState {
|
|
||||||
case formolv1alpha1.Failure:
|
|
||||||
// The last task failed. We mark the backupsession as failed and we stop here.
|
|
||||||
log.V(0).Info("last backup task failed. Stop here", "targetStatus", currentTargetStatus)
|
|
||||||
r.BackupSession.Status.SessionState = formolv1alpha1.Failure
|
|
||||||
log.V(1).Info("New BackupSession status", "status", r.BackupSession.Status.SessionState)
|
|
||||||
if err := r.Status().Update(ctx, r.BackupSession); err != nil {
|
|
||||||
log.Error(err, "unable to update BackupSession status")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case formolv1alpha1.Running:
|
|
||||||
// The current task is still running. Nothing to do
|
|
||||||
log.V(0).Info("task is still running", "targetStatus", currentTargetStatus)
|
|
||||||
case formolv1alpha1.Success:
|
|
||||||
// The last task successed. Let's try to start the next one
|
|
||||||
log.V(0).Info("last task was a success. start a new one", "currentTargetStatus", currentTargetStatus, "targetStatus", currentTargetStatus)
|
|
||||||
targetStatus, err := startNextTask()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if targetStatus == nil {
|
|
||||||
// No more task to start. The backup is a success
|
|
||||||
r.BackupSession.Status.SessionState = formolv1alpha1.Success
|
|
||||||
log.V(0).Info("Backup is successful. Let's try to do some cleanup")
|
|
||||||
backupSessionList := &formolv1alpha1.BackupSessionList{}
|
backupSessionList := &formolv1alpha1.BackupSessionList{}
|
||||||
if err := r.List(ctx, backupSessionList, client.InNamespace(r.BackupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: "Success"})}); err != nil {
|
if err := r.List(ctx, backupSessionList, client.InNamespace(backupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: string(formolv1alpha1.Success)})}); err != nil {
|
||||||
log.Error(err, "unable to get backupsessionlist")
|
log.Error(err, "unable to get backupsessionlist")
|
||||||
return nil
|
return
|
||||||
}
|
}
|
||||||
if len(backupSessionList.Items) < 2 {
|
if len(backupSessionList.Items) < 2 {
|
||||||
// Not enough backupSession to proceed
|
// Not enough backupSession to proceed
|
||||||
log.V(1).Info("Not enough successful backup jobs")
|
log.V(1).Info("Not enough successful backup jobs")
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(backupSessionList.Items, func(i, j int) bool {
|
sort.Slice(backupSessionList.Items, func(i, j int) bool {
|
||||||
@ -150,13 +275,13 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lastBackups, dailyBackups, weeklyBackups, monthlyBackups, yearlyBackups KeepBackup
|
var lastBackups, dailyBackups, weeklyBackups, monthlyBackups, yearlyBackups KeepBackup
|
||||||
lastBackups.Counter = r.BackupConf.Spec.Keep.Last
|
lastBackups.Counter = backupConf.Spec.Keep.Last
|
||||||
dailyBackups.Counter = r.BackupConf.Spec.Keep.Daily
|
dailyBackups.Counter = backupConf.Spec.Keep.Daily
|
||||||
weeklyBackups.Counter = r.BackupConf.Spec.Keep.Weekly
|
weeklyBackups.Counter = backupConf.Spec.Keep.Weekly
|
||||||
monthlyBackups.Counter = r.BackupConf.Spec.Keep.Monthly
|
monthlyBackups.Counter = backupConf.Spec.Keep.Monthly
|
||||||
yearlyBackups.Counter = r.BackupConf.Spec.Keep.Yearly
|
yearlyBackups.Counter = backupConf.Spec.Keep.Yearly
|
||||||
for _, session := range backupSessionList.Items {
|
for _, session := range backupSessionList.Items {
|
||||||
if session.Spec.Ref.Name != r.BackupConf.Name {
|
if session.Spec.Ref != backupConf.Name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
deleteSession := true
|
deleteSession := true
|
||||||
@ -217,103 +342,111 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.V(1).Info("New BackupSession status", "status", r.BackupSession.Status.SessionState)
|
// end helper functions
|
||||||
if err := r.Status().Update(ctx, r.BackupSession); err != nil {
|
|
||||||
log.Error(err, "unable to update BackupSession status")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case formolv1alpha1.Deleted:
|
|
||||||
for _, target := range r.BackupSession.Status.Targets {
|
|
||||||
if target.SessionState != formolv1alpha1.Deleted {
|
|
||||||
log.V(1).Info("snaphot has not been deleted. won't delete the backupsession", "target", target)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.V(1).Info("all the snapshots have been deleted. deleting the backupsession")
|
|
||||||
controllerutil.RemoveFinalizer(r.BackupSession, finalizerName)
|
|
||||||
if err := r.Update(ctx, r.BackupSession); err != nil {
|
|
||||||
log.Error(err, "unable to remove finalizer")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *BackupSessionReconciler) IsBackupOngoing() bool {
|
log.V(0).Info("backupSession", "backupSession.ObjectMeta", backupSession.ObjectMeta, "backupSession.Status", backupSession.Status)
|
||||||
log := r.Log.WithName("IsBackupOngoing")
|
if backupSession.ObjectMeta.DeletionTimestamp.IsZero() {
|
||||||
ctx := context.Background()
|
switch backupSession.Status.SessionState {
|
||||||
|
case formolv1alpha1.New:
|
||||||
backupSessionList := &formolv1alpha1.BackupSessionList{}
|
|
||||||
if err := r.List(ctx, backupSessionList, client.InNamespace(r.BackupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: "Running"})}); err != nil {
|
|
||||||
log.Error(err, "unable to get backupsessionlist")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if len(backupSessionList.Items) > 0 {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions,verbs=get;list;watch;create;update;patch;delete
|
|
||||||
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions/status,verbs=get;update;patch;create;delete
|
|
||||||
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=functions,verbs=get;list;watch
|
|
||||||
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;create;update;patch;delete;watch
|
|
||||||
|
|
||||||
func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|
||||||
log := r.Log.WithValues("backupsession", req.NamespacedName)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// your logic here
|
|
||||||
//time.Sleep(300 * time.Millisecond)
|
|
||||||
r.BackupSession = &formolv1alpha1.BackupSession{}
|
|
||||||
if err := r.Get(ctx, req.NamespacedName, r.BackupSession); err != nil {
|
|
||||||
log.Error(err, "unable to get backupsession")
|
|
||||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
|
||||||
}
|
|
||||||
log.V(0).Info("backupSession", "backupSession.ObjectMeta", r.BackupSession.ObjectMeta, "backupSession.Status", r.BackupSession.Status)
|
|
||||||
if r.BackupSession.Status.ObservedGeneration == r.BackupSession.ObjectMeta.Generation {
|
|
||||||
// status update
|
|
||||||
log.V(0).Info("status update")
|
|
||||||
return ctrl.Result{}, r.StatusUpdate()
|
|
||||||
}
|
|
||||||
if r.IsBackupOngoing() {
|
|
||||||
// There is already a backup ongoing. We don't do anything and we reschedule
|
|
||||||
log.V(0).Info("there is an ongoing backup. let's reschedule this operation")
|
|
||||||
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
|
|
||||||
} else if r.BackupSession.ObjectMeta.DeletionTimestamp.IsZero() {
|
|
||||||
// Check if the finalizer has been registered
|
// Check if the finalizer has been registered
|
||||||
if !controllerutil.ContainsFinalizer(r.BackupSession, finalizerName) {
|
if !controllerutil.ContainsFinalizer(backupSession, finalizerName) {
|
||||||
controllerutil.AddFinalizer(r.BackupSession, finalizerName)
|
controllerutil.AddFinalizer(backupSession, finalizerName)
|
||||||
// We update the BackupSession to add the finalizer
|
// We update the BackupSession to add the finalizer
|
||||||
// Reconcile will be called again
|
// Reconcile will be called again
|
||||||
// return now
|
// return now
|
||||||
err := r.Update(ctx, r.BackupSession)
|
err := r.Update(ctx, backupSession)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err, "unable to add finalizer")
|
log.Error(err, "unable to add finalizer")
|
||||||
}
|
}
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
// All signals are green
|
// Brand new backupsession
|
||||||
// We start the backup process
|
if isBackupOngoing() {
|
||||||
r.BackupSession.Status.ObservedGeneration = r.BackupSession.ObjectMeta.Generation
|
log.V(0).Info("There is an ongoing backup. Let's reschedule this operation")
|
||||||
r.BackupSession.Status.SessionState = formolv1alpha1.New
|
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
|
||||||
r.BackupSession.Status.StartTime = &metav1.Time{Time: time.Now()}
|
}
|
||||||
if err := r.Status().Update(ctx, r.BackupSession); err != nil {
|
// start the first task
|
||||||
|
backupSession.Status.SessionState = formolv1alpha1.Running
|
||||||
|
targetStatus, err := startNextTask()
|
||||||
|
if err != nil {
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
log.V(0).Info("New backup. Start the first task", "task", targetStatus)
|
||||||
|
if err := r.Status().Update(ctx, backupSession); err != nil {
|
||||||
|
log.Error(err, "unable to update BackupSession status")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
case formolv1alpha1.Running:
|
||||||
|
// Backup ongoing. Check the status of the last task to decide what to do
|
||||||
|
currentTargetStatus := &backupSession.Status.Targets[len(backupSession.Status.Targets)-1]
|
||||||
|
switch currentTargetStatus.SessionState {
|
||||||
|
case formolv1alpha1.Running:
|
||||||
|
// The current task is still running. Nothing to do
|
||||||
|
log.V(0).Info("task is still running", "targetStatus", currentTargetStatus)
|
||||||
|
case formolv1alpha1.Success:
|
||||||
|
// The last task succeed. Let's try to start the next one
|
||||||
|
targetStatus, err := startNextTask()
|
||||||
|
log.V(0).Info("last task was a success. start a new one", "currentTargetStatus", currentTargetStatus, "targetStatus", targetStatus)
|
||||||
|
if err != nil {
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
if targetStatus == nil {
|
||||||
|
// No more task to start. The backup is a success
|
||||||
|
backupSession.Status.SessionState = formolv1alpha1.Success
|
||||||
|
log.V(0).Info("Backup is successful. Let's try to do some cleanup")
|
||||||
|
cleanupSessions()
|
||||||
|
}
|
||||||
|
if err := r.Status().Update(ctx, backupSession); err != nil {
|
||||||
|
log.Error(err, "unable to update BackupSession status")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
case formolv1alpha1.Failure:
|
||||||
|
// last task failed. Try to run it again
|
||||||
|
currentTarget := backupConf.Spec.Targets[len(backupSession.Status.Targets)-1]
|
||||||
|
if currentTargetStatus.Try < currentTarget.Retry {
|
||||||
|
log.V(0).Info("last task was a failure. try again", "currentTargetStatus", currentTargetStatus)
|
||||||
|
currentTargetStatus.Try++
|
||||||
|
currentTargetStatus.SessionState = formolv1alpha1.New
|
||||||
|
currentTargetStatus.StartTime = &metav1.Time{Time: time.Now()}
|
||||||
|
switch currentTarget.Kind {
|
||||||
|
case formolv1alpha1.JobKind:
|
||||||
|
if err := createBackupJob(currentTarget); err != nil {
|
||||||
|
log.V(0).Info("unable to create task", "task", currentTarget)
|
||||||
|
currentTargetStatus.SessionState = formolv1alpha1.Failure
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.V(0).Info("task failed again and for the last time", "currentTargetStatus", currentTargetStatus)
|
||||||
|
backupSession.Status.SessionState = formolv1alpha1.Failure
|
||||||
|
}
|
||||||
|
if err := r.Status().Update(ctx, backupSession); err != nil {
|
||||||
|
log.Error(err, "unable to update BackupSession status")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case formolv1alpha1.Success:
|
||||||
|
// Should never go there
|
||||||
|
case formolv1alpha1.Failure:
|
||||||
|
// The backup failed
|
||||||
|
case "":
|
||||||
|
// BackupSession has just been created
|
||||||
|
backupSession.Status.SessionState = formolv1alpha1.New
|
||||||
|
backupSession.Status.StartTime = &metav1.Time{Time: time.Now()}
|
||||||
|
if err := r.Status().Update(ctx, backupSession); err != nil {
|
||||||
log.Error(err, "unable to update backupSession")
|
log.Error(err, "unable to update backupSession")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.V(0).Info("backupsession being deleted", "backupsession", r.BackupSession.Name)
|
log.V(0).Info("backupsession being deleted", "backupsession", backupSession.Name)
|
||||||
if controllerutil.ContainsFinalizer(r.BackupSession, finalizerName) {
|
if controllerutil.ContainsFinalizer(backupSession, finalizerName) {
|
||||||
if err := r.deleteExternalResources(); err != nil {
|
if err := deleteExternalResources(); err != nil {
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
controllerutil.RemoveFinalizer(r.BackupSession, finalizerName)
|
controllerutil.RemoveFinalizer(backupSession, finalizerName)
|
||||||
if err := r.Update(ctx, r.BackupSession); err != nil {
|
if err := r.Update(ctx, backupSession); err != nil {
|
||||||
log.Error(err, "unable to remove finalizer")
|
log.Error(err, "unable to remove finalizer")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
@ -323,144 +456,6 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
|
|||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BackupSessionReconciler) CreateBackupJob(target formolv1alpha1.Target) error {
|
|
||||||
log := r.Log.WithValues("createbackupjob", target.Name)
|
|
||||||
ctx := context.Background()
|
|
||||||
backupSessionEnv := []corev1.EnvVar{
|
|
||||||
corev1.EnvVar{
|
|
||||||
Name: "TARGET_NAME",
|
|
||||||
Value: target.Name,
|
|
||||||
},
|
|
||||||
corev1.EnvVar{
|
|
||||||
Name: "BACKUPSESSION_NAME",
|
|
||||||
Value: r.BackupSession.Name,
|
|
||||||
},
|
|
||||||
corev1.EnvVar{
|
|
||||||
Name: "BACKUPSESSION_NAMESPACE",
|
|
||||||
Value: r.BackupSession.Namespace,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
output := corev1.VolumeMount{
|
|
||||||
Name: "output",
|
|
||||||
MountPath: "/output",
|
|
||||||
}
|
|
||||||
restic := corev1.Container{
|
|
||||||
Name: "restic",
|
|
||||||
Image: "desmo999r/formolcli:latest",
|
|
||||||
Args: []string{"volume", "backup", "--tag", r.BackupSession.Name, "--path", "/output"},
|
|
||||||
VolumeMounts: []corev1.VolumeMount{output},
|
|
||||||
Env: backupSessionEnv,
|
|
||||||
}
|
|
||||||
log.V(1).Info("creating a tagged backup job", "container", restic)
|
|
||||||
// Gather information from the repo
|
|
||||||
repo := &formolv1alpha1.Repo{}
|
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
|
||||||
Namespace: r.BackupConf.Namespace,
|
|
||||||
Name: r.BackupConf.Spec.Repository,
|
|
||||||
}, repo); err != nil {
|
|
||||||
log.Error(err, "unable to get Repo from BackupConfiguration")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// S3 backing storage
|
|
||||||
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
|
|
||||||
jobTtl := JOBTTL
|
|
||||||
job := &batchv1.Job{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
GenerateName: fmt.Sprintf("%s-%s-", r.BackupSession.Name, target.Name),
|
|
||||||
Namespace: r.BackupConf.Namespace,
|
|
||||||
},
|
|
||||||
Spec: batchv1.JobSpec{
|
|
||||||
TTLSecondsAfterFinished: &jobTtl,
|
|
||||||
Template: corev1.PodTemplateSpec{
|
|
||||||
Spec: corev1.PodSpec{
|
|
||||||
InitContainers: []corev1.Container{},
|
|
||||||
Containers: []corev1.Container{restic},
|
|
||||||
Volumes: []corev1.Volume{
|
|
||||||
corev1.Volume{Name: "output"},
|
|
||||||
},
|
|
||||||
RestartPolicy: corev1.RestartPolicyOnFailure,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, step := range target.Steps {
|
|
||||||
function := &formolv1alpha1.Function{}
|
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
|
||||||
Namespace: step.Namespace,
|
|
||||||
Name: step.Name}, function); err != nil {
|
|
||||||
log.Error(err, "unable to get function", "Function", step)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
function.Spec.Env = append(step.Env, backupSessionEnv...)
|
|
||||||
function.Spec.VolumeMounts = append(function.Spec.VolumeMounts, output)
|
|
||||||
job.Spec.Template.Spec.InitContainers = append(job.Spec.Template.Spec.InitContainers, function.Spec)
|
|
||||||
}
|
|
||||||
if err := ctrl.SetControllerReference(r.BackupConf, job, r.Scheme); err != nil {
|
|
||||||
log.Error(err, "unable to set controller on job", "job", job, "backupconf", r.BackupConf)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.V(0).Info("creating a backup job", "target", target)
|
|
||||||
if err := r.Create(ctx, job); err != nil {
|
|
||||||
log.Error(err, "unable to create job", "job", job)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *BackupSessionReconciler) deleteExternalResources() error {
|
|
||||||
ctx := context.Background()
|
|
||||||
log := r.Log.WithValues("deleteExternalResources", r.BackupSession.Name)
|
|
||||||
// Gather information from the repo
|
|
||||||
repo := &formolv1alpha1.Repo{}
|
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
|
||||||
Namespace: r.BackupConf.Namespace,
|
|
||||||
Name: r.BackupConf.Spec.Repository,
|
|
||||||
}, repo); err != nil {
|
|
||||||
log.Error(err, "unable to get Repo from BackupConfiguration")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
env := formolutils.ConfigureResticEnvVar(r.BackupConf, repo)
|
|
||||||
// container that will delete the restic snapshot(s) matching the backupsession
|
|
||||||
deleteSnapshots := []corev1.Container{}
|
|
||||||
for _, target := range r.BackupSession.Status.Targets {
|
|
||||||
if target.SessionState == formolv1alpha1.Success {
|
|
||||||
deleteSnapshots = append(deleteSnapshots, corev1.Container{
|
|
||||||
Name: target.Name,
|
|
||||||
Image: "desmo999r/formolcli:latest",
|
|
||||||
Args: []string{"snapshot", "delete", "--snapshot-id", target.SnapshotId},
|
|
||||||
Env: env,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// create a job to delete the restic snapshot(s) with the backupsession name tag
|
|
||||||
if len(deleteSnapshots) > 0 {
|
|
||||||
jobTtl := JOBTTL
|
|
||||||
job := &batchv1.Job{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
GenerateName: fmt.Sprintf("delete-%s-", r.BackupSession.Name),
|
|
||||||
Namespace: r.BackupSession.Namespace,
|
|
||||||
},
|
|
||||||
Spec: batchv1.JobSpec{
|
|
||||||
TTLSecondsAfterFinished: &jobTtl,
|
|
||||||
Template: corev1.PodTemplateSpec{
|
|
||||||
Spec: corev1.PodSpec{
|
|
||||||
InitContainers: []corev1.Container{},
|
|
||||||
Containers: deleteSnapshots,
|
|
||||||
RestartPolicy: corev1.RestartPolicyOnFailure,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
log.V(0).Info("creating a job to delete restic snapshots")
|
|
||||||
if err := r.Create(ctx, job); err != nil {
|
|
||||||
log.Error(err, "unable to delete job", "job", job)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &formolv1alpha1.BackupSession{}, sessionState, func(rawObj runtime.Object) []string {
|
if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &formolv1alpha1.BackupSession{}, sessionState, func(rawObj runtime.Object) []string {
|
||||||
session := rawObj.(*formolv1alpha1.BackupSession)
|
session := rawObj.(*formolv1alpha1.BackupSession)
|
||||||
|
|||||||
144
controllers/backupsession_controller_test.go
Normal file
144
controllers/backupsession_controller_test.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
//corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Testing BackupSession controller", func() {
|
||||||
|
const (
|
||||||
|
BSBackupSessionName = "test-backupsession-controller"
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
key = types.NamespacedName{
|
||||||
|
Name: BSBackupSessionName,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
}
|
||||||
|
backupSession = &formolv1alpha1.BackupSession{}
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
backupSession = &formolv1alpha1.BackupSession{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: BSBackupSessionName,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
},
|
||||||
|
Spec: formolv1alpha1.BackupSessionSpec{
|
||||||
|
Ref: TestBackupConfName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Context("Creating a backupsession", func() {
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
Eventually(func() error {
|
||||||
|
return k8sClient.Create(ctx, backupSession)
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
realBackupSession := &formolv1alpha1.BackupSession{}
|
||||||
|
Eventually(func() error {
|
||||||
|
err := k8sClient.Get(ctx, key, realBackupSession)
|
||||||
|
return err
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
Eventually(func() formolv1alpha1.SessionState {
|
||||||
|
if err := k8sClient.Get(ctx, key, realBackupSession); err != nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return realBackupSession.Status.SessionState
|
||||||
|
}
|
||||||
|
}, timeout, interval).Should(Equal(formolv1alpha1.Running))
|
||||||
|
})
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(k8sClient.Delete(ctx, backupSession)).Should(Succeed())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should have a new task", func() {
|
||||||
|
realBackupSession := &formolv1alpha1.BackupSession{}
|
||||||
|
_ = k8sClient.Get(ctx, key, realBackupSession)
|
||||||
|
Expect(realBackupSession.Status.Targets[0].Name).Should(Equal(TestDeploymentName))
|
||||||
|
Expect(realBackupSession.Status.Targets[0].SessionState).Should(Equal(formolv1alpha1.New))
|
||||||
|
Expect(realBackupSession.Status.Targets[0].Kind).Should(Equal(formolv1alpha1.SidecarKind))
|
||||||
|
Expect(realBackupSession.Status.Targets[0].Try).Should(Equal(1))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should move to the next task when the first one is a success", func() {
|
||||||
|
realBackupSession := &formolv1alpha1.BackupSession{}
|
||||||
|
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
|
||||||
|
realBackupSession.Status.Targets[0].SessionState = formolv1alpha1.Success
|
||||||
|
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
|
||||||
|
Eventually(func() int {
|
||||||
|
_ = k8sClient.Get(ctx, key, realBackupSession)
|
||||||
|
return len(realBackupSession.Status.Targets)
|
||||||
|
}, timeout, interval).Should(Equal(2))
|
||||||
|
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
|
||||||
|
Expect(realBackupSession.Status.Targets[1].Name).Should(Equal(TestBackupFuncName))
|
||||||
|
Expect(realBackupSession.Status.Targets[1].SessionState).Should(Equal(formolv1alpha1.New))
|
||||||
|
Expect(realBackupSession.Status.Targets[1].Kind).Should(Equal(formolv1alpha1.JobKind))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should be a success when the last task is a success", func() {
|
||||||
|
realBackupSession := &formolv1alpha1.BackupSession{}
|
||||||
|
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
|
||||||
|
realBackupSession.Status.Targets[0].SessionState = formolv1alpha1.Success
|
||||||
|
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
|
||||||
|
Eventually(func() int {
|
||||||
|
_ = k8sClient.Get(ctx, key, realBackupSession)
|
||||||
|
return len(realBackupSession.Status.Targets)
|
||||||
|
}, timeout, interval).Should(Equal(2))
|
||||||
|
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
|
||||||
|
realBackupSession.Status.Targets[1].SessionState = formolv1alpha1.Success
|
||||||
|
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
|
||||||
|
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
|
||||||
|
Eventually(func() formolv1alpha1.SessionState {
|
||||||
|
_ = k8sClient.Get(ctx, key, realBackupSession)
|
||||||
|
return realBackupSession.Status.SessionState
|
||||||
|
}, timeout, interval).Should(Equal(formolv1alpha1.Success))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Should retry when the task is a failure", func() {
|
||||||
|
realBackupSession := &formolv1alpha1.BackupSession{}
|
||||||
|
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
|
||||||
|
realBackupSession.Status.Targets[0].SessionState = formolv1alpha1.Success
|
||||||
|
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
|
||||||
|
Eventually(func() int {
|
||||||
|
_ = k8sClient.Get(ctx, key, realBackupSession)
|
||||||
|
return len(realBackupSession.Status.Targets)
|
||||||
|
}, timeout, interval).Should(Equal(2))
|
||||||
|
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
|
||||||
|
realBackupSession.Status.Targets[1].SessionState = formolv1alpha1.Failure
|
||||||
|
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
|
||||||
|
Eventually(func() int {
|
||||||
|
_ = k8sClient.Get(ctx, key, realBackupSession)
|
||||||
|
return realBackupSession.Status.Targets[1].Try
|
||||||
|
}, timeout, interval).Should(Equal(2))
|
||||||
|
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
|
||||||
|
Expect(realBackupSession.Status.Targets[1].SessionState).Should(Equal(formolv1alpha1.New))
|
||||||
|
realBackupSession.Status.Targets[1].SessionState = formolv1alpha1.Failure
|
||||||
|
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
|
||||||
|
Eventually(func() formolv1alpha1.SessionState {
|
||||||
|
_ = k8sClient.Get(ctx, key, realBackupSession)
|
||||||
|
return realBackupSession.Status.SessionState
|
||||||
|
}, timeout, interval).Should(Equal(formolv1alpha1.Failure))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should create a backup job", func() {
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Context("When other BackupSession exist", func() {
|
||||||
|
const (
|
||||||
|
bs1Name = "test-backupsession-controller1"
|
||||||
|
bs2Name = "test-backupsession-controller2"
|
||||||
|
bs3Name = "test-backupsession-controller3"
|
||||||
|
)
|
||||||
|
var ()
|
||||||
|
BeforeEach(func() {
|
||||||
|
})
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
})
|
||||||
|
It("Should clean up old sessions", func() {
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -45,14 +45,40 @@ type RestoreSessionReconciler struct {
|
|||||||
client.Client
|
client.Client
|
||||||
Log logr.Logger
|
Log logr.Logger
|
||||||
Scheme *runtime.Scheme
|
Scheme *runtime.Scheme
|
||||||
RestoreSession *formolv1alpha1.RestoreSession
|
|
||||||
BackupSession *formolv1alpha1.BackupSession
|
|
||||||
BackupConf *formolv1alpha1.BackupConfiguration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RestoreSessionReconciler) CreateRestoreJob(target formolv1alpha1.Target) error {
|
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions,verbs=get;list;watch;create;update;patch;delete
|
||||||
log := r.Log.WithValues("createrestorejob", target.Name)
|
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions/status,verbs=get;update;patch
|
||||||
|
|
||||||
|
func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
log := r.Log.WithValues("restoresession", req.NamespacedName)
|
||||||
|
|
||||||
|
// Get the RestoreSession
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
// Get the BackupSession the RestoreSession references
|
||||||
|
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", "restoresession", restoreSession.Spec)
|
||||||
|
return ctrl.Result{}, client.IgnoreNotFound(err)
|
||||||
|
}
|
||||||
|
// Get the BackupConfiguration linked to the BackupSession
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
createRestoreJob := func(target formolv1alpha1.Target) error {
|
||||||
restoreSessionEnv := []corev1.EnvVar{
|
restoreSessionEnv := []corev1.EnvVar{
|
||||||
corev1.EnvVar{
|
corev1.EnvVar{
|
||||||
Name: "TARGET_NAME",
|
Name: "TARGET_NAME",
|
||||||
@ -60,11 +86,11 @@ func (r *RestoreSessionReconciler) CreateRestoreJob(target formolv1alpha1.Target
|
|||||||
},
|
},
|
||||||
corev1.EnvVar{
|
corev1.EnvVar{
|
||||||
Name: "RESTORESESSION_NAME",
|
Name: "RESTORESESSION_NAME",
|
||||||
Value: r.RestoreSession.Name,
|
Value: restoreSession.Name,
|
||||||
},
|
},
|
||||||
corev1.EnvVar{
|
corev1.EnvVar{
|
||||||
Name: "RESTORESESSION_NAMESPACE",
|
Name: "RESTORESESSION_NAMESPACE",
|
||||||
Value: r.RestoreSession.Namespace,
|
Value: restoreSession.Namespace,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +98,7 @@ func (r *RestoreSessionReconciler) CreateRestoreJob(target formolv1alpha1.Target
|
|||||||
Name: "output",
|
Name: "output",
|
||||||
MountPath: "/output",
|
MountPath: "/output",
|
||||||
}
|
}
|
||||||
for _, targetStatus := range r.BackupSession.Status.Targets {
|
for _, targetStatus := range backupSession.Status.Targets {
|
||||||
if targetStatus.Name == target.Name {
|
if targetStatus.Name == target.Name {
|
||||||
snapshotId := targetStatus.SnapshotId
|
snapshotId := targetStatus.SnapshotId
|
||||||
restic := corev1.Container{
|
restic := corev1.Container{
|
||||||
@ -91,19 +117,19 @@ func (r *RestoreSessionReconciler) CreateRestoreJob(target formolv1alpha1.Target
|
|||||||
}
|
}
|
||||||
repo := &formolv1alpha1.Repo{}
|
repo := &formolv1alpha1.Repo{}
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
if err := r.Get(ctx, client.ObjectKey{
|
||||||
Namespace: r.BackupConf.Namespace,
|
Namespace: backupConf.Namespace,
|
||||||
Name: r.BackupConf.Spec.Repository,
|
Name: backupConf.Spec.Repository,
|
||||||
}, repo); err != nil {
|
}, repo); err != nil {
|
||||||
log.Error(err, "unable to get Repo from BackupConfiguration")
|
log.Error(err, "unable to get Repo from BackupConfiguration")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// S3 backing storage
|
// S3 backing storage
|
||||||
var ttl int32 = 300
|
var ttl int32 = 300
|
||||||
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
|
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...)
|
||||||
job := &batchv1.Job{
|
job := &batchv1.Job{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
GenerateName: fmt.Sprintf("%s-%s-", r.RestoreSession.Name, target.Name),
|
GenerateName: fmt.Sprintf("%s-%s-", restoreSession.Name, target.Name),
|
||||||
Namespace: r.RestoreSession.Namespace,
|
Namespace: restoreSession.Namespace,
|
||||||
},
|
},
|
||||||
Spec: batchv1.JobSpec{
|
Spec: batchv1.JobSpec{
|
||||||
TTLSecondsAfterFinished: &ttl,
|
TTLSecondsAfterFinished: &ttl,
|
||||||
@ -121,18 +147,34 @@ func (r *RestoreSessionReconciler) CreateRestoreJob(target formolv1alpha1.Target
|
|||||||
}
|
}
|
||||||
for _, step := range target.Steps {
|
for _, step := range target.Steps {
|
||||||
function := &formolv1alpha1.Function{}
|
function := &formolv1alpha1.Function{}
|
||||||
|
// get the backup function
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
if err := r.Get(ctx, client.ObjectKey{
|
||||||
Namespace: r.RestoreSession.Namespace,
|
Namespace: restoreSession.Namespace,
|
||||||
Name: strings.Replace(step.Name, "backup", "restore", 1)}, function); err != nil {
|
Name: step.Name,
|
||||||
|
}, function); err != nil {
|
||||||
|
log.Error(err, "unable to get backup function", "name", step.Name)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var restoreName string
|
||||||
|
if function.Annotations["restoreFunction"] != "" {
|
||||||
|
restoreName = function.Annotations["restoreFunction"]
|
||||||
|
} else {
|
||||||
|
restoreName = strings.Replace(step.Name, "backup", "restore", 1)
|
||||||
|
}
|
||||||
|
if err := r.Get(ctx, client.ObjectKey{
|
||||||
|
Namespace: restoreSession.Namespace,
|
||||||
|
Name: restoreName,
|
||||||
|
}, function); err != nil {
|
||||||
log.Error(err, "unable to get function", "function", step)
|
log.Error(err, "unable to get function", "function", step)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
function.Spec.Name = function.Name
|
||||||
function.Spec.Env = append(step.Env, restoreSessionEnv...)
|
function.Spec.Env = append(step.Env, restoreSessionEnv...)
|
||||||
function.Spec.VolumeMounts = append(function.Spec.VolumeMounts, output)
|
function.Spec.VolumeMounts = append(function.Spec.VolumeMounts, output)
|
||||||
job.Spec.Template.Spec.InitContainers = append(job.Spec.Template.Spec.InitContainers, function.Spec)
|
job.Spec.Template.Spec.InitContainers = append(job.Spec.Template.Spec.InitContainers, function.Spec)
|
||||||
}
|
}
|
||||||
if err := ctrl.SetControllerReference(r.RestoreSession, job, r.Scheme); err != nil {
|
if err := ctrl.SetControllerReference(restoreSession, job, r.Scheme); err != nil {
|
||||||
log.Error(err, "unable to set controller on job", "job", job, "restoresession", r.RestoreSession)
|
log.Error(err, "unable to set controller on job", "job", job, "restoresession", restoreSession)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.V(0).Info("creating a restore job", "target", target.Name)
|
log.V(0).Info("creating a restore job", "target", target.Name)
|
||||||
@ -145,12 +187,10 @@ func (r *RestoreSessionReconciler) CreateRestoreJob(target formolv1alpha1.Target
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RestoreSessionReconciler) DeleteRestoreInitContainer(target formolv1alpha1.Target) error {
|
deleteRestoreInitContainer := func(target formolv1alpha1.Target) error {
|
||||||
log := r.Log.WithValues("createrestoreinitcontainer", target.Name)
|
|
||||||
ctx := context.Background()
|
|
||||||
deployment := &appsv1.Deployment{}
|
deployment := &appsv1.Deployment{}
|
||||||
if err := r.Get(context.Background(), client.ObjectKey{
|
if err := r.Get(context.Background(), client.ObjectKey{
|
||||||
Namespace: r.BackupConf.Namespace,
|
Namespace: backupConf.Namespace,
|
||||||
Name: target.Name,
|
Name: target.Name,
|
||||||
}, deployment); err != nil {
|
}, deployment); err != nil {
|
||||||
log.Error(err, "unable to get deployment")
|
log.Error(err, "unable to get deployment")
|
||||||
@ -173,12 +213,10 @@ func (r *RestoreSessionReconciler) DeleteRestoreInitContainer(target formolv1alp
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RestoreSessionReconciler) CreateRestoreInitContainer(target formolv1alpha1.Target) error {
|
createRestoreInitContainer := func(target formolv1alpha1.Target) error {
|
||||||
log := r.Log.WithValues("createrestoreinitcontainer", target.Name)
|
|
||||||
ctx := context.Background()
|
|
||||||
deployment := &appsv1.Deployment{}
|
deployment := &appsv1.Deployment{}
|
||||||
if err := r.Get(context.Background(), client.ObjectKey{
|
if err := r.Get(context.Background(), client.ObjectKey{
|
||||||
Namespace: r.RestoreSession.Namespace,
|
Namespace: restoreSession.Namespace,
|
||||||
Name: target.Name,
|
Name: target.Name,
|
||||||
}, deployment); err != nil {
|
}, deployment); err != nil {
|
||||||
log.Error(err, "unable to get deployment")
|
log.Error(err, "unable to get deployment")
|
||||||
@ -192,7 +230,7 @@ func (r *RestoreSessionReconciler) CreateRestoreInitContainer(target formolv1alp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var snapshotId string
|
var snapshotId string
|
||||||
for _, targetStatus := range r.BackupSession.Status.Targets {
|
for _, targetStatus := range backupSession.Status.Targets {
|
||||||
if targetStatus.Name == target.Name && targetStatus.Kind == target.Kind {
|
if targetStatus.Name == target.Name && targetStatus.Kind == target.Kind {
|
||||||
snapshotId = targetStatus.SnapshotId
|
snapshotId = targetStatus.SnapshotId
|
||||||
}
|
}
|
||||||
@ -204,11 +242,11 @@ func (r *RestoreSessionReconciler) CreateRestoreInitContainer(target formolv1alp
|
|||||||
},
|
},
|
||||||
corev1.EnvVar{
|
corev1.EnvVar{
|
||||||
Name: formolv1alpha1.RESTORESESSION_NAME,
|
Name: formolv1alpha1.RESTORESESSION_NAME,
|
||||||
Value: r.RestoreSession.Name,
|
Value: restoreSession.Name,
|
||||||
},
|
},
|
||||||
corev1.EnvVar{
|
corev1.EnvVar{
|
||||||
Name: formolv1alpha1.RESTORESESSION_NAMESPACE,
|
Name: formolv1alpha1.RESTORESESSION_NAMESPACE,
|
||||||
Value: r.RestoreSession.Namespace,
|
Value: restoreSession.Namespace,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
initContainer := corev1.Container{
|
initContainer := corev1.Container{
|
||||||
@ -220,14 +258,14 @@ func (r *RestoreSessionReconciler) CreateRestoreInitContainer(target formolv1alp
|
|||||||
}
|
}
|
||||||
repo := &formolv1alpha1.Repo{}
|
repo := &formolv1alpha1.Repo{}
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
if err := r.Get(ctx, client.ObjectKey{
|
||||||
Namespace: r.BackupConf.Namespace,
|
Namespace: backupConf.Namespace,
|
||||||
Name: r.BackupConf.Spec.Repository,
|
Name: backupConf.Spec.Repository,
|
||||||
}, repo); err != nil {
|
}, repo); err != nil {
|
||||||
log.Error(err, "unable to get Repo from BackupConfiguration")
|
log.Error(err, "unable to get Repo from BackupConfiguration")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// S3 backing storage
|
// S3 backing storage
|
||||||
initContainer.Env = append(initContainer.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
|
initContainer.Env = append(initContainer.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...)
|
||||||
deployment.Spec.Template.Spec.InitContainers = append([]corev1.Container{initContainer},
|
deployment.Spec.Template.Spec.InitContainers = append([]corev1.Container{initContainer},
|
||||||
deployment.Spec.Template.Spec.InitContainers...)
|
deployment.Spec.Template.Spec.InitContainers...)
|
||||||
if err := r.Update(ctx, deployment); err != nil {
|
if err := r.Update(ctx, deployment); err != nil {
|
||||||
@ -238,29 +276,26 @@ func (r *RestoreSessionReconciler) CreateRestoreInitContainer(target formolv1alp
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RestoreSessionReconciler) StatusUpdate() error {
|
|
||||||
log := r.Log.WithValues("statusupdate", r.RestoreSession.Name)
|
|
||||||
ctx := context.Background()
|
|
||||||
startNextTask := func() (*formolv1alpha1.TargetStatus, error) {
|
startNextTask := func() (*formolv1alpha1.TargetStatus, error) {
|
||||||
nextTarget := len(r.RestoreSession.Status.Targets)
|
nextTarget := len(restoreSession.Status.Targets)
|
||||||
if nextTarget < len(r.BackupConf.Spec.Targets) {
|
if nextTarget < len(backupConf.Spec.Targets) {
|
||||||
target := r.BackupConf.Spec.Targets[nextTarget]
|
target := backupConf.Spec.Targets[nextTarget]
|
||||||
targetStatus := formolv1alpha1.TargetStatus{
|
targetStatus := formolv1alpha1.TargetStatus{
|
||||||
Name: target.Name,
|
Name: target.Name,
|
||||||
Kind: target.Kind,
|
Kind: target.Kind,
|
||||||
SessionState: formolv1alpha1.New,
|
SessionState: formolv1alpha1.New,
|
||||||
StartTime: &metav1.Time{Time: time.Now()},
|
StartTime: &metav1.Time{Time: time.Now()},
|
||||||
}
|
}
|
||||||
r.RestoreSession.Status.Targets = append(r.RestoreSession.Status.Targets, targetStatus)
|
restoreSession.Status.Targets = append(restoreSession.Status.Targets, targetStatus)
|
||||||
switch target.Kind {
|
switch target.Kind {
|
||||||
case "Deployment":
|
case formolv1alpha1.SidecarKind:
|
||||||
if err := r.CreateRestoreInitContainer(target); err != nil {
|
if err := createRestoreInitContainer(target); err != nil {
|
||||||
log.V(0).Info("unable to create restore init container", "task", target)
|
log.V(0).Info("unable to create restore init container", "task", target)
|
||||||
targetStatus.SessionState = formolv1alpha1.Failure
|
targetStatus.SessionState = formolv1alpha1.Failure
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "Task":
|
case formolv1alpha1.JobKind:
|
||||||
if err := r.CreateRestoreJob(target); err != nil {
|
if err := createRestoreJob(target); err != nil {
|
||||||
log.V(0).Info("unable to create restore job", "task", target)
|
log.V(0).Info("unable to create restore job", "task", target)
|
||||||
targetStatus.SessionState = formolv1alpha1.Failure
|
targetStatus.SessionState = formolv1alpha1.Failure
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -271,110 +306,71 @@ func (r *RestoreSessionReconciler) StatusUpdate() error {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
endTask := func() error {
|
endTask := func() error {
|
||||||
target := r.BackupConf.Spec.Targets[len(r.RestoreSession.Status.Targets)-1]
|
target := backupConf.Spec.Targets[len(restoreSession.Status.Targets)-1]
|
||||||
switch target.Kind {
|
switch target.Kind {
|
||||||
case "Deployment":
|
case formolv1alpha1.SidecarKind:
|
||||||
if err := r.DeleteRestoreInitContainer(target); err != nil {
|
if err := deleteRestoreInitContainer(target); err != nil {
|
||||||
log.Error(err, "unable to delete restore init container")
|
log.Error(err, "unable to delete restore init container")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
switch r.RestoreSession.Status.SessionState {
|
|
||||||
|
switch restoreSession.Status.SessionState {
|
||||||
case formolv1alpha1.New:
|
case formolv1alpha1.New:
|
||||||
r.RestoreSession.Status.SessionState = formolv1alpha1.Running
|
restoreSession.Status.SessionState = formolv1alpha1.Running
|
||||||
targetStatus, err := startNextTask()
|
if targetStatus, err := startNextTask(); err != nil {
|
||||||
if err != nil {
|
log.Error(err, "unable to start next restore task")
|
||||||
return err
|
return ctrl.Result{}, err
|
||||||
}
|
} else {
|
||||||
log.V(0).Info("New restore. Start the first task", "task", targetStatus.Name)
|
log.V(0).Info("New restore. Start the first task", "task", targetStatus.Name)
|
||||||
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
|
if err := r.Status().Update(ctx, restoreSession); err != nil {
|
||||||
log.Error(err, "unable to update restoresession")
|
log.Error(err, "unable to update restoresession")
|
||||||
return err
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case formolv1alpha1.Running:
|
case formolv1alpha1.Running:
|
||||||
currentTargetStatus := r.RestoreSession.Status.Targets[len(r.RestoreSession.Status.Targets)-1]
|
currentTargetStatus := restoreSession.Status.Targets[len(restoreSession.Status.Targets)-1]
|
||||||
switch currentTargetStatus.SessionState {
|
switch currentTargetStatus.SessionState {
|
||||||
case formolv1alpha1.Failure:
|
case formolv1alpha1.Failure:
|
||||||
log.V(0).Info("last restore task failed. Stop here", "target", currentTargetStatus.Name)
|
log.V(0).Info("last restore task failed. Stop here", "target", currentTargetStatus.Name)
|
||||||
r.RestoreSession.Status.SessionState = formolv1alpha1.Failure
|
restoreSession.Status.SessionState = formolv1alpha1.Failure
|
||||||
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
|
if err := r.Status().Update(ctx, restoreSession); err != nil {
|
||||||
log.Error(err, "unable to update restoresession")
|
log.Error(err, "unable to update restoresession")
|
||||||
return err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
case formolv1alpha1.Running:
|
case formolv1alpha1.Running:
|
||||||
log.V(0).Info("task is still running", "target", currentTargetStatus.Name)
|
log.V(0).Info("task is still running", "target", currentTargetStatus.Name)
|
||||||
return nil
|
return ctrl.Result{}, nil
|
||||||
case formolv1alpha1.Success:
|
case formolv1alpha1.Success:
|
||||||
_ = endTask()
|
_ = endTask()
|
||||||
log.V(0).Info("last task was a success. start a new one", "target", currentTargetStatus)
|
log.V(0).Info("last task was a success. start a new one", "target", currentTargetStatus)
|
||||||
targetStatus, err := startNextTask()
|
targetStatus, err := startNextTask()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
if targetStatus == nil {
|
if targetStatus == nil {
|
||||||
// No more task to start. The restore is over
|
// No more task to start. The restore is over
|
||||||
r.RestoreSession.Status.SessionState = formolv1alpha1.Success
|
restoreSession.Status.SessionState = formolv1alpha1.Success
|
||||||
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
|
|
||||||
log.Error(err, "unable to update restoresession")
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
if err := r.Status().Update(ctx, restoreSession); err != nil {
|
||||||
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
|
|
||||||
log.Error(err, "unable to update restoresession")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// +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
|
|
||||||
|
|
||||||
func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
|
||||||
ctx := context.Background()
|
|
||||||
log := r.Log.WithValues("restoresession", req.NamespacedName)
|
|
||||||
|
|
||||||
r.RestoreSession = &formolv1alpha1.RestoreSession{}
|
|
||||||
if err := r.Get(ctx, req.NamespacedName, r.RestoreSession); err != nil {
|
|
||||||
log.Error(err, "unable to get restoresession")
|
|
||||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
|
||||||
}
|
|
||||||
r.BackupSession = &formolv1alpha1.BackupSession{}
|
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
|
||||||
Namespace: r.RestoreSession.Spec.BackupSessionRef.Namespace,
|
|
||||||
Name: r.RestoreSession.Spec.BackupSessionRef.Name}, r.BackupSession); err != nil {
|
|
||||||
log.Error(err, "unable to get backupsession")
|
|
||||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
|
||||||
}
|
|
||||||
r.BackupConf = &formolv1alpha1.BackupConfiguration{}
|
|
||||||
if err := r.Get(ctx, client.ObjectKey{
|
|
||||||
Namespace: r.BackupSession.Namespace,
|
|
||||||
Name: r.BackupSession.Spec.Ref.Name}, r.BackupConf); err != nil {
|
|
||||||
log.Error(err, "unable to get backupConfiguration")
|
|
||||||
return ctrl.Result{}, client.IgnoreNotFound(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.RestoreSession.Status.ObservedGeneration == r.RestoreSession.ObjectMeta.Generation {
|
|
||||||
// status update
|
|
||||||
log.V(0).Info("status update")
|
|
||||||
return ctrl.Result{}, r.StatusUpdate()
|
|
||||||
}
|
|
||||||
r.RestoreSession.Status.ObservedGeneration = r.RestoreSession.ObjectMeta.Generation
|
|
||||||
r.RestoreSession.Status.SessionState = formolv1alpha1.New
|
|
||||||
r.RestoreSession.Status.StartTime = &metav1.Time{Time: time.Now()}
|
|
||||||
reschedule := ctrl.Result{RequeueAfter: 5 * time.Second}
|
|
||||||
|
|
||||||
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
|
|
||||||
log.Error(err, "unable to update restoresession")
|
log.Error(err, "unable to update restoresession")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return reschedule, nil
|
case "":
|
||||||
|
// Restore session has just been created
|
||||||
|
restoreSession.Status.SessionState = formolv1alpha1.New
|
||||||
|
restoreSession.Status.StartTime = &metav1.Time{Time: time.Now()}
|
||||||
|
if err := r.Status().Update(ctx, restoreSession); err != nil {
|
||||||
|
log.Error(err, "unable to update restoreSession")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
|
|||||||
90
controllers/restoresession_controller_test.go
Normal file
90
controllers/restoresession_controller_test.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Testing RestoreSession controller", func() {
|
||||||
|
const (
|
||||||
|
RSRestoreSessionName = "test-restoresession-controller"
|
||||||
|
)
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
key = types.NamespacedName{
|
||||||
|
Name: RSRestoreSessionName,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
}
|
||||||
|
restoreSession = &formolv1alpha1.RestoreSession{}
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
restoreSession = &formolv1alpha1.RestoreSession{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: RSRestoreSessionName,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
},
|
||||||
|
Spec: formolv1alpha1.RestoreSessionSpec{
|
||||||
|
Ref: TestBackupSessionName,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Context("Creating a RestoreSession", func() {
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
Eventually(func() error {
|
||||||
|
return k8sClient.Create(ctx, restoreSession)
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
realRestoreSession := &formolv1alpha1.RestoreSession{}
|
||||||
|
Eventually(func() error {
|
||||||
|
return k8sClient.Get(ctx, key, realRestoreSession)
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
Eventually(func() formolv1alpha1.SessionState {
|
||||||
|
_ = k8sClient.Get(ctx, key, realRestoreSession)
|
||||||
|
return realRestoreSession.Status.SessionState
|
||||||
|
}, timeout, interval).Should(Equal(formolv1alpha1.Running))
|
||||||
|
})
|
||||||
|
AfterEach(func() {
|
||||||
|
Expect(k8sClient.Delete(ctx, restoreSession)).Should(Succeed())
|
||||||
|
})
|
||||||
|
It("Should have a new task and should fail if the task fails", func() {
|
||||||
|
restoreSession := &formolv1alpha1.RestoreSession{}
|
||||||
|
Expect(k8sClient.Get(ctx, key, restoreSession)).Should(Succeed())
|
||||||
|
Expect(len(restoreSession.Status.Targets)).Should(Equal(1))
|
||||||
|
Expect(restoreSession.Status.Targets[0].SessionState).Should(Equal(formolv1alpha1.New))
|
||||||
|
restoreSession.Status.Targets[0].SessionState = formolv1alpha1.Running
|
||||||
|
Expect(k8sClient.Status().Update(ctx, restoreSession)).Should(Succeed())
|
||||||
|
Expect(k8sClient.Get(ctx, key, restoreSession)).Should(Succeed())
|
||||||
|
Eventually(func() formolv1alpha1.SessionState {
|
||||||
|
_ = k8sClient.Get(ctx, key, restoreSession)
|
||||||
|
return restoreSession.Status.Targets[0].SessionState
|
||||||
|
}, timeout, interval).Should(Equal(formolv1alpha1.Running))
|
||||||
|
restoreSession.Status.Targets[0].SessionState = formolv1alpha1.Failure
|
||||||
|
Expect(k8sClient.Status().Update(ctx, restoreSession)).Should(Succeed())
|
||||||
|
Expect(k8sClient.Get(ctx, key, restoreSession)).Should(Succeed())
|
||||||
|
Eventually(func() formolv1alpha1.SessionState {
|
||||||
|
_ = k8sClient.Get(ctx, key, restoreSession)
|
||||||
|
return restoreSession.Status.SessionState
|
||||||
|
}, timeout, interval).Should(Equal(formolv1alpha1.Failure))
|
||||||
|
})
|
||||||
|
It("Should move to the new task if the first one is a success and be a success if all the tasks succeed", func() {
|
||||||
|
restoreSession := &formolv1alpha1.RestoreSession{}
|
||||||
|
Expect(k8sClient.Get(ctx, key, restoreSession)).Should(Succeed())
|
||||||
|
Expect(len(restoreSession.Status.Targets)).Should(Equal(1))
|
||||||
|
restoreSession.Status.Targets[0].SessionState = formolv1alpha1.Success
|
||||||
|
Expect(k8sClient.Status().Update(ctx, restoreSession)).Should(Succeed())
|
||||||
|
Eventually(func() int {
|
||||||
|
_ = k8sClient.Get(ctx, key, restoreSession)
|
||||||
|
return len(restoreSession.Status.Targets)
|
||||||
|
}, timeout, interval).Should(Equal(2))
|
||||||
|
restoreSession.Status.Targets[1].SessionState = formolv1alpha1.Success
|
||||||
|
Expect(k8sClient.Status().Update(ctx, restoreSession)).Should(Succeed())
|
||||||
|
Eventually(func() formolv1alpha1.SessionState {
|
||||||
|
_ = k8sClient.Get(ctx, key, restoreSession)
|
||||||
|
return restoreSession.Status.SessionState
|
||||||
|
}, timeout, interval).Should(Equal(formolv1alpha1.Success))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -44,10 +44,16 @@ import (
|
|||||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||||
const (
|
const (
|
||||||
BackupFuncName = "test-backup-func"
|
TestBackupFuncName = "test-backup-func"
|
||||||
|
TestFunc = "test-norestore-func"
|
||||||
|
TestRestoreFuncName = "test-restore-func"
|
||||||
TestNamespace = "test-namespace"
|
TestNamespace = "test-namespace"
|
||||||
RepoName = "test-repo"
|
TestRepoName = "test-repo"
|
||||||
DeploymentName = "test-deployment"
|
TestDeploymentName = "test-deployment"
|
||||||
|
TestBackupConfName = "test-backupconf"
|
||||||
|
TestBackupSessionName = "test-backupsession"
|
||||||
|
TestDataVolume = "data"
|
||||||
|
TestDataMountPath = "/data"
|
||||||
timeout = time.Second * 10
|
timeout = time.Second * 10
|
||||||
interval = time.Millisecond * 250
|
interval = time.Millisecond * 250
|
||||||
)
|
)
|
||||||
@ -64,7 +70,7 @@ var (
|
|||||||
}
|
}
|
||||||
deployment = &appsv1.Deployment{
|
deployment = &appsv1.Deployment{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: DeploymentName,
|
Name: TestDeploymentName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
},
|
},
|
||||||
Spec: appsv1.DeploymentSpec{
|
Spec: appsv1.DeploymentSpec{
|
||||||
@ -82,6 +88,11 @@ var (
|
|||||||
Image: "test-image",
|
Image: "test-image",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Volumes: []corev1.Volume{
|
||||||
|
corev1.Volume{
|
||||||
|
Name: TestDataVolume,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -105,7 +116,7 @@ var (
|
|||||||
}
|
}
|
||||||
repo = &formolv1alpha1.Repo{
|
repo = &formolv1alpha1.Repo{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: RepoName,
|
Name: TestRepoName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
},
|
},
|
||||||
Spec: formolv1alpha1.RepoSpec{
|
Spec: formolv1alpha1.RepoSpec{
|
||||||
@ -120,7 +131,29 @@ var (
|
|||||||
}
|
}
|
||||||
function = &formolv1alpha1.Function{
|
function = &formolv1alpha1.Function{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: BackupFuncName,
|
Name: TestFunc,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
},
|
||||||
|
Spec: corev1.Container{
|
||||||
|
Name: "norestore-func",
|
||||||
|
Image: "myimage",
|
||||||
|
Args: []string{"a", "set", "of", "args"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
backupFunc = &formolv1alpha1.Function{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: TestRestoreFuncName,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
},
|
||||||
|
Spec: corev1.Container{
|
||||||
|
Name: "restore-func",
|
||||||
|
Image: "myimage",
|
||||||
|
Args: []string{"a", "set", "of", "args"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
restoreFunc = &formolv1alpha1.Function{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: TestBackupFuncName,
|
||||||
Namespace: TestNamespace,
|
Namespace: TestNamespace,
|
||||||
},
|
},
|
||||||
Spec: corev1.Container{
|
Spec: corev1.Container{
|
||||||
@ -129,6 +162,66 @@ var (
|
|||||||
Args: []string{"a", "set", "of", "args"},
|
Args: []string{"a", "set", "of", "args"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
testBackupConf = &formolv1alpha1.BackupConfiguration{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: TestBackupConfName,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
},
|
||||||
|
Spec: formolv1alpha1.BackupConfigurationSpec{
|
||||||
|
Repository: TestRepoName,
|
||||||
|
Schedule: "1 * * * *",
|
||||||
|
Keep: formolv1alpha1.Keep{
|
||||||
|
Last: 2,
|
||||||
|
},
|
||||||
|
Targets: []formolv1alpha1.Target{
|
||||||
|
formolv1alpha1.Target{
|
||||||
|
Kind: formolv1alpha1.SidecarKind,
|
||||||
|
Name: TestDeploymentName,
|
||||||
|
Steps: []formolv1alpha1.Step{
|
||||||
|
formolv1alpha1.Step{
|
||||||
|
Name: TestFunc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Paths: []string{
|
||||||
|
TestDataMountPath,
|
||||||
|
},
|
||||||
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
|
corev1.VolumeMount{
|
||||||
|
Name: TestDataVolume,
|
||||||
|
MountPath: TestDataMountPath,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
formolv1alpha1.Target{
|
||||||
|
Kind: formolv1alpha1.JobKind,
|
||||||
|
Name: TestBackupFuncName,
|
||||||
|
Steps: []formolv1alpha1.Step{
|
||||||
|
formolv1alpha1.Step{
|
||||||
|
Name: TestFunc,
|
||||||
|
},
|
||||||
|
formolv1alpha1.Step{
|
||||||
|
Name: TestBackupFuncName,
|
||||||
|
Env: []corev1.EnvVar{
|
||||||
|
corev1.EnvVar{
|
||||||
|
Name: "foo",
|
||||||
|
Value: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testBackupSession = &formolv1alpha1.BackupSession{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: TestBackupSessionName,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
},
|
||||||
|
Spec: formolv1alpha1.BackupSessionSpec{
|
||||||
|
Ref: TestBackupConfName,
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIs(t *testing.T) {
|
func TestAPIs(t *testing.T) {
|
||||||
@ -168,6 +261,20 @@ var _ = BeforeSuite(func() {
|
|||||||
}).SetupWithManager(k8sManager)
|
}).SetupWithManager(k8sManager)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = (&BackupSessionReconciler{
|
||||||
|
Client: k8sManager.GetClient(),
|
||||||
|
Scheme: k8sManager.GetScheme(),
|
||||||
|
Log: ctrl.Log.WithName("controllers").WithName("BackupSession"),
|
||||||
|
}).SetupWithManager(k8sManager)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = (&RestoreSessionReconciler{
|
||||||
|
Client: k8sManager.GetClient(),
|
||||||
|
Scheme: k8sManager.GetScheme(),
|
||||||
|
Log: ctrl.Log.WithName("controllers").WithName("RestoreSession"),
|
||||||
|
}).SetupWithManager(k8sManager)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
err = k8sManager.Start(ctrl.SetupSignalHandler())
|
err = k8sManager.Start(ctrl.SetupSignalHandler())
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
@ -182,6 +289,32 @@ var _ = BeforeSuite(func() {
|
|||||||
Expect(k8sClient.Create(ctx, repo)).Should(Succeed())
|
Expect(k8sClient.Create(ctx, repo)).Should(Succeed())
|
||||||
Expect(k8sClient.Create(ctx, deployment)).Should(Succeed())
|
Expect(k8sClient.Create(ctx, deployment)).Should(Succeed())
|
||||||
Expect(k8sClient.Create(ctx, function)).Should(Succeed())
|
Expect(k8sClient.Create(ctx, function)).Should(Succeed())
|
||||||
|
Expect(k8sClient.Create(ctx, backupFunc)).Should(Succeed())
|
||||||
|
Expect(k8sClient.Create(ctx, restoreFunc)).Should(Succeed())
|
||||||
|
Expect(k8sClient.Create(ctx, testBackupConf)).Should(Succeed())
|
||||||
|
Expect(k8sClient.Create(ctx, testBackupSession)).Should(Succeed())
|
||||||
|
Eventually(func() error {
|
||||||
|
return k8sClient.Get(ctx, client.ObjectKey{
|
||||||
|
Name: TestBackupSessionName,
|
||||||
|
Namespace: TestNamespace,
|
||||||
|
}, testBackupSession)
|
||||||
|
}, timeout, interval).Should(Succeed())
|
||||||
|
testBackupSession.Status.SessionState = formolv1alpha1.Success
|
||||||
|
testBackupSession.Status.Targets = []formolv1alpha1.TargetStatus{
|
||||||
|
formolv1alpha1.TargetStatus{
|
||||||
|
Name: TestDeploymentName,
|
||||||
|
Kind: formolv1alpha1.SidecarKind,
|
||||||
|
SessionState: formolv1alpha1.Success,
|
||||||
|
SnapshotId: "12345abcdef",
|
||||||
|
},
|
||||||
|
formolv1alpha1.TargetStatus{
|
||||||
|
Name: TestBackupFuncName,
|
||||||
|
Kind: formolv1alpha1.JobKind,
|
||||||
|
SessionState: formolv1alpha1.Success,
|
||||||
|
SnapshotId: "67890ghijk",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Expect(k8sClient.Status().Update(ctx, testBackupSession)).Should(Succeed())
|
||||||
}, 60)
|
}, 60)
|
||||||
|
|
||||||
var _ = AfterSuite(func() {
|
var _ = AfterSuite(func() {
|
||||||
|
|||||||
4
main.go
4
main.go
@ -102,6 +102,10 @@ func main() {
|
|||||||
setupLog.Error(err, "unable to create controller", "controller", "RestoreSession")
|
setupLog.Error(err, "unable to create controller", "controller", "RestoreSession")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
if err = (&formoldesmojimfrv1alpha1.Function{}).SetupWebhookWithManager(mgr); err != nil {
|
||||||
|
setupLog.Error(err, "unable to create webhook", "webhook", "Function")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
// +kubebuilder:scaffold:builder
|
// +kubebuilder:scaffold:builder
|
||||||
|
|
||||||
setupLog.Info("starting manager")
|
setupLog.Info("starting manager")
|
||||||
|
|||||||
@ -8,6 +8,8 @@ metadata:
|
|||||||
app: nginx
|
app: nginx
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: nginx
|
app: nginx
|
||||||
|
|||||||
@ -8,7 +8,7 @@ spec:
|
|||||||
repository: repo-minio
|
repository: repo-minio
|
||||||
schedule: "15 * * * *"
|
schedule: "15 * * * *"
|
||||||
targets:
|
targets:
|
||||||
- kind: Deployment
|
- kind: Sidecar
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
name: nginx-deployment
|
name: nginx-deployment
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
@ -16,11 +16,10 @@ spec:
|
|||||||
mountPath: /data
|
mountPath: /data
|
||||||
paths:
|
paths:
|
||||||
- /data
|
- /data
|
||||||
- kind: Task
|
- kind: Job
|
||||||
name: backup-pg
|
name: backup-pg
|
||||||
steps:
|
steps:
|
||||||
- name: backup-pg
|
- name: backup-pg
|
||||||
namespace: demo
|
|
||||||
env:
|
env:
|
||||||
- name: PGHOST
|
- name: PGHOST
|
||||||
value: postgres
|
value: postgres
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user