github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controllerutil/controller_common.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 controllerutil 21 22 import ( 23 "context" 24 "fmt" 25 "reflect" 26 "strings" 27 "time" 28 29 "github.com/go-logr/logr" 30 corev1 "k8s.io/api/core/v1" 31 apierrors "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/conversion" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/client-go/tools/record" 36 ctrl "sigs.k8s.io/controller-runtime" 37 "sigs.k8s.io/controller-runtime/pkg/client" 38 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 39 "sigs.k8s.io/controller-runtime/pkg/reconcile" 40 41 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 42 "github.com/1aal/kubeblocks/pkg/constant" 43 ) 44 45 // ResultToP converts a Result object to a pointer. 46 func ResultToP(res reconcile.Result, err error) (*reconcile.Result, error) { 47 return &res, err 48 } 49 50 // Reconciled returns an empty result with nil error to signal a successful reconcile 51 // to the controller manager 52 func Reconciled() (reconcile.Result, error) { 53 return reconcile.Result{}, nil 54 } 55 56 // CheckedRequeueWithError passes the error through to the controller 57 // manager, it ignores unknown errors. 58 func CheckedRequeueWithError(err error, logger logr.Logger, msg string, keysAndValues ...interface{}) (reconcile.Result, error) { 59 if apierrors.IsNotFound(err) { 60 return Reconciled() 61 } 62 return RequeueWithError(err, logger, msg, keysAndValues...) 63 } 64 65 // RequeueWithErrorAndRecordEvent requeues when an error occurs. if it is an unknown error, triggers an event 66 func RequeueWithErrorAndRecordEvent(obj client.Object, recorder record.EventRecorder, err error, logger logr.Logger) (reconcile.Result, error) { 67 if apierrors.IsNotFound(err) && recorder != nil { 68 recorder.Eventf(obj, corev1.EventTypeWarning, constant.ReasonNotFoundCR, err.Error()) 69 } 70 return RequeueWithError(err, logger, "") 71 } 72 73 // RequeueWithError requeues when an error occurs 74 func RequeueWithError(err error, logger logr.Logger, msg string, keysAndValues ...interface{}) (reconcile.Result, error) { 75 if msg == "" { 76 logger.Info(err.Error()) 77 } else { 78 // Info log the error message and then let the reconciler dump the stacktrace 79 logger.Info(msg, keysAndValues...) 80 } 81 return reconcile.Result{}, err 82 } 83 84 func RequeueAfter(duration time.Duration, logger logr.Logger, msg string, keysAndValues ...interface{}) (reconcile.Result, error) { 85 keysAndValues = append(keysAndValues, "duration") 86 keysAndValues = append(keysAndValues, duration) 87 if msg != "" { 88 msg = fmt.Sprintf("reason: %s; retry-after", msg) 89 } else { 90 msg = "retry-after" 91 } 92 logger.V(1).Info(msg, keysAndValues...) 93 return reconcile.Result{ 94 Requeue: true, 95 RequeueAfter: duration, 96 }, nil 97 } 98 99 func Requeue(logger logr.Logger, msg string, keysAndValues ...interface{}) (reconcile.Result, error) { 100 if msg == "" { 101 msg = "requeue" 102 } 103 logger.V(1).Info(msg, keysAndValues...) 104 return reconcile.Result{Requeue: true}, nil 105 } 106 107 // HandleCRDeletion handles CR deletion, adds finalizer if found a non-deleting object and removes finalizer during 108 // deletion process. Passes optional 'deletionHandler' func for external dependency deletion. Returns Result pointer 109 // if required to return out of outer 'Reconcile' reconciliation loop. 110 func HandleCRDeletion(reqCtx RequestCtx, 111 r client.Writer, 112 cr client.Object, 113 finalizer string, 114 deletionHandler func() (*ctrl.Result, error)) (*ctrl.Result, error) { 115 // examine DeletionTimestamp to determine if object is under deletion 116 if cr.GetDeletionTimestamp().IsZero() { 117 // The object is not being deleted, so if it does not have our finalizer, 118 // then add the finalizer and update the object. This is equivalent to 119 // registering our finalizer. 120 if !controllerutil.ContainsFinalizer(cr, finalizer) { 121 controllerutil.AddFinalizer(cr, finalizer) 122 if err := r.Update(reqCtx.Ctx, cr); err != nil { 123 return ResultToP(CheckedRequeueWithError(err, reqCtx.Log, "")) 124 } 125 } 126 } else { 127 // The object is being deleted 128 if controllerutil.ContainsFinalizer(cr, finalizer) { 129 // We need to record the deletion event first. 130 // If the resource has dependencies, it will not be automatically deleted. 131 // It can also prevent users from manually deleting it without event records 132 if reqCtx.Recorder != nil { 133 cluster, ok := cr.(*v1alpha1.Cluster) 134 // throw warning event if terminationPolicy set to DoNotTerminate 135 if ok && cluster.Spec.TerminationPolicy == v1alpha1.DoNotTerminate { 136 reqCtx.Eventf(cr, corev1.EventTypeWarning, constant.ReasonDeleteFailed, 137 "Deleting %s: %s failed due to terminationPolicy set to DoNotTerminate", 138 strings.ToLower(cr.GetObjectKind().GroupVersionKind().Kind), cr.GetName()) 139 } else { 140 reqCtx.Eventf(cr, corev1.EventTypeNormal, constant.ReasonDeletingCR, "Deleting %s: %s", 141 strings.ToLower(cr.GetObjectKind().GroupVersionKind().Kind), cr.GetName()) 142 } 143 } 144 145 // our finalizer is present, so handle any external dependency 146 if deletionHandler != nil { 147 if res, err := deletionHandler(); err != nil { 148 // if failed to delete the external dependencies here, return with error 149 // so that it can be retried 150 if res == nil { 151 return ResultToP(CheckedRequeueWithError(err, reqCtx.Log, "")) 152 } 153 return res, err 154 } else if res != nil { 155 return res, nil 156 } 157 } 158 // remove our finalizer from the list and update it. 159 if controllerutil.RemoveFinalizer(cr, finalizer) { 160 if err := r.Update(reqCtx.Ctx, cr); err != nil { 161 return ResultToP(CheckedRequeueWithError(err, reqCtx.Log, "")) 162 } 163 // record resources deleted event 164 reqCtx.Eventf(cr, corev1.EventTypeNormal, constant.ReasonDeletedCR, "Deleted %s: %s", 165 strings.ToLower(cr.GetObjectKind().GroupVersionKind().Kind), cr.GetName()) 166 } 167 } 168 169 // Stop reconciliation as the item is being deleted 170 res, err := Reconciled() 171 return &res, err 172 } 173 return nil, nil 174 } 175 176 // ValidateReferenceCR validates existing referencing CRs, if exists, requeue reconcile after 30 seconds 177 func ValidateReferenceCR(reqCtx RequestCtx, cli client.Client, obj client.Object, 178 labelKey string, recordEvent func(), objLists ...client.ObjectList) (*ctrl.Result, error) { 179 for _, objList := range objLists { 180 // get referencing cr list 181 if err := cli.List(reqCtx.Ctx, objList, 182 client.MatchingLabels{labelKey: obj.GetName()}, client.Limit(1), 183 ); err != nil { 184 return nil, err 185 } 186 if v, err := conversion.EnforcePtr(objList); err != nil { 187 return nil, err 188 } else { 189 // check list items 190 items := v.FieldByName("Items") 191 if !items.IsValid() || items.Kind() != reflect.Slice || items.Len() == 0 { 192 continue 193 } 194 if recordEvent != nil { 195 recordEvent() 196 } 197 return ResultToP(RequeueAfter(time.Second, reqCtx.Log, "")) 198 } 199 } 200 return nil, nil 201 } 202 203 // RecordCreatedEvent records an event when a CR created successfully 204 func RecordCreatedEvent(r record.EventRecorder, cr client.Object) { 205 if r != nil && cr.GetGeneration() == 1 { 206 r.Eventf(cr, corev1.EventTypeNormal, constant.ReasonCreatedCR, "Created %s: %s", strings.ToLower(cr.GetObjectKind().GroupVersionKind().Kind), cr.GetName()) 207 } 208 } 209 210 // WorkloadFilterPredicate provides filter predicate for workload objects, i.e., deployment/statefulset/pod/pvc. 211 func WorkloadFilterPredicate(object client.Object) bool { 212 _, containCompNameLabelKey := object.GetLabels()[constant.KBAppComponentLabelKey] 213 return ManagedByKubeBlocksFilterPredicate(object) && containCompNameLabelKey 214 } 215 216 // ManagedByKubeBlocksFilterPredicate provides filter predicate for objects managed by kubeBlocks. 217 func ManagedByKubeBlocksFilterPredicate(object client.Object) bool { 218 return object.GetLabels()[constant.AppManagedByLabelKey] == constant.AppName 219 } 220 221 // IgnoreIsAlreadyExists returns errors if 'err' is not type of AlreadyExists 222 func IgnoreIsAlreadyExists(err error) error { 223 if !apierrors.IsAlreadyExists(err) { 224 return err 225 } 226 return nil 227 } 228 229 // BackgroundDeleteObject deletes the object in the background, usually used in the Reconcile method 230 func BackgroundDeleteObject(cli client.Client, ctx context.Context, obj client.Object) error { 231 deletePropagation := metav1.DeletePropagationBackground 232 deleteOptions := &client.DeleteOptions{ 233 PropagationPolicy: &deletePropagation, 234 } 235 236 if err := cli.Delete(ctx, obj, deleteOptions); err != nil { 237 return client.IgnoreNotFound(err) 238 } 239 return nil 240 } 241 242 // SetOwnership provides helper function controllerutil.SetControllerReference/controllerutil.SetOwnerReference 243 // and controllerutil.AddFinalizer if not exists. 244 func SetOwnership(owner, obj client.Object, scheme *runtime.Scheme, finalizer string, useOwnerReference ...bool) error { 245 if len(useOwnerReference) > 0 && useOwnerReference[0] { 246 if err := controllerutil.SetOwnerReference(owner, obj, scheme); err != nil { 247 return err 248 } 249 } else { 250 if err := controllerutil.SetControllerReference(owner, obj, scheme); err != nil { 251 return err 252 } 253 } 254 if !controllerutil.ContainsFinalizer(obj, finalizer) { 255 // pvc objects do not need to add finalizer 256 _, ok := obj.(*corev1.PersistentVolumeClaim) 257 if !ok { 258 if !controllerutil.AddFinalizer(obj, finalizer) { 259 return ErrFailedToAddFinalizer 260 } 261 } 262 } 263 return nil 264 } 265 266 // CheckResourceExists checks whether resource exist or not. 267 func CheckResourceExists( 268 ctx context.Context, 269 cli client.Client, 270 key client.ObjectKey, 271 obj client.Object) (bool, error) { 272 if err := cli.Get(ctx, key, obj); err != nil { 273 return false, client.IgnoreNotFound(err) 274 } 275 // if found, return true 276 return true, nil 277 }