github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/configuration/reconfigure_policy.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 "math" 24 25 "google.golang.org/grpc" 26 "google.golang.org/grpc/credentials/insecure" 27 appsv1 "k8s.io/api/apps/v1" 28 corev1 "k8s.io/api/core/v1" 29 "sigs.k8s.io/controller-runtime/pkg/client" 30 31 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 32 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 33 "github.com/1aal/kubeblocks/pkg/configuration/core" 34 cfgproto "github.com/1aal/kubeblocks/pkg/configuration/proto" 35 "github.com/1aal/kubeblocks/pkg/configuration/util" 36 "github.com/1aal/kubeblocks/pkg/constant" 37 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 38 viper "github.com/1aal/kubeblocks/pkg/viperx" 39 ) 40 41 // ExecStatus defines running result for Reconfiguring policy (fsm). 42 // ESNone describes policy has finished and quit. 43 // ESRetry describes fsm is running. 44 // ESFailed describes fsm is failed and exited. 45 // ESNotSupport describes fsm does not support the feature. 46 // ESFailedAndRetry describes fsm is failed in current state, but can be retried. 47 // +enum 48 type ExecStatus string 49 50 const ( 51 ESNone ExecStatus = "None" 52 ESRetry ExecStatus = "Retry" 53 ESFailed ExecStatus = "Failed" 54 ESNotSupport ExecStatus = "NotSupport" 55 ESFailedAndRetry ExecStatus = "FailedAndRetry" 56 ) 57 58 type ReturnedStatus struct { 59 Status ExecStatus 60 SucceedCount int32 61 ExpectedCount int32 62 } 63 64 type reconfigurePolicy interface { 65 // Upgrade is to enable the configuration to take effect. 66 Upgrade(params reconfigureParams) (ReturnedStatus, error) 67 68 // GetPolicyName returns name of policy. 69 GetPolicyName() string 70 } 71 72 type AutoReloadPolicy struct{} 73 74 type reconfigureParams struct { 75 // Only supports restart pod or container. 76 Restart bool 77 78 // Name is a config template name. 79 ConfigSpecName string 80 81 // Configuration files patch. 82 ConfigPatch *core.ConfigPatchInfo 83 84 // Configmap object of the configuration template instance in the component. 85 ConfigMap *corev1.ConfigMap 86 87 // ConfigConstraint pointer 88 ConfigConstraint *appsv1alpha1.ConfigConstraintSpec 89 90 // For grpc factory 91 ReconfigureClientFactory createReconfigureClient 92 93 // List of containers using this config volume. 94 ContainerNames []string 95 96 Client client.Client 97 Ctx intctrlutil.RequestCtx 98 99 Cluster *appsv1alpha1.Cluster 100 101 // Associated component for cluster. 102 ClusterComponent *appsv1alpha1.ClusterComponentSpec 103 // Associated component for clusterdefinition. 104 Component *appsv1alpha1.ClusterComponentDefinition 105 106 // List of StatefulSets using this config template. 107 ComponentUnits []appsv1.StatefulSet 108 // List of Deployment using this config template. 109 DeploymentUnits []appsv1.Deployment 110 // List of ReplicatedStateMachine using this config template. 111 RSMList []workloads.ReplicatedStateMachine 112 } 113 114 var ( 115 // lazy creation of grpc connection 116 // TODO support connection pool 117 newGRPCClient = func(addr string) (cfgproto.ReconfigureClient, error) { 118 conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials())) 119 if err != nil { 120 return nil, err 121 } 122 return cfgproto.NewReconfigureClient(conn), nil 123 } 124 ) 125 126 var upgradePolicyMap = map[appsv1alpha1.UpgradePolicy]reconfigurePolicy{} 127 128 func init() { 129 RegisterPolicy(appsv1alpha1.AutoReload, &AutoReloadPolicy{}) 130 } 131 132 func (param *reconfigureParams) WorkloadType() appsv1alpha1.WorkloadType { 133 return param.Component.WorkloadType 134 } 135 136 // GetClientFactory support ut mock 137 func GetClientFactory() createReconfigureClient { 138 return newGRPCClient 139 } 140 141 func (param *reconfigureParams) getConfigKey() string { 142 return param.ConfigSpecName 143 } 144 145 func (param *reconfigureParams) getTargetVersionHash() string { 146 hash, err := util.ComputeHash(param.ConfigMap.Data) 147 if err != nil { 148 param.Ctx.Log.Error(err, "failed to get configuration version!") 149 return "" 150 } 151 152 return hash 153 } 154 155 func (param *reconfigureParams) maxRollingReplicas() int32 { 156 var ( 157 defaultRolling int32 = 1 158 r int32 159 replicas = param.getTargetReplicas() 160 ) 161 162 if param.Component.GetMaxUnavailable() == nil { 163 return defaultRolling 164 } 165 166 v, isPercentage, err := intctrlutil.GetIntOrPercentValue(param.Component.GetMaxUnavailable()) 167 if err != nil { 168 param.Ctx.Log.Error(err, "failed to get maxUnavailable!") 169 return defaultRolling 170 } 171 172 if isPercentage { 173 r = int32(math.Floor(float64(v) * float64(replicas) / 100)) 174 } else { 175 r = util.Safe2Int32(util.Min(v, param.getTargetReplicas())) 176 } 177 return util.Max(r, defaultRolling) 178 } 179 180 func (param *reconfigureParams) getTargetReplicas() int { 181 return int(param.ClusterComponent.Replicas) 182 } 183 184 func (param *reconfigureParams) podMinReadySeconds() int32 { 185 minReadySeconds := param.ComponentUnits[0].Spec.MinReadySeconds 186 return util.Max(minReadySeconds, viper.GetInt32(constant.PodMinReadySecondsEnv)) 187 } 188 189 func RegisterPolicy(policy appsv1alpha1.UpgradePolicy, action reconfigurePolicy) { 190 upgradePolicyMap[policy] = action 191 } 192 193 func (receiver AutoReloadPolicy) Upgrade(params reconfigureParams) (ReturnedStatus, error) { 194 _ = params 195 return makeReturnedStatus(ESNone), nil 196 } 197 198 func (receiver AutoReloadPolicy) GetPolicyName() string { 199 return string(appsv1alpha1.AutoReload) 200 } 201 202 func NewReconfigurePolicy(cc *appsv1alpha1.ConfigConstraintSpec, cfgPatch *core.ConfigPatchInfo, policy appsv1alpha1.UpgradePolicy, restart bool) (reconfigurePolicy, error) { 203 if cfgPatch != nil && !cfgPatch.IsModify { 204 // not walk here 205 return nil, core.MakeError("cfg not modify. [%v]", cfgPatch) 206 } 207 208 if enableAutoDecision(restart, policy) { 209 if dynamicUpdate, err := core.IsUpdateDynamicParameters(cc, cfgPatch); err != nil { 210 return nil, err 211 } else if dynamicUpdate { 212 policy = appsv1alpha1.AutoReload 213 } 214 if enableSyncReload(policy, cc.ReloadOptions) { 215 policy = appsv1alpha1.OperatorSyncUpdate 216 } 217 } 218 if policy == appsv1alpha1.NonePolicy { 219 policy = appsv1alpha1.NormalPolicy 220 } 221 if action, ok := upgradePolicyMap[policy]; ok { 222 return action, nil 223 } 224 return nil, core.MakeError("not supported upgrade policy:[%s]", policy) 225 } 226 227 func enableAutoDecision(restart bool, policy appsv1alpha1.UpgradePolicy) bool { 228 return !restart && policy == appsv1alpha1.NonePolicy 229 } 230 231 func enableSyncReload(policyType appsv1alpha1.UpgradePolicy, options *appsv1alpha1.ReloadOptions) bool { 232 return policyType == appsv1alpha1.AutoReload && enableSyncTrigger(options) 233 } 234 235 func enableSyncTrigger(options *appsv1alpha1.ReloadOptions) bool { 236 if options == nil { 237 return false 238 } 239 240 if options.TPLScriptTrigger != nil { 241 return !core.IsWatchModuleForTplTrigger(options.TPLScriptTrigger) 242 } 243 244 if options.ShellTrigger != nil { 245 return !core.IsWatchModuleForShellTrigger(options.ShellTrigger) 246 } 247 return false 248 } 249 250 func withSucceed(succeedCount int32) func(status *ReturnedStatus) { 251 return func(status *ReturnedStatus) { 252 status.SucceedCount = succeedCount 253 } 254 } 255 256 func withExpected(expectedCount int32) func(status *ReturnedStatus) { 257 return func(status *ReturnedStatus) { 258 status.ExpectedCount = expectedCount 259 } 260 } 261 262 func makeReturnedStatus(status ExecStatus, ops ...func(status *ReturnedStatus)) ReturnedStatus { 263 ret := ReturnedStatus{ 264 Status: status, 265 SucceedCount: core.Unconfirmed, 266 ExpectedCount: core.Unconfirmed, 267 } 268 for _, o := range ops { 269 o(&ret) 270 } 271 return ret 272 } 273 274 func fromWorkloadObjects(params reconfigureParams) []client.Object { 275 r := make([]client.Object, 0) 276 for _, unit := range params.RSMList { 277 r = append(r, &unit) 278 } 279 // migrated workload 280 if len(r) != 0 { 281 return r 282 } 283 for _, unit := range params.ComponentUnits { 284 r = append(r, &unit) 285 } 286 for _, unit := range params.DeploymentUnits { 287 r = append(r, &unit) 288 } 289 return r 290 }