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