Repository: apecloud/kubeblocks

Issue:
[BUG]Restore job's `apps.kubeblocks.io/pod-name` label did not respect InstanceTemplate and ordinals
**Describe the bug**
Restore job's `apps.kubeblocks.io/pod-name` label did not respect InstanceTemplate and ordinals.

1. pod name does not respect InstanceTemplate

<img width="1496" height="896" alt="Image" src="https://github.com/user-attachments/assets/8350ac9f-29ac-4e08-8da9-4c64bf2c598c" />

2. pod name's ordinal does not match pvc's ordinal

<img width="1784" height="1588" alt="Image" src="https://github.com/user-attachments/assets/cf900eb4-5ac1-4882-a903-dfdfb0a22ab6" />

 
**To Reproduce**
Using cluster yaml likes below:
```
apiVersion: apps.kubeblocks.io/v1
kind: Cluster
metadata:
  annotations:
    kubeblocks.io/crd-api-version: apps.kubeblocks.io/v1
    # kubeblocks.io/restore-from-backup: '{"mysql":{"encryptedSystemAccounts":"abc","name":"t-vip-mysql57-xaz-backup-qz4nw","namespace":"thomas-demo","volumeRestorePolicy":"Parallel"}}'
    kubeblocks.io/restore-from-backup: '{"mysql":{"encryptedSystemAccounts":"abc","name":"t-vip-mysql57-xaz-backup-qz4nw","namespace":"thomas-demo","volumeRestorePolicy":"Serial"}}'
  name: mysql57-restore
  namespace: thomas-demo
spec:
  clusterDef: vip-mysql
  componentSpecs:
  - componentDef: vip-mysql-5.7-1.0.0-alpha.0
    instanceUpdateStrategy:
      type: OnDelete
    instances:
    - annotations:
        tpl: q9n0
      name: q9n0
      ordinals:
        ranges:
        - end: 299
          start: 0
      replicas: 2
    - annotations:
        tpl: iph1
      name: iph1
      ordinals:
        ranges:
        - end: 599
          start: 300
      replicas: 1
    - annotations:
        tpl: wte2
      name: wte2
      ordinals:
        ranges:
        - end: 899
          start: 600
      replicas: 2
    name: mysql
    persistentVolumeClaimRetentionPolicy:
      whenDeleted: Delete
      whenScaled: Retain
    replicas: 5
    resources:
      limits:
        cpu: "2"
        memory: 4G
      requests:
        cpu: "2"
        memory: 4G
    # serviceVersion: 5.7.44-vip.1
    volumeClaimTemplates:
    - name: data
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 10G
        # storageClassName: open-local-lvm
  terminationPolicy: Delete
  # topology: replset
```

**Expected behavior**
Restore job's pod label should respect InstanceTemplate and pvc ordinals

**Desktop (please complete the following information):**
 - KB: 1.0.1-beta.7



Discussion:

Solution patch:
diff --git a/controllers/extensions/addon_controller_stages.go b/controllers/extensions/addon_controller_stages.go
index 48bfb9345..820222aa3 100644
--- a/controllers/extensions/addon_controller_stages.go
+++ b/controllers/extensions/addon_controller_stages.go
@@ -62,7 +62,7 @@ const (
 )
 
 func init() {
-	viper.SetDefault(addonSANameKey, "kubeblocks-addon-installer")
+	viper.SetDefault(constant.KBServiceAccountName, "kubeblocks")
 	viper.SetDefault(addonHelmInstallOptKey, []string{
 		"--atomic",
 		"--cleanup-on-fail",
@@ -598,7 +598,6 @@ func (r *helmTypeInstallStage) Handle(ctx context.Context) {
 			chartsPath,
 			"--namespace",
 			"$(RELEASE_NS)",
-			"--create-namespace",
 		}, viper.GetStringSlice(addonHelmInstallOptKey)...)
 
 		installValues := addon.Spec.Helm.BuildMergedValues(addon.Spec.InstallSpec)
@@ -916,19 +915,7 @@ func createHelmJobProto(addon *extensionsv1alpha1.Addon) (*batchv1.Job, error) {
 		Name:            getJobMainContainerName(addon),
 		Image:           viper.GetString(constant.KBToolsImage),
 		ImagePullPolicy: corev1.PullPolicy(viper.GetString(constant.CfgAddonJobImgPullPolicy)),
-		// TODO: need have image that is capable of following settings, current settings
-		// may expose potential security risk, as this pod is using cluster-admin clusterrole.
-		// SecurityContext: &corev1.SecurityContext{
-		//	RunAsNonRoot:             &[]bool{true}[0],
-		//	RunAsUser:                &[]int64{1001}[0],
-		//	AllowPrivilegeEscalation: &[]bool{false}[0],
-		//	Capabilities: &corev1.Capabilities{
-		//		Drop: []corev1.Capability{
-		//			"ALL",
-		//		},
-		//	},
-		// },
-		Command: []string{"helm"},
+		Command:         []string{"helm"},
 		Env: []corev1.EnvVar{
 			{
 				Name:  "RELEASE_NAME",
@@ -966,7 +953,7 @@ func createHelmJobProto(addon *extensionsv1alpha1.Addon) (*batchv1.Job, error) {
 				},
 				Spec: corev1.PodSpec{
 					RestartPolicy:      corev1.RestartPolicyNever,
-					ServiceAccountName: viper.GetString("KUBEBLOCKS_ADDON_SA_NAME"),
+					ServiceAccountName: viper.GetString(constant.KBServiceAccountName),
 					Containers:         []corev1.Container{container},
 					Volumes:            []corev1.Volume{},
 					Tolerations:        []corev1.Toleration{},
diff --git a/controllers/extensions/const.go b/controllers/extensions/const.go
index 01e3ca25b..728c6971e 100644
--- a/controllers/extensions/const.go
+++ b/controllers/extensions/const.go
@@ -54,7 +54,6 @@ const (
 
 	// config keys used in viper
 	maxConcurrentReconcilesKey = "MAXCONCURRENTRECONCILES_ADDON"
-	addonSANameKey             = "KUBEBLOCKS_ADDON_SA_NAME"
 	addonHelmInstallOptKey     = "KUBEBLOCKS_ADDON_HELM_INSTALL_OPTIONS"
 	addonHelmUninstallOptKey   = "KUBEBLOCKS_ADDON_HELM_UNINSTALL_OPTIONS"
 )
diff --git a/deploy/helm/templates/_helpers.tpl b/deploy/helm/templates/_helpers.tpl
index 278290d64..6a48baaab 100644
--- a/deploy/helm/templates/_helpers.tpl
+++ b/deploy/helm/templates/_helpers.tpl
@@ -61,13 +61,6 @@ Create the name of the service account to use
 {{- end }}
 {{- end }}
 
-{{/*
-Create the addon installer name of the service account to use
-*/}}
-{{- define "kubeblocks.addonSAName" -}}
-{{- printf "%s-%s" (include "kubeblocks.serviceAccountName" .) "addon-installer" }}
-{{- end }}
-
 {{/*
 Create the name of the webhook service.
 */}}
diff --git a/deploy/helm/templates/deployment.yaml b/deploy/helm/templates/deployment.yaml
index 135516113..8be5d811f 100644
--- a/deploy/helm/templates/deployment.yaml
+++ b/deploy/helm/templates/deployment.yaml
@@ -146,8 +146,6 @@ spec:
             - name: ADDON_JOB_IMAGE_PULL_POLICY
               value: {{ .jobImagePullPolicy | default "IfNotPresent" }}
             {{- end }}
-            - name: KUBEBLOCKS_ADDON_SA_NAME
-              value: {{ include "kubeblocks.addonSAName" . }}
             - name: KUBEBLOCKS_ADDON_HELM_INSTALL_OPTIONS
               value: {{ join " " .Values.addonHelmInstallOptions }}
             - name: KUBEBLOCKS_ADDON_CHARTS_IMAGE_PULL_POLICY
diff --git a/deploy/helm/templates/rbac/clusterrole_binding.yaml b/deploy/helm/templates/rbac/clusterrole_binding.yaml
index 556a30cbc..bf0fb146b 100644
--- a/deploy/helm/templates/rbac/clusterrole_binding.yaml
+++ b/deploy/helm/templates/rbac/clusterrole_binding.yaml
@@ -11,22 +11,4 @@ roleRef:
 subjects:
 - kind: ServiceAccount
   name: {{ include "kubeblocks.serviceAccountName" . }}
-  namespace: {{ .Release.Namespace }}
-
-{{- if ( include "kubeblocks.addonControllerEnabled" . ) | deepEqual "true" }}
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: {{ include "kubeblocks.fullname" . }}-cluster-admin-rolebinding
-  labels:
-    {{- include "kubeblocks.labels" . | nindent 4 }}
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: cluster-admin
-subjects:
-  - kind: ServiceAccount
-    name: {{ include "kubeblocks.addonSAName" . }}
-    namespace: {{ .Release.Namespace }}
-{{- end }}
\ No newline at end of file
+  namespace: {{ .Release.Namespace }}
\ No newline at end of file
diff --git a/deploy/helm/templates/serviceaccount.yaml b/deploy/helm/templates/serviceaccount.yaml
index 016c5be4d..7cb20e3b4 100644
--- a/deploy/helm/templates/serviceaccount.yaml
+++ b/deploy/helm/templates/serviceaccount.yaml
@@ -14,24 +14,6 @@ imagePullSecrets:
     {{- toYaml . | nindent 2 }}
   {{- end }}
 
-{{- if ( include "kubeblocks.addonControllerEnabled" . ) | deepEqual "true" }}
----
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  name: {{ include "kubeblocks.addonSAName" . }}
-  labels:
-    {{- include "kubeblocks.labels" . | nindent 4 }}
-    {{- with .Values.serviceAccount.annotations }}
-  annotations:
-      {{- toYaml . | nindent 4 }}
-    {{- end }}
-  {{- with .Values.addonChartsImage.pullSecrets }}
-imagePullSecrets:
-    {{- toYaml . | nindent 2 }}
-  {{- end }}
-{{- end }}
-
 {{- if and .Values.dataProtection.enabled }}
 ---
 apiVersion: v1
diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml
index e814db122..8151ec791 100644
--- a/deploy/helm/values.yaml
+++ b/deploy/helm/values.yaml
@@ -453,7 +453,7 @@ backupRepo:
     accessKeyId: ""
     secretAccessKey: ""
 
-  ## Addon controller settings, this will require cluster-admin clusterrole.
+  ## Addon controller settings
   ##
   ## @param addonController.enabled
   ## @param addonController.jobTTL - is addon job time-to-live period, this value is time.Duration-parseable string.
diff --git a/pkg/dataprotection/restore/builder.go b/pkg/dataprotection/restore/builder.go
index e9a0084ef..15bd30a72 100644
--- a/pkg/dataprotection/restore/builder.go
+++ b/pkg/dataprotection/restore/builder.go
@@ -121,6 +121,11 @@ func (r *restoreJobBuilder) resetSpecificVolumesAndMounts() {
 	r.specificVolumeMounts = []corev1.VolumeMount{}
 }
 
+// resetSpecificLabels resets the specific labels.
+func (r *restoreJobBuilder) resetSpecificLabels(){
+	r.labels = BuildRestoreLabels(r.restore.Name)
+}
+
 // addToSpecificVolumesAndMounts adds the volume and volumeMount to specific volumes and volumeMounts slice.
 func (r *restoreJobBuilder) addToSpecificVolumesAndMounts(volume *corev1.Volume, volumeMount *corev1.VolumeMount) *restoreJobBuilder {
 	if volume != nil {
diff --git a/pkg/dataprotection/restore/manager.go b/pkg/dataprotection/restore/manager.go
index 1f5d47024..c8dd87e70 100644
--- a/pkg/dataprotection/restore/manager.go
+++ b/pkg/dataprotection/restore/manager.go
@@ -364,12 +364,18 @@ func addItsManagingLabels(claim *dpv1alpha1.RestoreVolumeClaim, index int) {
 		claim.Labels = make(map[string]string)
 	}
 
-	itsName := constant.GenerateWorkloadNamePattern(clusterName, compName)
-	itsMatchLabels := instanceset.GetMatchLabels(itsName)
+	compObjName := constant.GenerateWorkloadNamePattern(clusterName, compName)
+	itsMatchLabels := instanceset.GetMatchLabels(compObjName)
 	intctrlutil.MergeMetadataMapInplace(itsMatchLabels, &claim.Labels)
 
 	if claim.Labels[constant.KBAppPodNameLabelKey] == "" {
-		podName := fmt.Sprintf("%s-%d", itsName, index)
+		templateName, exist := claim.Labels[constant.KBAppInstanceTemplateLabelKey]
+		var podName string
+		if exist {
+			podName = fmt.Sprintf("%s-%s-%d", compObjName, templateName, index)
+		} else {
+			podName = fmt.Sprintf("%s-%d", compObjName, index)
+		}
 		claim.Labels[constant.KBAppPodNameLabelKey] = podName
 	}
 }
@@ -521,9 +527,13 @@ func (r *RestoreManager) BuildPrepareDataJobs(reqCtx intctrlutil.RequestCtx, cli
 	for i := 0; i < restoreJobReplicas; i++ {
 		// reset specific volumes and volumeMounts
 		jobBuilder.resetSpecificVolumesAndMounts()
+		// reset specific labels as addLabel does not override existing labels
+		jobBuilder.resetSpecificLabels()
 		if claimsTemplate != nil {
 			//  create pvc from claims template, build volumes and volumeMounts
-			for _, claim := range claimsTemplate.Templates {
+			for _, c := range claimsTemplate.Templates {
+				// deepcopy to avoid modify the original object
+				claim := *c.DeepCopy()
 				index := i + int(claimsTemplate.StartingIndex)
 				claim.Name = fmt.Sprintf("%s-%d", claim.Name, index)
 				// HACK: add InstanceSet related labels to the PVC,



Test patch:
diff --git a/pkg/dataprotection/restore/manager_test.go b/pkg/dataprotection/restore/manager_test.go
index 10f5d857f..3f2d9020d 100644
--- a/pkg/dataprotection/restore/manager_test.go
+++ b/pkg/dataprotection/restore/manager_test.go
@@ -22,6 +22,7 @@ package restore
 import (
 	"fmt"
 	"strconv"
+	"strings"
 	"time"
 
 	. "github.com/onsi/ginkgo/v2"
@@ -172,9 +173,9 @@ var _ = Describe("RestoreManager Test", func() {
 			return restoreMGR, backupSet
 		}
 
-		checkPVC := func(startingIndex int, useVolumeSnapshot bool) {
+		checkPVC := func(startingIndex int, useVolumeSnapshot bool, managedBy string) {
 			By("expect for pvcs are created")
-			pvcMatchingLabels := client.MatchingLabels{constant.AppManagedByLabelKey: "restore"}
+			pvcMatchingLabels := client.MatchingLabels{constant.AppManagedByLabelKey: managedBy}
 			Eventually(testapps.List(&testCtx, generics.PersistentVolumeClaimSignature, pvcMatchingLabels,
 				client.InNamespace(testCtx.DefaultNamespace))).Should(HaveLen(replicas))
 
@@ -183,7 +184,8 @@ var _ = Describe("RestoreManager Test", func() {
 			Expect(k8sClient.List(ctx, pvcList, pvcMatchingLabels,
 				client.InNamespace(testCtx.DefaultNamespace))).Should(Succeed())
 			for _, v := range pvcList.Items {
-				indexStr := string(v.Name[len(v.Name)-1])
+				parts := strings.Split(v.Name, "-")
+				indexStr := parts[len(parts)-1]
 				index, _ := strconv.Atoi(indexStr)
 				Expect(index >= startingIndex).Should(BeTrue())
 				if useVolumeSnapshot {
@@ -227,7 +229,7 @@ var _ = Describe("RestoreManager Test", func() {
 			target := utils.GetBackupStatusTarget(backupSet.Backup, restoreMGR.Restore.Spec.Backup.SourceTargetName)
 			Expect(restoreMGR.RestorePVCFromSnapshot(reqCtx, k8sClient, *backupSet, target)).Should(Succeed())
 
-			checkPVC(startingIndex, useVolumeSnapshot)
+			checkPVC(startingIndex, useVolumeSnapshot, "restore")
 		})
 
 		It("test with BuildPrepareDataJobs function and Parallel volumeRestorePolicy", func() {
@@ -251,7 +253,34 @@ var _ = Describe("RestoreManager Test", func() {
 			// image should be expanded by env
 			Expect(jobs[0].Spec.Template.Spec.Containers[0].Image).Should(ContainSubstring(testdp.ImageTag))
 
-			checkPVC(startingIndex, false)
+			checkPVC(startingIndex, false, "restore")
+		})
+
+		It("test with BuildPrepareDataJobs function with InstanceTemplates claims", func() {
+			reqCtx := getReqCtx()
+			startingIndex := 300
+			templateName := "abc"
+			cmpName := "mysql"
+			restoreMGR, backupSet := initResources(reqCtx, startingIndex, false, func(f *testdp.MockRestoreFactory) {
+				f.SetVolumeClaimsTemplate(testdp.MysqlTemplateName, testdp.DataVolumeName,
+					testdp.DataVolumeMountPath, "", int32(replicas), int32(startingIndex), map[string]string{
+						constant.AppInstanceLabelKey:           instanceName,
+						constant.KBAppComponentLabelKey:        cmpName,
+						constant.KBAppInstanceTemplateLabelKey: templateName,
+					})
+			})
+			By(fmt.Sprintf("test BuildPrepareDataJobs function, expect job label pod name contains template '%s' and ordinal correct", templateName))
+			actionSetName := "preparedata-0"
+			target := utils.GetBackupStatusTarget(backupSet.Backup, restoreMGR.Restore.Spec.Backup.SourceTargetName)
+			jobs, err := restoreMGR.BuildPrepareDataJobs(reqCtx, k8sClient, *backupSet, target, actionSetName)
+			Expect(err).ShouldNot(HaveOccurred())
+			Expect(len(jobs)).Should(Equal(replicas))
+			// job label contains pod name and ordinal match
+			for i := 0; i < replicas; i++ {
+				Expect(jobs[i].Spec.Template.Labels[constant.KBAppPodNameLabelKey]).Should(Equal(fmt.Sprintf("%s-%s-%s-%d", instanceName, cmpName, templateName, startingIndex+i)))
+			}
+
+			checkPVC(startingIndex, false, constant.AppName)
 		})
 
 		It("test with BuildPrepareDataJobs function and Serial volumeRestorePolicy", func() {