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  }