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  }