github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/controller/factory/builder.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 factory
    21  
    22  import (
    23  	"encoding/base64"
    24  	"encoding/hex"
    25  	"encoding/json"
    26  	"fmt"
    27  	"path/filepath"
    28  	"sort"
    29  	"strconv"
    30  	"strings"
    31  
    32  	"github.com/google/uuid"
    33  	snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
    34  	appsv1 "k8s.io/api/apps/v1"
    35  	batchv1 "k8s.io/api/batch/v1"
    36  	corev1 "k8s.io/api/core/v1"
    37  	policyv1 "k8s.io/api/policy/v1"
    38  	rbacv1 "k8s.io/api/rbac/v1"
    39  	"k8s.io/apimachinery/pkg/types"
    40  	"k8s.io/apimachinery/pkg/util/rand"
    41  
    42  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    43  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    44  	workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    45  	"github.com/1aal/kubeblocks/pkg/common"
    46  	cfgcm "github.com/1aal/kubeblocks/pkg/configuration/config_manager"
    47  	"github.com/1aal/kubeblocks/pkg/constant"
    48  	"github.com/1aal/kubeblocks/pkg/controller/builder"
    49  	"github.com/1aal/kubeblocks/pkg/controller/component"
    50  	"github.com/1aal/kubeblocks/pkg/controller/rsm"
    51  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    52  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    53  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    54  )
    55  
    56  const (
    57  	VolumeName = "tls"
    58  	CAName     = "ca.crt"
    59  	CertName   = "tls.crt"
    60  	KeyName    = "tls.key"
    61  	MountPath  = "/etc/pki/tls"
    62  )
    63  
    64  func processContainersInjection(reqCtx intctrlutil.RequestCtx,
    65  	cluster *appsv1alpha1.Cluster,
    66  	component *component.SynthesizedComponent,
    67  	envConfigName string,
    68  	podSpec *corev1.PodSpec) error {
    69  	for _, cc := range []*[]corev1.Container{
    70  		&podSpec.Containers,
    71  		&podSpec.InitContainers,
    72  	} {
    73  		for i := range *cc {
    74  			if err := injectEnvs(cluster, component, envConfigName, &(*cc)[i]); err != nil {
    75  				return err
    76  			}
    77  			intctrlutil.InjectZeroResourcesLimitsIfEmpty(&(*cc)[i])
    78  		}
    79  	}
    80  	return nil
    81  }
    82  
    83  func injectEnvs(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent, envConfigName string, c *corev1.Container) error {
    84  	// can not use map, it is unordered
    85  	envFieldPathSlice := []struct {
    86  		name      string
    87  		fieldPath string
    88  	}{
    89  		{name: constant.KBEnvPodName, fieldPath: "metadata.name"},
    90  		{name: constant.KBEnvPodUID, fieldPath: "metadata.uid"},
    91  		{name: constant.KBEnvNamespace, fieldPath: "metadata.namespace"},
    92  		{name: "KB_SA_NAME", fieldPath: "spec.serviceAccountName"},
    93  		{name: constant.KBEnvNodeName, fieldPath: "spec.nodeName"},
    94  		{name: constant.KBEnvHostIP, fieldPath: "status.hostIP"},
    95  		{name: "KB_POD_IP", fieldPath: "status.podIP"},
    96  		{name: "KB_POD_IPS", fieldPath: "status.podIPs"},
    97  		// TODO: need to deprecate following
    98  		{name: "KB_HOSTIP", fieldPath: "status.hostIP"},
    99  		{name: "KB_PODIP", fieldPath: "status.podIP"},
   100  		{name: "KB_PODIPS", fieldPath: "status.podIPs"},
   101  	}
   102  
   103  	toInjectEnvs := make([]corev1.EnvVar, 0, len(envFieldPathSlice)+len(c.Env))
   104  	for _, v := range envFieldPathSlice {
   105  		toInjectEnvs = append(toInjectEnvs, corev1.EnvVar{
   106  			Name: v.name,
   107  			ValueFrom: &corev1.EnvVarSource{
   108  				FieldRef: &corev1.ObjectFieldSelector{
   109  					APIVersion: "v1",
   110  					FieldPath:  v.fieldPath,
   111  				},
   112  			},
   113  		})
   114  	}
   115  
   116  	var kbClusterPostfix8 string
   117  	if len(cluster.UID) > 8 {
   118  		kbClusterPostfix8 = string(cluster.UID)[len(cluster.UID)-8:]
   119  	} else {
   120  		kbClusterPostfix8 = string(cluster.UID)
   121  	}
   122  	toInjectEnvs = append(toInjectEnvs, []corev1.EnvVar{
   123  		{Name: "KB_CLUSTER_NAME", Value: cluster.Name},
   124  		{Name: "KB_COMP_NAME", Value: component.Name},
   125  		{Name: "KB_CLUSTER_COMP_NAME", Value: cluster.Name + "-" + component.Name},
   126  		{Name: "KB_CLUSTER_UID_POSTFIX_8", Value: kbClusterPostfix8},
   127  		{Name: "KB_POD_FQDN", Value: fmt.Sprintf("%s.%s-headless.%s.svc", "$(KB_POD_NAME)",
   128  			"$(KB_CLUSTER_COMP_NAME)", "$(KB_NAMESPACE)")},
   129  	}...)
   130  
   131  	if component.TLS {
   132  		toInjectEnvs = append(toInjectEnvs, []corev1.EnvVar{
   133  			{Name: "KB_TLS_CERT_PATH", Value: MountPath},
   134  			{Name: "KB_TLS_CA_FILE", Value: CAName},
   135  			{Name: "KB_TLS_CERT_FILE", Value: CertName},
   136  			{Name: "KB_TLS_KEY_FILE", Value: KeyName},
   137  		}...)
   138  	}
   139  
   140  	if udeValue, ok := cluster.Annotations[constant.ExtraEnvAnnotationKey]; ok {
   141  		udeMap := make(map[string]string)
   142  		if err := json.Unmarshal([]byte(udeValue), &udeMap); err != nil {
   143  			return err
   144  		}
   145  		keys := make([]string, 0)
   146  		for k := range udeMap {
   147  			if k == "" || udeMap[k] == "" {
   148  				continue
   149  			}
   150  			keys = append(keys, k)
   151  		}
   152  		sort.Strings(keys)
   153  		for _, k := range keys {
   154  			toInjectEnvs = append(toInjectEnvs, corev1.EnvVar{
   155  				Name:  k,
   156  				Value: udeMap[k],
   157  			})
   158  		}
   159  	}
   160  
   161  	// have injected variables placed at the front of the slice
   162  	if len(c.Env) == 0 {
   163  		c.Env = toInjectEnvs
   164  	} else {
   165  		c.Env = append(toInjectEnvs, c.Env...)
   166  	}
   167  	if envConfigName == "" {
   168  		return nil
   169  	}
   170  	c.EnvFrom = append(c.EnvFrom, corev1.EnvFromSource{
   171  		ConfigMapRef: &corev1.ConfigMapEnvSource{
   172  			LocalObjectReference: corev1.LocalObjectReference{
   173  				Name: envConfigName,
   174  			},
   175  		},
   176  	})
   177  	return nil
   178  }
   179  
   180  // BuildPersistentVolumeClaimLabels builds a pvc name label, and synchronize the labels from sts to pvc.
   181  func BuildPersistentVolumeClaimLabels(component *component.SynthesizedComponent, pvc *corev1.PersistentVolumeClaim,
   182  	pvcTplName string) {
   183  	// strict args checking.
   184  	if pvc == nil || component == nil {
   185  		return
   186  	}
   187  	if pvc.Labels == nil {
   188  		pvc.Labels = make(map[string]string)
   189  	}
   190  	pvc.Labels[constant.VolumeClaimTemplateNameLabelKey] = pvcTplName
   191  
   192  	if component.VolumeTypes != nil {
   193  		for _, t := range component.VolumeTypes {
   194  			if t.Name == pvcTplName {
   195  				pvc.Labels[constant.VolumeTypeLabelKey] = string(t.Type)
   196  				break
   197  			}
   198  		}
   199  	}
   200  }
   201  
   202  func BuildCommonLabels(cluster *appsv1alpha1.Cluster,
   203  	component *component.SynthesizedComponent) map[string]string {
   204  	return map[string]string{
   205  		constant.AppManagedByLabelKey:   constant.AppName,
   206  		constant.AppNameLabelKey:        component.ClusterDefName,
   207  		constant.AppInstanceLabelKey:    cluster.Name,
   208  		constant.KBAppComponentLabelKey: component.Name,
   209  	}
   210  }
   211  
   212  func vctToPVC(vct corev1.PersistentVolumeClaimTemplate) corev1.PersistentVolumeClaim {
   213  	return corev1.PersistentVolumeClaim{
   214  		ObjectMeta: vct.ObjectMeta,
   215  		Spec:       vct.Spec,
   216  	}
   217  }
   218  
   219  func BuildSts(reqCtx intctrlutil.RequestCtx, cluster *appsv1alpha1.Cluster,
   220  	component *component.SynthesizedComponent, envConfigName string) (*appsv1.StatefulSet, error) {
   221  	commonLabels := BuildCommonLabels(cluster, component)
   222  	podBuilder := builder.NewPodBuilder("", "").
   223  		AddLabelsInMap(commonLabels).
   224  		AddLabels(constant.AppComponentLabelKey, component.CompDefName).
   225  		AddLabels(constant.WorkloadTypeLabelKey, string(component.WorkloadType))
   226  	if len(cluster.Spec.ClusterVersionRef) > 0 {
   227  		podBuilder.AddLabels(constant.AppVersionLabelKey, cluster.Spec.ClusterVersionRef)
   228  	}
   229  	template := corev1.PodTemplateSpec{
   230  		ObjectMeta: podBuilder.GetObject().ObjectMeta,
   231  		Spec:       *component.PodSpec,
   232  	}
   233  	stsBuilder := builder.NewStatefulSetBuilder(cluster.Namespace, cluster.Name+"-"+component.Name).
   234  		AddLabelsInMap(commonLabels).
   235  		AddLabels(constant.AppComponentLabelKey, component.CompDefName).
   236  		AddMatchLabelsInMap(commonLabels).
   237  		SetServiceName(cluster.Name + "-" + component.Name + "-headless").
   238  		SetReplicas(component.Replicas).
   239  		SetTemplate(template)
   240  
   241  	var vcts []corev1.PersistentVolumeClaim
   242  	for _, vct := range component.VolumeClaimTemplates {
   243  		vcts = append(vcts, vctToPVC(vct))
   244  	}
   245  	stsBuilder.SetVolumeClaimTemplates(vcts...)
   246  
   247  	if component.StatefulSetWorkload != nil {
   248  		podManagementPolicy, updateStrategy := component.StatefulSetWorkload.FinalStsUpdateStrategy()
   249  		stsBuilder.SetPodManagementPolicy(podManagementPolicy).SetUpdateStrategy(updateStrategy)
   250  	}
   251  
   252  	sts := stsBuilder.GetObject()
   253  
   254  	// update sts.spec.volumeClaimTemplates[].metadata.labels
   255  	if len(sts.Spec.VolumeClaimTemplates) > 0 && len(sts.GetLabels()) > 0 {
   256  		for index, vct := range sts.Spec.VolumeClaimTemplates {
   257  			BuildPersistentVolumeClaimLabels(component, &vct, vct.Name)
   258  			sts.Spec.VolumeClaimTemplates[index] = vct
   259  		}
   260  	}
   261  
   262  	if err := processContainersInjection(reqCtx, cluster, component, envConfigName, &sts.Spec.Template.Spec); err != nil {
   263  		return nil, err
   264  	}
   265  	return sts, nil
   266  }
   267  
   268  func buildWellKnownLabels(clusterDefName, clusterName, componentName string) map[string]string {
   269  	return map[string]string{
   270  		constant.AppManagedByLabelKey:   constant.AppName,
   271  		constant.AppNameLabelKey:        clusterDefName,
   272  		constant.AppInstanceLabelKey:    clusterName,
   273  		constant.KBAppComponentLabelKey: componentName,
   274  	}
   275  }
   276  
   277  func BuildRSM(reqCtx intctrlutil.RequestCtx, cluster *appsv1alpha1.Cluster,
   278  	component *component.SynthesizedComponent, envConfigName string) (*workloads.ReplicatedStateMachine, error) {
   279  	commonLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name)
   280  	addCommonLabels := func(service *corev1.Service) {
   281  		if service == nil {
   282  			return
   283  		}
   284  		labels := service.Labels
   285  		if labels == nil {
   286  			labels = make(map[string]string, 0)
   287  		}
   288  		for k, v := range commonLabels {
   289  			labels[k] = v
   290  		}
   291  		labels[constant.AppComponentLabelKey] = component.CompDefName
   292  		service.Labels = labels
   293  	}
   294  
   295  	podBuilder := builder.NewPodBuilder("", "").
   296  		AddLabelsInMap(commonLabels).
   297  		AddLabels(constant.AppComponentLabelKey, component.CompDefName).
   298  		AddLabels(constant.WorkloadTypeLabelKey, string(component.WorkloadType))
   299  	if len(cluster.Spec.ClusterVersionRef) > 0 {
   300  		podBuilder.AddLabels(constant.AppVersionLabelKey, cluster.Spec.ClusterVersionRef)
   301  	}
   302  	template := corev1.PodTemplateSpec{
   303  		ObjectMeta: podBuilder.GetObject().ObjectMeta,
   304  		Spec:       *component.PodSpec,
   305  	}
   306  
   307  	monitorAnnotations := func() map[string]string {
   308  		annotations := make(map[string]string, 0)
   309  		falseStr := "false"
   310  		trueStr := "true"
   311  		switch {
   312  		case !component.Monitor.Enable:
   313  			annotations["monitor.kubeblocks.io/scrape"] = falseStr
   314  			annotations["monitor.kubeblocks.io/agamotto"] = falseStr
   315  		case component.Monitor.BuiltIn:
   316  			annotations["monitor.kubeblocks.io/scrape"] = falseStr
   317  			annotations["monitor.kubeblocks.io/agamotto"] = trueStr
   318  		default:
   319  			annotations["monitor.kubeblocks.io/scrape"] = trueStr
   320  			annotations["monitor.kubeblocks.io/path"] = component.Monitor.ScrapePath
   321  			annotations["monitor.kubeblocks.io/port"] = strconv.Itoa(int(component.Monitor.ScrapePort))
   322  			annotations["monitor.kubeblocks.io/scheme"] = "http"
   323  			annotations["monitor.kubeblocks.io/agamotto"] = falseStr
   324  		}
   325  		return rsm.AddAnnotationScope(rsm.HeadlessServiceScope, annotations)
   326  	}()
   327  	rsmName := fmt.Sprintf("%s-%s", cluster.Name, component.Name)
   328  	rsmBuilder := builder.NewReplicatedStateMachineBuilder(cluster.Namespace, rsmName).
   329  		AddAnnotations(constant.KubeBlocksGenerationKey, strconv.FormatInt(cluster.Generation, 10)).
   330  		AddAnnotationsInMap(monitorAnnotations).
   331  		AddLabelsInMap(commonLabels).
   332  		AddLabels(constant.AppComponentLabelKey, component.CompDefName).
   333  		AddMatchLabelsInMap(commonLabels).
   334  		SetServiceName(rsmName + "-headless").
   335  		SetReplicas(component.Replicas).
   336  		SetTemplate(template)
   337  
   338  	var vcts []corev1.PersistentVolumeClaim
   339  	for _, vct := range component.VolumeClaimTemplates {
   340  		vcts = append(vcts, vctToPVC(vct))
   341  	}
   342  	rsmBuilder.SetVolumeClaimTemplates(vcts...)
   343  
   344  	if component.StatefulSetWorkload != nil {
   345  		podManagementPolicy, updateStrategy := component.StatefulSetWorkload.FinalStsUpdateStrategy()
   346  		rsmBuilder.SetPodManagementPolicy(podManagementPolicy).SetUpdateStrategy(updateStrategy)
   347  	}
   348  
   349  	service, alternativeServices := separateServices(component.Services)
   350  	addCommonLabels(service)
   351  	for i := range alternativeServices {
   352  		addCommonLabels(&alternativeServices[i])
   353  	}
   354  	if service != nil {
   355  		rsmBuilder.SetService(service)
   356  	}
   357  	if len(alternativeServices) == 0 {
   358  		alternativeServices = nil
   359  	}
   360  	alternativeServices = fixService(cluster.Namespace, rsmName, component, alternativeServices...)
   361  	rsmBuilder.SetAlternativeServices(alternativeServices)
   362  
   363  	secretName := fmt.Sprintf("%s-conn-credential", cluster.Name)
   364  	credential := workloads.Credential{
   365  		Username: workloads.CredentialVar{
   366  			ValueFrom: &corev1.EnvVarSource{
   367  				SecretKeyRef: &corev1.SecretKeySelector{
   368  					LocalObjectReference: corev1.LocalObjectReference{
   369  						Name: secretName,
   370  					},
   371  					Key: constant.AccountNameForSecret,
   372  				},
   373  			},
   374  		},
   375  		Password: workloads.CredentialVar{
   376  			ValueFrom: &corev1.EnvVarSource{
   377  				SecretKeyRef: &corev1.SecretKeySelector{
   378  					LocalObjectReference: corev1.LocalObjectReference{
   379  						Name: secretName,
   380  					},
   381  					Key: constant.AccountPasswdForSecret,
   382  				},
   383  			},
   384  		},
   385  	}
   386  	rsmBuilder.SetCredential(credential)
   387  
   388  	roles, roleProbe, membershipReconfiguration, memberUpdateStrategy := buildRoleInfo(component)
   389  	rsm := rsmBuilder.SetRoles(roles).
   390  		SetRoleProbe(roleProbe).
   391  		SetMembershipReconfiguration(membershipReconfiguration).
   392  		SetMemberUpdateStrategy(memberUpdateStrategy).
   393  		GetObject()
   394  
   395  	// update sts.spec.volumeClaimTemplates[].metadata.labels
   396  	if len(rsm.Spec.VolumeClaimTemplates) > 0 && len(rsm.GetLabels()) > 0 {
   397  		for index, vct := range rsm.Spec.VolumeClaimTemplates {
   398  			BuildPersistentVolumeClaimLabels(component, &vct, vct.Name)
   399  			rsm.Spec.VolumeClaimTemplates[index] = vct
   400  		}
   401  	}
   402  
   403  	if err := processContainersInjection(reqCtx, cluster, component, envConfigName, &rsm.Spec.Template.Spec); err != nil {
   404  		return nil, err
   405  	}
   406  	return rsm, nil
   407  }
   408  
   409  func fixService(namespace, prefix string, component *component.SynthesizedComponent, alternativeServices ...corev1.Service) []corev1.Service {
   410  	leaderName := getLeaderName(component)
   411  	for i := range alternativeServices {
   412  		if len(alternativeServices[i].Name) > 0 {
   413  			alternativeServices[i].Name = prefix + "-" + alternativeServices[i].Name
   414  		}
   415  		if len(alternativeServices[i].Namespace) == 0 {
   416  			alternativeServices[i].Namespace = namespace
   417  		}
   418  		if alternativeServices[i].Spec.Type == corev1.ServiceTypeLoadBalancer {
   419  			alternativeServices[i].Spec.ExternalTrafficPolicy = corev1.ServiceExternalTrafficPolicyTypeLocal
   420  		}
   421  		if len(leaderName) > 0 {
   422  			selector := alternativeServices[i].Spec.Selector
   423  			if selector == nil {
   424  				selector = make(map[string]string, 0)
   425  			}
   426  			selector[constant.RoleLabelKey] = leaderName
   427  			alternativeServices[i].Spec.Selector = selector
   428  		}
   429  	}
   430  	return alternativeServices
   431  }
   432  
   433  func getLeaderName(component *component.SynthesizedComponent) string {
   434  	if component == nil {
   435  		return ""
   436  	}
   437  	switch component.WorkloadType {
   438  	case appsv1alpha1.Consensus:
   439  		if component.ConsensusSpec != nil {
   440  			return component.ConsensusSpec.Leader.Name
   441  		}
   442  	case appsv1alpha1.Replication:
   443  		return constant.Primary
   444  	}
   445  	return ""
   446  }
   447  
   448  // separateServices separates 'services' to a main service from cd and alternative services from cluster
   449  func separateServices(services []corev1.Service) (*corev1.Service, []corev1.Service) {
   450  	if len(services) == 0 {
   451  		return nil, nil
   452  	}
   453  	// from component.buildComponent (which contains component.Services' building process), the first item should be the main service
   454  	// TODO(free6om): make two fields in component(i.e. Service and AlternativeServices) after RSM passes all testes.
   455  	return &services[0], services[1:]
   456  }
   457  
   458  func buildRoleInfo(component *component.SynthesizedComponent) ([]workloads.ReplicaRole, *workloads.RoleProbe, *workloads.MembershipReconfiguration, *workloads.MemberUpdateStrategy) {
   459  	if component.RSMSpec != nil {
   460  		return buildRoleInfo2(component)
   461  	}
   462  
   463  	var (
   464  		roles           []workloads.ReplicaRole
   465  		probe           *workloads.RoleProbe
   466  		reconfiguration *workloads.MembershipReconfiguration
   467  		strategy        *workloads.MemberUpdateStrategy
   468  	)
   469  
   470  	handler := convertCharacterTypeToHandler(component.CharacterType, component.WorkloadType == appsv1alpha1.Consensus)
   471  
   472  	if handler != nil && component.Probes != nil && component.Probes.RoleProbe != nil {
   473  		probe = &workloads.RoleProbe{}
   474  		probe.BuiltinHandler = (*string)(handler)
   475  		roleProbe := component.Probes.RoleProbe
   476  		probe.PeriodSeconds = roleProbe.PeriodSeconds
   477  		probe.TimeoutSeconds = roleProbe.TimeoutSeconds
   478  		probe.FailureThreshold = roleProbe.FailureThreshold
   479  		// set to default value
   480  		probe.SuccessThreshold = 1
   481  		probe.RoleUpdateMechanism = workloads.DirectAPIServerEventUpdate
   482  	}
   483  
   484  	reconfiguration = nil
   485  
   486  	switch component.WorkloadType {
   487  	case appsv1alpha1.Consensus:
   488  		roles, strategy = buildRoleInfoFromConsensus(component.ConsensusSpec)
   489  	case appsv1alpha1.Replication:
   490  		roles = buildRoleInfoFromReplication()
   491  		reconfiguration = nil
   492  		strgy := workloads.SerialUpdateStrategy
   493  		strategy = &strgy
   494  	}
   495  
   496  	return roles, probe, reconfiguration, strategy
   497  }
   498  
   499  func buildRoleInfo2(component *component.SynthesizedComponent) ([]workloads.ReplicaRole, *workloads.RoleProbe, *workloads.MembershipReconfiguration, *workloads.MemberUpdateStrategy) {
   500  	rsmSpec := component.RSMSpec
   501  	return rsmSpec.Roles, rsmSpec.RoleProbe, rsmSpec.MembershipReconfiguration, rsmSpec.MemberUpdateStrategy
   502  }
   503  
   504  func buildRoleInfoFromReplication() []workloads.ReplicaRole {
   505  	return []workloads.ReplicaRole{
   506  		{
   507  			Name:       constant.Primary,
   508  			IsLeader:   true,
   509  			CanVote:    true,
   510  			AccessMode: workloads.ReadWriteMode,
   511  		},
   512  		{
   513  			Name:       constant.Secondary,
   514  			IsLeader:   false,
   515  			CanVote:    true,
   516  			AccessMode: workloads.ReadonlyMode,
   517  		},
   518  	}
   519  }
   520  
   521  func buildRoleInfoFromConsensus(consensusSpec *appsv1alpha1.ConsensusSetSpec) ([]workloads.ReplicaRole, *workloads.MemberUpdateStrategy) {
   522  	if consensusSpec == nil {
   523  		return nil, nil
   524  	}
   525  
   526  	var (
   527  		roles    []workloads.ReplicaRole
   528  		strategy *workloads.MemberUpdateStrategy
   529  	)
   530  
   531  	roles = append(roles, workloads.ReplicaRole{
   532  		Name:       consensusSpec.Leader.Name,
   533  		IsLeader:   true,
   534  		CanVote:    true,
   535  		AccessMode: workloads.AccessMode(consensusSpec.Leader.AccessMode),
   536  	})
   537  	for _, follower := range consensusSpec.Followers {
   538  		roles = append(roles, workloads.ReplicaRole{
   539  			Name:       follower.Name,
   540  			IsLeader:   false,
   541  			CanVote:    true,
   542  			AccessMode: workloads.AccessMode(follower.AccessMode),
   543  		})
   544  	}
   545  	if consensusSpec.Learner != nil {
   546  		roles = append(roles, workloads.ReplicaRole{
   547  			Name:       consensusSpec.Learner.Name,
   548  			IsLeader:   false,
   549  			CanVote:    false,
   550  			AccessMode: workloads.AccessMode(consensusSpec.Learner.AccessMode),
   551  		})
   552  	}
   553  
   554  	strgy := workloads.MemberUpdateStrategy(consensusSpec.UpdateStrategy)
   555  	strategy = &strgy
   556  
   557  	return roles, strategy
   558  }
   559  
   560  func convertCharacterTypeToHandler(characterType string, isConsensus bool) *common.BuiltinHandler {
   561  	var handler common.BuiltinHandler
   562  	kind := strings.ToLower(characterType)
   563  	switch kind {
   564  	case "mysql": //nolint:goconst
   565  		if isConsensus {
   566  			handler = common.WeSQLHandler
   567  		} else {
   568  			handler = common.MySQLHandler
   569  		}
   570  	case "postgres", "postgresql":
   571  		handler = common.PostgresHandler
   572  	case "mongodb":
   573  
   574  		handler = common.MongoDBHandler
   575  	case "etcd":
   576  		handler = common.ETCDHandler
   577  	case "redis":
   578  		handler = common.RedisHandler
   579  	case "kafka":
   580  		handler = common.KafkaHandler
   581  	}
   582  	if handler != "" {
   583  		return &handler
   584  	}
   585  	return nil
   586  }
   587  
   588  func randomString(length int) string {
   589  	return rand.String(length)
   590  }
   591  
   592  func BuildConnCredential(clusterDefinition *appsv1alpha1.ClusterDefinition, cluster *appsv1alpha1.Cluster,
   593  	component *component.SynthesizedComponent) *corev1.Secret {
   594  	wellKnownLabels := buildWellKnownLabels(clusterDefinition.Name, cluster.Name, "")
   595  	delete(wellKnownLabels, constant.KBAppComponentLabelKey)
   596  	credentialBuilder := builder.NewSecretBuilder(cluster.Namespace, fmt.Sprintf("%s-conn-credential", cluster.Name)).
   597  		AddLabelsInMap(wellKnownLabels).
   598  		SetStringData(clusterDefinition.Spec.ConnectionCredential)
   599  	if len(clusterDefinition.Spec.Type) > 0 {
   600  		credentialBuilder.AddLabels("apps.kubeblocks.io/cluster-type", clusterDefinition.Spec.Type)
   601  	}
   602  	connCredential := credentialBuilder.GetObject()
   603  
   604  	if len(connCredential.StringData) == 0 {
   605  		return connCredential
   606  	}
   607  
   608  	replaceVarObjects := func(k, v *string, i int, origValue string, varObjectsMap map[string]string) {
   609  		toReplace := origValue
   610  		for j, r := range varObjectsMap {
   611  			replaced := strings.ReplaceAll(toReplace, j, r)
   612  			if replaced == toReplace {
   613  				continue
   614  			}
   615  			toReplace = replaced
   616  			// replace key
   617  			if i == 0 {
   618  				delete(connCredential.StringData, origValue)
   619  				*k = replaced
   620  			} else {
   621  				*v = replaced
   622  			}
   623  		}
   624  	}
   625  
   626  	// REVIEW: perhaps handles value replacement at `func mergeComponents`
   627  	replaceData := func(varObjectsMap map[string]string) {
   628  		copyStringData := connCredential.DeepCopy().StringData
   629  		for k, v := range copyStringData {
   630  			for i, vv := range []string{k, v} {
   631  				if !strings.Contains(vv, "$(") {
   632  					continue
   633  				}
   634  				replaceVarObjects(&k, &v, i, vv, varObjectsMap)
   635  			}
   636  			connCredential.StringData[k] = v
   637  		}
   638  	}
   639  
   640  	// get restore password if exists during recovery.
   641  	getRestorePassword := func() string {
   642  		valueString := cluster.Annotations[constant.RestoreFromBackupAnnotationKey]
   643  		if len(valueString) == 0 {
   644  			return ""
   645  		}
   646  		backupMap := map[string]map[string]string{}
   647  		err := json.Unmarshal([]byte(valueString), &backupMap)
   648  		if err != nil {
   649  			return ""
   650  		}
   651  		backupSource, ok := backupMap[component.Name]
   652  		if !ok {
   653  			return ""
   654  		}
   655  		password, ok := backupSource[constant.ConnectionPassword]
   656  		if !ok {
   657  			return ""
   658  		}
   659  		e := intctrlutil.NewEncryptor(viper.GetString(constant.CfgKeyDPEncryptionKey))
   660  		password, _ = e.Decrypt([]byte(password))
   661  		return password
   662  	}
   663  
   664  	// TODO: do JIT value generation for lower CPU resources
   665  	// 1st pass replace variables
   666  	uuidVal := uuid.New()
   667  	uuidBytes := uuidVal[:]
   668  	uuidStr := uuidVal.String()
   669  	uuidB64 := base64.RawStdEncoding.EncodeToString(uuidBytes)
   670  	uuidStrB64 := base64.RawStdEncoding.EncodeToString([]byte(strings.ReplaceAll(uuidStr, "-", "")))
   671  	uuidHex := hex.EncodeToString(uuidBytes)
   672  	randomPassword := randomString(8)
   673  	restorePassword := getRestorePassword()
   674  	// check if a connection password is specified during recovery.
   675  	// if exists, replace the random password
   676  	if restorePassword != "" {
   677  		randomPassword = restorePassword
   678  	}
   679  	m := map[string]string{
   680  		"$(RANDOM_PASSWD)":        randomPassword,
   681  		"$(UUID)":                 uuidStr,
   682  		"$(UUID_B64)":             uuidB64,
   683  		"$(UUID_STR_B64)":         uuidStrB64,
   684  		"$(UUID_HEX)":             uuidHex,
   685  		"$(SVC_FQDN)":             fmt.Sprintf("%s-%s.%s.svc", cluster.Name, component.Name, cluster.Namespace),
   686  		"$(KB_CLUSTER_COMP_NAME)": cluster.Name + "-" + component.Name,
   687  		"$(HEADLESS_SVC_FQDN)":    fmt.Sprintf("%s-%s-headless.%s.svc", cluster.Name, component.Name, cluster.Namespace),
   688  	}
   689  	if len(component.Services) > 0 {
   690  		for _, p := range component.Services[0].Spec.Ports {
   691  			m[fmt.Sprintf("$(SVC_PORT_%s)", p.Name)] = strconv.Itoa(int(p.Port))
   692  		}
   693  	}
   694  	replaceData(m)
   695  
   696  	// 2nd pass replace $(CONN_CREDENTIAL) variables
   697  	m = map[string]string{}
   698  	for k, v := range connCredential.StringData {
   699  		m[fmt.Sprintf("$(CONN_CREDENTIAL).%s", k)] = v
   700  	}
   701  	replaceData(m)
   702  	return connCredential
   703  }
   704  
   705  func BuildPDB(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent) *policyv1.PodDisruptionBudget {
   706  	wellKnownLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name)
   707  	return builder.NewPDBBuilder(cluster.Namespace, fmt.Sprintf("%s-%s", cluster.Name, component.Name)).
   708  		AddLabelsInMap(wellKnownLabels).
   709  		AddLabels(constant.AppComponentLabelKey, component.CompDefName).
   710  		AddSelectorsInMap(wellKnownLabels).
   711  		GetObject()
   712  }
   713  
   714  func BuildPVC(cluster *appsv1alpha1.Cluster,
   715  	component *component.SynthesizedComponent,
   716  	vct *corev1.PersistentVolumeClaimTemplate,
   717  	pvcKey types.NamespacedName,
   718  	snapshotName string) *corev1.PersistentVolumeClaim {
   719  	wellKnownLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name)
   720  	pvcBuilder := builder.NewPVCBuilder(pvcKey.Namespace, pvcKey.Name).
   721  		AddLabelsInMap(wellKnownLabels).
   722  		AddLabels(constant.VolumeClaimTemplateNameLabelKey, vct.Name).
   723  		SetAccessModes(vct.Spec.AccessModes).
   724  		SetResources(vct.Spec.Resources)
   725  	if vct.Spec.StorageClassName != nil {
   726  		pvcBuilder.SetStorageClass(*vct.Spec.StorageClassName)
   727  	}
   728  	if len(snapshotName) > 0 {
   729  		apiGroup := "snapshot.storage.k8s.io"
   730  		pvcBuilder.SetDataSource(corev1.TypedLocalObjectReference{
   731  			APIGroup: &apiGroup,
   732  			Kind:     "VolumeSnapshot",
   733  			Name:     snapshotName,
   734  		})
   735  	}
   736  	pvc := pvcBuilder.GetObject()
   737  	BuildPersistentVolumeClaimLabels(component, pvc, vct.Name)
   738  	return pvc
   739  }
   740  
   741  // BuildEnvConfig builds cluster component context ConfigMap object, which is to be used in workload container's
   742  // envFrom.configMapRef with name of "$(cluster.metadata.name)-$(component.name)-env" pattern.
   743  func BuildEnvConfig(cluster *appsv1alpha1.Cluster, component *component.SynthesizedComponent) *corev1.ConfigMap {
   744  	envData := map[string]string{}
   745  	// add component envs
   746  	if component.ComponentRefEnvs != nil {
   747  		for _, env := range component.ComponentRefEnvs {
   748  			envData[env.Name] = env.Value
   749  		}
   750  	}
   751  
   752  	wellKnownLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name)
   753  	wellKnownLabels[constant.AppComponentLabelKey] = component.CompDefName
   754  	return builder.NewConfigMapBuilder(cluster.Namespace, fmt.Sprintf("%s-%s-env", cluster.Name, component.Name)).
   755  		AddLabelsInMap(wellKnownLabels).
   756  		AddLabels(constant.AppConfigTypeLabelKey, "kubeblocks-env").
   757  		SetData(envData).
   758  		GetObject()
   759  }
   760  
   761  func BuildBackup(cluster *appsv1alpha1.Cluster,
   762  	component *component.SynthesizedComponent,
   763  	backupPolicyName string,
   764  	backupKey types.NamespacedName,
   765  	backupMethod string) *dpv1alpha1.Backup {
   766  	return builder.NewBackupBuilder(backupKey.Namespace, backupKey.Name).
   767  		AddLabels(dptypes.BackupMethodLabelKey, backupMethod).
   768  		AddLabels(dptypes.BackupPolicyLabelKey, backupPolicyName).
   769  		AddLabels(constant.KBManagedByKey, "cluster").
   770  		AddLabels(constant.AppNameLabelKey, component.ClusterDefName).
   771  		AddLabels(constant.AppInstanceLabelKey, cluster.Name).
   772  		AddLabels(constant.AppManagedByLabelKey, constant.AppName).
   773  		AddLabels(constant.KBAppComponentLabelKey, component.Name).
   774  		SetBackupPolicyName(backupPolicyName).
   775  		SetBackupMethod(backupMethod).
   776  		GetObject()
   777  }
   778  
   779  func BuildConfigMapWithTemplate(cluster *appsv1alpha1.Cluster,
   780  	component *component.SynthesizedComponent,
   781  	configs map[string]string,
   782  	cmName string,
   783  	configTemplateSpec appsv1alpha1.ComponentTemplateSpec) *corev1.ConfigMap {
   784  	wellKnownLabels := buildWellKnownLabels(component.ClusterDefName, cluster.Name, component.Name)
   785  	wellKnownLabels[constant.AppComponentLabelKey] = component.CompDefName
   786  	return builder.NewConfigMapBuilder(cluster.Namespace, cmName).
   787  		AddLabelsInMap(wellKnownLabels).
   788  		AddLabels(constant.CMConfigurationTypeLabelKey, constant.ConfigInstanceType).
   789  		AddLabels(constant.CMTemplateNameLabelKey, configTemplateSpec.TemplateRef).
   790  		AddAnnotations(constant.DisableUpgradeInsConfigurationAnnotationKey, strconv.FormatBool(false)).
   791  		SetData(configs).
   792  		GetObject()
   793  }
   794  
   795  func BuildCfgManagerContainer(sidecarRenderedParam *cfgcm.CfgManagerBuildParams, component *component.SynthesizedComponent) (*corev1.Container, error) {
   796  	var env []corev1.EnvVar
   797  	env = append(env, corev1.EnvVar{
   798  		Name: "CONFIG_MANAGER_POD_IP",
   799  		ValueFrom: &corev1.EnvVarSource{
   800  			FieldRef: &corev1.ObjectFieldSelector{
   801  				APIVersion: "v1",
   802  				FieldPath:  "status.podIP",
   803  			},
   804  		},
   805  	})
   806  	if len(sidecarRenderedParam.CharacterType) > 0 {
   807  		env = append(env, corev1.EnvVar{
   808  			Name:  "DB_TYPE",
   809  			Value: sidecarRenderedParam.CharacterType,
   810  		})
   811  	}
   812  	if sidecarRenderedParam.CharacterType == "mysql" {
   813  		env = append(env, corev1.EnvVar{
   814  			Name: "MYSQL_USER",
   815  			ValueFrom: &corev1.EnvVarSource{
   816  				SecretKeyRef: &corev1.SecretKeySelector{
   817  					Key:                  "username",
   818  					LocalObjectReference: corev1.LocalObjectReference{Name: sidecarRenderedParam.SecreteName},
   819  				},
   820  			},
   821  		},
   822  			corev1.EnvVar{
   823  				Name: "MYSQL_PASSWORD",
   824  				ValueFrom: &corev1.EnvVarSource{
   825  					SecretKeyRef: &corev1.SecretKeySelector{
   826  						Key:                  "password",
   827  						LocalObjectReference: corev1.LocalObjectReference{Name: sidecarRenderedParam.SecreteName},
   828  					},
   829  				},
   830  			},
   831  			corev1.EnvVar{
   832  				Name:  "DATA_SOURCE_NAME",
   833  				Value: "$(MYSQL_USER):$(MYSQL_PASSWORD)@(localhost:3306)/",
   834  			},
   835  		)
   836  	}
   837  	containerBuilder := builder.NewContainerBuilder(sidecarRenderedParam.ManagerName).
   838  		AddCommands("env").
   839  		AddArgs("PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$(TOOLS_PATH)").
   840  		AddArgs("/bin/reloader").
   841  		AddArgs(sidecarRenderedParam.Args...).
   842  		AddEnv(env...).
   843  		SetImage(sidecarRenderedParam.Image).
   844  		SetImagePullPolicy(corev1.PullIfNotPresent).
   845  		AddVolumeMounts(sidecarRenderedParam.Volumes...)
   846  	if sidecarRenderedParam.ShareProcessNamespace {
   847  		user := int64(0)
   848  		containerBuilder.SetSecurityContext(corev1.SecurityContext{
   849  			RunAsUser: &user,
   850  		})
   851  	}
   852  	container := containerBuilder.GetObject()
   853  
   854  	if err := injectEnvs(sidecarRenderedParam.Cluster, component, sidecarRenderedParam.EnvConfigName, container); err != nil {
   855  		return nil, err
   856  	}
   857  	intctrlutil.InjectZeroResourcesLimitsIfEmpty(container)
   858  	return container, nil
   859  }
   860  
   861  func BuildRestoreJob(cluster *appsv1alpha1.Cluster, synthesizedComponent *component.SynthesizedComponent, name, image string, command []string,
   862  	volumes []corev1.Volume, volumeMounts []corev1.VolumeMount, env []corev1.EnvVar, resources *corev1.ResourceRequirements) (*batchv1.Job, error) {
   863  	containerBuilder := builder.NewContainerBuilder("restore").
   864  		SetImage(image).
   865  		SetImagePullPolicy(corev1.PullIfNotPresent).
   866  		AddCommands(command...).
   867  		AddVolumeMounts(volumeMounts...).
   868  		AddEnv(env...)
   869  	if resources != nil {
   870  		containerBuilder.SetResources(*resources)
   871  	}
   872  	container := containerBuilder.GetObject()
   873  
   874  	ctx := corev1.PodSecurityContext{}
   875  	user := int64(0)
   876  	ctx.RunAsUser = &user
   877  	pod := builder.NewPodBuilder(cluster.Namespace, "").
   878  		AddContainer(*container).
   879  		AddVolumes(volumes...).
   880  		SetRestartPolicy(corev1.RestartPolicyOnFailure).
   881  		SetSecurityContext(ctx).
   882  		GetObject()
   883  	template := corev1.PodTemplateSpec{
   884  		Spec: pod.Spec,
   885  	}
   886  
   887  	job := builder.NewJobBuilder(cluster.Namespace, name).
   888  		AddLabels(constant.AppManagedByLabelKey, constant.AppName).
   889  		SetPodTemplateSpec(template).
   890  		GetObject()
   891  	containers := job.Spec.Template.Spec.Containers
   892  	if len(containers) > 0 {
   893  		if err := injectEnvs(cluster, synthesizedComponent, "", &containers[0]); err != nil {
   894  			return nil, err
   895  		}
   896  		intctrlutil.InjectZeroResourcesLimitsIfEmpty(&containers[0])
   897  	}
   898  	tolerations, err := component.BuildTolerations(cluster, cluster.Spec.GetComponentByName(synthesizedComponent.Name))
   899  	if err != nil {
   900  		return nil, err
   901  	}
   902  	job.Spec.Template.Spec.Tolerations = tolerations
   903  	return job, nil
   904  }
   905  
   906  func BuildCfgManagerToolsContainer(sidecarRenderedParam *cfgcm.CfgManagerBuildParams, component *component.SynthesizedComponent, toolsMetas []appsv1alpha1.ToolConfig, toolsMap map[string]cfgcm.ConfigSpecMeta) ([]corev1.Container, error) {
   907  	toolContainers := make([]corev1.Container, 0, len(toolsMetas))
   908  	for _, toolConfig := range toolsMetas {
   909  		toolContainerBuilder := builder.NewContainerBuilder(toolConfig.Name).
   910  			AddCommands(toolConfig.Command...).
   911  			SetImagePullPolicy(corev1.PullIfNotPresent).
   912  			AddVolumeMounts(sidecarRenderedParam.Volumes...)
   913  		if len(toolConfig.Image) > 0 {
   914  			toolContainerBuilder.SetImage(toolConfig.Image)
   915  		}
   916  		toolContainers = append(toolContainers, *toolContainerBuilder.GetObject())
   917  	}
   918  	for i := range toolContainers {
   919  		container := &toolContainers[i]
   920  		if err := injectEnvs(sidecarRenderedParam.Cluster, component, sidecarRenderedParam.EnvConfigName, container); err != nil {
   921  			return nil, err
   922  		}
   923  		intctrlutil.InjectZeroResourcesLimitsIfEmpty(container)
   924  		if meta, ok := toolsMap[container.Name]; ok {
   925  			setToolsScriptsPath(container, meta)
   926  		}
   927  	}
   928  	return toolContainers, nil
   929  }
   930  
   931  func setToolsScriptsPath(container *corev1.Container, meta cfgcm.ConfigSpecMeta) {
   932  	container.Env = append(container.Env, corev1.EnvVar{
   933  		Name:  cfgcm.KBTOOLSScriptsPathEnv,
   934  		Value: filepath.Join(cfgcm.KBScriptVolumePath, meta.ConfigSpec.Name),
   935  	})
   936  }
   937  
   938  func BuildVolumeSnapshotClass(name string, driver string) *snapshotv1.VolumeSnapshotClass {
   939  	return builder.NewVolumeSnapshotClassBuilder("", name).
   940  		AddLabels(constant.AppManagedByLabelKey, constant.AppName).
   941  		SetDriver(driver).
   942  		SetDeletionPolicy(snapshotv1.VolumeSnapshotContentDelete).
   943  		GetObject()
   944  }
   945  
   946  func BuildServiceAccount(cluster *appsv1alpha1.Cluster) *corev1.ServiceAccount {
   947  	wellKnownLabels := buildWellKnownLabels(cluster.Spec.ClusterDefRef, cluster.Name, "")
   948  	delete(wellKnownLabels, constant.KBAppComponentLabelKey)
   949  	return builder.NewServiceAccountBuilder(cluster.Namespace, fmt.Sprintf("kb-%s", cluster.Name)).
   950  		AddLabelsInMap(wellKnownLabels).
   951  		GetObject()
   952  }
   953  
   954  func BuildRoleBinding(cluster *appsv1alpha1.Cluster) *rbacv1.RoleBinding {
   955  	wellKnownLabels := buildWellKnownLabels(cluster.Spec.ClusterDefRef, cluster.Name, "")
   956  	delete(wellKnownLabels, constant.KBAppComponentLabelKey)
   957  	return builder.NewRoleBindingBuilder(cluster.Namespace, fmt.Sprintf("kb-%s", cluster.Name)).
   958  		AddLabelsInMap(wellKnownLabels).
   959  		SetRoleRef(rbacv1.RoleRef{
   960  			APIGroup: rbacv1.GroupName,
   961  			Kind:     "ClusterRole",
   962  			Name:     constant.RBACRoleName,
   963  		}).
   964  		AddSubjects(rbacv1.Subject{
   965  			Kind:      rbacv1.ServiceAccountKind,
   966  			Namespace: cluster.Namespace,
   967  			Name:      fmt.Sprintf("kb-%s", cluster.Name),
   968  		}).
   969  		GetObject()
   970  }
   971  
   972  func BuildClusterRoleBinding(cluster *appsv1alpha1.Cluster) *rbacv1.ClusterRoleBinding {
   973  	wellKnownLabels := buildWellKnownLabels(cluster.Spec.ClusterDefRef, cluster.Name, "")
   974  	delete(wellKnownLabels, constant.KBAppComponentLabelKey)
   975  	return builder.NewClusterRoleBindingBuilder(cluster.Namespace, fmt.Sprintf("kb-%s", cluster.Name)).
   976  		AddLabelsInMap(wellKnownLabels).
   977  		SetRoleRef(rbacv1.RoleRef{
   978  			APIGroup: rbacv1.GroupName,
   979  			Kind:     "ClusterRole",
   980  			Name:     constant.RBACClusterRoleName,
   981  		}).
   982  		AddSubjects(rbacv1.Subject{
   983  			Kind:      rbacv1.ServiceAccountKind,
   984  			Namespace: cluster.Namespace,
   985  			Name:      fmt.Sprintf("kb-%s", cluster.Name),
   986  		}).
   987  		GetObject()
   988  }