github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/configuration/reconfigure_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 configuration 21 22 import ( 23 "context" 24 "encoding/json" 25 "fmt" 26 "strings" 27 "time" 28 29 corev1 "k8s.io/api/core/v1" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/client-go/tools/record" 33 ctrl "sigs.k8s.io/controller-runtime" 34 "sigs.k8s.io/controller-runtime/pkg/client" 35 "sigs.k8s.io/controller-runtime/pkg/log" 36 "sigs.k8s.io/controller-runtime/pkg/predicate" 37 38 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 39 cfgcm "github.com/1aal/kubeblocks/pkg/configuration/config_manager" 40 "github.com/1aal/kubeblocks/pkg/configuration/core" 41 "github.com/1aal/kubeblocks/pkg/constant" 42 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 43 ) 44 45 // ReconfigureReconciler reconciles a ReconfigureRequest object 46 type ReconfigureReconciler struct { 47 client.Client 48 Scheme *runtime.Scheme 49 Recorder record.EventRecorder 50 } 51 52 const ( 53 ConfigReconcileInterval = time.Second * 1 54 ) 55 56 const ( 57 configurationNoChangedMessage = "the configuration file has not been modified, skip reconfigure" 58 configurationNotUsingMessage = "the configmap is not used by any container, skip reconfigure" 59 configurationNotRelatedComponentMessage = "related component does not found any configSpecs, skip reconfigure" 60 ) 61 62 var ConfigurationRequiredLabels = []string{ 63 constant.AppNameLabelKey, 64 constant.AppInstanceLabelKey, 65 constant.KBAppComponentLabelKey, 66 constant.CMConfigurationTemplateNameLabelKey, 67 constant.CMConfigurationTypeLabelKey, 68 constant.CMConfigurationSpecProviderLabelKey, 69 } 70 71 // +kubebuilder:rbac:groups=core,resources=configmap,verbs=get;list;watch;create;update;patch;delete 72 // +kubebuilder:rbac:groups=core,resources=configmap/finalizers,verbs=update 73 74 // Reconcile is part of the main kubernetes reconciliation loop which aims to 75 // move the current state of the cluster closer to the desired state. 76 // TODO(user): Modify the Reconcile function to compare the state specified by 77 // the ReconfigureRequest object against the actual cluster state, and then 78 // perform operations to make the cluster state reflect the state specified by 79 // the user. 80 // 81 // For more details, check Reconcile and its Result here: 82 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.2/pkg/reconcile 83 func (r *ReconfigureReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 84 reqCtx := intctrlutil.RequestCtx{ 85 Ctx: ctx, 86 Req: req, 87 Log: log.FromContext(ctx).WithName("ReconfigureRequestReconcile").WithValues("ConfigMap", req.NamespacedName), 88 Recorder: r.Recorder, 89 } 90 91 config := &corev1.ConfigMap{} 92 if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, config); err != nil { 93 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "cannot find configmap") 94 } 95 96 if !checkConfigurationObject(config) { 97 return intctrlutil.Reconciled() 98 } 99 100 reqCtx.Log = reqCtx.Log. 101 WithValues("ClusterName", config.Labels[constant.AppInstanceLabelKey]). 102 WithValues("ComponentName", config.Labels[constant.KBAppComponentLabelKey]) 103 if hash, ok := config.Labels[constant.CMInsConfigurationHashLabelKey]; ok && hash == config.ResourceVersion { 104 return intctrlutil.Reconciled() 105 } 106 107 isAppliedConfigs, err := checkAndApplyConfigsChanged(r.Client, reqCtx, config) 108 if err != nil { 109 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to check last-applied-configuration") 110 } else if isAppliedConfigs { 111 return updateConfigPhase(r.Client, reqCtx, config, appsv1alpha1.CFinishedPhase, configurationNoChangedMessage) 112 } 113 114 // process configuration without ConfigConstraints 115 cfgConstraintsName, ok := config.Labels[constant.CMConfigurationConstraintsNameLabelKey] 116 if !ok || cfgConstraintsName == "" { 117 reqCtx.Log.Info("configuration without ConfigConstraints.") 118 return r.sync(reqCtx, config, &appsv1alpha1.ConfigConstraint{}) 119 } 120 121 // process configuration with ConfigConstraints 122 key := types.NamespacedName{ 123 Namespace: config.Namespace, 124 Name: config.Labels[constant.CMConfigurationConstraintsNameLabelKey], 125 } 126 tpl := &appsv1alpha1.ConfigConstraint{} 127 if err := r.Client.Get(reqCtx.Ctx, key, tpl); err != nil { 128 return intctrlutil.RequeueWithErrorAndRecordEvent(config, r.Recorder, err, reqCtx.Log) 129 } 130 return r.sync(reqCtx, config, tpl) 131 } 132 133 // SetupWithManager sets up the controller with the Manager. 134 func (r *ReconfigureReconciler) SetupWithManager(mgr ctrl.Manager) error { 135 return ctrl.NewControllerManagedBy(mgr). 136 For(&corev1.ConfigMap{}). 137 WithEventFilter(predicate.NewPredicateFuncs(checkConfigurationObject)). 138 Complete(r) 139 } 140 141 func checkConfigurationObject(object client.Object) bool { 142 return checkConfigLabels(object, ConfigurationRequiredLabels) 143 } 144 145 func (r *ReconfigureReconciler) sync(reqCtx intctrlutil.RequestCtx, configMap *corev1.ConfigMap, configConstraint *appsv1alpha1.ConfigConstraint) (ctrl.Result, error) { 146 147 var ( 148 componentName = configMap.Labels[constant.KBAppComponentLabelKey] 149 configSpecName = configMap.Labels[constant.CMConfigurationSpecProviderLabelKey] 150 ) 151 152 componentLabels := map[string]string{ 153 constant.AppNameLabelKey: configMap.Labels[constant.AppNameLabelKey], 154 constant.AppInstanceLabelKey: configMap.Labels[constant.AppInstanceLabelKey], 155 constant.KBAppComponentLabelKey: configMap.Labels[constant.KBAppComponentLabelKey], 156 } 157 158 var keySelector []string 159 if keysLabel, ok := configMap.Labels[constant.CMConfigurationCMKeysLabelKey]; ok && keysLabel != "" { 160 keySelector = strings.Split(keysLabel, ",") 161 } 162 163 configPatch, forceRestart, err := createConfigPatch(configMap, configConstraint.Spec.FormatterConfig, keySelector) 164 if err != nil { 165 return intctrlutil.RequeueWithErrorAndRecordEvent(configMap, r.Recorder, err, reqCtx.Log) 166 } 167 168 // No parameters updated 169 if configPatch != nil && !configPatch.IsModify { 170 reqCtx.Recorder.Eventf(configMap, corev1.EventTypeNormal, appsv1alpha1.ReasonReconfigureRunning, 171 "nothing changed, skip reconfigure") 172 return r.updateConfigCMStatus(reqCtx, configMap, core.ReconfigureNoChangeType, nil) 173 } 174 175 if configPatch != nil { 176 reqCtx.Log.V(1).Info(fmt.Sprintf( 177 "reconfigure params: \n\tadd: %s\n\tdelete: %s\n\tupdate: %s", 178 configPatch.AddConfig, 179 configPatch.DeleteConfig, 180 configPatch.UpdateConfig)) 181 } 182 183 reconcileContext := newConfigReconcileContext( 184 &intctrlutil.ResourceCtx{ 185 Context: reqCtx.Ctx, 186 Client: r.Client, 187 Namespace: configMap.Namespace, 188 ClusterName: configMap.Labels[constant.AppInstanceLabelKey], 189 ComponentName: componentName, 190 }, 191 configMap, 192 configConstraint, 193 configSpecName, 194 componentLabels) 195 if err := reconcileContext.GetRelatedObjects(); err != nil { 196 return intctrlutil.RequeueWithErrorAndRecordEvent(configMap, r.Recorder, err, reqCtx.Log) 197 } 198 199 // Assumption: It is required that the cluster must have a component. 200 if reconcileContext.ClusterComObj == nil { 201 reqCtx.Log.Info("not found component.") 202 return intctrlutil.Reconciled() 203 } 204 if reconcileContext.ConfigSpec == nil { 205 reqCtx.Log.Info(fmt.Sprintf("not found configSpec[%s] in the component[%s].", configSpecName, componentName)) 206 reqCtx.Recorder.Eventf(configMap, 207 corev1.EventTypeWarning, 208 appsv1alpha1.ReasonReconfigureFailed, 209 configurationNotRelatedComponentMessage) 210 return updateConfigPhase(r.Client, reqCtx, configMap, appsv1alpha1.CFinishedPhase, configurationNotRelatedComponentMessage) 211 } 212 if len(reconcileContext.StatefulSets) == 0 && len(reconcileContext.Deployments) == 0 { 213 reqCtx.Recorder.Eventf(configMap, 214 corev1.EventTypeWarning, appsv1alpha1.ReasonReconfigureFailed, 215 "the configmap is not used by any container, skip reconfigure") 216 return updateConfigPhase(r.Client, reqCtx, configMap, appsv1alpha1.CFinishedPhase, configurationNotUsingMessage) 217 } 218 219 return r.performUpgrade(reconfigureParams{ 220 ConfigSpecName: configSpecName, 221 ConfigPatch: configPatch, 222 ConfigMap: configMap, 223 ConfigConstraint: &configConstraint.Spec, 224 Client: r.Client, 225 Ctx: reqCtx, 226 Cluster: reconcileContext.ClusterObj, 227 ContainerNames: reconcileContext.Containers, 228 ComponentUnits: reconcileContext.StatefulSets, 229 DeploymentUnits: reconcileContext.Deployments, 230 RSMList: reconcileContext.RSMList, 231 Component: reconcileContext.ClusterDefComObj, 232 ClusterComponent: reconcileContext.ClusterComObj, 233 Restart: forceRestart || !cfgcm.IsSupportReload(configConstraint.Spec.ReloadOptions), 234 ReconfigureClientFactory: GetClientFactory(), 235 }) 236 } 237 238 func (r *ReconfigureReconciler) updateConfigCMStatus(reqCtx intctrlutil.RequestCtx, cfg *corev1.ConfigMap, reconfigureType string, result *intctrlutil.Result) (ctrl.Result, error) { 239 configData, err := json.Marshal(cfg.Data) 240 if err != nil { 241 return intctrlutil.RequeueWithErrorAndRecordEvent(cfg, r.Recorder, err, reqCtx.Log) 242 } 243 244 if ok, err := updateAppliedConfigs(r.Client, reqCtx, cfg, configData, reconfigureType, result); err != nil || !ok { 245 return intctrlutil.RequeueAfter(ConfigReconcileInterval, reqCtx.Log, "failed to patch status and retry...", "error", err) 246 } 247 248 return intctrlutil.Reconciled() 249 } 250 251 func (r *ReconfigureReconciler) performUpgrade(params reconfigureParams) (ctrl.Result, error) { 252 policy, err := NewReconfigurePolicy(params.ConfigConstraint, params.ConfigPatch, getUpgradePolicy(params.ConfigMap), params.Restart) 253 if err != nil { 254 return intctrlutil.RequeueWithErrorAndRecordEvent(params.ConfigMap, r.Recorder, err, params.Ctx.Log) 255 } 256 257 returnedStatus, err := policy.Upgrade(params) 258 if err != nil { 259 params.Ctx.Log.Error(err, "failed to update engine parameters") 260 } 261 262 switch returnedStatus.Status { 263 default: 264 return updateConfigPhaseWithResult( 265 params.Client, 266 params.Ctx, 267 params.ConfigMap, 268 reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedAndPausePhase, 269 withFailed(core.MakeError("unknown status"), false)), 270 ) 271 case ESFailedAndRetry: 272 return updateConfigPhaseWithResult( 273 params.Client, 274 params.Ctx, 275 params.ConfigMap, 276 reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedPhase, 277 withFailed(err, true)), 278 ) 279 case ESRetry: 280 return updateConfigPhaseWithResult( 281 params.Client, 282 params.Ctx, 283 params.ConfigMap, 284 reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CUpgradingPhase), 285 ) 286 case ESFailed: 287 return updateConfigPhaseWithResult( 288 params.Client, 289 params.Ctx, 290 params.ConfigMap, 291 reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFailedAndPausePhase, 292 withFailed(err, false)), 293 ) 294 case ESNone: 295 params.Ctx.Recorder.Eventf( 296 params.ConfigMap, 297 corev1.EventTypeNormal, 298 appsv1alpha1.ReasonReconfigureSucceed, 299 "the reconfigure[%s] request[%s] has been processed successfully", 300 policy.GetPolicyName(), 301 getOpsRequestID(params.ConfigMap)) 302 result := reconciled(returnedStatus, policy.GetPolicyName(), appsv1alpha1.CFinishedPhase) 303 return r.updateConfigCMStatus(params.Ctx, params.ConfigMap, policy.GetPolicyName(), &result) 304 } 305 } 306 307 func getOpsRequestID(cm *corev1.ConfigMap) string { 308 if len(cm.Annotations) != 0 { 309 return cm.Annotations[constant.LastAppliedOpsCRAnnotationKey] 310 } 311 return "" 312 }