github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/rsm/utils.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 rsm 21 22 import ( 23 "context" 24 "fmt" 25 "regexp" 26 "sort" 27 "strconv" 28 "strings" 29 30 "github.com/go-logr/logr" 31 appsv1 "k8s.io/api/apps/v1" 32 batchv1 "k8s.io/api/batch/v1" 33 corev1 "k8s.io/api/core/v1" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/runtime" 36 "k8s.io/apimachinery/pkg/runtime/schema" 37 "k8s.io/apimachinery/pkg/util/intstr" 38 "sigs.k8s.io/controller-runtime/pkg/client" 39 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 40 41 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 42 "github.com/1aal/kubeblocks/pkg/constant" 43 "github.com/1aal/kubeblocks/pkg/controller/builder" 44 roclient "github.com/1aal/kubeblocks/pkg/controller/client" 45 "github.com/1aal/kubeblocks/pkg/controller/graph" 46 "github.com/1aal/kubeblocks/pkg/controller/model" 47 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 48 viper "github.com/1aal/kubeblocks/pkg/viperx" 49 ) 50 51 type getRole func(int) string 52 type getOrdinal func(int) int 53 54 const ( 55 leaderPriority = 1 << 5 56 followerReadWritePriority = 1 << 4 57 followerReadonlyPriority = 1 << 3 58 followerNonePriority = 1 << 2 59 learnerPriority = 1 << 1 60 emptyPriority = 1 << 0 61 // unknownPriority = 0 62 ) 63 64 var podNameRegex = regexp.MustCompile(`(.*)-([0-9]+)$`) 65 66 // SortPods sorts pods by their role priority 67 // e.g.: unknown -> empty -> learner -> follower1 -> follower2 -> leader, with follower1.Name < follower2.Name 68 // reverse it if reverse==true 69 func SortPods(pods []corev1.Pod, rolePriorityMap map[string]int, reverse bool) { 70 getRoleFunc := func(i int) string { 71 return getRoleName(pods[i]) 72 } 73 getOrdinalFunc := func(i int) int { 74 _, ordinal := intctrlutil.GetParentNameAndOrdinal(&pods[i]) 75 return ordinal 76 } 77 sortMembers(pods, rolePriorityMap, getRoleFunc, getOrdinalFunc, reverse) 78 } 79 80 func sortMembersStatus(membersStatus []workloads.MemberStatus, rolePriorityMap map[string]int) { 81 getRoleFunc := func(i int) string { 82 return membersStatus[i].Name 83 } 84 getOrdinalFunc := func(i int) int { 85 ordinal, _ := getPodOrdinal(membersStatus[i].PodName) 86 return ordinal 87 } 88 sortMembers(membersStatus, rolePriorityMap, getRoleFunc, getOrdinalFunc, true) 89 } 90 91 // sortMembers sorts items by role priority and pod ordinal. 92 func sortMembers[T any](items []T, 93 rolePriorityMap map[string]int, 94 getRoleFunc getRole, getOrdinalFunc getOrdinal, 95 reverse bool) { 96 sort.SliceStable(items, func(i, j int) bool { 97 if reverse { 98 i, j = j, i 99 } 100 roleI := getRoleFunc(i) 101 roleJ := getRoleFunc(j) 102 if rolePriorityMap[roleI] == rolePriorityMap[roleJ] { 103 ordinal1 := getOrdinalFunc(i) 104 ordinal2 := getOrdinalFunc(j) 105 return ordinal1 < ordinal2 106 } 107 return rolePriorityMap[roleI] < rolePriorityMap[roleJ] 108 }) 109 } 110 111 // ComposeRolePriorityMap generates a priority map based on roles. 112 func ComposeRolePriorityMap(roles []workloads.ReplicaRole) map[string]int { 113 rolePriorityMap := make(map[string]int, 0) 114 rolePriorityMap[""] = emptyPriority 115 for _, role := range roles { 116 roleName := strings.ToLower(role.Name) 117 switch { 118 case role.IsLeader: 119 rolePriorityMap[roleName] = leaderPriority 120 case role.CanVote: 121 switch role.AccessMode { 122 case workloads.NoneMode: 123 rolePriorityMap[roleName] = followerNonePriority 124 case workloads.ReadonlyMode: 125 rolePriorityMap[roleName] = followerReadonlyPriority 126 case workloads.ReadWriteMode: 127 rolePriorityMap[roleName] = followerReadWritePriority 128 } 129 default: 130 rolePriorityMap[roleName] = learnerPriority 131 } 132 } 133 134 return rolePriorityMap 135 } 136 137 // updatePodRoleLabel updates pod role label when internal container role changed 138 func updatePodRoleLabel(cli client.Client, reqCtx intctrlutil.RequestCtx, 139 rsm workloads.ReplicatedStateMachine, pod *corev1.Pod, roleName string, version string) error { 140 ctx := reqCtx.Ctx 141 roleMap := composeRoleMap(rsm) 142 // role not defined in CR, ignore it 143 roleName = strings.ToLower(roleName) 144 145 // update pod role label 146 patch := client.MergeFrom(pod.DeepCopy()) 147 role, ok := roleMap[roleName] 148 switch ok { 149 case true: 150 pod.Labels[roleLabelKey] = role.Name 151 pod.Labels[rsmAccessModeLabelKey] = string(role.AccessMode) 152 case false: 153 delete(pod.Labels, roleLabelKey) 154 delete(pod.Labels, rsmAccessModeLabelKey) 155 } 156 157 if pod.Annotations == nil { 158 pod.Annotations = map[string]string{} 159 } 160 pod.Annotations[constant.LastRoleSnapshotVersionAnnotationKey] = version 161 return cli.Patch(ctx, pod, patch) 162 } 163 164 func composeRoleMap(rsm workloads.ReplicatedStateMachine) map[string]workloads.ReplicaRole { 165 roleMap := make(map[string]workloads.ReplicaRole, 0) 166 for _, role := range rsm.Spec.Roles { 167 roleMap[strings.ToLower(role.Name)] = role 168 } 169 return roleMap 170 } 171 172 func setMembersStatus(rsm *workloads.ReplicatedStateMachine, pods []corev1.Pod) { 173 // compose new status 174 newMembersStatus := make([]workloads.MemberStatus, 0) 175 roleMap := composeRoleMap(*rsm) 176 for _, pod := range pods { 177 if !intctrlutil.PodIsReadyWithLabel(pod) { 178 continue 179 } 180 roleName := getRoleName(pod) 181 role, ok := roleMap[roleName] 182 if !ok { 183 continue 184 } 185 memberStatus := workloads.MemberStatus{ 186 PodName: pod.Name, 187 ReplicaRole: role, 188 } 189 newMembersStatus = append(newMembersStatus, memberStatus) 190 } 191 192 // sort and set 193 rolePriorityMap := ComposeRolePriorityMap(rsm.Spec.Roles) 194 sortMembersStatus(newMembersStatus, rolePriorityMap) 195 rsm.Status.MembersStatus = newMembersStatus 196 } 197 198 // getRoleName gets role name of pod 'pod' 199 func getRoleName(pod corev1.Pod) string { 200 return strings.ToLower(pod.Labels[constant.RoleLabelKey]) 201 } 202 203 func ownedKinds() []client.ObjectList { 204 return []client.ObjectList{ 205 &appsv1.StatefulSetList{}, 206 &corev1.ServiceList{}, 207 &corev1.ConfigMapList{}, 208 } 209 } 210 211 func deletionKinds() []client.ObjectList { 212 kinds := ownedKinds() 213 kinds = append(kinds, &batchv1.JobList{}) 214 return kinds 215 } 216 217 func getPodsOfStatefulSet(ctx context.Context, cli roclient.ReadonlyClient, stsObj *appsv1.StatefulSet) ([]corev1.Pod, error) { 218 podList := &corev1.PodList{} 219 selector, err := metav1.LabelSelectorAsMap(stsObj.Spec.Selector) 220 if err != nil { 221 return nil, err 222 } 223 if err := cli.List(ctx, podList, 224 &client.ListOptions{Namespace: stsObj.Namespace}, 225 client.MatchingLabels(selector)); err != nil { 226 return nil, err 227 } 228 isMemberOf := func(stsName string, pod *corev1.Pod) bool { 229 parent, _ := intctrlutil.GetParentNameAndOrdinal(pod) 230 return parent == stsName 231 } 232 var pods []corev1.Pod 233 for _, pod := range podList.Items { 234 if isMemberOf(stsObj.Name, &pod) { 235 pods = append(pods, pod) 236 } 237 } 238 return pods, nil 239 } 240 241 func getHeadlessSvcName(rsm workloads.ReplicatedStateMachine) string { 242 return strings.Join([]string{rsm.Name, "headless"}, "-") 243 } 244 245 func findSvcPort(rsm workloads.ReplicatedStateMachine) int { 246 if rsm.Spec.Service == nil || len(rsm.Spec.Service.Spec.Ports) == 0 { 247 return 0 248 } 249 port := rsm.Spec.Service.Spec.Ports[0] 250 for _, c := range rsm.Spec.Template.Spec.Containers { 251 for _, p := range c.Ports { 252 if port.TargetPort.Type == intstr.String && p.Name == port.TargetPort.StrVal || 253 port.TargetPort.Type == intstr.Int && p.ContainerPort == port.TargetPort.IntVal { 254 return int(p.ContainerPort) 255 } 256 } 257 } 258 return 0 259 } 260 261 func getActionList(transCtx *rsmTransformContext, actionScenario string) ([]*batchv1.Job, error) { 262 labels := getLabels(transCtx.rsm) 263 labels[jobScenarioLabel] = actionScenario 264 labels[jobHandledLabel] = jobHandledFalse 265 ml := client.MatchingLabels(labels) 266 267 var actionList []*batchv1.Job 268 jobList := &batchv1.JobList{} 269 if err := transCtx.Client.List(transCtx.Context, jobList, ml); err != nil { 270 return nil, err 271 } 272 for i := range jobList.Items { 273 actionList = append(actionList, &jobList.Items[i]) 274 } 275 printActionList(transCtx.Logger, actionList) 276 return actionList, nil 277 } 278 279 // TODO(free6om): remove all printActionList when all testes pass 280 func printActionList(logger logr.Logger, actionList []*batchv1.Job) { 281 var actionNameList []string 282 for _, action := range actionList { 283 actionNameList = append(actionNameList, fmt.Sprintf("%s-%v", action.Name, *action.Spec.Suspend)) 284 } 285 logger.Info(fmt.Sprintf("action list: %v\n", actionNameList)) 286 } 287 288 func getPodName(parent string, ordinal int) string { 289 return fmt.Sprintf("%s-%d", parent, ordinal) 290 } 291 292 func getActionName(parent string, generation, ordinal int, actionType string) string { 293 return fmt.Sprintf("%s-%d-%d-%s", parent, generation, ordinal, actionType) 294 } 295 296 func getLeaderPodName(membersStatus []workloads.MemberStatus) string { 297 for _, memberStatus := range membersStatus { 298 if memberStatus.IsLeader { 299 return memberStatus.PodName 300 } 301 } 302 return "" 303 } 304 305 func getPodOrdinal(podName string) (int, error) { 306 subMatches := podNameRegex.FindStringSubmatch(podName) 307 if len(subMatches) < 3 { 308 return 0, fmt.Errorf("wrong pod name: %s", podName) 309 } 310 return strconv.Atoi(subMatches[2]) 311 } 312 313 // ordinal is the ordinal of pod which this action applies to 314 func createAction(dag *graph.DAG, cli model.GraphClient, rsm *workloads.ReplicatedStateMachine, action *batchv1.Job) error { 315 if err := setOwnership(rsm, action, model.GetScheme(), getFinalizer(action)); err != nil { 316 return err 317 } 318 cli.Create(dag, action) 319 return nil 320 } 321 322 func buildAction(rsm *workloads.ReplicatedStateMachine, actionName, actionType, actionScenario string, leader, target string) *batchv1.Job { 323 env := buildActionEnv(rsm, leader, target) 324 template := buildActionPodTemplate(rsm, env, actionType) 325 labels := getLabels(rsm) 326 return builder.NewJobBuilder(rsm.Namespace, actionName). 327 AddLabelsInMap(labels). 328 AddLabels(jobScenarioLabel, actionScenario). 329 AddLabels(jobTypeLabel, actionType). 330 AddLabels(jobHandledLabel, jobHandledFalse). 331 SetSuspend(false). 332 SetPodTemplateSpec(*template). 333 GetObject() 334 } 335 336 func buildActionPodTemplate(rsm *workloads.ReplicatedStateMachine, env []corev1.EnvVar, actionType string) *corev1.PodTemplateSpec { 337 credential := rsm.Spec.Credential 338 credentialEnv := make([]corev1.EnvVar, 0) 339 if credential != nil { 340 credentialEnv = append(credentialEnv, 341 corev1.EnvVar{ 342 Name: usernameCredentialVarName, 343 Value: credential.Username.Value, 344 ValueFrom: credential.Username.ValueFrom, 345 }, 346 corev1.EnvVar{ 347 Name: passwordCredentialVarName, 348 Value: credential.Password.Value, 349 ValueFrom: credential.Password.ValueFrom, 350 }) 351 } 352 env = append(env, credentialEnv...) 353 reconfiguration := rsm.Spec.MembershipReconfiguration 354 image := findActionImage(reconfiguration, actionType) 355 command := getActionCommand(reconfiguration, actionType) 356 container := corev1.Container{ 357 Name: actionType, 358 Image: image, 359 ImagePullPolicy: corev1.PullIfNotPresent, 360 Command: command, 361 Env: env, 362 } 363 template := &corev1.PodTemplateSpec{ 364 Spec: corev1.PodSpec{ 365 Containers: []corev1.Container{container}, 366 RestartPolicy: corev1.RestartPolicyOnFailure, 367 }, 368 } 369 return template 370 } 371 372 func buildActionEnv(rsm *workloads.ReplicatedStateMachine, leader, target string) []corev1.EnvVar { 373 svcName := getHeadlessSvcName(*rsm) 374 leaderHost := fmt.Sprintf("%s.%s", leader, svcName) 375 targetHost := fmt.Sprintf("%s.%s", target, svcName) 376 svcPort := findSvcPort(*rsm) 377 return []corev1.EnvVar{ 378 { 379 Name: leaderHostVarName, 380 Value: leaderHost, 381 }, 382 { 383 Name: servicePortVarName, 384 Value: strconv.Itoa(svcPort), 385 }, 386 { 387 Name: targetHostVarName, 388 Value: targetHost, 389 }, 390 } 391 } 392 393 func findActionImage(reconfiguration *workloads.MembershipReconfiguration, actionType string) string { 394 if reconfiguration == nil { 395 return "" 396 } 397 398 getImage := func(action *workloads.Action) string { 399 if action != nil && len(action.Image) > 0 { 400 return action.Image 401 } 402 return "" 403 } 404 switch actionType { 405 case jobTypePromote: 406 if image := getImage(reconfiguration.PromoteAction); len(image) > 0 { 407 return image 408 } 409 fallthrough 410 case jobTypeLogSync: 411 if image := getImage(reconfiguration.LogSyncAction); len(image) > 0 { 412 return image 413 } 414 fallthrough 415 case jobTypeMemberLeaveNotifying: 416 if image := getImage(reconfiguration.MemberLeaveAction); len(image) > 0 { 417 return image 418 } 419 fallthrough 420 case jobTypeMemberJoinNotifying: 421 if image := getImage(reconfiguration.MemberJoinAction); len(image) > 0 { 422 return image 423 } 424 fallthrough 425 case jobTypeSwitchover: 426 if image := getImage(reconfiguration.SwitchoverAction); len(image) > 0 { 427 return image 428 } 429 return defaultActionImage 430 } 431 432 return "" 433 } 434 435 func getActionCommand(reconfiguration *workloads.MembershipReconfiguration, actionType string) []string { 436 if reconfiguration == nil { 437 return nil 438 } 439 getCommand := func(action *workloads.Action) []string { 440 if action == nil { 441 return nil 442 } 443 return action.Command 444 } 445 switch actionType { 446 case jobTypeSwitchover: 447 return getCommand(reconfiguration.SwitchoverAction) 448 case jobTypeMemberJoinNotifying: 449 return getCommand(reconfiguration.MemberJoinAction) 450 case jobTypeMemberLeaveNotifying: 451 return getCommand(reconfiguration.MemberLeaveAction) 452 case jobTypeLogSync: 453 return getCommand(reconfiguration.LogSyncAction) 454 case jobTypePromote: 455 return getCommand(reconfiguration.PromoteAction) 456 } 457 return nil 458 } 459 460 func doActionCleanup(dag *graph.DAG, graphCli model.GraphClient, action *batchv1.Job) { 461 actionOld := action.DeepCopy() 462 actionNew := actionOld.DeepCopy() 463 actionNew.Labels[jobHandledLabel] = jobHandledTrue 464 graphCli.Update(dag, actionOld, actionNew) 465 } 466 467 func emitEvent(transCtx *rsmTransformContext, action *batchv1.Job) { 468 switch { 469 case action.Status.Succeeded > 0: 470 emitActionSucceedEvent(transCtx, action.Labels[jobTypeLabel], action.Name) 471 case action.Status.Failed > 0: 472 emitActionFailedEvent(transCtx, action.Labels[jobTypeLabel], action.Name) 473 } 474 } 475 476 func emitActionSucceedEvent(transCtx *rsmTransformContext, actionType, actionName string) { 477 message := fmt.Sprintf("%s succeed, job name: %s", actionType, actionName) 478 emitActionEvent(transCtx, corev1.EventTypeNormal, actionType, message) 479 } 480 481 func emitActionFailedEvent(transCtx *rsmTransformContext, actionType, actionName string) { 482 message := fmt.Sprintf("%s failed, job name: %s", actionType, actionName) 483 emitActionEvent(transCtx, corev1.EventTypeWarning, actionType, message) 484 } 485 486 func emitAbnormalEvent(transCtx *rsmTransformContext, actionType, actionName string, err error) { 487 message := fmt.Sprintf("%s, job name: %s", err.Error(), actionName) 488 emitActionEvent(transCtx, corev1.EventTypeWarning, actionType, message) 489 } 490 491 func emitActionEvent(transCtx *rsmTransformContext, eventType, reason, message string) { 492 transCtx.EventRecorder.Event(transCtx.rsm, eventType, strings.ToUpper(reason), message) 493 } 494 495 func getFinalizer(obj client.Object) string { 496 if _, ok := obj.(*workloads.ReplicatedStateMachine); ok { 497 return rsmFinalizerName 498 } 499 if viper.GetBool(FeatureGateRSMCompatibilityMode) { 500 return constant.DBClusterFinalizerName 501 } 502 return rsmFinalizerName 503 } 504 505 func getLabels(rsm *workloads.ReplicatedStateMachine) map[string]string { 506 if viper.GetBool(FeatureGateRSMCompatibilityMode) { 507 labels := make(map[string]string, 0) 508 keys := []string{ 509 constant.AppManagedByLabelKey, 510 constant.AppNameLabelKey, 511 constant.AppComponentLabelKey, 512 constant.AppInstanceLabelKey, 513 constant.KBAppComponentLabelKey, 514 } 515 for _, key := range keys { 516 if value, ok := rsm.Labels[key]; ok { 517 labels[key] = value 518 } 519 } 520 return labels 521 } 522 return map[string]string{ 523 workloadsManagedByLabelKey: kindReplicatedStateMachine, 524 workloadsInstanceLabelKey: rsm.Name, 525 } 526 } 527 528 func getSvcSelector(rsm *workloads.ReplicatedStateMachine, headless bool) map[string]string { 529 selectors := make(map[string]string, 0) 530 531 if !headless { 532 for _, role := range rsm.Spec.Roles { 533 if role.IsLeader && len(role.Name) > 0 { 534 selectors[constant.RoleLabelKey] = role.Name 535 break 536 } 537 } 538 } 539 540 if viper.GetBool(FeatureGateRSMCompatibilityMode) { 541 keys := []string{ 542 constant.AppManagedByLabelKey, 543 constant.AppInstanceLabelKey, 544 constant.KBAppComponentLabelKey, 545 } 546 for _, key := range keys { 547 if value, ok := rsm.Labels[key]; ok { 548 selectors[key] = value 549 } 550 } 551 return selectors 552 } 553 554 for k, v := range rsm.Spec.Selector.MatchLabels { 555 selectors[k] = v 556 } 557 return selectors 558 } 559 560 func setOwnership(owner, obj client.Object, scheme *runtime.Scheme, finalizer string) error { 561 // if viper.GetBool(FeatureGateRSMCompatibilityMode) { 562 // return CopyOwnership(owner, obj, scheme, finalizer) 563 // } 564 return intctrlutil.SetOwnership(owner, obj, scheme, finalizer) 565 } 566 567 // CopyOwnership copies owner ref fields of 'owner' to 'obj' 568 // and calls controllerutil.AddFinalizer if not exists. 569 func CopyOwnership(owner, obj client.Object, scheme *runtime.Scheme, finalizer string) error { 570 // Returns true if a and b point to the same object. 571 referSameObject := func(a, b metav1.OwnerReference) bool { 572 aGV, err := schema.ParseGroupVersion(a.APIVersion) 573 if err != nil { 574 return false 575 } 576 bGV, err := schema.ParseGroupVersion(b.APIVersion) 577 if err != nil { 578 return false 579 } 580 return aGV.Group == bGV.Group && a.Kind == b.Kind && a.Name == b.Name 581 } 582 // indexOwnerRef returns the index of the owner reference in the slice if found, or -1. 583 indexOwnerRef := func(ownerReferences []metav1.OwnerReference, ref metav1.OwnerReference) int { 584 for index, r := range ownerReferences { 585 if referSameObject(r, ref) { 586 return index 587 } 588 } 589 return -1 590 } 591 upsertOwnerRef := func(ref metav1.OwnerReference, object metav1.Object) { 592 owners := object.GetOwnerReferences() 593 if idx := indexOwnerRef(owners, ref); idx == -1 { 594 owners = append(owners, ref) 595 } else { 596 owners[idx] = ref 597 } 598 object.SetOwnerReferences(owners) 599 } 600 601 ownerRefs := owner.GetOwnerReferences() 602 for _, ref := range ownerRefs { 603 if ref.Controller == nil || !*ref.Controller { 604 continue 605 } 606 // Return early with an error if the object is already controlled. 607 if existing := metav1.GetControllerOf(obj); existing != nil && !referSameObject(*existing, ref) { 608 return &controllerutil.AlreadyOwnedError{ 609 Object: obj, 610 Owner: *existing, 611 } 612 } 613 614 // Update owner references and return. 615 upsertOwnerRef(ref, obj) 616 } 617 618 if !controllerutil.ContainsFinalizer(obj, finalizer) { 619 // pvc objects do not need to add finalizer 620 _, ok := obj.(*corev1.PersistentVolumeClaim) 621 if !ok { 622 if !controllerutil.AddFinalizer(obj, finalizer) { 623 return intctrlutil.ErrFailedToAddFinalizer 624 } 625 } 626 } 627 return nil 628 } 629 630 // IsRSMReady gives rsm level 'ready' state: 631 // 1. all replicas exist 632 // 2. all members have role set 633 func IsRSMReady(rsm *workloads.ReplicatedStateMachine) bool { 634 if rsm == nil { 635 return false 636 } 637 // check whether the rsm cluster has been initialized 638 if rsm.Status.ReadyInitReplicas != rsm.Status.InitReplicas { 639 return false 640 } 641 // check whether latest spec has been sent to the underlying workload(sts) 642 if rsm.Status.ObservedGeneration != rsm.Generation || 643 rsm.Status.CurrentGeneration != rsm.Generation { 644 return false 645 } 646 // check whether the underlying workload(sts) is ready 647 if rsm.Spec.Replicas == nil { 648 return false 649 } 650 replicas := *rsm.Spec.Replicas 651 if rsm.Status.Replicas != replicas || 652 rsm.Status.ReadyReplicas != replicas || 653 rsm.Status.AvailableReplicas != replicas || 654 rsm.Status.UpdatedReplicas != replicas { 655 return false 656 } 657 // check whether role probe has done 658 if rsm.Spec.Roles == nil || rsm.Spec.RoleProbe == nil { 659 return true 660 } 661 membersStatus := rsm.Status.MembersStatus 662 if len(membersStatus) != int(*rsm.Spec.Replicas) { 663 return false 664 } 665 for i := 0; i < int(*rsm.Spec.Replicas); i++ { 666 podName := getPodName(rsm.Name, i) 667 if !isMemberReady(podName, membersStatus) { 668 return false 669 } 670 } 671 hasLeader := false 672 for _, status := range membersStatus { 673 if status.IsLeader { 674 hasLeader = true 675 break 676 } 677 } 678 return hasLeader 679 } 680 681 func isMemberReady(podName string, membersStatus []workloads.MemberStatus) bool { 682 for _, memberStatus := range membersStatus { 683 if memberStatus.PodName == podName { 684 return true 685 } 686 } 687 return false 688 } 689 690 // AddAnnotationScope will add AnnotationScope defined by 'scope' to all keys in map 'annotations'. 691 func AddAnnotationScope(scope AnnotationScope, annotations map[string]string) map[string]string { 692 if annotations == nil { 693 return nil 694 } 695 scopedAnnotations := make(map[string]string, len(annotations)) 696 for k, v := range annotations { 697 scopedAnnotations[fmt.Sprintf("%s%s", k, scope)] = v 698 } 699 return scopedAnnotations 700 } 701 702 // ParseAnnotationsOfScope parses all annotations with AnnotationScope defined by 'scope'. 703 // the AnnotationScope suffix of keys in result map will be trimmed. 704 func ParseAnnotationsOfScope(scope AnnotationScope, scopedAnnotations map[string]string) map[string]string { 705 if scopedAnnotations == nil { 706 return nil 707 } 708 709 annotations := make(map[string]string, 0) 710 if scope == RootScope { 711 for k, v := range scopedAnnotations { 712 if strings.HasSuffix(k, scopeSuffix) { 713 continue 714 } 715 annotations[k] = v 716 } 717 return annotations 718 } 719 720 for k, v := range scopedAnnotations { 721 if strings.HasSuffix(k, string(scope)) { 722 annotations[strings.TrimSuffix(k, string(scope))] = v 723 } 724 } 725 return annotations 726 } 727 728 func getEnvConfigMapName(rsmName string) string { 729 return fmt.Sprintf("%s-rsm-env", rsmName) 730 }