github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/dataprotection/backupschedule_controller.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package dataprotection 21 22 import ( 23 "context" 24 "reflect" 25 "time" 26 27 batchv1 "k8s.io/api/batch/v1" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 k8sruntime "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/client-go/tools/record" 32 ctrl "sigs.k8s.io/controller-runtime" 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 "sigs.k8s.io/controller-runtime/pkg/controller" 35 "sigs.k8s.io/controller-runtime/pkg/log" 36 37 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 38 "github.com/1aal/kubeblocks/pkg/constant" 39 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 40 dpbackup "github.com/1aal/kubeblocks/pkg/dataprotection/backup" 41 dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types" 42 dputils "github.com/1aal/kubeblocks/pkg/dataprotection/utils" 43 viper "github.com/1aal/kubeblocks/pkg/viperx" 44 ) 45 46 // BackupScheduleReconciler reconciles a BackupSchedule object 47 type BackupScheduleReconciler struct { 48 client.Client 49 Scheme *k8sruntime.Scheme 50 Recorder record.EventRecorder 51 } 52 53 // +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backupschedules,verbs=get;list;watch;create;update;patch;delete 54 // +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backupschedules/status,verbs=get;update;patch 55 // +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backupschedules/finalizers,verbs=update 56 57 // +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete 58 // +kubebuilder:rbac:groups=batch,resources=cronjobs/status,verbs=get 59 // +kubebuilder:rbac:groups=batch,resources=cronjobs/finalizers,verbs=update;patch 60 61 // Reconcile is part of the main kubernetes reconciliation loop which aims to 62 // move the current state of the backupschedule closer to the desired state. 63 func (r *BackupScheduleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 64 reqCtx := intctrlutil.RequestCtx{ 65 Ctx: ctx, 66 Req: req, 67 Log: log.FromContext(ctx).WithValues("backupSchedule", req.NamespacedName), 68 Recorder: r.Recorder, 69 } 70 71 backupSchedule := &dpv1alpha1.BackupSchedule{} 72 if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, backupSchedule); err != nil { 73 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 74 } 75 76 original := backupSchedule.DeepCopy() 77 78 // handle finalizer 79 res, err := intctrlutil.HandleCRDeletion(reqCtx, r, backupSchedule, dptypes.DataProtectionFinalizerName, func() (*ctrl.Result, error) { 80 return nil, r.deleteExternalResources(reqCtx, backupSchedule) 81 }) 82 if res != nil { 83 return *res, err 84 } 85 86 if err = r.handleSchedule(reqCtx, backupSchedule); err != nil { 87 return r.patchStatusFailed(reqCtx, backupSchedule, "HandleBackupScheduleFailed", err) 88 } 89 90 return r.patchStatusAvailable(reqCtx, original, backupSchedule) 91 } 92 93 // SetupWithManager sets up the controller with the Manager. 94 func (r *BackupScheduleReconciler) SetupWithManager(mgr ctrl.Manager) error { 95 return ctrl.NewControllerManagedBy(mgr). 96 For(&dpv1alpha1.BackupSchedule{}). 97 Owns(&batchv1.CronJob{}). 98 WithOptions(controller.Options{ 99 MaxConcurrentReconciles: viper.GetInt(maxConcurDataProtectionReconKey), 100 }). 101 Complete(r) 102 } 103 104 func (r *BackupScheduleReconciler) deleteExternalResources( 105 reqCtx intctrlutil.RequestCtx, 106 backupSchedule *dpv1alpha1.BackupSchedule) error { 107 // delete cronjob resource 108 cronJobList := &batchv1.CronJobList{} 109 if err := r.Client.List(reqCtx.Ctx, cronJobList, 110 client.InNamespace(backupSchedule.Namespace), 111 client.MatchingLabels{ 112 dptypes.BackupScheduleLabelKey: backupSchedule.Name, 113 constant.AppManagedByLabelKey: constant.AppName, 114 }, 115 ); err != nil { 116 return err 117 } 118 for _, cronjob := range cronJobList.Items { 119 if err := dputils.RemoveDataProtectionFinalizer(reqCtx.Ctx, r.Client, &cronjob); err != nil { 120 return err 121 } 122 if err := intctrlutil.BackgroundDeleteObject(r.Client, reqCtx.Ctx, &cronjob); err != nil { 123 // failed delete k8s job, return error info. 124 return err 125 } 126 } 127 // notice running backup to completed 128 // TODO(ldm): is it necessary to notice running backup to completed? 129 backup := &dpv1alpha1.Backup{} 130 for _, s := range backupSchedule.Spec.Schedules { 131 backupKey := client.ObjectKey{ 132 Namespace: backupSchedule.Namespace, 133 Name: dpbackup.GenerateCRNameByBackupSchedule(backupSchedule, s.BackupMethod), 134 } 135 if err := r.Client.Get(reqCtx.Ctx, backupKey, backup); err != nil { 136 if client.IgnoreNotFound(err) == nil { 137 continue 138 } 139 return err 140 } 141 patch := client.MergeFrom(backup.DeepCopy()) 142 backup.Status.Phase = dpv1alpha1.BackupPhaseCompleted 143 backup.Status.CompletionTimestamp = &metav1.Time{Time: time.Now().UTC()} 144 if err := r.Client.Status().Patch(reqCtx.Ctx, backup, patch); err != nil { 145 return err 146 } 147 } 148 return nil 149 } 150 151 // patchStatusAvailable patches backup policy status phase to available. 152 func (r *BackupScheduleReconciler) patchStatusAvailable(reqCtx intctrlutil.RequestCtx, 153 origin, backupSchedule *dpv1alpha1.BackupSchedule) (ctrl.Result, error) { 154 if !reflect.DeepEqual(origin.Spec, backupSchedule.Spec) { 155 if err := r.Client.Update(reqCtx.Ctx, backupSchedule); err != nil { 156 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 157 } 158 } 159 // update status phase 160 if backupSchedule.Status.Phase != dpv1alpha1.BackupSchedulePhaseAvailable || 161 backupSchedule.Status.ObservedGeneration != backupSchedule.Generation { 162 patch := client.MergeFrom(backupSchedule.DeepCopy()) 163 backupSchedule.Status.ObservedGeneration = backupSchedule.Generation 164 backupSchedule.Status.Phase = dpv1alpha1.BackupSchedulePhaseAvailable 165 backupSchedule.Status.FailureReason = "" 166 if err := r.Client.Status().Patch(reqCtx.Ctx, backupSchedule, patch); err != nil { 167 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 168 } 169 } 170 return intctrlutil.Reconciled() 171 } 172 173 // patchStatusFailed patches backup policy status phase to failed. 174 func (r *BackupScheduleReconciler) patchStatusFailed(reqCtx intctrlutil.RequestCtx, 175 backupSchedule *dpv1alpha1.BackupSchedule, 176 reason string, 177 err error) (ctrl.Result, error) { 178 if intctrlutil.IsTargetError(err, intctrlutil.ErrorTypeRequeue) { 179 return intctrlutil.RequeueAfter(reconcileInterval, reqCtx.Log, "") 180 } 181 backupScheduleDeepCopy := backupSchedule.DeepCopy() 182 backupSchedule.Status.Phase = dpv1alpha1.BackupSchedulePhaseFailed 183 backupSchedule.Status.FailureReason = err.Error() 184 if !reflect.DeepEqual(backupSchedule.Status, backupScheduleDeepCopy.Status) { 185 if patchErr := r.Client.Status().Patch(reqCtx.Ctx, backupSchedule, client.MergeFrom(backupScheduleDeepCopy)); patchErr != nil { 186 return intctrlutil.RequeueWithError(patchErr, reqCtx.Log, "") 187 } 188 } 189 r.Recorder.Event(backupSchedule, corev1.EventTypeWarning, reason, err.Error()) 190 return intctrlutil.RequeueWithError(err, reqCtx.Log, "") 191 } 192 193 // handleSchedule handles backup schedules for different backup method. 194 func (r *BackupScheduleReconciler) handleSchedule( 195 reqCtx intctrlutil.RequestCtx, 196 backupSchedule *dpv1alpha1.BackupSchedule) error { 197 backupPolicy, err := getBackupPolicyByName(reqCtx, r.Client, backupSchedule.Spec.BackupPolicyName) 198 if err != nil { 199 return err 200 } 201 scheduler := dpbackup.Scheduler{ 202 RequestCtx: reqCtx, 203 BackupSchedule: backupSchedule, 204 BackupPolicy: backupPolicy, 205 Client: r.Client, 206 Scheme: r.Scheme, 207 } 208 return scheduler.Schedule() 209 }