github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/components/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 components
    21  
    22  import (
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"reflect"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	"golang.org/x/exp/slices"
    32  	appsv1 "k8s.io/api/apps/v1"
    33  	corev1 "k8s.io/api/core/v1"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/runtime/schema"
    36  	"sigs.k8s.io/controller-runtime/pkg/client"
    37  
    38  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    39  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    40  	"github.com/1aal/kubeblocks/pkg/constant"
    41  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    42  	client2 "github.com/1aal/kubeblocks/pkg/controller/client"
    43  	componentutil "github.com/1aal/kubeblocks/pkg/controller/component"
    44  	"github.com/1aal/kubeblocks/pkg/controller/graph"
    45  	"github.com/1aal/kubeblocks/pkg/controller/model"
    46  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    47  	"github.com/1aal/kubeblocks/pkg/generics"
    48  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    49  )
    50  
    51  var (
    52  	errReqClusterObj = errors.New("required arg *appsv1alpha1.Cluster is nil")
    53  )
    54  
    55  func listObjWithLabelsInNamespace[T generics.Object, PT generics.PObject[T], L generics.ObjList[T], PL generics.PObjList[T, L]](
    56  	ctx context.Context, cli client.Client, _ func(T, PT, L, PL), namespace string, labels client.MatchingLabels) ([]PT, error) {
    57  	var objList L
    58  	if err := cli.List(ctx, PL(&objList), labels, client.InNamespace(namespace)); err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	objs := make([]PT, 0)
    63  	items := reflect.ValueOf(&objList).Elem().FieldByName("Items").Interface().([]T)
    64  	for i := range items {
    65  		objs = append(objs, &items[i])
    66  	}
    67  	return objs, nil
    68  }
    69  
    70  func listRSMOwnedByComponent(ctx context.Context, cli client.Client, namespace string, labels client.MatchingLabels) ([]*workloads.ReplicatedStateMachine, error) {
    71  	return listObjWithLabelsInNamespace(ctx, cli, generics.RSMSignature, namespace, labels)
    72  }
    73  
    74  func listPodOwnedByComponent(ctx context.Context, cli client.Client, namespace string, labels client.MatchingLabels) ([]*corev1.Pod, error) {
    75  	return listObjWithLabelsInNamespace(ctx, cli, generics.PodSignature, namespace, labels)
    76  }
    77  
    78  // restartPod restarts a Pod through updating the pod's annotation
    79  func restartPod(podTemplate *corev1.PodTemplateSpec) error {
    80  	if podTemplate.Annotations == nil {
    81  		podTemplate.Annotations = map[string]string{}
    82  	}
    83  
    84  	startTimestamp := time.Now() // TODO(impl): opsRes.OpsRequest.Status.StartTimestamp
    85  	restartTimestamp := podTemplate.Annotations[constant.RestartAnnotationKey]
    86  	// if res, _ := time.Parse(time.RFC3339, restartTimestamp); startTimestamp.After(res) {
    87  	if res, _ := time.Parse(time.RFC3339, restartTimestamp); startTimestamp.Before(res) {
    88  		podTemplate.Annotations[constant.RestartAnnotationKey] = startTimestamp.Format(time.RFC3339)
    89  	}
    90  	return nil
    91  }
    92  
    93  // mergeAnnotations keeps the original annotations.
    94  // if annotations exist and are replaced, the Deployment/StatefulSet will be updated.
    95  func mergeAnnotations(originalAnnotations map[string]string, targetAnnotations *map[string]string) {
    96  	if targetAnnotations == nil || originalAnnotations == nil {
    97  		return
    98  	}
    99  	if *targetAnnotations == nil {
   100  		*targetAnnotations = map[string]string{}
   101  	}
   102  	for k, v := range originalAnnotations {
   103  		// if the annotation not exist in targetAnnotations, copy it from original.
   104  		if _, ok := (*targetAnnotations)[k]; !ok {
   105  			(*targetAnnotations)[k] = v
   106  		}
   107  	}
   108  }
   109  
   110  // buildWorkLoadAnnotations builds the annotations for Deployment/StatefulSet
   111  func buildWorkLoadAnnotations(obj client.Object, cluster *appsv1alpha1.Cluster) {
   112  	workloadAnnotations := obj.GetAnnotations()
   113  	if workloadAnnotations == nil {
   114  		workloadAnnotations = map[string]string{}
   115  	}
   116  	// record the cluster generation to check if the sts is latest
   117  	workloadAnnotations[constant.KubeBlocksGenerationKey] = strconv.FormatInt(cluster.Generation, 10)
   118  	obj.SetAnnotations(workloadAnnotations)
   119  }
   120  
   121  // GetClusterByObject gets cluster by related k8s workloads.
   122  func GetClusterByObject(ctx context.Context,
   123  	cli client.Client,
   124  	obj client.Object) (*appsv1alpha1.Cluster, error) {
   125  	labels := obj.GetLabels()
   126  	if labels == nil {
   127  		return nil, nil
   128  	}
   129  	cluster := &appsv1alpha1.Cluster{}
   130  	if err := cli.Get(ctx, client.ObjectKey{
   131  		Name:      labels[constant.AppInstanceLabelKey],
   132  		Namespace: obj.GetNamespace(),
   133  	}, cluster); err != nil {
   134  		return nil, err
   135  	}
   136  	return cluster, nil
   137  }
   138  
   139  func IsFailedOrAbnormal(phase appsv1alpha1.ClusterComponentPhase) bool {
   140  	return slices.Index([]appsv1alpha1.ClusterComponentPhase{
   141  		appsv1alpha1.FailedClusterCompPhase,
   142  		appsv1alpha1.AbnormalClusterCompPhase}, phase) != -1
   143  }
   144  
   145  // getComponentMatchLabels gets the labels for matching the cluster component
   146  func getComponentMatchLabels(clusterName, componentName string) map[string]string {
   147  	return client.MatchingLabels{
   148  		constant.AppInstanceLabelKey:    clusterName,
   149  		constant.KBAppComponentLabelKey: componentName,
   150  		constant.AppManagedByLabelKey:   constant.AppName,
   151  	}
   152  }
   153  
   154  // GetComponentPodList gets the pod list by cluster and componentName
   155  func GetComponentPodList(ctx context.Context, cli client.Client, cluster appsv1alpha1.Cluster, componentName string) (*corev1.PodList, error) {
   156  	podList := &corev1.PodList{}
   157  	err := cli.List(ctx, podList, client.InNamespace(cluster.Namespace),
   158  		client.MatchingLabels(getComponentMatchLabels(cluster.Name, componentName)))
   159  	return podList, err
   160  }
   161  
   162  // GetComponentPodListWithRole gets the pod list with target role by cluster and componentName
   163  func GetComponentPodListWithRole(ctx context.Context, cli client.Client, cluster appsv1alpha1.Cluster, compSpecName, role string) (*corev1.PodList, error) {
   164  	matchLabels := client.MatchingLabels{
   165  		constant.AppInstanceLabelKey:    cluster.Name,
   166  		constant.KBAppComponentLabelKey: compSpecName,
   167  		constant.AppManagedByLabelKey:   constant.AppName,
   168  		constant.RoleLabelKey:           role,
   169  	}
   170  	podList := &corev1.PodList{}
   171  	if err := cli.List(ctx, podList, client.InNamespace(cluster.Namespace), matchLabels); err != nil {
   172  		return nil, err
   173  	}
   174  	return podList, nil
   175  }
   176  
   177  // isProbeTimeout checks if the application of the pod is probe timed out.
   178  func isProbeTimeout(probes *appsv1alpha1.ClusterDefinitionProbes, podsReadyTime *metav1.Time) bool {
   179  	if podsReadyTime == nil {
   180  		return false
   181  	}
   182  	if probes == nil || probes.RoleProbe == nil {
   183  		return false
   184  	}
   185  	roleProbeTimeout := time.Duration(appsv1alpha1.DefaultRoleProbeTimeoutAfterPodsReady) * time.Second
   186  	if probes.RoleProbeTimeoutAfterPodsReady != 0 {
   187  		roleProbeTimeout = time.Duration(probes.RoleProbeTimeoutAfterPodsReady) * time.Second
   188  	}
   189  	return time.Now().After(podsReadyTime.Add(roleProbeTimeout))
   190  }
   191  
   192  // getObjectListByComponentName gets k8s workload list with component
   193  func getObjectListByComponentName(ctx context.Context, cli client2.ReadonlyClient, cluster appsv1alpha1.Cluster,
   194  	objectList client.ObjectList, componentName string) error {
   195  	matchLabels := getComponentMatchLabels(cluster.Name, componentName)
   196  	inNamespace := client.InNamespace(cluster.Namespace)
   197  	return cli.List(ctx, objectList, client.MatchingLabels(matchLabels), inNamespace)
   198  }
   199  
   200  // getObjectListByCustomLabels gets k8s workload list with custom labels
   201  func getObjectListByCustomLabels(ctx context.Context, cli client.Client, cluster appsv1alpha1.Cluster,
   202  	objectList client.ObjectList, matchLabels client.ListOption) error {
   203  	inNamespace := client.InNamespace(cluster.Namespace)
   204  	return cli.List(ctx, objectList, matchLabels, inNamespace)
   205  }
   206  
   207  // getClusterComponentSpecByName gets componentSpec from cluster with compSpecName.
   208  func getClusterComponentSpecByName(cluster appsv1alpha1.Cluster, compSpecName string) *appsv1alpha1.ClusterComponentSpec {
   209  	for _, compSpec := range cluster.Spec.ComponentSpecs {
   210  		if compSpec.Name == compSpecName {
   211  			return &compSpec
   212  		}
   213  	}
   214  	return nil
   215  }
   216  
   217  // initClusterComponentStatusIfNeed Initialize the state of the corresponding component in cluster.status.components
   218  func initClusterComponentStatusIfNeed(
   219  	cluster *appsv1alpha1.Cluster,
   220  	componentName string,
   221  	workloadType appsv1alpha1.WorkloadType) error {
   222  	if cluster == nil {
   223  		return errReqClusterObj
   224  	}
   225  
   226  	// REVIEW: should have following removed
   227  	// if _, ok := cluster.Status.Components[componentName]; !ok {
   228  	// 	cluster.Status.SetComponentStatus(componentName, appsv1alpha1.ClusterComponentStatus{
   229  	// 		Phase: cluster.Status.Phase,
   230  	// 	})
   231  	// }
   232  	componentStatus := cluster.Status.Components[componentName]
   233  	switch workloadType {
   234  	case appsv1alpha1.Consensus:
   235  		if componentStatus.ConsensusSetStatus != nil {
   236  			break
   237  		}
   238  		componentStatus.ConsensusSetStatus = &appsv1alpha1.ConsensusSetStatus{
   239  			Leader: appsv1alpha1.ConsensusMemberStatus{
   240  				Pod:        constant.ComponentStatusDefaultPodName,
   241  				AccessMode: appsv1alpha1.None,
   242  				Name:       "",
   243  			},
   244  		}
   245  	case appsv1alpha1.Replication:
   246  		if componentStatus.ReplicationSetStatus != nil {
   247  			break
   248  		}
   249  		componentStatus.ReplicationSetStatus = &appsv1alpha1.ReplicationSetStatus{
   250  			Primary: appsv1alpha1.ReplicationMemberStatus{
   251  				Pod: constant.ComponentStatusDefaultPodName,
   252  			},
   253  		}
   254  	}
   255  	cluster.Status.SetComponentStatus(componentName, componentStatus)
   256  	return nil
   257  }
   258  
   259  // GetComponentDeployMinReadySeconds gets the deployment minReadySeconds of the component.
   260  func GetComponentDeployMinReadySeconds(ctx context.Context,
   261  	cli client.Client,
   262  	cluster appsv1alpha1.Cluster,
   263  	componentName string) (minReadySeconds int32, err error) {
   264  	deployList := &appsv1.DeploymentList{}
   265  	if err = getObjectListByComponentName(ctx, cli, cluster, deployList, componentName); err != nil {
   266  		return
   267  	}
   268  	if len(deployList.Items) > 0 {
   269  		minReadySeconds = deployList.Items[0].Spec.MinReadySeconds
   270  		return
   271  	}
   272  	return minReadySeconds, err
   273  }
   274  
   275  // GetComponentStsMinReadySeconds gets the statefulSet minReadySeconds of the component.
   276  func GetComponentStsMinReadySeconds(ctx context.Context,
   277  	cli client.Client,
   278  	cluster appsv1alpha1.Cluster,
   279  	componentName string) (minReadySeconds int32, err error) {
   280  	stsList := &appsv1.StatefulSetList{}
   281  	if err = getObjectListByComponentName(ctx, cli, cluster, stsList, componentName); err != nil {
   282  		return
   283  	}
   284  	if len(stsList.Items) > 0 {
   285  		minReadySeconds = stsList.Items[0].Spec.MinReadySeconds
   286  		return
   287  	}
   288  	return minReadySeconds, err
   289  }
   290  
   291  // GetComponentWorkloadMinReadySeconds gets the workload minReadySeconds of the component.
   292  func GetComponentWorkloadMinReadySeconds(ctx context.Context,
   293  	cli client.Client,
   294  	cluster appsv1alpha1.Cluster,
   295  	workloadType appsv1alpha1.WorkloadType,
   296  	componentName string) (minReadySeconds int32, err error) {
   297  	switch workloadType {
   298  	case appsv1alpha1.Stateless:
   299  		return GetComponentDeployMinReadySeconds(ctx, cli, cluster, componentName)
   300  	default:
   301  		return GetComponentStsMinReadySeconds(ctx, cli, cluster, componentName)
   302  	}
   303  }
   304  
   305  // GetComponentInfoByPod gets componentName and componentDefinition info by Pod.
   306  func GetComponentInfoByPod(ctx context.Context,
   307  	cli client.Client,
   308  	cluster appsv1alpha1.Cluster,
   309  	pod *corev1.Pod) (componentName string, componentDef *appsv1alpha1.ClusterComponentDefinition, err error) {
   310  	if pod == nil || pod.Labels == nil {
   311  		return "", nil, errors.New("pod or pod's label is nil")
   312  	}
   313  	componentName, ok := pod.Labels[constant.KBAppComponentLabelKey]
   314  	if !ok {
   315  		return "", nil, errors.New("pod component name label is nil")
   316  	}
   317  	compDefName := cluster.Spec.GetComponentDefRefName(componentName)
   318  	// if no componentSpec found, then componentName is componentDefName
   319  	if len(compDefName) == 0 && len(cluster.Spec.ComponentSpecs) == 0 {
   320  		compDefName = componentName
   321  	}
   322  	componentDef, err = appsv1alpha1.GetComponentDefByCluster(ctx, cli, cluster, compDefName)
   323  	if err != nil {
   324  		return componentName, componentDef, err
   325  	}
   326  	return componentName, componentDef, nil
   327  }
   328  
   329  // getCompRelatedObjectList gets the related pods and workloads of the component
   330  func getCompRelatedObjectList(ctx context.Context,
   331  	cli client.Client,
   332  	cluster appsv1alpha1.Cluster,
   333  	compName string,
   334  	relatedWorkloads client.ObjectList) (*corev1.PodList, error) {
   335  	podList, err := GetComponentPodList(ctx, cli, cluster, compName)
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  	if err = getObjectListByComponentName(ctx,
   340  		cli, cluster, relatedWorkloads, compName); err != nil {
   341  		return nil, err
   342  	}
   343  	return podList, nil
   344  }
   345  
   346  // parseCustomLabelPattern parses the custom label pattern to GroupVersionKind.
   347  func parseCustomLabelPattern(pattern string) (schema.GroupVersionKind, error) {
   348  	patterns := strings.Split(pattern, "/")
   349  	switch len(patterns) {
   350  	case 2:
   351  		return schema.GroupVersionKind{
   352  			Group:   "",
   353  			Version: patterns[0],
   354  			Kind:    patterns[1],
   355  		}, nil
   356  	case 3:
   357  		return schema.GroupVersionKind{
   358  			Group:   patterns[0],
   359  			Version: patterns[1],
   360  			Kind:    patterns[2],
   361  		}, nil
   362  	}
   363  	return schema.GroupVersionKind{}, fmt.Errorf("invalid pattern %s", pattern)
   364  }
   365  
   366  // replaceKBEnvPlaceholderTokens replaces the placeholder tokens in the string strToReplace with builtInEnvMap and return new string.
   367  func replaceKBEnvPlaceholderTokens(clusterName, uid, componentName, strToReplace string) string {
   368  	builtInEnvMap := componentutil.GetReplacementMapForBuiltInEnv(clusterName, uid, componentName)
   369  	return componentutil.ReplaceNamedVars(builtInEnvMap, strToReplace, -1, true)
   370  }
   371  
   372  // resolvePodSpecDefaultFields set default value for some known fields of proto PodSpec @pobj.
   373  func resolvePodSpecDefaultFields(obj corev1.PodSpec, pobj *corev1.PodSpec) {
   374  	resolveVolume := func(v corev1.Volume, vv *corev1.Volume) {
   375  		if vv.DownwardAPI != nil && v.DownwardAPI != nil {
   376  			for i := range vv.DownwardAPI.Items {
   377  				vf := v.DownwardAPI.Items[i]
   378  				if vf.FieldRef == nil {
   379  					continue
   380  				}
   381  				vvf := &vv.DownwardAPI.Items[i]
   382  				if vvf.FieldRef != nil && len(vvf.FieldRef.APIVersion) == 0 {
   383  					vvf.FieldRef.APIVersion = vf.FieldRef.APIVersion
   384  				}
   385  			}
   386  			if vv.DownwardAPI.DefaultMode == nil {
   387  				vv.DownwardAPI.DefaultMode = v.DownwardAPI.DefaultMode
   388  			}
   389  		}
   390  		if vv.ConfigMap != nil && v.ConfigMap != nil {
   391  			if vv.ConfigMap.DefaultMode == nil {
   392  				vv.ConfigMap.DefaultMode = v.ConfigMap.DefaultMode
   393  			}
   394  		}
   395  	}
   396  	resolveContainer := func(c corev1.Container, cc *corev1.Container) {
   397  		if len(cc.TerminationMessagePath) == 0 {
   398  			cc.TerminationMessagePath = c.TerminationMessagePath
   399  		}
   400  		if len(cc.TerminationMessagePolicy) == 0 {
   401  			cc.TerminationMessagePolicy = c.TerminationMessagePolicy
   402  		}
   403  		if len(cc.ImagePullPolicy) == 0 {
   404  			cc.ImagePullPolicy = c.ImagePullPolicy
   405  		}
   406  
   407  		resolveContainerProbe := func(p corev1.Probe, pp *corev1.Probe) {
   408  			if pp.TimeoutSeconds == 0 {
   409  				pp.TimeoutSeconds = p.TimeoutSeconds
   410  			}
   411  			if pp.PeriodSeconds == 0 {
   412  				pp.PeriodSeconds = p.PeriodSeconds
   413  			}
   414  			if pp.SuccessThreshold == 0 {
   415  				pp.SuccessThreshold = p.SuccessThreshold
   416  			}
   417  			if pp.FailureThreshold == 0 {
   418  				pp.FailureThreshold = p.FailureThreshold
   419  			}
   420  			if pp.HTTPGet != nil && len(pp.HTTPGet.Scheme) == 0 {
   421  				if p.HTTPGet != nil {
   422  					pp.HTTPGet.Scheme = p.HTTPGet.Scheme
   423  				}
   424  			}
   425  		}
   426  		if cc.LivenessProbe != nil && c.LivenessProbe != nil {
   427  			resolveContainerProbe(*c.LivenessProbe, cc.LivenessProbe)
   428  		}
   429  		if cc.ReadinessProbe != nil && c.ReadinessProbe != nil {
   430  			resolveContainerProbe(*c.ReadinessProbe, cc.ReadinessProbe)
   431  		}
   432  		if cc.StartupProbe != nil && c.StartupProbe != nil {
   433  			resolveContainerProbe(*c.StartupProbe, cc.StartupProbe)
   434  		}
   435  	}
   436  	for i := 0; i < min(len(obj.Volumes), len(pobj.Volumes)); i++ {
   437  		resolveVolume(obj.Volumes[i], &pobj.Volumes[i])
   438  	}
   439  	for i := 0; i < min(len(obj.InitContainers), len(pobj.InitContainers)); i++ {
   440  		resolveContainer(obj.InitContainers[i], &pobj.InitContainers[i])
   441  	}
   442  	for i := 0; i < min(len(obj.Containers), len(pobj.Containers)); i++ {
   443  		resolveContainer(obj.Containers[i], &pobj.Containers[i])
   444  	}
   445  	if len(pobj.RestartPolicy) == 0 {
   446  		pobj.RestartPolicy = obj.RestartPolicy
   447  	}
   448  	if pobj.TerminationGracePeriodSeconds == nil {
   449  		pobj.TerminationGracePeriodSeconds = obj.TerminationGracePeriodSeconds
   450  	}
   451  	if len(pobj.DNSPolicy) == 0 {
   452  		pobj.DNSPolicy = obj.DNSPolicy
   453  	}
   454  	if len(pobj.DeprecatedServiceAccount) == 0 {
   455  		pobj.DeprecatedServiceAccount = obj.DeprecatedServiceAccount
   456  	}
   457  	if pobj.SecurityContext == nil {
   458  		pobj.SecurityContext = obj.SecurityContext
   459  	}
   460  	if len(pobj.SchedulerName) == 0 {
   461  		pobj.SchedulerName = obj.SchedulerName
   462  	}
   463  	if len(pobj.Tolerations) == 0 {
   464  		pobj.Tolerations = obj.Tolerations
   465  	}
   466  	if pobj.Priority == nil {
   467  		pobj.Priority = obj.Priority
   468  	}
   469  	if pobj.EnableServiceLinks == nil {
   470  		pobj.EnableServiceLinks = obj.EnableServiceLinks
   471  	}
   472  	if pobj.PreemptionPolicy == nil {
   473  		pobj.PreemptionPolicy = obj.PreemptionPolicy
   474  	}
   475  }
   476  
   477  // ConvertRSMToSTS converts a rsm to sts
   478  // TODO(free6om): refactor this func out
   479  func ConvertRSMToSTS(rsm *workloads.ReplicatedStateMachine) *appsv1.StatefulSet {
   480  	if rsm == nil {
   481  		return nil
   482  	}
   483  	sts := builder.NewStatefulSetBuilder(rsm.Namespace, rsm.Name).
   484  		SetUID(rsm.UID).
   485  		AddLabelsInMap(rsm.Labels).
   486  		AddAnnotationsInMap(rsm.Annotations).
   487  		SetReplicas(*rsm.Spec.Replicas).
   488  		SetSelector(rsm.Spec.Selector).
   489  		SetServiceName(rsm.Spec.ServiceName).
   490  		SetTemplate(rsm.Spec.Template).
   491  		SetVolumeClaimTemplates(rsm.Spec.VolumeClaimTemplates...).
   492  		SetPodManagementPolicy(rsm.Spec.PodManagementPolicy).
   493  		SetUpdateStrategy(rsm.Spec.UpdateStrategy).
   494  		GetObject()
   495  	sts.Generation = rsm.Generation
   496  	sts.Status = rsm.Status.StatefulSetStatus
   497  	sts.Status.ObservedGeneration = rsm.Status.ObservedGeneration
   498  	return sts
   499  }
   500  
   501  // delayUpdatePodSpecSystemFields to delay the updating to system fields in pod spec.
   502  func delayUpdatePodSpecSystemFields(obj corev1.PodSpec, pobj *corev1.PodSpec) {
   503  	for i := range pobj.Containers {
   504  		delayUpdateKubeBlocksToolsImage(obj.Containers, &pobj.Containers[i])
   505  	}
   506  }
   507  
   508  // updatePodSpecSystemFields to update system fields in pod spec.
   509  func updatePodSpecSystemFields(pobj *corev1.PodSpec) {
   510  	for i := range pobj.Containers {
   511  		updateKubeBlocksToolsImage(&pobj.Containers[i])
   512  	}
   513  }
   514  
   515  func delayUpdateKubeBlocksToolsImage(containers []corev1.Container, pc *corev1.Container) {
   516  	if pc.Image != viper.GetString(constant.KBToolsImage) {
   517  		return
   518  	}
   519  	for _, c := range containers {
   520  		if c.Name == pc.Name {
   521  			if getImageName(c.Image) == getImageName(pc.Image) {
   522  				pc.Image = c.Image
   523  			}
   524  			break
   525  		}
   526  	}
   527  }
   528  
   529  func updateKubeBlocksToolsImage(pc *corev1.Container) {
   530  	if getImageName(pc.Image) == getImageName(viper.GetString(constant.KBToolsImage)) {
   531  		pc.Image = viper.GetString(constant.KBToolsImage)
   532  	}
   533  }
   534  
   535  func getImageName(image string) string {
   536  	subs := strings.Split(image, ":")
   537  	switch len(subs) {
   538  	case 2:
   539  		return subs[0]
   540  	case 3:
   541  		lastIndex := strings.LastIndex(image, ":")
   542  		return image[:lastIndex]
   543  	default:
   544  		return ""
   545  	}
   546  }
   547  
   548  // getCustomLabelSupportKind returns the kinds that support custom label.
   549  func getCustomLabelSupportKind() []string {
   550  	return []string{
   551  		constant.CronJobKind,
   552  		constant.StatefulSetKind,
   553  		constant.DeploymentKind,
   554  		constant.ReplicaSetKind,
   555  		constant.ServiceKind,
   556  		constant.ConfigMapKind,
   557  		constant.PodKind,
   558  	}
   559  }
   560  
   561  // updateComponentInfoToPods patches current component's replicas to all belonging pods, as an annotation.
   562  func updateComponentInfoToPods(
   563  	ctx context.Context,
   564  	cli client.Client,
   565  	cluster *appsv1alpha1.Cluster,
   566  	component *componentutil.SynthesizedComponent,
   567  	dag *graph.DAG) error {
   568  	if cluster == nil || component == nil {
   569  		return nil
   570  	}
   571  	ml := client.MatchingLabels{
   572  		constant.AppInstanceLabelKey:    cluster.GetName(),
   573  		constant.KBAppComponentLabelKey: component.Name,
   574  	}
   575  	// list all pods in cache
   576  	podList := corev1.PodList{}
   577  	if err := cli.List(ctx, &podList, client.InNamespace(cluster.Namespace), ml); err != nil {
   578  		return err
   579  	}
   580  	// list all pods in dag
   581  	graphCli := model.NewGraphClient(cli)
   582  	pods := graphCli.FindAll(dag, &corev1.Pod{})
   583  
   584  	replicasStr := strconv.Itoa(int(component.Replicas))
   585  	updateAnnotation := func(obj client.Object) {
   586  		annotations := obj.GetAnnotations()
   587  		if annotations == nil {
   588  			annotations = make(map[string]string, 0)
   589  		}
   590  		annotations[constant.ComponentReplicasAnnotationKey] = replicasStr
   591  		obj.SetAnnotations(annotations)
   592  	}
   593  
   594  	for i := range podList.Items {
   595  		pod := &podList.Items[i]
   596  		if pod.Annotations != nil &&
   597  			pod.Annotations[constant.ComponentReplicasAnnotationKey] == replicasStr {
   598  			continue
   599  		}
   600  		idx := slices.IndexFunc(pods, func(obj client.Object) bool {
   601  			return obj.GetName() == pod.Name
   602  		})
   603  		// pod already in dag, merge annotations
   604  		if idx >= 0 {
   605  			updateAnnotation(pods[idx])
   606  			continue
   607  		}
   608  		// pod not in dag, add a new vertex
   609  		updateAnnotation(pod)
   610  		graphCli.Do(dag, nil, pod, model.ActionUpdatePtr(), nil)
   611  	}
   612  	return nil
   613  }
   614  
   615  // updateCustomLabelToPods updates custom label to pods
   616  func updateCustomLabelToPods(ctx context.Context,
   617  	cli client.Client,
   618  	cluster *appsv1alpha1.Cluster,
   619  	component *componentutil.SynthesizedComponent,
   620  	dag *graph.DAG) error {
   621  	if cluster == nil || component == nil {
   622  		return nil
   623  	}
   624  	// list all pods in dag
   625  	graphCli := model.NewGraphClient(cli)
   626  	pods := graphCli.FindAll(dag, &corev1.Pod{})
   627  
   628  	for _, customLabelSpec := range component.CustomLabelSpecs {
   629  		for _, resource := range customLabelSpec.Resources {
   630  			gvk, err := parseCustomLabelPattern(resource.GVK)
   631  			if err != nil {
   632  				return err
   633  			}
   634  			if gvk.Kind != constant.PodKind {
   635  				continue
   636  			}
   637  
   638  			podList := &corev1.PodList{}
   639  			matchLabels := getComponentMatchLabels(cluster.Name, component.Name)
   640  			for k, v := range resource.Selector {
   641  				matchLabels[k] = v
   642  			}
   643  			if err = getObjectListByCustomLabels(ctx, cli, *cluster, podList, client.MatchingLabels(matchLabels)); err != nil {
   644  				return err
   645  			}
   646  
   647  			for i := range podList.Items {
   648  				idx := slices.IndexFunc(pods, func(obj client.Object) bool {
   649  					return obj.GetName() == podList.Items[i].Name
   650  				})
   651  				// pod already in dag, merge labels
   652  				if idx >= 0 {
   653  					updateObjLabel(cluster.Name, string(cluster.UID), component.Name, customLabelSpec, pods[idx])
   654  					continue
   655  				}
   656  				pod := &podList.Items[i]
   657  				updateObjLabel(cluster.Name, string(cluster.UID), component.Name, customLabelSpec, pod)
   658  				graphCli.Do(dag, nil, pod, model.ActionUpdatePtr(), nil)
   659  			}
   660  		}
   661  	}
   662  	return nil
   663  }
   664  
   665  func updateObjLabel(clusterName, uid, componentName string, customLabelSpec appsv1alpha1.CustomLabelSpec,
   666  	obj client.Object) {
   667  	key := replaceKBEnvPlaceholderTokens(clusterName, uid, componentName, customLabelSpec.Key)
   668  	value := replaceKBEnvPlaceholderTokens(clusterName, uid, componentName, customLabelSpec.Value)
   669  
   670  	labels := obj.GetLabels()
   671  	if labels == nil {
   672  		labels = make(map[string]string, 0)
   673  	}
   674  	labels[key] = value
   675  	obj.SetLabels(labels)
   676  }
   677  
   678  func updateCustomLabelToObjs(clusterName, uid, componentName string,
   679  	customLabelSpecs []appsv1alpha1.CustomLabelSpec,
   680  	objs []client.Object) error {
   681  	for _, obj := range objs {
   682  		kinds, _, err := model.GetScheme().ObjectKinds(obj)
   683  		if err != nil {
   684  			return err
   685  		}
   686  		if len(kinds) != 1 {
   687  			return fmt.Errorf("expected exactly 1 kind for object %T, but found %s kinds", obj, kinds)
   688  		}
   689  		kind := kinds[0].Kind
   690  		if !slices.Contains(getCustomLabelSupportKind(), kind) {
   691  			continue
   692  		}
   693  
   694  		for _, customLabelSpec := range customLabelSpecs {
   695  			for _, res := range customLabelSpec.Resources {
   696  				gvk, err := parseCustomLabelPattern(res.GVK)
   697  				if err != nil {
   698  					return err
   699  				}
   700  				if gvk.Kind != kind {
   701  					continue
   702  				}
   703  				updateObjLabel(clusterName, uid, componentName, customLabelSpec, obj)
   704  			}
   705  		}
   706  	}
   707  	return nil
   708  }
   709  
   710  // IsComponentPodsWithLatestRevision checks whether the underlying pod spec matches the one declared in the Cluster/Component.
   711  func IsComponentPodsWithLatestRevision(ctx context.Context, cli client.Client,
   712  	cluster *appsv1alpha1.Cluster, rsm *workloads.ReplicatedStateMachine) (bool, error) {
   713  	if cluster == nil || rsm == nil {
   714  		return false, nil
   715  	}
   716  	// check whether component spec has been sent to rsm
   717  	rsmComponentGeneration := rsm.GetAnnotations()[constant.KubeBlocksGenerationKey]
   718  	if cluster.Status.ObservedGeneration != cluster.Generation ||
   719  		rsmComponentGeneration != strconv.FormatInt(cluster.Generation, 10) {
   720  		return false, nil
   721  	}
   722  	// check whether rsm spec has been sent to the underlying workload(sts)
   723  	if rsm.Status.ObservedGeneration != rsm.Generation ||
   724  		rsm.Status.CurrentGeneration != rsm.Generation {
   725  		return false, nil
   726  	}
   727  	// check whether the underlying workload(sts) has sent the latest template to pods
   728  	sts := &appsv1.StatefulSet{}
   729  	if err := cli.Get(ctx, client.ObjectKeyFromObject(rsm), sts); err != nil {
   730  		return false, err
   731  	}
   732  	if sts.Status.ObservedGeneration != sts.Generation {
   733  		return false, nil
   734  	}
   735  	pods, err := listPodOwnedByComponent(ctx, cli, rsm.Namespace, rsm.Spec.Selector.MatchLabels)
   736  	if err != nil {
   737  		return false, err
   738  	}
   739  	for _, pod := range pods {
   740  		if intctrlutil.GetPodRevision(pod) != sts.Status.UpdateRevision {
   741  			return false, nil
   742  		}
   743  	}
   744  	return true, nil
   745  }