github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/rsm/transformer_object_generation.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  	"encoding/json"
    24  	"fmt"
    25  	"reflect"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"golang.org/x/exp/maps"
    30  	"golang.org/x/exp/slices"
    31  	apps "k8s.io/api/apps/v1"
    32  	corev1 "k8s.io/api/core/v1"
    33  	"k8s.io/apimachinery/pkg/util/intstr"
    34  	"k8s.io/apimachinery/pkg/util/sets"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  
    37  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    38  	"github.com/1aal/kubeblocks/pkg/constant"
    39  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    40  	"github.com/1aal/kubeblocks/pkg/controller/graph"
    41  	"github.com/1aal/kubeblocks/pkg/controller/model"
    42  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    43  )
    44  
    45  type ObjectGenerationTransformer struct{}
    46  
    47  var _ graph.Transformer = &ObjectGenerationTransformer{}
    48  
    49  func (t *ObjectGenerationTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error {
    50  	transCtx, _ := ctx.(*rsmTransformContext)
    51  	rsm := transCtx.rsm
    52  	rsmOrig := transCtx.rsmOrig
    53  	cli, _ := transCtx.Client.(model.GraphClient)
    54  
    55  	if model.IsObjectDeleting(rsmOrig) {
    56  		return nil
    57  	}
    58  
    59  	// generate objects by current spec
    60  	svc := buildSvc(*rsm)
    61  	altSvs := buildAlternativeSvs(*rsm)
    62  	headLessSvc := buildHeadlessSvc(*rsm)
    63  	envConfig := buildEnvConfigMap(*rsm)
    64  	sts := buildSts(*rsm, headLessSvc.Name, *envConfig)
    65  	objects := []client.Object{headLessSvc, envConfig, sts}
    66  	if svc != nil {
    67  		objects = append(objects, svc)
    68  	}
    69  	for _, s := range altSvs {
    70  		objects = append(objects, s)
    71  	}
    72  
    73  	for _, object := range objects {
    74  		if err := setOwnership(rsm, object, model.GetScheme(), getFinalizer(object)); err != nil {
    75  			return err
    76  		}
    77  	}
    78  
    79  	// read cache snapshot
    80  	ml := getLabels(rsm)
    81  	oldSnapshot, err := model.ReadCacheSnapshot(ctx, rsm, ml, ownedKinds()...)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	// compute create/update/delete set
    87  	newSnapshot := make(map[model.GVKNObjKey]client.Object)
    88  	for _, object := range objects {
    89  		name, err := model.GetGVKName(object)
    90  		if err != nil {
    91  			return err
    92  		}
    93  		newSnapshot[*name] = object
    94  	}
    95  
    96  	// now compute the diff between old and target snapshot and generate the plan
    97  	oldNameSet := sets.KeySet(oldSnapshot)
    98  	newNameSet := sets.KeySet(newSnapshot)
    99  
   100  	createSet := newNameSet.Difference(oldNameSet)
   101  	updateSet := newNameSet.Intersection(oldNameSet)
   102  	deleteSet := oldNameSet.Difference(newNameSet)
   103  
   104  	createNewObjects := func() {
   105  		for name := range createSet {
   106  			cli.Create(dag, newSnapshot[name])
   107  		}
   108  	}
   109  	updateObjects := func() {
   110  		for name := range updateSet {
   111  			oldObj := oldSnapshot[name]
   112  			newObj := copyAndMerge(oldObj, newSnapshot[name])
   113  			cli.Update(dag, oldObj, newObj)
   114  		}
   115  	}
   116  	deleteOrphanObjects := func() {
   117  		for name := range deleteSet {
   118  			if viper.GetBool(FeatureGateRSMCompatibilityMode) {
   119  				// filter non-env configmaps
   120  				if _, ok := oldSnapshot[name].(*corev1.ConfigMap); ok {
   121  					continue
   122  				}
   123  			}
   124  			cli.Delete(dag, oldSnapshot[name])
   125  		}
   126  	}
   127  	handleDependencies := func() {
   128  		cli.DependOn(dag, sts, headLessSvc, envConfig)
   129  		if svc != nil {
   130  			cli.DependOn(dag, sts, svc)
   131  		}
   132  	}
   133  
   134  	// objects to be created
   135  	createNewObjects()
   136  	// objects to be updated
   137  	updateObjects()
   138  	// objects to be deleted
   139  	deleteOrphanObjects()
   140  	// handle object dependencies
   141  	handleDependencies()
   142  
   143  	return nil
   144  }
   145  
   146  // copyAndMerge merges two objects for updating:
   147  // 1. new an object targetObj by copying from oldObj
   148  // 2. merge all fields can be updated from newObj into targetObj
   149  func copyAndMerge(oldObj, newObj client.Object) client.Object {
   150  	if reflect.TypeOf(oldObj) != reflect.TypeOf(newObj) {
   151  		return nil
   152  	}
   153  
   154  	// mergeAnnotations keeps the original annotations.
   155  	mergeMetadataMap := func(originalMap map[string]string, targetMap *map[string]string) {
   156  		if targetMap == nil || originalMap == nil {
   157  			return
   158  		}
   159  		if *targetMap == nil {
   160  			*targetMap = map[string]string{}
   161  		}
   162  		for k, v := range originalMap {
   163  			// if the annotation not exist in targetAnnotations, copy it from original.
   164  			if _, ok := (*targetMap)[k]; !ok {
   165  				(*targetMap)[k] = v
   166  			}
   167  		}
   168  	}
   169  
   170  	copyAndMergeSts := func(oldSts, newSts *apps.StatefulSet) client.Object {
   171  		mergeMetadataMap(oldSts.Labels, &newSts.Labels)
   172  		oldSts.Labels = newSts.Labels
   173  		// if annotations exist and are replaced, the StatefulSet will be updated.
   174  		mergeMetadataMap(oldSts.Spec.Template.Annotations, &newSts.Spec.Template.Annotations)
   175  		oldSts.Spec.Template = newSts.Spec.Template
   176  		oldSts.Spec.Replicas = newSts.Spec.Replicas
   177  		oldSts.Spec.UpdateStrategy = newSts.Spec.UpdateStrategy
   178  		return oldSts
   179  	}
   180  
   181  	copyAndMergeSvc := func(oldSvc *corev1.Service, newSvc *corev1.Service) client.Object {
   182  		mergeMetadataMap(oldSvc.Annotations, &newSvc.Annotations)
   183  		oldSvc.Annotations = newSvc.Annotations
   184  		oldSvc.Spec = newSvc.Spec
   185  		return oldSvc
   186  	}
   187  
   188  	copyAndMergeCm := func(oldCm, newCm *corev1.ConfigMap) client.Object {
   189  		oldCm.Data = newCm.Data
   190  		oldCm.BinaryData = newCm.BinaryData
   191  		return oldCm
   192  	}
   193  
   194  	targetObj := oldObj.DeepCopyObject()
   195  	switch o := newObj.(type) {
   196  	case *apps.StatefulSet:
   197  		return copyAndMergeSts(targetObj.(*apps.StatefulSet), o)
   198  	case *corev1.Service:
   199  		return copyAndMergeSvc(targetObj.(*corev1.Service), o)
   200  	case *corev1.ConfigMap:
   201  		return copyAndMergeCm(targetObj.(*corev1.ConfigMap), o)
   202  	default:
   203  		return newObj
   204  	}
   205  }
   206  
   207  func buildSvc(rsm workloads.ReplicatedStateMachine) *corev1.Service {
   208  	if rsm.Spec.Service == nil {
   209  		return nil
   210  	}
   211  	annotations := ParseAnnotationsOfScope(ServiceScope, rsm.Annotations)
   212  	labels := getLabels(&rsm)
   213  	selectors := getSvcSelector(&rsm, false)
   214  	return builder.NewServiceBuilder(rsm.Namespace, rsm.Name).
   215  		AddAnnotationsInMap(annotations).
   216  		AddLabelsInMap(rsm.Spec.Service.Labels).
   217  		AddLabelsInMap(labels).
   218  		AddSelectorsInMap(selectors).
   219  		AddPorts(rsm.Spec.Service.Spec.Ports...).
   220  		SetType(rsm.Spec.Service.Spec.Type).
   221  		GetObject()
   222  }
   223  
   224  func buildAlternativeSvs(rsm workloads.ReplicatedStateMachine) []*corev1.Service {
   225  	if rsm.Spec.Service == nil {
   226  		return nil
   227  	}
   228  	annotations := ParseAnnotationsOfScope(AlternativeServiceScope, rsm.Annotations)
   229  	svcLabels := getLabels(&rsm)
   230  	var services []*corev1.Service
   231  	for i := range rsm.Spec.AlternativeServices {
   232  		service := rsm.Spec.AlternativeServices[i]
   233  		if len(service.Namespace) == 0 {
   234  			service.Namespace = rsm.Namespace
   235  		}
   236  		labels := service.Labels
   237  		if labels == nil {
   238  			labels = make(map[string]string, 0)
   239  		}
   240  		for k, v := range svcLabels {
   241  			labels[k] = v
   242  		}
   243  		service.Labels = labels
   244  		newAnnotations := make(map[string]string, 0)
   245  		maps.Copy(newAnnotations, service.Annotations)
   246  		maps.Copy(newAnnotations, annotations)
   247  		if len(newAnnotations) > 0 {
   248  			service.Annotations = newAnnotations
   249  		}
   250  		services = append(services, &service)
   251  	}
   252  	return services
   253  }
   254  
   255  func buildHeadlessSvc(rsm workloads.ReplicatedStateMachine) *corev1.Service {
   256  	annotations := ParseAnnotationsOfScope(HeadlessServiceScope, rsm.Annotations)
   257  	labels := getLabels(&rsm)
   258  	selectors := getSvcSelector(&rsm, true)
   259  	hdlBuilder := builder.NewHeadlessServiceBuilder(rsm.Namespace, getHeadlessSvcName(rsm)).
   260  		AddLabelsInMap(labels).
   261  		AddSelectorsInMap(selectors).
   262  		AddAnnotationsInMap(annotations)
   263  
   264  	for _, container := range rsm.Spec.Template.Spec.Containers {
   265  		for _, port := range container.Ports {
   266  			servicePort := corev1.ServicePort{
   267  				Protocol: port.Protocol,
   268  				Port:     port.ContainerPort,
   269  			}
   270  			switch {
   271  			case len(port.Name) > 0:
   272  				servicePort.Name = port.Name
   273  				servicePort.TargetPort = intstr.FromString(port.Name)
   274  			default:
   275  				servicePort.Name = fmt.Sprintf("%s-%d", strings.ToLower(string(port.Protocol)), port.ContainerPort)
   276  				servicePort.TargetPort = intstr.FromInt(int(port.ContainerPort))
   277  			}
   278  			hdlBuilder.AddPorts(servicePort)
   279  		}
   280  	}
   281  	return hdlBuilder.GetObject()
   282  }
   283  
   284  func buildSts(rsm workloads.ReplicatedStateMachine, headlessSvcName string, envConfig corev1.ConfigMap) *apps.StatefulSet {
   285  	template := buildStsPodTemplate(rsm, envConfig)
   286  	annotations := ParseAnnotationsOfScope(RootScope, rsm.Annotations)
   287  	labels := getLabels(&rsm)
   288  	return builder.NewStatefulSetBuilder(rsm.Namespace, rsm.Name).
   289  		AddLabelsInMap(labels).
   290  		AddLabels(rsmGenerationLabelKey, strconv.FormatInt(rsm.Generation, 10)).
   291  		AddAnnotationsInMap(annotations).
   292  		SetSelector(rsm.Spec.Selector).
   293  		SetServiceName(headlessSvcName).
   294  		SetReplicas(*rsm.Spec.Replicas).
   295  		SetPodManagementPolicy(rsm.Spec.PodManagementPolicy).
   296  		SetVolumeClaimTemplates(rsm.Spec.VolumeClaimTemplates...).
   297  		SetTemplate(*template).
   298  		SetUpdateStrategy(rsm.Spec.UpdateStrategy).
   299  		GetObject()
   300  }
   301  
   302  func buildEnvConfigMap(rsm workloads.ReplicatedStateMachine) *corev1.ConfigMap {
   303  	envData := buildEnvConfigData(rsm)
   304  	annotations := ParseAnnotationsOfScope(ConfigMapScope, rsm.Annotations)
   305  	labels := getLabels(&rsm)
   306  	if viper.GetBool(FeatureGateRSMCompatibilityMode) {
   307  		labels[constant.AppConfigTypeLabelKey] = "kubeblocks-env"
   308  	}
   309  	return builder.NewConfigMapBuilder(rsm.Namespace, getEnvConfigMapName(rsm.Name)).
   310  		AddAnnotationsInMap(annotations).
   311  		AddLabelsInMap(labels).
   312  		SetData(envData).GetObject()
   313  }
   314  
   315  func buildStsPodTemplate(rsm workloads.ReplicatedStateMachine, envConfig corev1.ConfigMap) *corev1.PodTemplateSpec {
   316  	template := rsm.Spec.Template
   317  	// inject env ConfigMap into workload pods only
   318  	for i := range template.Spec.Containers {
   319  		template.Spec.Containers[i].EnvFrom = append(template.Spec.Containers[i].EnvFrom,
   320  			corev1.EnvFromSource{
   321  				ConfigMapRef: &corev1.ConfigMapEnvSource{
   322  					LocalObjectReference: corev1.LocalObjectReference{
   323  						Name: envConfig.Name,
   324  					},
   325  					Optional: func() *bool { optional := false; return &optional }(),
   326  				}})
   327  	}
   328  
   329  	injectRoleProbeContainer(rsm, &template)
   330  
   331  	return &template
   332  }
   333  
   334  func injectRoleProbeContainer(rsm workloads.ReplicatedStateMachine, template *corev1.PodTemplateSpec) {
   335  	roleProbe := rsm.Spec.RoleProbe
   336  	if roleProbe == nil {
   337  		return
   338  	}
   339  	credential := rsm.Spec.Credential
   340  	credentialEnv := make([]corev1.EnvVar, 0)
   341  	if credential != nil {
   342  		credentialEnv = append(credentialEnv,
   343  			corev1.EnvVar{
   344  				Name:      usernameCredentialVarName,
   345  				Value:     credential.Username.Value,
   346  				ValueFrom: credential.Username.ValueFrom,
   347  			},
   348  			corev1.EnvVar{
   349  				Name:      passwordCredentialVarName,
   350  				Value:     credential.Password.Value,
   351  				ValueFrom: credential.Password.ValueFrom,
   352  			})
   353  	}
   354  
   355  	actionSvcPorts := buildActionSvcPorts(template, roleProbe.CustomHandler)
   356  
   357  	actionSvcList, _ := json.Marshal(actionSvcPorts)
   358  	injectRoleProbeBaseContainer(rsm, template, string(actionSvcList), credentialEnv)
   359  
   360  	if roleProbe.CustomHandler != nil {
   361  		injectCustomRoleProbeContainer(rsm, template, actionSvcPorts, credentialEnv)
   362  	}
   363  }
   364  
   365  func buildActionSvcPorts(template *corev1.PodTemplateSpec, actions []workloads.Action) []int32 {
   366  	findAllUsedPorts := func() []int32 {
   367  		allUsedPorts := make([]int32, 0)
   368  		for _, container := range template.Spec.Containers {
   369  			for _, port := range container.Ports {
   370  				allUsedPorts = append(allUsedPorts, port.ContainerPort)
   371  				allUsedPorts = append(allUsedPorts, port.HostPort)
   372  			}
   373  		}
   374  		return allUsedPorts
   375  	}
   376  
   377  	findNextAvailablePort := func(base int32, allUsedPorts []int32) int32 {
   378  		for port := base + 1; port < 65535; port++ {
   379  			available := true
   380  			for _, usedPort := range allUsedPorts {
   381  				if port == usedPort {
   382  					available = false
   383  					break
   384  				}
   385  			}
   386  			if available {
   387  				return port
   388  			}
   389  		}
   390  		return 0
   391  	}
   392  
   393  	allUsedPorts := findAllUsedPorts()
   394  	svcPort := actionSvcPortBase
   395  	var actionSvcPorts []int32
   396  	for range actions {
   397  		svcPort = findNextAvailablePort(svcPort, allUsedPorts)
   398  		actionSvcPorts = append(actionSvcPorts, svcPort)
   399  	}
   400  	return actionSvcPorts
   401  }
   402  
   403  func injectRoleProbeBaseContainer(rsm workloads.ReplicatedStateMachine, template *corev1.PodTemplateSpec, actionSvcList string, credentialEnv []corev1.EnvVar) {
   404  	// compute parameters for role probe base container
   405  	roleProbe := rsm.Spec.RoleProbe
   406  	if roleProbe == nil {
   407  		return
   408  	}
   409  	credential := rsm.Spec.Credential
   410  	image := viper.GetString(constant.KBToolsImage)
   411  	probeDaemonPort := viper.GetInt("ROLE_PROBE_SERVICE_PORT")
   412  	if probeDaemonPort == 0 {
   413  		probeDaemonPort = defaultRoleProbeDaemonPort
   414  	}
   415  	probeGRPCPort := viper.GetInt("ROLE_PROBE_GRPC_PORT")
   416  	if probeGRPCPort == 0 {
   417  		probeGRPCPort = defaultRoleProbeGRPCPort
   418  	}
   419  	env := credentialEnv
   420  	env = append(env,
   421  		corev1.EnvVar{
   422  			Name:  actionSvcListVarName,
   423  			Value: actionSvcList,
   424  		})
   425  	if credential != nil {
   426  		// for compatibility with old probe env var names
   427  		env = append(env,
   428  			corev1.EnvVar{
   429  				Name:      "KB_SERVICE_USER",
   430  				Value:     credential.Username.Value,
   431  				ValueFrom: credential.Username.ValueFrom,
   432  			},
   433  			corev1.EnvVar{
   434  				Name:      "KB_SERVICE_PASSWORD",
   435  				Value:     credential.Password.Value,
   436  				ValueFrom: credential.Password.ValueFrom,
   437  			})
   438  	}
   439  	// find service port of th db engine
   440  	servicePort := findSvcPort(rsm)
   441  	if servicePort > 0 {
   442  		env = append(env,
   443  			corev1.EnvVar{
   444  				Name:  servicePortVarName,
   445  				Value: strconv.Itoa(servicePort),
   446  			},
   447  			// for compatibility with old probe env var names
   448  			corev1.EnvVar{
   449  				Name:  "KB_SERVICE_PORT",
   450  				Value: strconv.Itoa(servicePort),
   451  			})
   452  	}
   453  
   454  	// inject role update mechanism env
   455  	env = append(env,
   456  		corev1.EnvVar{
   457  			Name:  RoleUpdateMechanismVarName,
   458  			Value: string(roleProbe.RoleUpdateMechanism),
   459  		})
   460  
   461  	// inject role probe timeout env
   462  	env = append(env,
   463  		corev1.EnvVar{
   464  			Name:  roleProbeTimeoutVarName,
   465  			Value: strconv.Itoa(int(roleProbe.TimeoutSeconds)),
   466  		})
   467  
   468  	// lorry related envs
   469  	env = append(env,
   470  		corev1.EnvVar{
   471  			Name: constant.KBEnvPodName,
   472  			ValueFrom: &corev1.EnvVarSource{
   473  				FieldRef: &corev1.ObjectFieldSelector{
   474  					FieldPath: "metadata.name",
   475  				},
   476  			},
   477  		},
   478  		corev1.EnvVar{
   479  			Name: constant.KBEnvNamespace,
   480  			ValueFrom: &corev1.EnvVarSource{
   481  				FieldRef: &corev1.ObjectFieldSelector{
   482  					FieldPath: "metadata.namespace",
   483  				},
   484  			},
   485  		},
   486  		corev1.EnvVar{
   487  			Name: constant.KBEnvPodUID,
   488  			ValueFrom: &corev1.EnvVarSource{
   489  				FieldRef: &corev1.ObjectFieldSelector{
   490  					FieldPath: "metadata.uid",
   491  				},
   492  			},
   493  		},
   494  		corev1.EnvVar{
   495  			Name: constant.KBEnvNodeName,
   496  			ValueFrom: &corev1.EnvVarSource{
   497  				FieldRef: &corev1.ObjectFieldSelector{
   498  					FieldPath: "spec.nodeName",
   499  				},
   500  			},
   501  		},
   502  	)
   503  
   504  	characterType := "custom"
   505  	if roleProbe.BuiltinHandler != nil {
   506  		characterType = *roleProbe.BuiltinHandler
   507  	}
   508  	env = append(env, corev1.EnvVar{
   509  		Name:  constant.KBEnvCharacterType,
   510  		Value: characterType,
   511  	})
   512  
   513  	readinessProbe := &corev1.Probe{
   514  		InitialDelaySeconds: roleProbe.InitialDelaySeconds,
   515  		TimeoutSeconds:      roleProbe.TimeoutSeconds,
   516  		PeriodSeconds:       roleProbe.PeriodSeconds,
   517  		SuccessThreshold:    roleProbe.SuccessThreshold,
   518  		FailureThreshold:    roleProbe.FailureThreshold,
   519  	}
   520  
   521  	if roleProbe.RoleUpdateMechanism == workloads.ReadinessProbeEventUpdate {
   522  		readinessProbe.ProbeHandler = corev1.ProbeHandler{
   523  			Exec: &corev1.ExecAction{
   524  				Command: []string{
   525  					grpcHealthProbeBinaryPath,
   526  					fmt.Sprintf(grpcHealthProbeArgsFormat, probeGRPCPort),
   527  				},
   528  			},
   529  		}
   530  	} else {
   531  		readinessProbe.HTTPGet = &corev1.HTTPGetAction{
   532  			Path: httpRoleProbePath,
   533  			Port: intstr.FromInt(probeDaemonPort),
   534  		}
   535  	}
   536  
   537  	tryToGetRoleProbeContainer := func() *corev1.Container {
   538  		for i, container := range template.Spec.Containers {
   539  			if container.Name == constant.RoleProbeContainerName {
   540  				return &template.Spec.Containers[i]
   541  			}
   542  		}
   543  		return nil
   544  	}
   545  
   546  	tryToGetLorryGrpcPort := func(container *corev1.Container) *corev1.ContainerPort {
   547  		for i, port := range container.Ports {
   548  			if port.Name == constant.LorryGRPCPortName {
   549  				return &container.Ports[i]
   550  			}
   551  		}
   552  		return nil
   553  	}
   554  
   555  	tryToGetLorryHTTPPort := func(container *corev1.Container) *corev1.ContainerPort {
   556  		for i, port := range container.Ports {
   557  			if port.Name == constant.LorryHTTPPortName {
   558  				return &container.Ports[i]
   559  			}
   560  		}
   561  		return nil
   562  	}
   563  
   564  	// if role probe container exists, update the readiness probe, env and serving container port
   565  	if container := tryToGetRoleProbeContainer(); container != nil {
   566  		if roleProbe.RoleUpdateMechanism == workloads.ReadinessProbeEventUpdate {
   567  			port := tryToGetLorryGrpcPort(container)
   568  			var portNum int
   569  			if port == nil {
   570  				portNum = probeGRPCPort
   571  				grpcPort := corev1.ContainerPort{
   572  					Name:          roleProbeGRPCPortName,
   573  					ContainerPort: int32(portNum),
   574  					Protocol:      "TCP",
   575  				}
   576  				container.Ports = append(container.Ports, grpcPort)
   577  			} else {
   578  				// if containerPort is invalid, adjust it
   579  				if port.ContainerPort < 0 || port.ContainerPort > 65536 {
   580  					port.ContainerPort = int32(probeGRPCPort)
   581  				}
   582  				portNum = int(port.ContainerPort)
   583  			}
   584  			readinessProbe.Exec.Command = []string{
   585  				grpcHealthProbeBinaryPath,
   586  				fmt.Sprintf(grpcHealthProbeArgsFormat, portNum),
   587  			}
   588  		} else {
   589  			port := tryToGetLorryHTTPPort(container)
   590  			var portNum int
   591  			if port == nil {
   592  				portNum = probeDaemonPort
   593  				httpPort := corev1.ContainerPort{
   594  					Name:          constant.LorryHTTPPortName,
   595  					ContainerPort: int32(portNum),
   596  					Protocol:      "TCP",
   597  				}
   598  				container.Ports = append(container.Ports, httpPort)
   599  			} else {
   600  				// if containerPort is invalid, adjust it
   601  				if port.ContainerPort < 0 || port.ContainerPort > 65536 {
   602  					port.ContainerPort = int32(probeDaemonPort)
   603  				}
   604  				portNum = int(port.ContainerPort)
   605  			}
   606  			readinessProbe.HTTPGet = &corev1.HTTPGetAction{
   607  				Path: httpRoleProbePath,
   608  				Port: intstr.FromInt(portNum),
   609  			}
   610  		}
   611  		container.ReadinessProbe = readinessProbe
   612  		for _, e := range env {
   613  			if slices.IndexFunc(container.Env, func(v corev1.EnvVar) bool {
   614  				return v.Name == e.Name
   615  			}) >= 0 {
   616  				continue
   617  			}
   618  			container.Env = append(container.Env, e)
   619  		}
   620  		return
   621  	}
   622  
   623  	// if role probe container doesn't exist, create a new one
   624  	// build container
   625  	container := builder.NewContainerBuilder(roleProbeContainerName).
   626  		SetImage(image).
   627  		SetImagePullPolicy(corev1.PullIfNotPresent).
   628  		AddCommands([]string{
   629  			roleProbeBinaryName,
   630  			"--port", strconv.Itoa(probeDaemonPort),
   631  			"--grpcport", strconv.Itoa(probeGRPCPort),
   632  		}...).
   633  		AddEnv(env...).
   634  		AddPorts(
   635  			corev1.ContainerPort{
   636  				ContainerPort: int32(probeDaemonPort),
   637  				Name:          roleProbeContainerName,
   638  				Protocol:      "TCP",
   639  			},
   640  			corev1.ContainerPort{
   641  				ContainerPort: int32(probeGRPCPort),
   642  				Name:          roleProbeGRPCPortName,
   643  				Protocol:      "TCP",
   644  			},
   645  		).
   646  		SetReadinessProbe(*readinessProbe).
   647  		GetObject()
   648  
   649  	// inject role probe container
   650  	template.Spec.Containers = append(template.Spec.Containers, *container)
   651  }
   652  
   653  func injectCustomRoleProbeContainer(rsm workloads.ReplicatedStateMachine, template *corev1.PodTemplateSpec, actionSvcPorts []int32, credentialEnv []corev1.EnvVar) {
   654  	if rsm.Spec.RoleProbe == nil {
   655  		return
   656  	}
   657  
   658  	// inject shared volume
   659  	agentVolume := corev1.Volume{
   660  		Name: roleAgentVolumeName,
   661  		VolumeSource: corev1.VolumeSource{
   662  			EmptyDir: &corev1.EmptyDirVolumeSource{},
   663  		},
   664  	}
   665  	template.Spec.Volumes = append(template.Spec.Volumes, agentVolume)
   666  
   667  	// inject init container
   668  	agentVolumeMount := corev1.VolumeMount{
   669  		Name:      roleAgentVolumeName,
   670  		MountPath: roleAgentVolumeMountPath,
   671  	}
   672  	agentPath := strings.Join([]string{roleAgentVolumeMountPath, roleAgentName}, "/")
   673  	initContainer := corev1.Container{
   674  		Name:            roleAgentInstallerName,
   675  		Image:           shell2httpImage,
   676  		ImagePullPolicy: corev1.PullIfNotPresent,
   677  		VolumeMounts:    []corev1.VolumeMount{agentVolumeMount},
   678  		Command: []string{
   679  			"cp",
   680  			shell2httpBinaryPath,
   681  			agentPath,
   682  		},
   683  	}
   684  	template.Spec.InitContainers = append(template.Spec.InitContainers, initContainer)
   685  
   686  	// inject action containers based on utility images
   687  	for i, action := range rsm.Spec.RoleProbe.CustomHandler {
   688  		image := action.Image
   689  		if len(image) == 0 {
   690  			image = defaultActionImage
   691  		}
   692  		command := []string{
   693  			agentPath,
   694  			"-port", fmt.Sprintf("%d", actionSvcPorts[i]),
   695  			"-export-all-vars",
   696  			"-form",
   697  			shell2httpServePath,
   698  			strings.Join(action.Command, " "),
   699  		}
   700  		container := corev1.Container{
   701  			Name:            fmt.Sprintf("action-%d", i),
   702  			Image:           image,
   703  			ImagePullPolicy: corev1.PullIfNotPresent,
   704  			VolumeMounts:    []corev1.VolumeMount{agentVolumeMount},
   705  			Env:             credentialEnv,
   706  			Command:         command,
   707  		}
   708  		template.Spec.Containers = append(template.Spec.Containers, container)
   709  	}
   710  }
   711  
   712  func buildEnvConfigData(set workloads.ReplicatedStateMachine) map[string]string {
   713  	envData := map[string]string{}
   714  	svcName := getHeadlessSvcName(set)
   715  	uid := string(set.UID)
   716  	strReplicas := strconv.Itoa(int(*set.Spec.Replicas))
   717  	generateReplicaEnv := func(prefix string) {
   718  		for i := 0; i < int(*set.Spec.Replicas); i++ {
   719  			hostNameTplKey := prefix + strconv.Itoa(i) + "_HOSTNAME"
   720  			hostNameTplValue := set.Name + "-" + strconv.Itoa(i)
   721  			envData[hostNameTplKey] = fmt.Sprintf("%s.%s", hostNameTplValue, svcName)
   722  		}
   723  	}
   724  	// build member related envs from set.Status.MembersStatus
   725  	generateMemberEnv := func(prefix string) {
   726  		followers := ""
   727  		for _, memberStatus := range set.Status.MembersStatus {
   728  			if memberStatus.PodName == "" || memberStatus.PodName == defaultPodName {
   729  				continue
   730  			}
   731  			switch {
   732  			case memberStatus.IsLeader:
   733  				envData[prefix+"LEADER"] = memberStatus.PodName
   734  			case memberStatus.CanVote:
   735  				if len(followers) > 0 {
   736  					followers += ","
   737  				}
   738  				followers += memberStatus.PodName
   739  			}
   740  		}
   741  		if followers != "" {
   742  			envData[prefix+"FOLLOWERS"] = followers
   743  		}
   744  	}
   745  
   746  	prefix := constant.KBPrefix + "_RSM_"
   747  	envData[prefix+"N"] = strReplicas
   748  	generateReplicaEnv(prefix)
   749  	generateMemberEnv(prefix)
   750  	// set owner uid to let pod know if the owner is recreated
   751  	envData[prefix+"OWNER_UID"] = uid
   752  	envData[prefix+"OWNER_UID_SUFFIX8"] = uid[len(uid)-4:]
   753  
   754  	// have backward compatible handling for env generated in version prior 0.6.0
   755  	prefix = constant.KBPrefix + "_"
   756  	envData[prefix+"REPLICA_COUNT"] = strReplicas
   757  	generateReplicaEnv(prefix)
   758  	generateMemberEnv(prefix)
   759  	envData[prefix+"CLUSTER_UID"] = uid
   760  
   761  	// have backward compatible handling for CM key with 'compDefName' being part of the key name, prior 0.5.0
   762  	// and introduce env/cm key naming reference complexity
   763  	componentDefName := set.Labels[constant.AppComponentLabelKey]
   764  	prefixWithCompDefName := prefix + strings.ToUpper(componentDefName) + "_"
   765  	envData[prefixWithCompDefName+"N"] = strReplicas
   766  	generateReplicaEnv(prefixWithCompDefName)
   767  	generateMemberEnv(prefixWithCompDefName)
   768  	envData[prefixWithCompDefName+"CLUSTER_UID"] = uid
   769  
   770  	return envData
   771  }