github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/clusterversion_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 apps 21 22 import ( 23 "context" 24 "fmt" 25 "runtime" 26 "strings" 27 "time" 28 29 "golang.org/x/exp/slices" 30 corev1 "k8s.io/api/core/v1" 31 apierrors "k8s.io/apimachinery/pkg/api/errors" 32 "k8s.io/apimachinery/pkg/labels" 33 k8sruntime "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/apimachinery/pkg/types" 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" 39 "sigs.k8s.io/controller-runtime/pkg/log" 40 41 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 42 appsconfig "github.com/1aal/kubeblocks/controllers/apps/configuration" 43 "github.com/1aal/kubeblocks/pkg/constant" 44 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 45 viper "github.com/1aal/kubeblocks/pkg/viperx" 46 ) 47 48 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusterversions,verbs=get;list;watch;create;update;patch;delete 49 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusterversions/status,verbs=get;update;patch 50 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusterversions/finalizers,verbs=update 51 52 // ClusterVersionReconciler reconciles a ClusterVersion object 53 type ClusterVersionReconciler struct { 54 client.Client 55 Scheme *k8sruntime.Scheme 56 Recorder record.EventRecorder 57 } 58 59 func init() { 60 clusterDefUpdateHandlers["clusterVersion"] = clusterVersionUpdateHandler 61 viper.SetDefault(maxConcurReconClusterVersionKey, runtime.NumCPU()) 62 } 63 64 // Reconcile is part of the main kubernetes reconciliation loop which aims to 65 // move the current state of the cluster closer to the desired state. 66 // 67 // For more details, check Reconcile and its Result here: 68 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile 69 func (r *ClusterVersionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 70 reqCtx := intctrlutil.RequestCtx{ 71 Ctx: ctx, 72 Req: req, 73 Log: log.FromContext(ctx).WithValues("clusterDefinition", req.NamespacedName), 74 Recorder: r.Recorder, 75 } 76 77 clusterVersion := &appsv1alpha1.ClusterVersion{} 78 if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, clusterVersion); err != nil { 79 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 80 } 81 82 res, err := intctrlutil.HandleCRDeletion(reqCtx, r, clusterVersion, clusterVersionFinalizerName, func() (*ctrl.Result, error) { 83 recordEvent := func() { 84 r.Recorder.Event(clusterVersion, corev1.EventTypeWarning, constant.ReasonRefCRUnavailable, 85 "cannot be deleted because of existing referencing Cluster.") 86 } 87 if res, err := intctrlutil.ValidateReferenceCR(reqCtx, r.Client, clusterVersion, 88 constant.ClusterVerLabelKey, recordEvent, &appsv1alpha1.ClusterList{}); res != nil || err != nil { 89 return res, err 90 } 91 return nil, r.deleteExternalResources(reqCtx, clusterVersion) 92 }) 93 if res != nil { 94 return *res, err 95 } 96 97 if clusterVersion.Status.ObservedGeneration == clusterVersion.Generation && 98 slices.Contains(clusterVersion.Status.GetTerminalPhases(), clusterVersion.Status.Phase) { 99 return intctrlutil.Reconciled() 100 } 101 102 clusterdefinition := &appsv1alpha1.ClusterDefinition{} 103 if err := r.Client.Get(reqCtx.Ctx, types.NamespacedName{ 104 Name: clusterVersion.Spec.ClusterDefinitionRef, 105 }, clusterdefinition); err != nil { 106 if apierrors.IsNotFound(err) { 107 if res, patchErr := r.patchClusterDefLabel(reqCtx, clusterVersion); res != nil { 108 return *res, patchErr 109 } 110 if err = r.handleClusterDefNotFound(reqCtx, clusterVersion, err.Error()); err != nil { 111 return intctrlutil.RequeueWithErrorAndRecordEvent(clusterVersion, r.Recorder, err, reqCtx.Log) 112 } 113 return intctrlutil.Reconciled() 114 } 115 return intctrlutil.RequeueWithErrorAndRecordEvent(clusterVersion, r.Recorder, err, reqCtx.Log) 116 } 117 118 patchStatus := func(phase appsv1alpha1.Phase, message string) error { 119 patch := client.MergeFrom(clusterVersion.DeepCopy()) 120 clusterVersion.Status.Phase = phase 121 clusterVersion.Status.Message = message 122 clusterVersion.Status.ObservedGeneration = clusterVersion.Generation 123 clusterVersion.Status.ClusterDefGeneration = clusterdefinition.Generation 124 return r.Client.Status().Patch(ctx, clusterVersion, patch) 125 } 126 127 if statusMsg := validateClusterVersion(clusterVersion, clusterdefinition); statusMsg != "" { 128 if err := patchStatus(appsv1alpha1.UnavailablePhase, statusMsg); err != nil { 129 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 130 } 131 return intctrlutil.Reconciled() 132 } 133 134 if err = appsconfig.ReconcileConfigSpecsForReferencedCR(r.Client, reqCtx, clusterVersion); err != nil { 135 return intctrlutil.RequeueAfter(time.Second, reqCtx.Log, err.Error()) 136 } 137 138 if res, err = r.patchClusterDefLabel(reqCtx, clusterVersion); res != nil { 139 return *res, err 140 } 141 142 if err = patchStatus(appsv1alpha1.AvailablePhase, ""); err != nil { 143 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "") 144 } 145 intctrlutil.RecordCreatedEvent(r.Recorder, clusterVersion) 146 return intctrlutil.Reconciled() 147 } 148 149 // SetupWithManager sets up the controller with the Manager. 150 func (r *ClusterVersionReconciler) SetupWithManager(mgr ctrl.Manager) error { 151 return ctrl.NewControllerManagedBy(mgr). 152 For(&appsv1alpha1.ClusterVersion{}). 153 WithOptions(controller.Options{ 154 MaxConcurrentReconciles: viper.GetInt(maxConcurReconClusterVersionKey), 155 }). 156 Complete(r) 157 } 158 159 func (r *ClusterVersionReconciler) patchClusterDefLabel(reqCtx intctrlutil.RequestCtx, 160 clusterVersion *appsv1alpha1.ClusterVersion) (*ctrl.Result, error) { 161 if v, ok := clusterVersion.ObjectMeta.Labels[constant.ClusterDefLabelKey]; !ok || v != clusterVersion.Spec.ClusterDefinitionRef { 162 patch := client.MergeFrom(clusterVersion.DeepCopy()) 163 if clusterVersion.ObjectMeta.Labels == nil { 164 clusterVersion.ObjectMeta.Labels = map[string]string{} 165 } 166 clusterVersion.ObjectMeta.Labels[constant.ClusterDefLabelKey] = clusterVersion.Spec.ClusterDefinitionRef 167 if err := r.Client.Patch(reqCtx.Ctx, clusterVersion, patch); err != nil { 168 return intctrlutil.ResultToP(intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "")) 169 } 170 return intctrlutil.ResultToP(intctrlutil.Reconciled()) 171 } 172 return nil, nil 173 } 174 175 // handleClusterDefNotFound handles clusterVersion status when clusterDefinition not found. 176 func (r *ClusterVersionReconciler) handleClusterDefNotFound(reqCtx intctrlutil.RequestCtx, 177 clusterVersion *appsv1alpha1.ClusterVersion, message string) error { 178 if clusterVersion.Status.Message == message { 179 return nil 180 } 181 patch := client.MergeFrom(clusterVersion.DeepCopy()) 182 clusterVersion.Status.Phase = appsv1alpha1.UnavailablePhase 183 clusterVersion.Status.Message = message 184 return r.Client.Status().Patch(reqCtx.Ctx, clusterVersion, patch) 185 } 186 187 func validateClusterVersion(clusterVersion *appsv1alpha1.ClusterVersion, clusterDef *appsv1alpha1.ClusterDefinition) string { 188 notFoundComponentDefNames, noContainersComponents := clusterVersion.GetInconsistentComponentsInfo(clusterDef) 189 var statusMsgs []string 190 if len(notFoundComponentDefNames) > 0 { 191 statusMsgs = append(statusMsgs, fmt.Sprintf("spec.componentSpecs[*].componentDefRef %v not found in ClusterDefinition.spec.componentDefs[*].name", notFoundComponentDefNames)) 192 } else if len(noContainersComponents) > 0 { 193 statusMsgs = append(statusMsgs, fmt.Sprintf("spec.componentSpecs[*].componentDefRef %v missing spec.componentSpecs[*].containers in ClusterDefinition.spec.componentDefs[*] and ClusterVersion.spec.componentVersions[*]", noContainersComponents)) 194 } 195 return strings.Join(statusMsgs, ";") 196 } 197 198 func (r *ClusterVersionReconciler) deleteExternalResources(reqCtx intctrlutil.RequestCtx, clusterVersion *appsv1alpha1.ClusterVersion) error { 199 // 200 // delete any external resources associated with the cronJob 201 // 202 // Ensure that delete implementation is idempotent and safe to invoke 203 // multiple times for same object. 204 return appsconfig.DeleteConfigMapFinalizer(r.Client, reqCtx, clusterVersion) 205 } 206 207 func clusterVersionUpdateHandler(cli client.Client, ctx context.Context, clusterDef *appsv1alpha1.ClusterDefinition) error { 208 labelSelector, err := labels.Parse(constant.ClusterDefLabelKey + "=" + clusterDef.GetName()) 209 if err != nil { 210 return err 211 } 212 o := &client.ListOptions{LabelSelector: labelSelector} 213 214 list := &appsv1alpha1.ClusterVersionList{} 215 if err := cli.List(ctx, list, o); err != nil { 216 return err 217 } 218 for _, item := range list.Items { 219 if item.Status.ClusterDefGeneration != clusterDef.Generation { 220 patch := client.MergeFrom(item.DeepCopy()) 221 if statusMsg := validateClusterVersion(&item, clusterDef); statusMsg != "" { 222 item.Status.Phase = appsv1alpha1.UnavailablePhase 223 item.Status.Message = statusMsg 224 } else { 225 item.Status.Phase = appsv1alpha1.AvailablePhase 226 item.Status.Message = "" 227 item.Status.ClusterDefGeneration = clusterDef.Generation 228 } 229 if err = cli.Status().Patch(ctx, &item, patch); err != nil { 230 return err 231 } 232 } 233 } 234 235 return nil 236 }