github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/operations/reconfigure.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 operations 21 22 import ( 23 "time" 24 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 29 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 30 "github.com/1aal/kubeblocks/pkg/configuration/core" 31 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 32 ) 33 34 type reconfigureAction struct { 35 } 36 37 func init() { 38 reAction := reconfigureAction{} 39 opsManager := GetOpsManager() 40 reconfigureBehaviour := OpsBehaviour{ 41 // REVIEW: can do opsrequest if not running? 42 FromClusterPhases: appsv1alpha1.GetReconfiguringRunningPhases(), 43 // TODO: add cluster reconcile Reconfiguring phase. 44 ToClusterPhase: appsv1alpha1.UpdatingClusterPhase, 45 OpsHandler: &reAction, 46 ProcessingReasonInClusterCondition: ProcessingReasonReconfiguring, 47 } 48 opsManager.RegisterOps(appsv1alpha1.ReconfiguringType, reconfigureBehaviour) 49 } 50 51 // ActionStartedCondition the started condition when handle the reconfiguring request. 52 func (r *reconfigureAction) ActionStartedCondition(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (*metav1.Condition, error) { 53 return appsv1alpha1.NewReconfigureCondition(opsRes.OpsRequest), nil 54 } 55 56 func (r *reconfigureAction) SaveLastConfiguration(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) error { 57 return nil 58 } 59 60 func handleReconfigureStatusProgress(result *appsv1alpha1.ReconcileDetail, opsStatus *appsv1alpha1.OpsRequestStatus, phase appsv1alpha1.ConfigurationPhase) handleReconfigureOpsStatus { 61 return func(cmStatus *appsv1alpha1.ConfigurationItemStatus) (err error) { 62 // the Pending phase is waiting to be executed, and there is currently no valid ReconcileDetail information. 63 if result != nil && phase != appsv1alpha1.CPendingPhase { 64 cmStatus.LastAppliedStatus = result.ExecResult 65 cmStatus.UpdatePolicy = appsv1alpha1.UpgradePolicy(result.Policy) 66 cmStatus.SucceedCount = result.SucceedCount 67 cmStatus.ExpectedCount = result.ExpectedCount 68 cmStatus.Message = result.ErrMessage 69 cmStatus.Status = string(phase) 70 } 71 if cmStatus.SucceedCount != core.Unconfirmed && cmStatus.ExpectedCount != core.Unconfirmed { 72 opsStatus.Progress = getSlowestReconfiguringProgress(opsStatus.ReconfiguringStatus.ConfigurationStatus) 73 } 74 return 75 } 76 } 77 78 func handleNewReconfigureRequest(configPatch *core.ConfigPatchInfo, lastAppliedConfigs map[string]string) handleReconfigureOpsStatus { 79 return func(cmStatus *appsv1alpha1.ConfigurationItemStatus) (err error) { 80 cmStatus.Status = appsv1alpha1.ReasonReconfigurePersisted 81 cmStatus.LastAppliedConfiguration = lastAppliedConfigs 82 if configPatch != nil { 83 cmStatus.UpdatedParameters = appsv1alpha1.UpdatedParameters{ 84 AddedKeys: i2sMap(configPatch.AddConfig), 85 UpdatedKeys: b2sMap(configPatch.UpdateConfig), 86 DeletedKeys: i2sMap(configPatch.DeleteConfig), 87 } 88 } 89 return 90 } 91 } 92 93 func (r *reconfigureAction) syncDependResources(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (*intctrlutil.Fetcher, error) { 94 ops := &opsRes.OpsRequest.Spec 95 configSpec := ops.Reconfigure.Configurations[0] 96 fetcher := intctrlutil.NewResourceFetcher(&intctrlutil.ResourceCtx{ 97 Context: reqCtx.Ctx, 98 Client: cli, 99 Namespace: opsRes.Cluster.Namespace, 100 ClusterName: ops.ClusterRef, 101 ComponentName: ops.Reconfigure.ComponentName, 102 }) 103 104 err := fetcher.Cluster(). 105 ClusterDef(). 106 ClusterVer(). 107 Configuration(). 108 ConfigMap(configSpec.Name). 109 Complete() 110 if err != nil { 111 return nil, err 112 } 113 return fetcher, nil 114 } 115 116 func (r *reconfigureAction) ReconcileAction(reqCtx intctrlutil.RequestCtx, cli client.Client, opsRes *OpsResource) (appsv1alpha1.OpsPhase, time.Duration, error) { 117 status := opsRes.OpsRequest.Status 118 if len(status.Conditions) == 0 { 119 return status.Phase, 30 * time.Second, nil 120 } 121 if isNoChange(status.Conditions) { 122 return appsv1alpha1.OpsSucceedPhase, 0, nil 123 } 124 125 ops := &opsRes.OpsRequest.Spec 126 if ops.Reconfigure == nil || len(ops.Reconfigure.Configurations) == 0 { 127 return appsv1alpha1.OpsFailedPhase, 0, nil 128 } 129 130 resource, err := r.syncDependResources(reqCtx, cli, opsRes) 131 if err != nil { 132 return "", 30 * time.Second, err 133 } 134 configSpec := ops.Reconfigure.Configurations[0] 135 item := resource.ConfigurationObj.Spec.GetConfigurationItem(configSpec.Name) 136 itemStatus := resource.ConfigurationObj.Status.GetItemStatus(configSpec.Name) 137 if item == nil || itemStatus == nil { 138 return appsv1alpha1.OpsRunningPhase, 30 * time.Second, nil 139 } 140 141 switch phase := reconfiguringPhase(resource, *item, itemStatus); phase { 142 case appsv1alpha1.CCreatingPhase, appsv1alpha1.CInitPhase: 143 return appsv1alpha1.OpsFailedPhase, 0, core.MakeError("the configuration is creating or initializing, is not ready to reconfigure") 144 case appsv1alpha1.CFailedAndPausePhase: 145 return syncStatus(cli, reqCtx, opsRes, itemStatus, phase, appsv1alpha1.OpsFailedPhase, appsv1alpha1.ReasonReconfigureFailed) 146 case appsv1alpha1.CFinishedPhase: 147 return syncStatus(cli, reqCtx, opsRes, itemStatus, phase, appsv1alpha1.OpsSucceedPhase, appsv1alpha1.ReasonReconfigureSucceed) 148 default: 149 return syncStatus(cli, reqCtx, opsRes, itemStatus, phase, appsv1alpha1.OpsRunningPhase, appsv1alpha1.ReasonReconfigureRunning) 150 } 151 } 152 153 func (r *reconfigureAction) Action(reqCtx intctrlutil.RequestCtx, cli client.Client, resource *OpsResource) error { 154 var ( 155 opsRequest = resource.OpsRequest 156 spec = &opsRequest.Spec 157 clusterName = spec.ClusterRef 158 componentName = spec.Reconfigure.ComponentName 159 reconfigure = spec.Reconfigure 160 ) 161 162 if !needReconfigure(opsRequest) { 163 return nil 164 } 165 166 // TODO support multi tpl conditions merge 167 item := reconfigure.Configurations[0] 168 opsPipeline := newPipeline(reconfigureContext{ 169 cli: cli, 170 reqCtx: reqCtx, 171 resource: resource, 172 config: item, 173 clusterName: clusterName, 174 componentName: componentName, 175 }) 176 177 result := opsPipeline. 178 Configuration(). 179 Validate(). 180 ConfigMap(item.Name). 181 ConfigConstraints(). 182 Merge(). 183 UpdateOpsLabel(). 184 Sync(). 185 Complete() 186 187 if result.err != nil { 188 return processMergedFailed(resource, result.failed, result.err) 189 } 190 191 reqCtx.Recorder.Eventf(resource.OpsRequest, 192 corev1.EventTypeNormal, 193 appsv1alpha1.ReasonReconfigurePersisted, 194 "the reconfiguring operation of component[%s] in cluster[%s] merged successfully", componentName, clusterName) 195 196 // merged successfully 197 if err := patchReconfigureOpsStatus(resource, opsPipeline.configSpec.Name, 198 handleNewReconfigureRequest(result.configPatch, result.lastAppliedConfigs)); err != nil { 199 return err 200 } 201 condition := constructReconfiguringConditions(result, resource, opsPipeline.configSpec) 202 resource.OpsRequest.SetStatusCondition(*condition) 203 return nil 204 } 205 206 func needReconfigure(request *appsv1alpha1.OpsRequest) bool { 207 // Update params to configmap 208 if request.Spec.Type != appsv1alpha1.ReconfiguringType || 209 request.Spec.Reconfigure == nil || 210 len(request.Spec.Reconfigure.Configurations) == 0 { 211 return false 212 } 213 214 // Check if the reconfiguring operation has been processed. 215 for _, condition := range request.Status.Conditions { 216 if isExpectedPhase(condition, []string{appsv1alpha1.ReasonReconfigurePersisted, appsv1alpha1.ReasonReconfigureNoChanged}, metav1.ConditionTrue) { 217 return false 218 } 219 } 220 return true 221 } 222 223 func syncStatus(cli client.Client, 224 reqCtx intctrlutil.RequestCtx, 225 opsRes *OpsResource, 226 status *appsv1alpha1.ConfigurationItemDetailStatus, 227 phase appsv1alpha1.ConfigurationPhase, 228 opsPhase appsv1alpha1.OpsPhase, 229 reconfigurePhase string) (appsv1alpha1.OpsPhase, time.Duration, error) { 230 opsDeepCopy := opsRes.OpsRequest.DeepCopy() 231 if err := patchReconfigureOpsStatus(opsRes, status.Name, 232 handleReconfigureStatusProgress(status.ReconcileDetail, &opsRes.OpsRequest.Status, phase)); err != nil { 233 return "", 30 * time.Second, err 234 } 235 if err := PatchOpsStatusWithOpsDeepCopy(reqCtx.Ctx, cli, opsRes, opsDeepCopy, appsv1alpha1.OpsRunningPhase, 236 appsv1alpha1.NewReconfigureRunningCondition(opsRes.OpsRequest, reconfigurePhase, status.Name)); err != nil { 237 return "", 30 * time.Second, err 238 } 239 return opsPhase, 30 * time.Second, nil 240 } 241 242 func reconfiguringPhase(resource *intctrlutil.Fetcher, 243 detail appsv1alpha1.ConfigurationItemDetail, 244 status *appsv1alpha1.ConfigurationItemDetailStatus) appsv1alpha1.ConfigurationPhase { 245 if status.ReconcileDetail == nil || status.ReconcileDetail.CurrentRevision != status.UpdateRevision { 246 return appsv1alpha1.CPendingPhase 247 } 248 return intctrlutil.GetConfigSpecReconcilePhase(resource.ConfigMapObj, detail, status) 249 } 250 251 func isExpectedPhase(condition metav1.Condition, expectedTypes []string, expectedStatus metav1.ConditionStatus) bool { 252 for _, t := range expectedTypes { 253 if t == condition.Type && condition.Status == expectedStatus { 254 return true 255 } 256 } 257 return false 258 } 259 260 func isNoChange(conditions []metav1.Condition) bool { 261 for i := len(conditions); i > 0; i-- { 262 if isExpectedPhase(conditions[i-1], []string{appsv1alpha1.ReasonReconfigureNoChanged}, metav1.ConditionTrue) { 263 return true 264 } 265 } 266 return false 267 }