github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/configuration/configuration_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 "fmt" 25 "strconv" 26 "time" 27 28 corev1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 utilerrors "k8s.io/apimachinery/pkg/util/errors" 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/log" 35 36 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 37 "github.com/1aal/kubeblocks/pkg/controller/component" 38 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 39 ) 40 41 // ConfigurationReconciler reconciles a Configuration object 42 type ConfigurationReconciler struct { 43 client.Client 44 Scheme *runtime.Scheme 45 Recorder record.EventRecorder 46 } 47 48 const reconcileInterval = time.Second * 2 49 50 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configurations,verbs=get;list;watch;create;update;patch;delete 51 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configurations/status,verbs=get;update;patch 52 // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=configurations/finalizers,verbs=update 53 54 // Reconcile is part of the main kubernetes reconciliation loop which aims to 55 // move the current state of the cluster closer to the desired state. 56 // TODO(user): Modify the Reconcile function to compare the state specified by 57 // the Configuration object against the actual cluster state, and then 58 // perform operations to make the cluster state reflect the state specified by 59 // the user. 60 // 61 // For more details, check Reconcile and its Result here: 62 // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile 63 func (r *ConfigurationReconciler) 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).WithName("ConfigurationReconcile").WithValues("configuration", req.NamespacedName), 68 Recorder: r.Recorder, 69 } 70 71 configuration := &appsv1alpha1.Configuration{} 72 if err := r.Client.Get(reqCtx.Ctx, reqCtx.Req.NamespacedName, configuration); err != nil { 73 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "cannot find configuration") 74 } 75 76 if !configuration.GetDeletionTimestamp().IsZero() { 77 reqCtx.Log.Info("configuration is deleting, skip reconcile") 78 return intctrlutil.Reconciled() 79 } 80 81 tasks := make([]Task, 0, len(configuration.Spec.ConfigItemDetails)) 82 for _, item := range configuration.Spec.ConfigItemDetails { 83 if status := fromItemStatus(reqCtx, &configuration.Status, item); status != nil { 84 tasks = append(tasks, NewTask(item, status)) 85 } 86 } 87 if len(tasks) == 0 { 88 return intctrlutil.Reconciled() 89 } 90 91 fetcherTask := &Task{} 92 err := fetcherTask.Init(&intctrlutil.ResourceCtx{ 93 Context: ctx, 94 Client: r.Client, 95 Namespace: configuration.Namespace, 96 ClusterName: configuration.Spec.ClusterRef, 97 ComponentName: configuration.Spec.ComponentName, 98 }, fetcherTask).Cluster(). 99 ClusterDef(). 100 ClusterVer(). 101 ClusterComponent(). 102 ClusterDefComponent(). 103 ClusterVerComponent(). 104 Complete() 105 if err != nil { 106 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to get related object.") 107 } 108 109 if fetcherTask.ClusterComObj == nil || fetcherTask.ClusterDefComObj == nil { 110 return r.failWithInvalidComponent(configuration, reqCtx) 111 } 112 113 if err := r.runTasks(TaskContext{configuration, reqCtx, fetcherTask}, tasks); err != nil { 114 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to run configuration reconcile task.") 115 } 116 if !isAllReady(configuration) { 117 return intctrlutil.RequeueAfter(reconcileInterval, reqCtx.Log, "") 118 } 119 return intctrlutil.Reconciled() 120 } 121 122 func (r *ConfigurationReconciler) failWithInvalidComponent(configuration *appsv1alpha1.Configuration, reqCtx intctrlutil.RequestCtx) (ctrl.Result, error) { 123 msg := fmt.Sprintf("not found cluster component or cluster definition component: [%s]", configuration.Spec.ComponentName) 124 reqCtx.Log.Error(fmt.Errorf(msg), "") 125 patch := client.MergeFrom(configuration.DeepCopy()) 126 configuration.Status.Message = msg 127 if err := r.Client.Status().Patch(reqCtx.Ctx, configuration, patch); err != nil { 128 return intctrlutil.CheckedRequeueWithError(err, reqCtx.Log, "failed to update configuration status.") 129 } 130 return intctrlutil.Reconciled() 131 } 132 133 func isAllReady(configuration *appsv1alpha1.Configuration) bool { 134 for _, item := range configuration.Spec.ConfigItemDetails { 135 itemStatus := configuration.Status.GetItemStatus(item.Name) 136 if itemStatus != nil && !isFinishStatus(itemStatus.Phase) { 137 return false 138 } 139 } 140 return true 141 } 142 143 func (r *ConfigurationReconciler) runTasks(taskCtx TaskContext, tasks []Task) (err error) { 144 var ( 145 errs []error 146 synthesizedComp *component.SynthesizedComponent 147 148 ctx = taskCtx.reqCtx.Ctx 149 configuration = taskCtx.configuration 150 ) 151 152 synthesizedComp, err = component.BuildComponent(taskCtx.reqCtx, 153 nil, 154 taskCtx.fetcher.ClusterObj, 155 taskCtx.fetcher.ClusterDefObj, 156 taskCtx.fetcher.ClusterDefComObj, 157 taskCtx.fetcher.ClusterComObj, 158 nil, 159 taskCtx.fetcher.ClusterVerComObj) 160 if err != nil { 161 return err 162 } 163 164 // TODO manager multiple version 165 patch := client.MergeFrom(configuration.DeepCopy()) 166 revision := strconv.FormatInt(configuration.GetGeneration(), 10) 167 for _, task := range tasks { 168 if err := task.Do(taskCtx.fetcher, synthesizedComp, revision); err != nil { 169 errs = append(errs, err) 170 continue 171 } 172 } 173 174 configuration.Status.Message = "" 175 if len(errs) > 0 { 176 configuration.Status.Message = utilerrors.NewAggregate(errs).Error() 177 } 178 if err := r.Client.Status().Patch(ctx, configuration, patch); err != nil { 179 errs = append(errs, err) 180 } 181 if len(errs) == 0 { 182 return nil 183 } 184 return utilerrors.NewAggregate(errs) 185 } 186 187 // SetupWithManager sets up the controller with the Manager. 188 func (r *ConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { 189 return ctrl.NewControllerManagedBy(mgr). 190 For(&appsv1alpha1.Configuration{}). 191 Owns(&corev1.ConfigMap{}). 192 Complete(r) 193 } 194 195 func fromItemStatus(ctx intctrlutil.RequestCtx, status *appsv1alpha1.ConfigurationStatus, item appsv1alpha1.ConfigurationItemDetail) *appsv1alpha1.ConfigurationItemDetailStatus { 196 if item.ConfigSpec == nil { 197 ctx.Log.V(1).WithName(item.Name).Info(fmt.Sprintf("configuration is creating and pass: %s", item.Name)) 198 return nil 199 } 200 itemStatus := status.GetItemStatus(item.Name) 201 if itemStatus == nil || itemStatus.Phase == "" { 202 ctx.Log.V(1).WithName(item.Name).Info(fmt.Sprintf("configuration cr is creating and pass: %v", item)) 203 return nil 204 } 205 if !isReconcileStatus(itemStatus.Phase) { 206 ctx.Log.V(1).WithName(item.Name).Info(fmt.Sprintf("configuration cr is creating or deleting and pass: %v", itemStatus)) 207 return nil 208 } 209 return itemStatus 210 } 211 212 func isReconcileStatus(phase appsv1alpha1.ConfigurationPhase) bool { 213 return phase != "" && 214 phase != appsv1alpha1.CCreatingPhase && 215 phase != appsv1alpha1.CDeletingPhase 216 } 217 218 func isFinishStatus(phase appsv1alpha1.ConfigurationPhase) bool { 219 return phase == appsv1alpha1.CFinishedPhase || phase == appsv1alpha1.CFailedAndPausePhase 220 }