github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/transformer_backup_policy_tpl.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 apps 21 22 import ( 23 "fmt" 24 25 "golang.org/x/exp/slices" 26 corev1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 30 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 31 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 32 workloads "github.com/1aal/kubeblocks/apis/workloads/v1alpha1" 33 "github.com/1aal/kubeblocks/pkg/constant" 34 "github.com/1aal/kubeblocks/pkg/controller/graph" 35 "github.com/1aal/kubeblocks/pkg/controller/model" 36 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 37 dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types" 38 dputils "github.com/1aal/kubeblocks/pkg/dataprotection/utils" 39 ) 40 41 // BackupPolicyTplTransformer transforms the backup policy template to the data 42 // protection backup policy and backup schedule. 43 type BackupPolicyTplTransformer struct { 44 *clusterTransformContext 45 46 tplCount int 47 tplIdentifier string 48 isDefaultTemplate string 49 50 backupPolicyTpl *appsv1alpha1.BackupPolicyTemplate 51 backupPolicy *appsv1alpha1.BackupPolicy 52 compWorkloadType appsv1alpha1.WorkloadType 53 } 54 55 var _ graph.Transformer = &BackupPolicyTplTransformer{} 56 57 // Transform transforms the backup policy template to the backup policy and 58 // backup schedule. 59 func (r *BackupPolicyTplTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error { 60 r.clusterTransformContext = ctx.(*clusterTransformContext) 61 graphCli, _ := r.clusterTransformContext.Client.(model.GraphClient) 62 63 clusterDefName := r.ClusterDef.Name 64 backupPolicyTpls := &appsv1alpha1.BackupPolicyTemplateList{} 65 if err := r.Client.List(r.Context, backupPolicyTpls, 66 client.MatchingLabels{constant.ClusterDefLabelKey: clusterDefName}); err != nil { 67 return err 68 } 69 r.tplCount = len(backupPolicyTpls.Items) 70 if r.tplCount == 0 { 71 return nil 72 } 73 74 backupPolicyNames := map[string]struct{}{} 75 backupScheduleNames := map[string]struct{}{} 76 for _, tpl := range backupPolicyTpls.Items { 77 r.isDefaultTemplate = tpl.Annotations[dptypes.DefaultBackupPolicyTemplateAnnotationKey] 78 r.tplIdentifier = tpl.Spec.Identifier 79 r.backupPolicyTpl = &tpl 80 81 for i, bp := range tpl.Spec.BackupPolicies { 82 compDef := r.ClusterDef.GetComponentDefByName(bp.ComponentDefRef) 83 if compDef == nil { 84 return intctrlutil.NewNotFound("componentDef %s not found in ClusterDefinition: %s ", 85 bp.ComponentDefRef, clusterDefName) 86 } 87 88 r.backupPolicy = &tpl.Spec.BackupPolicies[i] 89 r.compWorkloadType = compDef.WorkloadType 90 91 transformBackupPolicy := func() *dpv1alpha1.BackupPolicy { 92 // build the data protection backup policy from the template. 93 dpBackupPolicy, action := r.transformBackupPolicy() 94 if dpBackupPolicy == nil { 95 return nil 96 } 97 98 // if exist multiple backup policy templates and duplicate spec.identifier, 99 // the generated backupPolicy may have duplicate names, so it is 100 // necessary to check if it already exists. 101 if _, ok := backupPolicyNames[dpBackupPolicy.Name]; ok { 102 return dpBackupPolicy 103 } 104 105 switch *action { 106 case model.CREATE: 107 graphCli.Create(dag, dpBackupPolicy) 108 case model.UPDATE: 109 graphCli.Update(dag, dpBackupPolicy, dpBackupPolicy) 110 } 111 backupPolicyNames[dpBackupPolicy.Name] = struct{}{} 112 return dpBackupPolicy 113 } 114 115 transformBackupSchedule := func(backupPolicy *dpv1alpha1.BackupPolicy) { 116 // if backup policy is nil, it means that the backup policy template 117 // is invalid, backup schedule depends on backup policy, so we do 118 // not need to transform backup schedule. 119 if backupPolicy == nil { 120 return 121 } 122 123 // only create backup schedule for the default backup policy template 124 // if there are more than one backup policy templates. 125 if r.isDefaultTemplate != trueVal && r.tplCount > 1 { 126 return 127 } 128 129 // build the data protection backup schedule from the template. 130 dpBackupSchedule, action := r.transformBackupSchedule(backupPolicy) 131 132 // merge cluster backup configuration into the backup schedule. 133 // If the backup schedule is nil, create a new backup schedule 134 // based on the cluster backup configuration. 135 if dpBackupSchedule == nil { 136 action = model.ActionCreatePtr() 137 } else if action == nil { 138 action = model.ActionUpdatePtr() 139 } 140 141 // for a cluster, the default backup schedule is created by backup 142 // policy template, user can also configure cluster backup in the 143 // cluster custom object, such as enable cluster backup, set backup 144 // schedule, etc. 145 // We always prioritize the cluster backup configuration in the 146 // cluster object, so we need to merge the cluster backup configuration 147 // into the default backup schedule created by backup policy template 148 // if it exists. 149 dpBackupSchedule = r.mergeClusterBackup(backupPolicy, dpBackupSchedule) 150 if dpBackupSchedule == nil { 151 return 152 } 153 154 // if exist multiple backup policy templates and duplicate spec.identifier, 155 // the backupPolicy that may be generated may have duplicate names, 156 // and it is necessary to check if it already exists. 157 if _, ok := backupScheduleNames[dpBackupSchedule.Name]; ok { 158 return 159 } 160 161 switch *action { 162 case model.CREATE: 163 graphCli.Create(dag, dpBackupSchedule) 164 case model.UPDATE: 165 graphCli.Update(dag, dpBackupSchedule, dpBackupSchedule) 166 } 167 graphCli.DependOn(dag, backupPolicy, dpBackupSchedule) 168 backupScheduleNames[dpBackupSchedule.Name] = struct{}{} 169 } 170 171 // transform backup policy template to data protection backupPolicy 172 // and backupSchedule 173 policy := transformBackupPolicy() 174 transformBackupSchedule(policy) 175 } 176 } 177 return nil 178 } 179 180 // transformBackupPolicy transforms backup policy template to backup policy. 181 func (r *BackupPolicyTplTransformer) transformBackupPolicy() (*dpv1alpha1.BackupPolicy, *model.Action) { 182 cluster := r.OrigCluster 183 backupPolicyName := generateBackupPolicyName(cluster.Name, r.backupPolicy.ComponentDefRef, r.tplIdentifier) 184 backupPolicy := &dpv1alpha1.BackupPolicy{} 185 if err := r.Client.Get(r.Context, client.ObjectKey{ 186 Namespace: cluster.Namespace, 187 Name: backupPolicyName, 188 }, backupPolicy); client.IgnoreNotFound(err) != nil { 189 return nil, nil 190 } 191 192 if len(backupPolicy.Name) == 0 { 193 // build a new backup policy by the backup policy template. 194 return r.buildBackupPolicy(backupPolicyName), model.ActionCreatePtr() 195 } 196 197 // sync the existing backup policy with the cluster changes 198 r.syncBackupPolicy(backupPolicy) 199 return backupPolicy, model.ActionUpdatePtr() 200 } 201 202 func (r *BackupPolicyTplTransformer) transformBackupSchedule( 203 backupPolicy *dpv1alpha1.BackupPolicy) (*dpv1alpha1.BackupSchedule, *model.Action) { 204 cluster := r.OrigCluster 205 scheduleName := generateBackupScheduleName(cluster.Name, r.backupPolicy.ComponentDefRef, r.tplIdentifier) 206 backupSchedule := &dpv1alpha1.BackupSchedule{} 207 if err := r.Client.Get(r.Context, client.ObjectKey{ 208 Namespace: cluster.Namespace, 209 Name: scheduleName, 210 }, backupSchedule); client.IgnoreNotFound(err) != nil { 211 return nil, nil 212 } 213 214 if len(backupSchedule.Name) == 0 { 215 // build a new backup schedule from the backup policy template. 216 return r.buildBackupSchedule(scheduleName, backupPolicy), model.ActionCreatePtr() 217 } 218 // sync backup schedule 219 r.syncBackupSchedule(backupSchedule) 220 return backupSchedule, model.ActionUpdatePtr() 221 } 222 223 func (r *BackupPolicyTplTransformer) buildBackupSchedule( 224 name string, 225 backupPolicy *dpv1alpha1.BackupPolicy) *dpv1alpha1.BackupSchedule { 226 cluster := r.OrigCluster 227 backupSchedule := &dpv1alpha1.BackupSchedule{ 228 ObjectMeta: metav1.ObjectMeta{ 229 Name: name, 230 Namespace: cluster.Namespace, 231 Labels: r.buildLabels(), 232 Annotations: r.buildAnnotations(), 233 }, 234 Spec: dpv1alpha1.BackupScheduleSpec{ 235 BackupPolicyName: backupPolicy.Name, 236 }, 237 } 238 239 var schedules []dpv1alpha1.SchedulePolicy 240 for _, s := range r.backupPolicy.Schedules { 241 schedules = append(schedules, dpv1alpha1.SchedulePolicy{ 242 BackupMethod: s.BackupMethod, 243 CronExpression: s.CronExpression, 244 Enabled: s.Enabled, 245 RetentionPeriod: r.backupPolicy.RetentionPeriod, 246 }) 247 } 248 backupSchedule.Spec.Schedules = schedules 249 return backupSchedule 250 } 251 252 func (r *BackupPolicyTplTransformer) syncBackupSchedule(backupSchedule *dpv1alpha1.BackupSchedule) { 253 scheduleMethodMap := map[string]struct{}{} 254 for _, s := range backupSchedule.Spec.Schedules { 255 scheduleMethodMap[s.BackupMethod] = struct{}{} 256 } 257 // sync the newly added schedule policies. 258 for _, s := range r.backupPolicy.Schedules { 259 if _, ok := scheduleMethodMap[s.BackupMethod]; ok { 260 continue 261 } 262 backupSchedule.Spec.Schedules = append(backupSchedule.Spec.Schedules, dpv1alpha1.SchedulePolicy{ 263 BackupMethod: s.BackupMethod, 264 CronExpression: s.CronExpression, 265 Enabled: s.Enabled, 266 RetentionPeriod: r.backupPolicy.RetentionPeriod, 267 }) 268 } 269 } 270 271 // syncBackupPolicy syncs labels and annotations of the backup policy with the cluster changes. 272 func (r *BackupPolicyTplTransformer) syncBackupPolicy(backupPolicy *dpv1alpha1.BackupPolicy) { 273 // update labels and annotations of the backup policy. 274 if backupPolicy.Annotations == nil { 275 backupPolicy.Annotations = map[string]string{} 276 } 277 if backupPolicy.Labels == nil { 278 backupPolicy.Labels = map[string]string{} 279 } 280 mergeMap(backupPolicy.Annotations, r.buildAnnotations()) 281 mergeMap(backupPolicy.Labels, r.buildLabels()) 282 283 // update backup repo of the backup policy. 284 if r.Cluster.Spec.Backup != nil && r.Cluster.Spec.Backup.RepoName != "" { 285 backupPolicy.Spec.BackupRepoName = &r.Cluster.Spec.Backup.RepoName 286 } 287 288 r.syncBackupMethods(backupPolicy) 289 290 // only update the role labelSelector of the backup target instance when 291 // component workload is Replication/Consensus. Because the replicas of 292 // component will change, such as 2->1. then if the target role is 'follower' 293 // and replicas is 1, the target instance can not be found. so we sync the 294 // label selector automatically. 295 if !workloadHasRoleLabel(r.compWorkloadType) { 296 return 297 } 298 299 comp := r.getClusterComponentSpec() 300 if comp == nil { 301 return 302 } 303 304 // convert role labelSelector based on the replicas of the component automatically. 305 // TODO(ldm): need more review. 306 role := r.backupPolicy.Target.Role 307 if len(role) == 0 { 308 return 309 } 310 311 podSelector := backupPolicy.Spec.Target.PodSelector 312 if podSelector.LabelSelector == nil || podSelector.LabelSelector.MatchLabels == nil { 313 podSelector.LabelSelector = &metav1.LabelSelector{MatchLabels: map[string]string{}} 314 } 315 if r.getCompReplicas() == 1 { 316 delete(podSelector.LabelSelector.MatchLabels, constant.RoleLabelKey) 317 } else { 318 podSelector.LabelSelector.MatchLabels[constant.RoleLabelKey] = role 319 } 320 } 321 322 func (r *BackupPolicyTplTransformer) getCompReplicas() int32 { 323 rsm := &workloads.ReplicatedStateMachine{} 324 compSpec := r.getClusterComponentSpec() 325 rsmName := fmt.Sprintf("%s-%s", r.Cluster.Name, compSpec.Name) 326 if err := r.Client.Get(r.Context, client.ObjectKey{Name: rsmName, Namespace: r.Cluster.Namespace}, rsm); err != nil { 327 return compSpec.Replicas 328 } 329 return *rsm.Spec.Replicas 330 } 331 332 // buildBackupPolicy builds a new backup policy by the backup policy template. 333 func (r *BackupPolicyTplTransformer) buildBackupPolicy(backupPolicyName string) *dpv1alpha1.BackupPolicy { 334 comp := r.getClusterComponentSpec() 335 if comp == nil { 336 return nil 337 } 338 339 cluster := r.OrigCluster 340 backupPolicy := &dpv1alpha1.BackupPolicy{ 341 ObjectMeta: metav1.ObjectMeta{ 342 Name: backupPolicyName, 343 Namespace: cluster.Namespace, 344 Labels: r.buildLabels(), 345 Annotations: r.buildAnnotations(), 346 }, 347 } 348 r.syncBackupMethods(backupPolicy) 349 bpSpec := backupPolicy.Spec 350 // if cluster have backup repo, set backup repo name to backup policy. 351 if cluster.Spec.Backup != nil && cluster.Spec.Backup.RepoName != "" { 352 bpSpec.BackupRepoName = &cluster.Spec.Backup.RepoName 353 } 354 bpSpec.PathPrefix = buildBackupPathPrefix(cluster, comp.Name) 355 bpSpec.Target = r.buildBackupTarget(comp) 356 backupPolicy.Spec = bpSpec 357 return backupPolicy 358 } 359 360 // syncBackupMethods syncs the backupMethod of tpl to backupPolicy. 361 func (r *BackupPolicyTplTransformer) syncBackupMethods(backupPolicy *dpv1alpha1.BackupPolicy) { 362 var backupMethods []dpv1alpha1.BackupMethod 363 for _, v := range r.backupPolicy.BackupMethods { 364 mappingEnv := r.doEnvMapping(v.EnvMapping) 365 v.BackupMethod.Env = dputils.MergeEnv(v.BackupMethod.Env, mappingEnv) 366 backupMethods = append(backupMethods, v.BackupMethod) 367 } 368 backupPolicy.Spec.BackupMethods = backupMethods 369 } 370 371 func (r *BackupPolicyTplTransformer) doEnvMapping(envMapping []appsv1alpha1.EnvMappingVar) []corev1.EnvVar { 372 var env []corev1.EnvVar 373 for _, v := range envMapping { 374 for _, cv := range v.ValueFrom.ClusterVersionRef { 375 if !slices.Contains(cv.Names, r.Cluster.Spec.ClusterVersionRef) { 376 continue 377 } 378 env = append(env, corev1.EnvVar{ 379 Name: v.Key, 380 Value: cv.MappingValue, 381 }) 382 } 383 } 384 return env 385 } 386 387 func (r *BackupPolicyTplTransformer) buildBackupTarget( 388 comp *appsv1alpha1.ClusterComponentSpec) *dpv1alpha1.BackupTarget { 389 targetTpl := r.backupPolicy.Target 390 clusterName := r.OrigCluster.Name 391 392 getSAName := func() string { 393 if comp.ServiceAccountName != "" { 394 return comp.ServiceAccountName 395 } 396 return "kb-" + r.Cluster.Name 397 } 398 399 // build the target connection credential 400 cc := dpv1alpha1.ConnectionCredential{} 401 if len(targetTpl.Account) > 0 { 402 cc.SecretName = fmt.Sprintf("%s-%s-%s", clusterName, comp.Name, targetTpl.Account) 403 cc.PasswordKey = constant.AccountPasswdForSecret 404 cc.PasswordKey = constant.AccountNameForSecret 405 } else { 406 cc.SecretName = fmt.Sprintf("%s-conn-credential", clusterName) 407 ccKey := targetTpl.ConnectionCredentialKey 408 if ccKey.PasswordKey != nil { 409 cc.PasswordKey = *ccKey.PasswordKey 410 } 411 if ccKey.UsernameKey != nil { 412 cc.UsernameKey = *ccKey.UsernameKey 413 } 414 if ccKey.PortKey != nil { 415 cc.PortKey = *ccKey.PortKey 416 } 417 if ccKey.HostKey != nil { 418 cc.HostKey = *ccKey.HostKey 419 } 420 } 421 422 target := &dpv1alpha1.BackupTarget{ 423 PodSelector: &dpv1alpha1.PodSelector{ 424 Strategy: dpv1alpha1.PodSelectionStrategyAny, 425 LabelSelector: &metav1.LabelSelector{ 426 MatchLabels: r.buildTargetPodLabels(comp), 427 }, 428 }, 429 ConnectionCredential: &cc, 430 ServiceAccountName: getSAName(), 431 } 432 return target 433 } 434 435 func (r *BackupPolicyTplTransformer) mergeClusterBackup( 436 backupPolicy *dpv1alpha1.BackupPolicy, 437 backupSchedule *dpv1alpha1.BackupSchedule) *dpv1alpha1.BackupSchedule { 438 cluster := r.OrigCluster 439 backupEnabled := func() bool { 440 return cluster.Spec.Backup != nil && boolValue(cluster.Spec.Backup.Enabled) 441 } 442 443 if backupPolicy == nil || cluster.Spec.Backup == nil { 444 // backup policy is nil, can not enable cluster backup, so record event and return. 445 if backupEnabled() { 446 r.EventRecorder.Event(r.Cluster, corev1.EventTypeWarning, 447 "BackupPolicyNotFound", "backup policy is nil, can not enable cluster backup") 448 } 449 return backupSchedule 450 } 451 452 backup := cluster.Spec.Backup 453 // there is no backup schedule created by backup policy template, so we need to 454 // create a new backup schedule for cluster backup. 455 if backupSchedule == nil { 456 backupSchedule = &dpv1alpha1.BackupSchedule{ 457 ObjectMeta: metav1.ObjectMeta{ 458 Name: generateBackupScheduleName(cluster.Name, r.backupPolicy.ComponentDefRef, r.tplIdentifier), 459 Namespace: cluster.Namespace, 460 Labels: r.buildLabels(), 461 Annotations: r.buildAnnotations(), 462 }, 463 Spec: dpv1alpha1.BackupScheduleSpec{ 464 BackupPolicyName: backupPolicy.Name, 465 StartingDeadlineMinutes: backup.StartingDeadlineMinutes, 466 Schedules: []dpv1alpha1.SchedulePolicy{}, 467 }, 468 } 469 } 470 471 // build backup schedule policy by cluster backup spec 472 sp := &dpv1alpha1.SchedulePolicy{ 473 Enabled: backup.Enabled, 474 RetentionPeriod: backup.RetentionPeriod, 475 BackupMethod: backup.Method, 476 CronExpression: backup.CronExpression, 477 } 478 479 // merge cluster backup schedule policy into backup schedule, if the backup 480 // schedule with specified method already exists, we need to update it 481 // using the cluster backup schedule policy. Otherwise, we need to append 482 // it to the backup schedule. 483 for i, s := range backupSchedule.Spec.Schedules { 484 if s.BackupMethod == backup.Method { 485 mergeSchedulePolicy(sp, &backupSchedule.Spec.Schedules[i]) 486 return backupSchedule 487 } 488 } 489 backupSchedule.Spec.Schedules = append(backupSchedule.Spec.Schedules, *sp) 490 return backupSchedule 491 } 492 493 // getClusterComponentSpec returns the first component name of the componentDefRef. 494 func (r *BackupPolicyTplTransformer) getClusterComponentSpec() *appsv1alpha1.ClusterComponentSpec { 495 for _, v := range r.OrigCluster.Spec.ComponentSpecs { 496 if v.ComponentDefRef == r.backupPolicy.ComponentDefRef { 497 return &v 498 } 499 } 500 return nil 501 } 502 503 func (r *BackupPolicyTplTransformer) defaultPolicyAnnotationValue() string { 504 if r.tplCount > 1 && r.isDefaultTemplate != trueVal { 505 return "false" 506 } 507 return trueVal 508 } 509 510 func (r *BackupPolicyTplTransformer) buildAnnotations() map[string]string { 511 annotations := map[string]string{ 512 dptypes.DefaultBackupPolicyAnnotationKey: r.defaultPolicyAnnotationValue(), 513 constant.BackupPolicyTemplateAnnotationKey: r.backupPolicyTpl.Name, 514 } 515 if r.backupPolicyTpl.Annotations[dptypes.ReconfigureRefAnnotationKey] != "" { 516 annotations[dptypes.ReconfigureRefAnnotationKey] = r.backupPolicyTpl.Annotations[dptypes.ReconfigureRefAnnotationKey] 517 } 518 return annotations 519 } 520 521 func (r *BackupPolicyTplTransformer) buildLabels() map[string]string { 522 return map[string]string{ 523 constant.AppInstanceLabelKey: r.OrigCluster.Name, 524 constant.KBAppComponentDefRefLabelKey: r.backupPolicy.ComponentDefRef, 525 constant.AppManagedByLabelKey: constant.AppName, 526 } 527 } 528 529 // buildTargetPodLabels builds the target labels for the backup policy that will be 530 // used to select the target pod. 531 func (r *BackupPolicyTplTransformer) buildTargetPodLabels(comp *appsv1alpha1.ClusterComponentSpec) map[string]string { 532 labels := map[string]string{ 533 constant.AppInstanceLabelKey: r.OrigCluster.Name, 534 constant.KBAppComponentLabelKey: comp.Name, 535 constant.AppManagedByLabelKey: constant.AppName, 536 } 537 // append label to filter specific role of the component. 538 targetTpl := &r.backupPolicy.Target 539 if workloadHasRoleLabel(r.compWorkloadType) && 540 len(targetTpl.Role) > 0 && r.getCompReplicas() > 1 { 541 // the role only works when the component has multiple replicas. 542 labels[constant.RoleLabelKey] = targetTpl.Role 543 } 544 return labels 545 } 546 547 // generateBackupPolicyName generates the backup policy name which is created from backup policy template. 548 func generateBackupPolicyName(clusterName, componentDef, identifier string) string { 549 if len(identifier) == 0 { 550 return fmt.Sprintf("%s-%s-backup-policy", clusterName, componentDef) 551 } 552 return fmt.Sprintf("%s-%s-backup-policy-%s", clusterName, componentDef, identifier) 553 } 554 555 // generateBackupScheduleName generates the backup schedule name which is created from backup policy template. 556 func generateBackupScheduleName(clusterName, componentDef, identifier string) string { 557 if len(identifier) == 0 { 558 return fmt.Sprintf("%s-%s-backup-schedule", clusterName, componentDef) 559 } 560 return fmt.Sprintf("%s-%s-backup-schedule-%s", clusterName, componentDef, identifier) 561 } 562 563 func buildBackupPathPrefix(cluster *appsv1alpha1.Cluster, compName string) string { 564 return fmt.Sprintf("/%s-%s/%s", cluster.Name, cluster.UID, compName) 565 } 566 567 func workloadHasRoleLabel(workloadType appsv1alpha1.WorkloadType) bool { 568 return slices.Contains([]appsv1alpha1.WorkloadType{appsv1alpha1.Replication, appsv1alpha1.Consensus}, workloadType) 569 } 570 571 func mergeSchedulePolicy(src *dpv1alpha1.SchedulePolicy, dst *dpv1alpha1.SchedulePolicy) { 572 if src.Enabled != nil { 573 dst.Enabled = src.Enabled 574 } 575 if src.RetentionPeriod.String() != "" { 576 dst.RetentionPeriod = src.RetentionPeriod 577 } 578 if src.BackupMethod != "" { 579 dst.BackupMethod = src.BackupMethod 580 } 581 if src.CronExpression != "" { 582 dst.CronExpression = src.CronExpression 583 } 584 }