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 }