github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/dataprotection/gc_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 "time" 25 26 corev1 "k8s.io/api/core/v1" 27 apierrors "k8s.io/apimachinery/pkg/api/errors" 28 "k8s.io/client-go/tools/record" 29 "k8s.io/utils/clock" 30 ctrl "sigs.k8s.io/controller-runtime" 31 "sigs.k8s.io/controller-runtime/pkg/builder" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 "sigs.k8s.io/controller-runtime/pkg/event" 34 "sigs.k8s.io/controller-runtime/pkg/log" 35 "sigs.k8s.io/controller-runtime/pkg/predicate" 36 37 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 38 ctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 39 dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types" 40 dputils "github.com/1aal/kubeblocks/pkg/dataprotection/utils" 41 viper "github.com/1aal/kubeblocks/pkg/viperx" 42 ) 43 44 // GCReconciler garbage collection reconciler, which periodically deletes expired backups. 45 type GCReconciler struct { 46 client.Client 47 Recorder record.EventRecorder 48 clock clock.WithTickerAndDelayedExecution 49 frequency time.Duration 50 } 51 52 func NewGCReconciler(mgr ctrl.Manager) *GCReconciler { 53 return &GCReconciler{ 54 Client: mgr.GetClient(), 55 Recorder: mgr.GetEventRecorderFor("gc-controller"), 56 clock: clock.RealClock{}, 57 frequency: getGCFrequency(), 58 } 59 } 60 61 // SetupWithManager sets up the GCReconciler using the supplied manager. 62 // GCController only watches on CreateEvent for ensuring every new backup will be 63 // taken care of. Other events will be filtered to decrease the load on the controller. 64 func (r *GCReconciler) SetupWithManager(mgr ctrl.Manager) error { 65 s := dputils.NewPeriodicalEnqueueSource(mgr.GetClient(), &dpv1alpha1.BackupList{}, r.frequency, dputils.PeriodicalEnqueueSourceOption{}) 66 return ctrl.NewControllerManagedBy(mgr). 67 For(&dpv1alpha1.Backup{}, builder.WithPredicates(predicate.Funcs{ 68 CreateFunc: func(_ event.CreateEvent) bool { 69 return false 70 }, 71 UpdateFunc: func(_ event.UpdateEvent) bool { 72 return false 73 }, 74 DeleteFunc: func(_ event.DeleteEvent) bool { 75 return false 76 }, 77 GenericFunc: func(_ event.GenericEvent) bool { 78 return false 79 }, 80 })). 81 WatchesRawSource(s, nil). 82 Complete(r) 83 } 84 85 // +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backups,verbs=get;list;watch;delete 86 // +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=backups/status,verbs=get 87 88 // Reconcile is part of the main kubernetes reconciliation loop which aims to 89 // delete expired backups. 90 func (r *GCReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 91 reqCtx := ctrlutil.RequestCtx{ 92 Ctx: ctx, 93 Req: req, 94 Log: log.FromContext(ctx).WithValues("gc backup", req.NamespacedName), 95 Recorder: r.Recorder, 96 } 97 reqCtx.Log.V(1).Info("gcController getting backup") 98 99 backup := &dpv1alpha1.Backup{} 100 if err := r.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, backup); err != nil { 101 if apierrors.IsNotFound(err) { 102 reqCtx.Log.Error(err, "backup ont found") 103 return ctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 104 } 105 } 106 107 // backup is being deleted, skip 108 if !backup.DeletionTimestamp.IsZero() { 109 reqCtx.Log.V(1).Info("backup is being deleted, skipping") 110 return ctrlutil.Reconciled() 111 } 112 113 reqCtx.Log.V(1).Info("gc reconcile", "backup", req.String(), 114 "phase", backup.Status.Phase, "expiration", backup.Status.Expiration) 115 reqCtx.Log = reqCtx.Log.WithValues("expiration", backup.Status.Expiration) 116 117 now := r.clock.Now() 118 if backup.Status.Expiration == nil || backup.Status.Expiration.After(now) { 119 reqCtx.Log.V(1).Info("backup is not expired yet, skipping") 120 return ctrlutil.Reconciled() 121 } 122 123 reqCtx.Log.Info("backup has expired, delete it", "backup", req.String()) 124 if err := ctrlutil.BackgroundDeleteObject(r.Client, reqCtx.Ctx, backup); err != nil { 125 reqCtx.Log.Error(err, "failed to delete backup") 126 r.Recorder.Event(backup, corev1.EventTypeWarning, "RemoveExpiredBackupsFailed", err.Error()) 127 return ctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 128 } 129 130 return ctrlutil.Reconciled() 131 } 132 133 func getGCFrequency() time.Duration { 134 gcFrequencySeconds := viper.GetInt(dptypes.CfgKeyGCFrequencySeconds) 135 if gcFrequencySeconds > 0 { 136 return time.Duration(gcFrequencySeconds) * time.Second 137 } 138 return dptypes.DefaultGCFrequencySeconds 139 }