Repository: karmada-io/karmada

Issue:
Enhance the scheduling performance of  karmada-schduler 
<!-- Please only use this template for submitting enhancement requests -->

When karmada-scheduler schedules the rb object for the first time, it would respectively calculate the number of available replicas in the subcluster in the `Select` phase and the `AssignReplica` phase. The picture below records metric of karmada_scheduler_scheduling_algorithm_duration_seconds_bucket.
![image](https://github.com/user-attachments/assets/d9ac5f88-9a19-4584-bce8-0d9a8538b67b)

there could be optimized to once calculate, The result calculated in the `Select` phase could be directly repeated in the `AssignReplica` phase. In order to improve the qps of the karmada-scheduler
[[](url)](https://github.com/karmada-io/karmada/blob/d4c58906efec6fa6b6060dda4c65b4bedb944abc/pkg/scheduler/core/generic_scheduler.go#L97-L107)

Discussion:
@chaosi-zju: Since the time interval between the `selectClusters` step and `assignReplicas` step is only 20 milliseconds, it does seem unnecessary to calculate `availableReplicas` twice. So, I also think that the second calculation can be omitted. 

Hi @Garrybest ,what is your opinion?@LivingCcj: In the scheduler step, there have 4 steps, and the `findClustersThatFit` & `priortizeClusters` only cost less time, the `selectClusters` or `assignReplicas` would cost more time from the picture.

![Image](https://github.com/user-attachments/assets/a024d70e-dece-44fe-9902-1d35731c6e41)

The large cost time will be happened in calculate the available replicas for each member cluster. and the `selectClusters` and `assignReplicas` step will invoke the calAvailableReplicas twice in followed picture.

![Image](https://github.com/user-attachments/assets/8472330c-1db5-4598-85f1-171946bd403f)

There is an optimization points：
In the `selectClusters` step, that has got the available replicas for each member cluster, and the result could be passed to the next `assignReplicas` step. it could reduce to invoke the calAvailableReplicas function for once.

<img width="759" alt="Image" src="https://github.com/user-attachments/assets/88faa25d-ee44-4967-9f56-9e978bc24e39" />@RainbowMango: cc @zhzhuang-zju @LivingCcj: #### first，update some structs
`ClusterDetailInfo` struct add `AvailableClusterReplicas`  field, which indicate the cluster and the available replicas.
```
// ClusterDetailInfo indicate the cluster information
type ClusterDetailInfo struct {
	Name              string
	Score             int64
	AvailableReplicas int64

	Cluster                  *clusterv1alpha1.Cluster
	AvailableClusterReplicas AvailableClusterReplicas // new field 
}

// AvailableClusterReplicas is used to record the latest available replicas of the cluster.
type AvailableClusterReplicas struct {
	Cluster  *clusterv1alpha1.Cluster
	Replicas int64
}
```
`assignState` struct add `availableClusterReplicas` field, which indicate the cluster and the available replicas from select cluster step.

```
// assignState is a wrapper of the input for assigning function.
type assignState struct {
	candidates []*clusterv1alpha1.Cluster
	strategy   *policyv1alpha1.ReplicaSchedulingStrategy
	spec       *workv1alpha2.ResourceBindingSpec

	// fields below are indirect results
	strategyType string

	// assignmentMode represents the mode how to assign replicas
	assignmentMode assignmentMode

	scheduledClusters []workv1alpha2.TargetCluster
	assignedReplicas  int32
	availableClusters []workv1alpha2.TargetCluster
	availableReplicas int32

	// targetReplicas is the replicas that we need to schedule in this round 
	targetReplicas int32

	//availableClusterReplicas is the available replicas of candidate clusters
	availableClusterReplicas []spreadconstraint.AvailableClusterReplicas // new field 
}
```
#### second,refresh the input and output function, and some core functions like 
https://github.com/karmada-io/karmada/blob/efca312a572175da133164b3f8d7246840f195fb/pkg/scheduler/core/generic_scheduler.go#L97-L108

Update the core function
`selectClusters(...) ([]*clusterv1alpha1.Cluster, error)` to `selectClusters(...) ([]spreadconstraint.AvailableClusterReplicas, error)`

And update the core function `assignReplicas(clusters []*clusterv1alpha1.Cluster,...) ([]workv1alpha2.TargetCluster, error)` to `assignReplicas(clusters []spreadconstraint.AvailableClusterReplicas, ...) ([]workv1alpha2.TargetCluster, error)`@RainbowMango: As we talked at the community meeting, I will look at it and you can continue to send a PR for this.@LivingCcj: > As we talked at the community meeting, I will look at it and you can continue to send a PR for this.

Ok@RainbowMango: /close
In favor of #6597@karmada-bot: @RainbowMango: Closing this issue.

<details>

In response to [this](https://github.com/karmada-io/karmada/issues/5202#issuecomment-3227031400):

>/close
>In favor of #6597


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

Solution patch:
diff --git a/.github/workflows/ci-image-scanning-on-schedule.yml b/.github/workflows/ci-image-scanning-on-schedule.yml
index 1a0ff795a..566a2dc4a 100644
--- a/.github/workflows/ci-image-scanning-on-schedule.yml
+++ b/.github/workflows/ci-image-scanning-on-schedule.yml
@@ -30,7 +30,7 @@ jobs:
         karmada-version: [ release-1.14, release-1.13, release-1.12 ]
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           ref: ${{ matrix.karmada-version }}
       - name: install Go
diff --git a/.github/workflows/ci-image-scanning.yaml b/.github/workflows/ci-image-scanning.yaml
index e7b16646f..99ada99ba 100644
--- a/.github/workflows/ci-image-scanning.yaml
+++ b/.github/workflows/ci-image-scanning.yaml
@@ -31,7 +31,7 @@ jobs:
           - karmada-metrics-adapter
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
          # fetch-depth:
           # 0 indicates all history for all branches and tags.
diff --git a/.github/workflows/ci-schedule-compatibility.yaml b/.github/workflows/ci-schedule-compatibility.yaml
index 02a103956..b71be5cd3 100644
--- a/.github/workflows/ci-schedule-compatibility.yaml
+++ b/.github/workflows/ci-schedule-compatibility.yaml
@@ -38,7 +38,7 @@ jobs:
           docker-images: false
           swap-storage: false
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # Number of commits to fetch. 0 indicates all history for all branches and tags.
           # We need to guess version via git tags.
diff --git a/.github/workflows/ci-schedule.yml b/.github/workflows/ci-schedule.yml
index abbc89be0..e4b958a67 100644
--- a/.github/workflows/ci-schedule.yml
+++ b/.github/workflows/ci-schedule.yml
@@ -35,7 +35,7 @@ jobs:
           docker-images: false
           swap-storage: false
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # Number of commits to fetch. 0 indicates all history for all branches and tags.
           # We need to guess version via git tags.
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9cfd42514..f3983a0c5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
       - name: install Go
         uses: actions/setup-go@v5
         with:
@@ -38,7 +38,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
       - name: install Go
         uses: actions/setup-go@v5
         with:
@@ -66,7 +66,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # Number of commits to fetch. 0 indicates all history for all branches and tags.
           # We need to guess version via git tags.
@@ -85,7 +85,7 @@ jobs:
       GOTESTSUM_ENABLED: enabled
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
       - name: install Go
         uses: actions/setup-go@v5
         with:
@@ -132,7 +132,7 @@ jobs:
           docker-images: false
           swap-storage: false
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # Number of commits to fetch. 0 indicates all history for all branches and tags.
           # We need to guess version via git tags.
@@ -144,6 +144,7 @@ jobs:
       - name: setup e2e test environment
         run: |
           export CLUSTER_VERSION=kindest/node:${{ matrix.k8s }}
+          export KUBE_CACHE_MUTATION_DETECTOR=true
           hack/local-up-karmada.sh
       - name: run e2e
         run: |
@@ -188,7 +189,7 @@ jobs:
           docker-images: false
           swap-storage: false
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # Number of commits to fetch. 0 indicates all history for all branches and tags.
           # We need to guess version via git tags.
diff --git a/.github/workflows/dockerhub-latest-chart.yml b/.github/workflows/dockerhub-latest-chart.yml
index 7bbd8bfca..057f6c957 100644
--- a/.github/workflows/dockerhub-latest-chart.yml
+++ b/.github/workflows/dockerhub-latest-chart.yml
@@ -16,7 +16,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # fetch-depth:
           # 0 indicates all history for all branches and tags.
diff --git a/.github/workflows/dockerhub-latest-image.yml b/.github/workflows/dockerhub-latest-image.yml
index 9c539a919..cfe9ffb35 100644
--- a/.github/workflows/dockerhub-latest-image.yml
+++ b/.github/workflows/dockerhub-latest-image.yml
@@ -31,7 +31,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # fetch-depth:
           # 0 indicates all history for all branches and tags.
diff --git a/.github/workflows/dockerhub-released-chart.yml b/.github/workflows/dockerhub-released-chart.yml
index a08091fb5..5076fb0e1 100644
--- a/.github/workflows/dockerhub-released-chart.yml
+++ b/.github/workflows/dockerhub-released-chart.yml
@@ -15,7 +15,7 @@ jobs:
     if: ${{ github.repository == 'karmada-io/karmada' }}
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # fetch-depth:
           # 0 indicates all history for all branches and tags.
diff --git a/.github/workflows/dockerhub-released-image.yml b/.github/workflows/dockerhub-released-image.yml
index d82e698bf..9518b9f2e 100644
--- a/.github/workflows/dockerhub-released-image.yml
+++ b/.github/workflows/dockerhub-released-image.yml
@@ -31,7 +31,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # fetch-depth:
           # 0 indicates all history for all branches and tags.
diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml
index 829ea2607..cabcc5199 100644
--- a/.github/workflows/fossa.yml
+++ b/.github/workflows/fossa.yml
@@ -7,7 +7,7 @@ on:
       - 'dependabot/**'
 
 permissions:
-  contents: read # Required by actions/checkout@v4 to fetch the repository contents.
+  contents: read # Required by actions/checkout@v5 to fetch the repository contents.
   
 jobs:
   fossa:
@@ -19,7 +19,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
       - name: Run FOSSA scan and upload build data
         uses: fossas/fossa-action@v1
         with:
diff --git a/.github/workflows/installation-chart.yaml b/.github/workflows/installation-chart.yaml
index 41be1aabb..803122a2f 100644
--- a/.github/workflows/installation-chart.yaml
+++ b/.github/workflows/installation-chart.yaml
@@ -29,7 +29,7 @@ jobs:
         k8s: [ v1.31.0, v1.32.0, v1.33.0 ]
     steps:
       - name: Checkout
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           fetch-depth: 0
 
diff --git a/.github/workflows/installation-cli.yaml b/.github/workflows/installation-cli.yaml
index a60690681..12a92100c 100644
--- a/.github/workflows/installation-cli.yaml
+++ b/.github/workflows/installation-cli.yaml
@@ -27,7 +27,7 @@ jobs:
         k8s: [ v1.31.0, v1.32.0, v1.33.0 ]
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # Number of commits to fetch. 0 indicates all history for all branches and tags.
           # We need to guess version via git tags.
@@ -73,7 +73,7 @@ jobs:
         k8s: [ v1.31.0, v1.32.0, v1.33.0 ]
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           fetch-depth: 0
       - name: install Go
diff --git a/.github/workflows/installation-operator.yaml b/.github/workflows/installation-operator.yaml
index 00fd4cd06..f4070f270 100644
--- a/.github/workflows/installation-operator.yaml
+++ b/.github/workflows/installation-operator.yaml
@@ -40,7 +40,7 @@ jobs:
           docker-images: false
           swap-storage: false
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # Number of commits to fetch. 0 indicates all history for all branches and tags.
           # We need to guess version via git tags.
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index df6628769..ea8e7ab0d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -23,7 +23,7 @@ jobs:
           - amd64
           - arm64
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - name: Set up Go
       uses: actions/setup-go@v5
       with:
@@ -84,7 +84,7 @@ jobs:
       hashes: ${{ steps.hash.outputs.hashes }}
     runs-on: ubuntu-22.04
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - name: Rename the crds directory
       run: |
         mv ./charts/karmada/_crds ./charts/karmada/crds
@@ -126,7 +126,7 @@ jobs:
       hashes: ${{ steps.hash.outputs.hashes }}
     runs-on: ubuntu-22.04
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - name: Making helm charts
       env:
         VERSION: ${{ github.ref_name }}
@@ -165,7 +165,7 @@ jobs:
       hashes: ${{ steps.sbom-hash.outputs.hashes}}
     runs-on: ubuntu-22.04
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
     - name: Generate sbom for karmada file system
       uses: aquasecurity/trivy-action@0.32.0
       with:
@@ -220,7 +220,7 @@ jobs:
         echo "Got the latest tag:$LATEST_TAG"
         echo "event.tag:"${{ github.event.release.tag_name }}
         echo "latestTag=$LATEST_TAG" >> "$GITHUB_OUTPUT"
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v5
       if: steps.get-latest-tag.outputs.latestTag == github.event.release.tag_name
     - name: Update new version in krew-index
       if: steps.get-latest-tag.outputs.latestTag == github.event.release.tag_name
diff --git a/.github/workflows/swr-latest-image.yml b/.github/workflows/swr-latest-image.yml
index aa344b53f..6dc36210a 100644
--- a/.github/workflows/swr-latest-image.yml
+++ b/.github/workflows/swr-latest-image.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           # fetch-depth:
           # 0 indicates all history for all branches and tags.
diff --git a/.github/workflows/swr-released-image.yml b/.github/workflows/swr-released-image.yml
index 83730446c..d9a1bbbb4 100644
--- a/.github/workflows/swr-released-image.yml
+++ b/.github/workflows/swr-released-image.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - name: checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v5
         with:
           fetch-depth: 0
       - name: install Go
diff --git a/artifacts/deploy/karmada-controller-manager.yaml b/artifacts/deploy/karmada-controller-manager.yaml
index ff4881cf1..4f625f17d 100644
--- a/artifacts/deploy/karmada-controller-manager.yaml
+++ b/artifacts/deploy/karmada-controller-manager.yaml
@@ -31,6 +31,8 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: status.podIP
+            - name: KUBE_CACHE_MUTATION_DETECTOR
+              value: "{{KUBE_CACHE_MUTATION_DETECTOR}}"
           command:
             - /bin/karmada-controller-manager
             - --kubeconfig=/etc/karmada/config/karmada.config
diff --git a/artifacts/deploy/karmada-descheduler.yaml b/artifacts/deploy/karmada-descheduler.yaml
index b1c59a6a4..d3c15c998 100644
--- a/artifacts/deploy/karmada-descheduler.yaml
+++ b/artifacts/deploy/karmada-descheduler.yaml
@@ -31,6 +31,8 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: status.podIP
+            - name: KUBE_CACHE_MUTATION_DETECTOR
+              value: "{{KUBE_CACHE_MUTATION_DETECTOR}}"
           command:
             - /bin/karmada-descheduler
             - --kubeconfig=/etc/karmada/config/karmada.config
diff --git a/artifacts/deploy/karmada-metrics-adapter.yaml b/artifacts/deploy/karmada-metrics-adapter.yaml
index bd6401247..82195c591 100644
--- a/artifacts/deploy/karmada-metrics-adapter.yaml
+++ b/artifacts/deploy/karmada-metrics-adapter.yaml
@@ -32,6 +32,8 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: status.podIP
+            - name: KUBE_CACHE_MUTATION_DETECTOR
+              value: "{{KUBE_CACHE_MUTATION_DETECTOR}}"
           command:
             - /bin/karmada-metrics-adapter
             - --kubeconfig=/etc/karmada/config/karmada.config
diff --git a/artifacts/deploy/karmada-scheduler-estimator.yaml b/artifacts/deploy/karmada-scheduler-estimator.yaml
index 99f259ae5..d3e0f8b2c 100644
--- a/artifacts/deploy/karmada-scheduler-estimator.yaml
+++ b/artifacts/deploy/karmada-scheduler-estimator.yaml
@@ -31,6 +31,8 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: status.podIP
+            - name: KUBE_CACHE_MUTATION_DETECTOR
+              value: "{{KUBE_CACHE_MUTATION_DETECTOR}}"
           command:
             - /bin/karmada-scheduler-estimator
             - --kubeconfig=/etc/{{member_cluster_name}}-kubeconfig
diff --git a/artifacts/deploy/karmada-scheduler.yaml b/artifacts/deploy/karmada-scheduler.yaml
index 4ce9b47a0..a0459b6a5 100644
--- a/artifacts/deploy/karmada-scheduler.yaml
+++ b/artifacts/deploy/karmada-scheduler.yaml
@@ -44,6 +44,8 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: status.podIP
+            - name: KUBE_CACHE_MUTATION_DETECTOR
+              value: "{{KUBE_CACHE_MUTATION_DETECTOR}}"
           command:
             - /bin/karmada-scheduler
             - --kubeconfig=/etc/karmada/config/karmada.config
diff --git a/artifacts/deploy/karmada-search.yaml b/artifacts/deploy/karmada-search.yaml
index 00771c848..adfa53c32 100644
--- a/artifacts/deploy/karmada-search.yaml
+++ b/artifacts/deploy/karmada-search.yaml
@@ -32,6 +32,8 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: status.podIP
+            - name: KUBE_CACHE_MUTATION_DETECTOR
+              value: "{{KUBE_CACHE_MUTATION_DETECTOR}}"
           command:
             - /bin/karmada-search
             - --kubeconfig=/etc/karmada/config/karmada.config
diff --git a/charts/karmada/templates/_helpers.tpl b/charts/karmada/templates/_helpers.tpl
index dace87448..5b251304c 100644
--- a/charts/karmada/templates/_helpers.tpl
+++ b/charts/karmada/templates/_helpers.tpl
@@ -556,18 +556,22 @@ Return the proper Docker Image Registry Secret Names
 {{ include "common.images.pullSecrets" (dict "images" (list .Values.cfssl.image .Values.kubectl.image .Values.etcd.internal.image .Values.agent.image .Values.apiServer.image .Values.controllerManager.image .Values.descheduler.image .Values.schedulerEstimator.image .Values.scheduler.image .Values.webhook.image .Values.aggregatedApiServer.image .Values.metricsAdapter.image .Values.search.image .Values.kubeControllerManager.image) "global" .Values.global) }}
 {{- end -}}
 
+{{- /* 
+Generate the --feature-gates command line argument for karmada-controllerManager.
+Iterates over .Values.controllerManager.featureGates and constructs a comma-separated key=value list.
+If any feature gates are set, outputs: --feature-gates=Foo=true,Bar=false
+If none are set, outputs nothing.
+*/ -}}
 {{- define "karmada.controllerManager.featureGates" -}}
-     {{- if (not (empty .Values.controllerManager.featureGates)) }}
-          {{- $featureGatesFlag := "" -}}
+     {{- if .Values.controllerManager.featureGates -}}
+          {{- $featureGates := list -}}
           {{- range $key, $value := .Values.controllerManager.featureGates -}}
-               {{- if not (empty (toString $value)) }}
-                    {{- $featureGatesFlag = cat $featureGatesFlag $key "=" $value ","  -}}
+               {{- if not (empty (toString $value)) -}}
+                    {{- $featureGates = append $featureGates (printf "%s=%s" $key (toString $value)) -}}
                {{- end -}}
           {{- end -}}
-
-          {{- if gt (len $featureGatesFlag) 0 }}
-               {{- $featureGatesFlag := trimSuffix "," $featureGatesFlag  | nospace -}}
-               {{- printf "%s=%s" "--feature-gates" $featureGatesFlag -}}
+          {{- if $featureGates -}}
+               {{- printf "--feature-gates=%s" (join "," $featureGates) | nospace -}}
           {{- end -}}
      {{- end -}}
 {{- end -}}
@@ -579,34 +583,55 @@ If any feature gates are set, outputs: --feature-gates=Foo=true,Bar=false
 If none are set, outputs nothing.
 */ -}}
 {{- define "karmada.webhook.featureGates" -}}
-     {{- if (not (empty .Values.webhook.featureGates)) }}
-          {{- $featureGatesFlag := "" -}}
+     {{- if .Values.webhook.featureGates -}}
+          {{- $featureGates := list -}}
           {{- range $key, $value := .Values.webhook.featureGates -}}
-               {{- if not (empty (toString $value)) }}
-                    {{- $featureGatesFlag = cat $featureGatesFlag $key "=" $value ","  -}}
+               {{- if not (empty (toString $value)) -}}
+                    {{- $featureGates = append $featureGates (printf "%s=%s" $key (toString $value)) -}}
                {{- end -}}
           {{- end -}}
+          {{- if $featureGates -}}
+               {{- printf "--feature-gates=%s" (join "," $featureGates) | nospace -}}
+          {{- end -}}
+     {{- end -}}
+{{- end -}}
 
-          {{- if gt (len $featureGatesFlag) 0 }}
-               {{- $featureGatesFlag := trimSuffix "," $featureGatesFlag  | nospace -}}
-               {{- printf "%s=%s" "--feature-gates" $featureGatesFlag -}}
+{{- /* 
+Generate the --feature-gates command line argument for karmada-scheduler.
+Iterates over .Values.scheduler.featureGates and constructs a comma-separated key=value list.
+If any feature gates are set, outputs: --feature-gates=Foo=true,Bar=false
+If none are set, outputs nothing.
+*/ -}}
+{{- define "karmada.scheduler.featureGates" -}}
+     {{- if .Values.scheduler.featureGates -}}
+          {{- $featureGates := list -}}
+          {{- range $key, $value := .Values.scheduler.featureGates -}}
+               {{- if not (empty (toString $value)) -}}
+                    {{- $featureGates = append $featureGates (printf "%s=%s" $key (toString $value)) -}}
+               {{- end -}}
+          {{- end -}}
+          {{- if $featureGates -}}
+               {{- printf "--feature-gates=%s" (join "," $featureGates) | nospace -}}
           {{- end -}}
      {{- end -}}
 {{- end -}}
 
+{{- /* 
+Generate the --feature-gates command line argument for karmada-schedulerEstimator.
+Iterates over .Values.schedulerEstimator.featureGates and constructs a comma-separated key=value list.
+If any feature gates are set, outputs: --feature-gates=Foo=true,Bar=false
+If none are set, outputs nothing.
+*/ -}}
 {{- define "karmada.schedulerEstimator.featureGates" -}}
-     {{- $featureGatesArg := index . "featureGatesArg" -}}
-     {{- if (not (empty $featureGatesArg)) }}
-          {{- $featureGatesFlag := "" -}}
-          {{- range $key, $value := $featureGatesArg -}}
-               {{- if not (empty (toString $value)) }}
-                    {{- $featureGatesFlag = cat $featureGatesFlag $key "=" $value ","  -}}
+     {{- if .Values.schedulerEstimator.featureGates -}}
+          {{- $featureGates := list -}}
+          {{- range $key, $value := .Values.schedulerEstimator.featureGates -}}
+               {{- if not (empty (toString $value)) -}}
+                    {{- $featureGates = append $featureGates (printf "%s=%s" $key (toString $value)) -}}
                {{- end -}}
           {{- end -}}
-
-          {{- if gt (len $featureGatesFlag) 0 }}
-               {{- $featureGatesFlag := trimSuffix "," $featureGatesFlag  | nospace -}}
-               {{- printf "%s=%s" "--feature-gates" $featureGatesFlag -}}
+          {{- if $featureGates -}}
+               {{- printf "--feature-gates=%s" (join "," $featureGates) | nospace -}}
           {{- end -}}
      {{- end -}}
 {{- end -}}
diff --git a/charts/karmada/templates/karmada-scheduler-estimator.yaml b/charts/karmada/templates/karmada-scheduler-estimator.yaml
index 8536bac55..3e54bf016 100644
--- a/charts/karmada/templates/karmada-scheduler-estimator.yaml
+++ b/charts/karmada/templates/karmada-scheduler-estimator.yaml
@@ -55,9 +55,14 @@ spec:
             - --grpc-client-ca-file=/etc/karmada/pki/server-ca.crt
             - --metrics-bind-address=$(POD_IP):8080
             - --health-probe-bind-address=$(POD_IP):10351
-            {{- with (include "karmada.schedulerEstimator.featureGates" (dict "featureGatesArg" $.Values.schedulerEstimator.featureGates)) }}
+            {{- /*
+            We use '$' to refer to the root context.
+            Inside this 'range' loop, '.' refers to the current item from '.Values.schedulerEstimator.memberClusters'.
+            Using '$' ensures that we can access the top-level '.Values.schedulerEstimator.featureGates'.
+            */}}
+            {{- with (include "karmada.schedulerEstimator.featureGates" $) }}
             - {{ . }}
-            {{- end}}
+            {{- end }}
           livenessProbe:
             httpGet:
               path: /healthz
diff --git a/charts/karmada/templates/karmada-scheduler.yaml b/charts/karmada/templates/karmada-scheduler.yaml
index aceb9d0d7..ab4866321 100644
--- a/charts/karmada/templates/karmada-scheduler.yaml
+++ b/charts/karmada/templates/karmada-scheduler.yaml
@@ -61,6 +61,9 @@ spec:
             {{- if .Values.scheduler.enableSchedulerEstimator }}
             - --enable-scheduler-estimator=true
             {{- end }}
+            {{- with (include "karmada.scheduler.featureGates" .) }}
+            - {{ . }}
+            {{- end }}
           livenessProbe:
             httpGet:
               path: /healthz
diff --git a/charts/karmada/values.yaml b/charts/karmada/values.yaml
index 8c42bf7bc..bd752d812 100644
--- a/charts/karmada/values.yaml
+++ b/charts/karmada/values.yaml
@@ -245,6 +245,8 @@ scheduler:
   priorityClassName: "system-node-critical"
   ## @param scheduler.enableSchedulerEstimator enable scheduler estimator
   enableSchedulerEstimator: false
+  ## @param scheduler.featureGates A set of key=value pairs that describe feature gates for karmada-scheduler
+  featureGates: {}
 
 ## webhook config
 webhook:
@@ -299,11 +301,11 @@ webhook:
     rollingUpdate:
       maxUnavailable: 0
       maxSurge: 50%
-  ## @param apiServer.podDisruptionBudget
+  ## @param webhook.podDisruptionBudget
   podDisruptionBudget: *podDisruptionBudget
   ## @param webhook.priorityClassName the priority class name for the karmada-webhook
   priorityClassName: "system-node-critical"
-  ## @param featureGate to webhook
+  ## @param webhook.featureGates A set of key=value pairs that describe feature gates for karmada-webhook
   featureGates: {}
 
 ## controller manager config
diff --git a/hack/deploy-karmada.sh b/hack/deploy-karmada.sh
index 6a916d5dd..c67675f33 100755
--- a/hack/deploy-karmada.sh
+++ b/hack/deploy-karmada.sh
@@ -19,6 +19,9 @@ set -o nounset
 # This script deploy karmada control plane to any cluster you want.	REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
 # This script depends on utils in: ${REPO_ROOT}/hack/util.sh
 
+# Do not run the mutation detector by default on the local karmada instance.
+KUBE_CACHE_MUTATION_DETECTOR="${KUBE_CACHE_MUTATION_DETECTOR:-false}"
+
 REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
 CERT_DIR=${CERT_DIR:-"${HOME}/.karmada"}
 mkdir -p "${CERT_DIR}" &>/dev/null ||  mkdir -p "${CERT_DIR}"
@@ -185,6 +188,19 @@ function installCRDs() {
     kubectl --context="${context_name}" apply -k "${crd_path}"/_crds
 }
 
+function deploy_karmada_component() {
+  local component_name=$1
+  local temp_dir=$2
+  local wait_label=$3
+  
+  local component_yaml_tmp="${temp_dir}/${component_name}.yaml"
+  cp "${REPO_ROOT}/artifacts/deploy/${component_name}.yaml" "${component_yaml_tmp}"
+  sed -i'' -e "s/{{KUBE_CACHE_MUTATION_DETECTOR}}/${KUBE_CACHE_MUTATION_DETECTOR}/g" "${component_yaml_tmp}"
+  kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${component_yaml_tmp}"
+  
+  util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${wait_label}" "${KARMADA_SYSTEM_NAMESPACE}"
+}
+
 # Use x.x.x.8 IP address, which is the same CIDR with the node address of the Kind cluster,
 # as the loadBalancer service address of component karmada-interpreter-webhook-example.
 interpreter_webhook_example_service_external_ip_prefix=$(echo $(util::get_apiserver_ip_from_kubeconfig "${HOST_CLUSTER_NAME}") | awk -F. '{printf "%s.%s.%s",$1,$2,$3}')
@@ -376,15 +392,14 @@ util::append_client_kubeconfig "${HOST_CLUSTER_KUBECONFIG}" "${ROOT_CA_FILE}" "$
 cp "${REPO_ROOT}"/artifacts/deploy/kube-controller-manager.yaml "${TEMP_PATH_APISERVER}"/kube-controller-manager.yaml
 sed -i'' -e "s/{{karmada_apiserver_version}}/${KARMADA_APISERVER_VERSION}/g" "${TEMP_PATH_APISERVER}"/kube-controller-manager.yaml
 kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${TEMP_PATH_APISERVER}"/kube-controller-manager.yaml
+util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${KUBE_CONTROLLER_POD_LABEL}" "${KARMADA_SYSTEM_NAMESPACE}"
 # deploy aggregated-apiserver on host cluster
 kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${REPO_ROOT}/artifacts/deploy/karmada-aggregated-apiserver.yaml"
 util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${KARMADA_AGGREGATION_APISERVER_LABEL}" "${KARMADA_SYSTEM_NAMESPACE}"
-# deploy karmada-search on host cluster
-kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${REPO_ROOT}/artifacts/deploy/karmada-search.yaml"
-util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${KARMADA_SEARCH_LABEL}" "${KARMADA_SYSTEM_NAMESPACE}"
-# deploy karmada-metrics-adapter on host cluster
-kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${REPO_ROOT}/artifacts/deploy/karmada-metrics-adapter.yaml"
-util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${KARMADA_METRICS_ADAPTER_LABEL}" "${KARMADA_SYSTEM_NAMESPACE}"
+
+# Deploy components that need to wait for readiness
+deploy_karmada_component "karmada-search" "${TEMP_PATH_APISERVER}" "${KARMADA_SEARCH_LABEL}"
+deploy_karmada_component "karmada-metrics-adapter" "${TEMP_PATH_APISERVER}" "${KARMADA_METRICS_ADAPTER_LABEL}"
 
 # install CRD APIs on karmada apiserver.
 if ! kubectl config get-contexts "karmada-apiserver" > /dev/null 2>&1;
@@ -449,17 +464,10 @@ sed -i'' -e "s/{{ca_crt}}/${karmada_ca}/g" "${TEMP_PATH_BOOTSTRAP}"/bootstrap-to
 sed -i'' -e "s|{{apiserver_address}}|${karmada_apiserver_address}|g" "${TEMP_PATH_BOOTSTRAP}"/bootstrap-token-configuration-tmp.yaml
 kubectl --context="karmada-apiserver" apply -f "${TEMP_PATH_BOOTSTRAP}"/bootstrap-token-configuration-tmp.yaml
 
-# deploy controller-manager on host cluster
-kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${REPO_ROOT}/artifacts/deploy/karmada-controller-manager.yaml"
-# deploy scheduler on host cluster
-kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${REPO_ROOT}/artifacts/deploy/karmada-scheduler.yaml"
-# deploy descheduler on host cluster
-kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${REPO_ROOT}/artifacts/deploy/karmada-descheduler.yaml"
+deploy_karmada_component "karmada-controller-manager" "${TEMP_PATH_BOOTSTRAP}" "${KARMADA_CONTROLLER_LABEL}"
+deploy_karmada_component "karmada-scheduler" "${TEMP_PATH_BOOTSTRAP}" "${KARMADA_SCHEDULER_LABEL}"
+deploy_karmada_component "karmada-descheduler" "${TEMP_PATH_BOOTSTRAP}" "${KARMADA_DESCHEDULER_LABEL}"
+
 # deploy webhook on host cluster
 kubectl --context="${HOST_CLUSTER_NAME}" apply -f "${REPO_ROOT}/artifacts/deploy/karmada-webhook.yaml"
-
-# make sure all karmada control plane components are ready
-util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${KARMADA_CONTROLLER_LABEL}" "${KARMADA_SYSTEM_NAMESPACE}"
-util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${KARMADA_SCHEDULER_LABEL}" "${KARMADA_SYSTEM_NAMESPACE}"
-util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${KUBE_CONTROLLER_POD_LABEL}" "${KARMADA_SYSTEM_NAMESPACE}"
 util::wait_pod_ready "${HOST_CLUSTER_NAME}" "${KARMADA_WEBHOOK_LABEL}" "${KARMADA_SYSTEM_NAMESPACE}"
diff --git a/hack/deploy-scheduler-estimator.sh b/hack/deploy-scheduler-estimator.sh
index 70e0e12d7..78dd2b486 100755
--- a/hack/deploy-scheduler-estimator.sh
+++ b/hack/deploy-scheduler-estimator.sh
@@ -17,6 +17,9 @@
 set -o errexit
 set -o nounset
 
+# Do not run the mutation detector by default on the local karmada instance.
+KUBE_CACHE_MUTATION_DETECTOR="${KUBE_CACHE_MUTATION_DETECTOR:-false}"
+
 REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
 function usage() {
   echo "This script will deploy karmada-scheduler-estimator of a cluster."
@@ -85,6 +88,7 @@ rm -rf "${TEMP_PATH}"
 TEMP_PATH=$(mktemp -d)
 cp "${REPO_ROOT}"/artifacts/deploy/karmada-scheduler-estimator.yaml "${TEMP_PATH}"/karmada-scheduler-estimator.yaml
 sed -i'' -e "s/{{member_cluster_name}}/${MEMBER_CLUSTER_NAME}/g" "${TEMP_PATH}"/karmada-scheduler-estimator.yaml
+sed -i'' -e "s/{{KUBE_CACHE_MUTATION_DETECTOR}}/${KUBE_CACHE_MUTATION_DETECTOR}/g" "${TEMP_PATH}"/karmada-scheduler-estimator.yaml
 echo -e "Apply dynamic rendered deployment in ${TEMP_PATH}/karmada-scheduler-estimator.yaml\n"
 kubectl --kubeconfig="${HOST_CLUSTER_KUBECONFIG}" --context="${HOST_CLUSTER_NAME}" apply -f "${TEMP_PATH}"/karmada-scheduler-estimator.yaml
 rm -rf "${TEMP_PATH}"
diff --git a/hack/run-e2e.sh b/hack/run-e2e.sh
index e2f654417..1051b3a2b 100755
--- a/hack/run-e2e.sh
+++ b/hack/run-e2e.sh
@@ -13,7 +13,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-
 set -o errexit
 set -o nounset
 set -o pipefail
@@ -26,9 +25,14 @@ set -o pipefail
 # Example 1: hack/run-e2e.sh (run e2e with default config)
 # Example 2: export KARMADA_APISERVER_KUBECONFIG=<KUBECONFIG PATH> hack/run-e2e.sh (run e2e with your KUBECONFIG)
 
+# Cluster name definitions
+MEMBER_CLUSTER_1_NAME=${MEMBER_CLUSTER_1_NAME:-"member1"}
+MEMBER_CLUSTER_2_NAME=${MEMBER_CLUSTER_2_NAME:-"member2"}
+PULL_MODE_CLUSTER_NAME=${PULL_MODE_CLUSTER_NAME:-"member3"}
+
 KUBECONFIG_PATH=${KUBECONFIG_PATH:-"${HOME}/.kube"}
 KARMADA_APISERVER_KUBECONFIG=${KARMADA_APISERVER_KUBECONFIG:-"$KUBECONFIG_PATH/karmada.config"}
-PULL_BASED_CLUSTERS=${PULL_BASED_CLUSTERS:-"member3:$KUBECONFIG_PATH/members.config"}
+PULL_BASED_CLUSTERS=${PULL_BASED_CLUSTERS:-"${PULL_MODE_CLUSTER_NAME}:$KUBECONFIG_PATH/members.config"}
 
 # KARMADA_RUNNING_ON_KIND indicates if current testing against on karmada that installed on a kind cluster.
 # Defaults to true.
@@ -36,7 +40,6 @@ PULL_BASED_CLUSTERS=${PULL_BASED_CLUSTERS:-"member3:$KUBECONFIG_PATH/members.con
 KARMADA_RUNNING_ON_KIND=${KARMADA_RUNNING_ON_KIND:-true}
 
 KARMADA_HOST_CLUSTER_NAME=${KARMADA_HOST_CLUSTER_NAME:-"karmada-host"}
-KARMADA_PULL_CLUSTER_NAME=${KARMADA_PULL_CLUSTER_NAME:-"member3"}
 
 ARTIFACTS_PATH=${ARTIFACTS_PATH:-"${HOME}/karmada-e2e-logs"}
 mkdir -p "$ARTIFACTS_PATH"
@@ -65,9 +68,9 @@ if [ "$KARMADA_RUNNING_ON_KIND" = true ]; then
   mkdir -p "$ARTIFACTS_PATH/$KARMADA_HOST_CLUSTER_NAME"
   kind export logs --name="$KARMADA_HOST_CLUSTER_NAME" "$ARTIFACTS_PATH/$KARMADA_HOST_CLUSTER_NAME"
 
-  echo "Collecting $KARMADA_PULL_CLUSTER_NAME logs..."
-  mkdir -p "$ARTIFACTS_PATH/KARMADA_PULL_CLUSTER_NAME"
-  kind export logs --name="$KARMADA_PULL_CLUSTER_NAME" "$ARTIFACTS_PATH/$KARMADA_PULL_CLUSTER_NAME"
+  echo "Collecting $PULL_MODE_CLUSTER_NAME logs..."
+  mkdir -p "$ARTIFACTS_PATH/$PULL_MODE_CLUSTER_NAME"
+  kind export logs --name="$PULL_MODE_CLUSTER_NAME" "$ARTIFACTS_PATH/$PULL_MODE_CLUSTER_NAME"
 fi
 
 echo "Collected logs at $ARTIFACTS_PATH:"
@@ -76,4 +79,78 @@ ls -al "$ARTIFACTS_PATH"
 # Post run e2e for delete extra components
 "${REPO_ROOT}"/hack/post-run-e2e.sh
 
+# If E2E test failed, exit directly with the test result
+if [ $TESTING_RESULT -ne 0 ]; then
+  echo "E2E test failed with exit code $TESTING_RESULT, skipping component restart check."
+  exit $TESTING_RESULT
+fi
+
+# Check if Karmada components have restarted, if any has, it means that OOM or panic has occurred
+# due to memory modification, and needs to be investigated.
+echo "E2E run successfully."
+echo "Checking if Karmada components have restarted..."
+
+# Function to check pod restart count for a given component
+check_component_restart() {
+  local component_label=$1
+  local component_name=$2
+  
+  echo "Checking ${component_name} pods..."
+  
+  # Get pod information in a single call, including both name and restart count
+  # Use a template that handles missing containerStatuses gracefully
+  local pod_info
+  pod_info=$(kubectl --context="${KARMADA_HOST_CLUSTER_NAME}" get pod -n karmada-system -l "${component_label}" \
+    -o go-template='{{range .items}}{{.metadata.name}}:{{if .status.containerStatuses}}{{(index .status.containerStatuses 0).restartCount}}{{else}}0{{end}}{{"\n"}}{{end}}' 2>/dev/null)
+  
+  if [ -z "$pod_info" ]; then
+    echo "No pods found for ${component_name}, skipping..."
+    return 0
+  fi
+  
+  # Process each pod's information
+  while IFS=: read -r pod_name restart_count; do
+    # Skip empty lines
+    [ -z "$pod_name" ] && continue
+    
+    # Ensure restart_count is a number (default to 0 if empty or invalid)
+    if ! [[ "$restart_count" =~ ^[0-9]+$ ]]; then
+      echo "Warning: Unable to get restart count for pod $pod_name, assuming 0"
+      restart_count=0
+    fi
+    
+    if [ "$restart_count" -gt 0 ]; then
+      echo "ERROR: ${component_name} pod $pod_name has restarted $restart_count times."
+      echo "This indicates OOM or panic occurred and needs to be investigated."
+      return 1  # Return failure to stop checking
+    else
+      echo "${component_name} pod $pod_name: no restarts"
+    fi
+  done <<< "$pod_info"
+  
+  return 0
+}
+
+# List of components to check (label=component_name)
+components=(
+  "app=karmada-controller-manager:karmada-controller-manager"
+  "app=karmada-descheduler:karmada-descheduler"
+  "app=karmada-metrics-adapter:karmada-metrics-adapter"
+  "app=karmada-scheduler:karmada-scheduler"
+  "app=karmada-search:karmada-search"
+  "app=karmada-scheduler-estimator-${MEMBER_CLUSTER_1_NAME}:karmada-scheduler-estimator-${MEMBER_CLUSTER_1_NAME}"
+  "app=karmada-scheduler-estimator-${MEMBER_CLUSTER_2_NAME}:karmada-scheduler-estimator-${MEMBER_CLUSTER_2_NAME}"
+  "app=karmada-scheduler-estimator-${PULL_MODE_CLUSTER_NAME}:karmada-scheduler-estimator-${PULL_MODE_CLUSTER_NAME}"
+)
+
+# Check each component, stop at first failure
+for component in "${components[@]}"; do
+  IFS=':' read -r label name <<< "$component"
+  if ! check_component_restart "$label" "$name"; then
+    echo "COMPONENT RESTART CHECK FAILED: Component $name has restarted, stopping further checks."
+    exit 1
+  fi
+done
+
+echo "All component restart checks passed."
 exit $TESTING_RESULT
diff --git a/hack/setup-dev-base.sh b/hack/setup-dev-base.sh
index fde5d5d87..bdf85f33d 100755
--- a/hack/setup-dev-base.sh
+++ b/hack/setup-dev-base.sh
@@ -83,10 +83,15 @@ else
 fi
 
 #step1. create host cluster and member clusters in parallel
-# host IP address: script parameter ahead of macOS IP
+# host IP address: script parameter ahead of WSL2 or macOS IP
 if [[ -z "${HOST_IPADDRESS}" ]]; then
-  util::get_macos_ipaddress # Adapt for macOS
-  HOST_IPADDRESS=${MAC_NIC_IPADDRESS:-}
+  if util::is_wsl2; then
+    util::get_wsl2_ipaddress # adapt for WSL2
+    HOST_IPADDRESS=${WSL2_HOST_IP_ADDRESS:-}
+  else
+    util::get_macos_ipaddress # Adapt for macOS
+    HOST_IPADDRESS=${MAC_NIC_IPADDRESS:-}
+  fi
 fi
 #prepare for kindClusterConfig
 TEMP_PATH=$(mktemp -d)
diff --git a/hack/util.sh b/hack/util.sh
index caf065e77..216cac427 100755
--- a/hack/util.sh
+++ b/hack/util.sh
@@ -27,6 +27,7 @@ KUBE_CONTROLLER_POD_LABEL="kube-controller-manager"
 KARMADA_AGGREGATION_APISERVER_LABEL="karmada-aggregated-apiserver"
 KARMADA_CONTROLLER_LABEL="karmada-controller-manager"
 KARMADA_SCHEDULER_LABEL="karmada-scheduler"
+KARMADA_DESCHEDULER_LABEL="karmada-descheduler"
 KARMADA_WEBHOOK_LABEL="karmada-webhook"
 AGENT_POD_LABEL="karmada-agent"
 INTERPRETER_WEBHOOK_EXAMPLE_LABEL="karmada-interpreter-webhook-example"
@@ -533,6 +534,16 @@ function util::get_docker_host_ip_port(){
   docker inspect --format='{{range $key, $value := index .NetworkSettings.Ports "6443/tcp"}}{{if eq $key 0}}{{$value.HostIp}}:{{$value.HostPort}}{{end}}{{end}}' "${container_name}"
 }
 
+# util::is_wsl2 checks if the current environment is WSL2.
+# Returns:
+#   0 if running in WSL2, 1 otherwise.
+# Usage:
+#   util::is_wsl2 && echo "WSL2 detected" || echo "Not WSL2"
+function util::is_wsl2(){
+  # /proc/sys/fs/binfmt_misc/WSLInterop exists only in WSL2 environments
+  [[ -f /proc/sys/fs/binfmt_misc/WSLInterop ]] || \
+    ( [[ -f /proc/version ]] && grep -q "WSL2" /proc/version )
+}
 # util::check_clusters_ready checks if a cluster is ready, if not, wait until timeout
 function util::check_clusters_ready() {
   local kubeconfig_path=${1}
@@ -548,7 +559,13 @@ function util::check_clusters_ready() {
   os_name=$(go env GOOS)
   local container_ip_port
   case $os_name in
-    linux) container_ip_port=$(util::get_docker_native_ipaddress "${context_name}-control-plane")":6443"
+    linux)
+      if util::is_wsl2; then
+        # WSL2 uses a different method to get the container IP address
+        container_ip_port=$(util::get_docker_host_ip_port "${context_name}-control-plane")
+      else # normal linux environment
+        container_ip_port=$(util::get_docker_native_ipaddress "${context_name}-control-plane")":6443"
+      fi
     ;;
     darwin) container_ip_port=$(util::get_docker_host_ip_port "${context_name}-control-plane")
     ;;
@@ -683,6 +700,16 @@ function util::add_routes() {
   unset IFS
 }
 
+# util::get_wsl2_ipaddress will get ip address on wsl2, store to 'WSL2_HOST_IP_ADDRESS' if available
+WSL2_HOST_IP_ADDRESS=''
+function util::get_wsl2_ipaddress() {
+  echo "====================================================" 
+  echo "= Detected that you are installing Karmada on WSL2 =" 
+  echo "====================================================" 
+  WSL2_HOST_IP_ADDRESS=$(hostname -I | awk '{print $1}')
+  util::verify_ip_address "${WSL2_HOST_IP_ADDRESS}"
+}
+
 # util::get_macos_ipaddress will get ip address on macos interactively, store to 'MAC_NIC_IPADDRESS' if available
 MAC_NIC_IPADDRESS=''
 function util::get_macos_ipaddress() {
diff --git a/pkg/controllers/mcs/endpointslice_controller.go b/pkg/controllers/mcs/endpointslice_controller.go
index 2381e50db..4b0a32816 100644
--- a/pkg/controllers/mcs/endpointslice_controller.go
+++ b/pkg/controllers/mcs/endpointslice_controller.go
@@ -153,6 +153,7 @@ func (c *EndpointSliceController) collectEndpointSliceFromWork(ctx context.Conte
 		desiredEndpointSlice.Labels = util.DedupeAndMergeLabels(desiredEndpointSlice.Labels, map[string]string{
 			workv1alpha2.WorkPermanentIDLabel: work.Labels[workv1alpha2.WorkPermanentIDLabel],
 			discoveryv1.LabelServiceName:      names.GenerateDerivedServiceName(work.Labels[util.ServiceNameLabel]),
+			discoveryv1.LabelManagedBy:        util.EndpointSliceControllerLabelValue,
 		})
 		desiredEndpointSlice.Annotations = util.DedupeAndMergeAnnotations(desiredEndpointSlice.Annotations, map[string]string{
 			workv1alpha2.WorkNamespaceAnnotation: work.Namespace,
diff --git a/pkg/controllers/mcs/service_export_controller.go b/pkg/controllers/mcs/service_export_controller.go
index d4e060a56..1e03be0b5 100644
--- a/pkg/controllers/mcs/service_export_controller.go
+++ b/pkg/controllers/mcs/service_export_controller.go
@@ -394,6 +394,11 @@ func (c *ServiceExportController) handleEndpointSliceEvent(ctx context.Context,
 		return err
 	}
 
+	// Exclude EndpointSlice resources that are managed by Karmada system to avoid duplicate reporting.
+	if helper.IsEndpointSliceManagedByKarmada(endpointSliceObj.GetLabels()) {
+		return nil
+	}
+
 	if err = c.reportEndpointSliceWithEndpointSliceCreateOrUpdate(ctx, endpointSliceKey.Cluster, endpointSliceObj); err != nil {
 		klog.ErrorS(err, "Failed to handle endpointSlice event", "namespace", endpointSliceKey.Namespace, "name", endpointSliceKey.Name)
 		return err
@@ -440,7 +445,13 @@ func (c *ServiceExportController) reportEndpointSliceWithServiceExportCreate(ctx
 	}
 
 	for index := range endpointSliceObjects {
-		if err = reportEndpointSlice(ctx, c.Client, endpointSliceObjects[index].(*unstructured.Unstructured), serviceExportKey.Cluster); err != nil {
+		endpointSlice := endpointSliceObjects[index].(*unstructured.Unstructured)
+		// Exclude EndpointSlice resources that are managed by Karmada system to avoid duplicate reporting.
+		if helper.IsEndpointSliceManagedByKarmada(endpointSlice.GetLabels()) {
+			continue
+		}
+
+		if err = reportEndpointSlice(ctx, c.Client, endpointSlice, serviceExportKey.Cluster); err != nil {
 			errs = append(errs, err)
 		}
 	}
diff --git a/pkg/controllers/multiclusterservice/endpointslice_collect_controller.go b/pkg/controllers/multiclusterservice/endpointslice_collect_controller.go
index 2f31fb8bd..307cd17ba 100644
--- a/pkg/controllers/multiclusterservice/endpointslice_collect_controller.go
+++ b/pkg/controllers/multiclusterservice/endpointslice_collect_controller.go
@@ -299,7 +299,8 @@ func (c *EndpointSliceCollectController) handleEndpointSliceEvent(ctx context.Co
 		return err
 	}
 
-	if util.GetLabelValue(endpointSliceObj.GetLabels(), discoveryv1.LabelManagedBy) == util.EndpointSliceDispatchControllerLabelValue {
+	// Exclude EndpointSlice resources that are managed by Karmada system to avoid duplicate reporting.
+	if helper.IsEndpointSliceManagedByKarmada(endpointSliceObj.GetLabels()) {
 		return nil
 	}
 
@@ -358,7 +359,8 @@ func (c *EndpointSliceCollectController) collectTargetEndpointSlice(ctx context.
 			klog.ErrorS(err, "Failed to convert object to EndpointSlice")
 			return err
 		}
-		if util.GetLabelValue(eps.GetLabels(), discoveryv1.LabelManagedBy) == util.EndpointSliceDispatchControllerLabelValue {
+		// Exclude EndpointSlice resources that are managed by Karmada system to avoid duplicate reporting.
+		if helper.IsEndpointSliceManagedByKarmada(eps.GetLabels()) {
 			continue
 		}
 		epsUnstructured, err := helper.ToUnstructured(eps)
diff --git a/pkg/detector/detector.go b/pkg/detector/detector.go
index 4d9f04655..bba2303cb 100644
--- a/pkg/detector/detector.go
+++ b/pkg/detector/detector.go
@@ -690,6 +690,64 @@ func (d *ResourceDetector) GetUnstructuredObject(objectKey keys.ClusterWideKey)
 	return unstructuredObj.DeepCopy(), nil
 }
 
+// fetchResourceBinding fetches a ResourceBinding from the client or dynamic client.
+func (d *ResourceDetector) fetchResourceBinding(ctx context.Context, rbNamespace, rbName string) (*workv1alpha2.ResourceBinding, error) {
+	// First try to get ResourceBinding using cached client
+	rb := &workv1alpha2.ResourceBinding{}
+	err := d.Client.Get(ctx, client.ObjectKey{Namespace: rbNamespace, Name: rbName}, rb)
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			// If not found with client, try using dynamic client
+			gvr := workv1alpha2.SchemeGroupVersion.WithResource(workv1alpha2.ResourcePluralResourceBinding)
+			unstructuredRB, dynamicErr := d.DynamicClient.Resource(gvr).Namespace(rbNamespace).Get(ctx, rbName, metav1.GetOptions{})
+			if dynamicErr != nil {
+				return nil, dynamicErr
+			}
+
+			// Convert unstructured to ResourceBinding
+			if err = helper.ConvertToTypedObject(unstructuredRB, rb); err != nil {
+				klog.Errorf("Failed to convert unstructured to ResourceBinding(%s/%s): %v", rbNamespace, rbName, err)
+				return nil, err
+			}
+
+			return rb, nil
+		}
+		klog.Errorf("Failed to get ResourceBinding(%s/%s): %v", rbNamespace, rbName, err)
+		return nil, err
+	}
+
+	return rb, nil
+}
+
+// fetchClusterResourceBinding fetches a ClusterResourceBinding from the client or dynamic client.
+func (d *ResourceDetector) fetchClusterResourceBinding(ctx context.Context, crbName string) (*workv1alpha2.ClusterResourceBinding, error) {
+	// First try to get ClusterResourceBinding using cached client
+	crb := &workv1alpha2.ClusterResourceBinding{}
+	err := d.Client.Get(ctx, client.ObjectKey{Name: crbName}, crb)
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			// If not found with client, try using dynamic client
+			gvr := workv1alpha2.SchemeGroupVersion.WithResource(workv1alpha2.ResourcePluralClusterResourceBinding)
+			unstructuredRB, dynamicErr := d.DynamicClient.Resource(gvr).Get(ctx, crbName, metav1.GetOptions{})
+			if dynamicErr != nil {
+				return nil, dynamicErr
+			}
+
+			// Convert unstructured to ClusterResourceBinding
+			if err = helper.ConvertToTypedObject(unstructuredRB, crb); err != nil {
+				klog.Errorf("Failed to convert unstructured to ClusterResourceBinding(%s): %v", crbName, err)
+				return nil, err
+			}
+
+			return crb, nil
+		}
+		klog.Errorf("Failed to get ClusterResourceBinding(%s): %v", crbName, err)
+		return nil, err
+	}
+
+	return crb, nil
+}
+
 // ClaimPolicyForObject set policy identifier which the object associated with.
 // It will add policy labels and annotations to the object, and update the obj with the content returned by the Server, ensuring that the obj used when generating the binding is the latest.
 func (d *ResourceDetector) ClaimPolicyForObject(object *unstructured.Unstructured, policy *policyv1alpha1.PropagationPolicy) (string, error) {
@@ -940,7 +998,7 @@ func (d *ResourceDetector) ReconcilePropagationPolicy(key util.QueueKey) error {
 
 	if !propagationObject.DeletionTimestamp.IsZero() {
 		klog.Infof("PropagationPolicy(%s) is being deleted.", nkey.NamespaceKey())
-		if err = d.HandlePropagationPolicyDeletion(propagationObject.Labels[policyv1alpha1.PropagationPolicyPermanentIDLabel]); err != nil {
+		if err = d.HandlePropagationPolicyDeletion(propagationObject.Labels[policyv1alpha1.PropagationPolicyPermanentIDLabel], propagationObject.Spec.ResourceSelectors); err != nil {
 			return err
 		}
 		if controllerutil.RemoveFinalizer(propagationObject, util.PropagationPolicyControllerFinalizer) {
@@ -1012,7 +1070,7 @@ func (d *ResourceDetector) ReconcileClusterPropagationPolicy(key util.QueueKey)
 
 	if !propagationObject.DeletionTimestamp.IsZero() {
 		klog.Infof("ClusterPropagationPolicy(%s) is being deleted.", nkey.NamespaceKey())
-		if err = d.HandleClusterPropagationPolicyDeletion(propagationObject.Labels[policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel]); err != nil {
+		if err = d.HandleClusterPropagationPolicyDeletion(propagationObject.Labels[policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel], propagationObject.Spec.ResourceSelectors); err != nil {
 			return err
 		}
 		if controllerutil.RemoveFinalizer(propagationObject, util.ClusterPropagationPolicyControllerFinalizer) {
@@ -1033,32 +1091,26 @@ func (d *ResourceDetector) ReconcileClusterPropagationPolicy(key util.QueueKey)
 // the resource template a change to match another policy).
 //
 // Note: The relevant ResourceBinding will continue to exist until the resource template is gone.
-func (d *ResourceDetector) HandlePropagationPolicyDeletion(policyID string) error {
+func (d *ResourceDetector) HandlePropagationPolicyDeletion(policyID string, resources []policyv1alpha1.ResourceSelector) error {
 	claimMetadata := labels.Set{policyv1alpha1.PropagationPolicyPermanentIDLabel: policyID}
-	rbs, err := helper.GetResourceBindings(d.Client, claimMetadata)
-	if err != nil {
-		klog.Errorf("Failed to list propagation bindings with policy permanentID(%s): %v", policyID, err)
-		return err
-	}
-
 	var errs []error
-	for index, binding := range rbs.Items {
-		// Must remove the claim metadata, such as labels and annotations, from the resource template ahead of ResourceBinding,
-		// otherwise might lose the chance to do that in a retry loop (in particular, the claim metadata was successfully removed
-		// from ResourceBinding, but resource template not), since the ResourceBinding will not be listed again.
-		if err := d.CleanupResourceTemplateClaimMetadata(binding.Spec.Resource, claimMetadata, CleanupPPClaimMetadata); err != nil {
-			klog.Errorf("Failed to clean up claim metadata from resource(%s-%s/%s) when propagationPolicy removed, error: %v",
-				binding.Spec.Resource.Kind, binding.Spec.Resource.Namespace, binding.Spec.Resource.Name, err)
-			errs = append(errs, err)
-			// Skip cleaning up policy labels and annotations from ResourceBinding, give a chance to do that in a retry loop.
-			continue
+	for _, resource := range util.ExtractUniqueNamespacedSelectors(resources) {
+		objRef := workv1alpha2.ObjectReference{
+			APIVersion: resource.APIVersion,
+			Kind:       resource.Kind,
+			Namespace:  resource.Namespace,
 		}
 
-		// Clean up the claim metadata from the reference binding so that the karmada scheduler won't reschedule the binding.
-		if err := d.CleanupResourceBindingClaimMetadata(&rbs.Items[index], claimMetadata, CleanupPPClaimMetadata); err != nil {
-			klog.Errorf("Failed to clean up claim metadata from resource binding(%s/%s) when propagationPolicy removed, error: %v",
-				binding.Namespace, binding.Name, err)
+		rawObjects, err := helper.FetchResourceTemplatesByLabelSelector(d.DynamicClient, d.InformerManager, d.RESTMapper, objRef, labels.SelectorFromSet(claimMetadata))
+		if err != nil {
 			errs = append(errs, err)
+			continue
+		}
+		for _, rawObject := range rawObjects {
+			err := d.handleResourceTemplateAndBindingCleanup(rawObject, objRef, claimMetadata, CleanupPPClaimMetadata)
+			if err != nil {
+				errs = append(errs, err)
+			}
 		}
 	}
 	return errors.NewAggregate(errs)
@@ -1069,66 +1121,64 @@ func (d *ResourceDetector) HandlePropagationPolicyDeletion(policyID string) erro
 // the resource template a change to match another policy).
 //
 // Note: The relevant ClusterResourceBinding or ResourceBinding will continue to exist until the resource template is gone.
-func (d *ResourceDetector) HandleClusterPropagationPolicyDeletion(policyID string) error {
+func (d *ResourceDetector) HandleClusterPropagationPolicyDeletion(policyID string, resources []policyv1alpha1.ResourceSelector) error {
 	var errs []error
-	labelSet := labels.Set{
-		policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: policyID,
-	}
+	claimMetadata := labels.Set{policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: policyID}
 
-	// load the ClusterResourceBindings which labeled with current policy
-	crbs, err := helper.GetClusterResourceBindings(d.Client, labelSet)
-	if err != nil {
-		klog.Errorf("Failed to list clusterResourceBindings with clusterPropagationPolicy permanentID(%s), error: %v", policyID, err)
-		errs = append(errs, err)
-	} else if len(crbs.Items) > 0 {
-		for index, binding := range crbs.Items {
-			// Must remove the claim metadata, such as labels and annotations, from the resource template ahead of
-			// ClusterResourceBinding, otherwise might lose the chance to do that in a retry loop (in particular, the
-			// claim metadata was successfully removed from ClusterResourceBinding, but resource template not), since the
-			// ClusterResourceBinding will not be listed again.
-			if err := d.CleanupResourceTemplateClaimMetadata(binding.Spec.Resource, labelSet, CleanupCPPClaimMetadata); err != nil {
-				klog.Errorf("Failed to clean up claim metadata from resource(%s-%s) when clusterPropagationPolicy removed, error: %v",
-					binding.Spec.Resource.Kind, binding.Spec.Resource.Name, err)
-				// Skip cleaning up policy labels and annotations from ClusterResourceBinding, give a chance to do that in a retry loop.
-				continue
-			}
+	for _, resource := range util.ExtractUniqueNamespacedSelectors(resources) {
+		objRef := workv1alpha2.ObjectReference{
+			APIVersion: resource.APIVersion,
+			Kind:       resource.Kind,
+			Namespace:  resource.Namespace,
+		}
 
-			// Clean up the claim metadata from the reference binding so that the Karmada scheduler won't reschedule the binding.
-			if err := d.CleanupClusterResourceBindingClaimMetadata(&crbs.Items[index], labelSet); err != nil {
-				klog.Errorf("Failed to clean up claim metadata from clusterResourceBinding(%s) when clusterPropagationPolicy removed, error: %v",
-					binding.Name, err)
+		rawObjects, err := helper.FetchResourceTemplatesByLabelSelector(d.DynamicClient, d.InformerManager, d.RESTMapper, objRef, labels.SelectorFromSet(claimMetadata))
+		if err != nil {
+			errs = append(errs, err)
+			continue
+		}
+		for _, rawObject := range rawObjects {
+			err := d.handleResourceTemplateAndBindingCleanup(rawObject, objRef, claimMetadata, CleanupCPPClaimMetadata)
+			if err != nil {
 				errs = append(errs, err)
 			}
 		}
 	}
 
-	// load the ResourceBindings which labeled with current policy
-	rbs, err := helper.GetResourceBindings(d.Client, labelSet)
-	if err != nil {
-		klog.Errorf("Failed to list resourceBindings with clusterPropagationPolicy permanentID(%s), error: %v", policyID, err)
-		errs = append(errs, err)
-	} else if len(rbs.Items) > 0 {
-		for index, binding := range rbs.Items {
-			// Must remove the claim metadata, such as labels and annotations, from the resource template ahead of ResourceBinding,
-			// otherwise might lose the chance to do that in a retry loop (in particular, the label was successfully
-			// removed from ResourceBinding, but resource template not), since the ResourceBinding will not be listed again.
-			if err := d.CleanupResourceTemplateClaimMetadata(binding.Spec.Resource, labelSet, CleanupCPPClaimMetadata); err != nil {
-				klog.Errorf("Failed to clean up claim metadata from resource(%s-%s/%s) when clusterPropagationPolicy removed, error: %v",
-					binding.Spec.Resource.Kind, binding.Spec.Resource.Namespace, binding.Spec.Resource.Name, err)
-				errs = append(errs, err)
-				// Skip cleaning up policy labels and annotations from ResourceBinding, give a chance to do that in a retry loop.
-				continue
-			}
+	return errors.NewAggregate(errs)
+}
 
-			// Clean up the claim metadata from the reference binding so that the Karmada scheduler won't reschedule the binding.
-			if err := d.CleanupResourceBindingClaimMetadata(&rbs.Items[index], labelSet, CleanupCPPClaimMetadata); err != nil {
-				klog.Errorf("Failed to clean up claim metadata from resourceBinding(%s/%s) when clusterPropagationPolicy removed, error: %v",
-					binding.Namespace, binding.Name, err)
-				errs = append(errs, err)
-			}
+func (d *ResourceDetector) handleResourceTemplateAndBindingCleanup(template *unstructured.Unstructured, objRef workv1alpha2.ObjectReference, targetClaimMetadata map[string]string, cleanupFunc func(obj metav1.Object)) error {
+	bindingName := names.GenerateBindingName(template.GetKind(), template.GetName())
+	if template.GetNamespace() != "" {
+		// Clean up the claim metadata from the reference binding so that the karmada scheduler won't reschedule the binding.
+		// Must remove the claim metadata, such as labels and annotations, from the ResourceBinding ahead of resource template,
+		// otherwise might lose the chance to do that in a retry loop (in particular, the claim metadata was successfully removed
+		// from resource template, but ResourceBinding not), since the resource template will not be listed again.
+		if err := d.CleanupResourceBindingClaimMetadata(template.GetNamespace(), bindingName, targetClaimMetadata, cleanupFunc); err != nil {
+			klog.Errorf("Failed to clean up claim metadata from ResourceBinding(%s/%s), error: %v",
+				objRef.Namespace, bindingName, err)
+			return err
+		}
+	} else {
+		// Clean up the claim metadata from the reference binding so that the Karmada scheduler won't reschedule the binding.
+		// Must remove the claim metadata, such as labels and annotations, from the ClusterResourceBinding ahead of resource template,
+		// otherwise might lose the chance to do that in a retry loop (in particular, the claim metadata was successfully removed
+		// from resource template, but ClusterResourceBinding not), since the resource template will not be listed again.
+		if err := d.CleanupClusterResourceBindingClaimMetadata(bindingName, targetClaimMetadata, cleanupFunc); err != nil {
+			klog.Errorf("Failed to clean up claim metadata from ClusterResourceBinding(%s), error: %v",
+				bindingName, err)
+			return err
 		}
 	}
-	return errors.NewAggregate(errs)
+
+	if err := d.CleanupResourceTemplateClaimMetadata(template, objRef, targetClaimMetadata, cleanupFunc); err != nil {
+		klog.Errorf("Failed to clean up claim metadata from resource(%s-%s/%s) when propagationPolicy removed, error: %v",
+			template.GetKind(), template.GetNamespace(), template.GetName(), err)
+		return err
+	}
+
+	return nil
 }
 
 // HandlePropagationPolicyCreationOrUpdate handles PropagationPolicy add and update event.
@@ -1263,24 +1313,14 @@ func (d *ResourceDetector) HandleClusterPropagationPolicyCreationOrUpdate(policy
 }
 
 // CleanupResourceTemplateClaimMetadata removes claim metadata, such as labels and annotations, from object referencing by objRef.
-func (d *ResourceDetector) CleanupResourceTemplateClaimMetadata(objRef workv1alpha2.ObjectReference, targetClaimMetadata map[string]string, cleanupFunc func(obj metav1.Object)) error {
+func (d *ResourceDetector) CleanupResourceTemplateClaimMetadata(obj *unstructured.Unstructured, objRef workv1alpha2.ObjectReference, targetClaimMetadata map[string]string, cleanupFunc func(obj metav1.Object)) error {
 	gvr, err := restmapper.GetGroupVersionResource(d.RESTMapper, schema.FromAPIVersionAndKind(objRef.APIVersion, objRef.Kind))
 	if err != nil {
 		klog.Errorf("Failed to convert GVR from GVK(%s/%s), err: %v", objRef.APIVersion, objRef.Kind, err)
 		return err
 	}
-
+	workload := obj.DeepCopy()
 	return retry.RetryOnConflict(retry.DefaultRetry, func() (err error) {
-		workload, err := d.DynamicClient.Resource(gvr).Namespace(objRef.Namespace).Get(context.TODO(), objRef.Name, metav1.GetOptions{})
-		if err != nil {
-			// do nothing if resource template not exist, it might have been removed.
-			if apierrors.IsNotFound(err) {
-				return nil
-			}
-			klog.Errorf("Failed to fetch resource(kind=%s, %s/%s): err is %v", objRef.Kind, objRef.Namespace, objRef.Name, err)
-			return err
-		}
-
 		if !NeedCleanupClaimMetadata(workload, targetClaimMetadata) {
 			klog.Infof("No need to clean up the claim metadata on resource(kind=%s, %s/%s) since they have changed", workload.GetKind(), workload.GetNamespace(), workload.GetName())
 			return nil
@@ -1291,15 +1331,37 @@ func (d *ResourceDetector) CleanupResourceTemplateClaimMetadata(objRef workv1alp
 		_, err = d.DynamicClient.Resource(gvr).Namespace(workload.GetNamespace()).Update(context.TODO(), workload, metav1.UpdateOptions{})
 		if err != nil {
 			klog.Errorf("Failed to update resource(kind=%s, %s/%s): err is %v", workload.GetKind(), workload.GetNamespace(), workload.GetName(), err)
+			if apierrors.IsConflict(err) {
+				newWorkload, getErr := d.DynamicClient.Resource(gvr).Namespace(workload.GetNamespace()).Get(context.TODO(), workload.GetName(), metav1.GetOptions{})
+				if getErr != nil {
+					// do nothing if resource template not exist, it might have been removed.
+					if apierrors.IsNotFound(getErr) {
+						return nil
+					}
+					return getErr
+				}
+				workload = newWorkload
+			}
 			return err
 		}
-		klog.V(2).Infof("Updated resource template(kind=%s, %s/%s) successfully", workload.GetKind(), workload.GetNamespace(), workload.GetName())
+		klog.V(2).Infof("Clean claimed label for resource template(kind=%s, %s/%s) successfully", workload.GetKind(), workload.GetNamespace(), workload.GetName())
 		return nil
 	})
 }
 
 // CleanupResourceBindingClaimMetadata removes claim metadata, such as labels and annotations, from resource binding.
-func (d *ResourceDetector) CleanupResourceBindingClaimMetadata(rb *workv1alpha2.ResourceBinding, targetClaimMetadata map[string]string, cleanupFunc func(obj metav1.Object)) error {
+func (d *ResourceDetector) CleanupResourceBindingClaimMetadata(rbNamespace, rbName string, targetClaimMetadata map[string]string, cleanupFunc func(obj metav1.Object)) error {
+	var rb *workv1alpha2.ResourceBinding
+	var err error
+
+	rb, err = d.fetchResourceBinding(context.TODO(), rbNamespace, rbName)
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			return nil // do nothing if resource binding not exist, it might have been removed.
+		}
+		return err
+	}
+
 	return retry.RetryOnConflict(retry.DefaultRetry, func() (err error) {
 		if !NeedCleanupClaimMetadata(rb, targetClaimMetadata) {
 			klog.Infof("No need to clean up the claim metadata on ResourceBinding(%s/%s) since they have changed", rb.GetNamespace(), rb.GetName())
@@ -1311,35 +1373,72 @@ func (d *ResourceDetector) CleanupResourceBindingClaimMetadata(rb *workv1alpha2.
 			return nil
 		}
 
-		updated := &workv1alpha2.ResourceBinding{}
-		if err = d.Client.Get(context.TODO(), client.ObjectKey{Namespace: rb.GetNamespace(), Name: rb.GetName()}, updated); err == nil {
-			rb = updated.DeepCopy()
-		} else {
-			klog.Errorf("Failed to get updated ResourceBinding(%s/%s): %v", rb.GetNamespace(), rb.GetName(), err)
+		if apierrors.IsConflict(updateErr) {
+			updated := &workv1alpha2.ResourceBinding{}
+			gvr := workv1alpha2.SchemeGroupVersion.WithResource(workv1alpha2.ResourcePluralResourceBinding)
+			if unstructuredRB, dynamicErr := d.DynamicClient.Resource(gvr).Namespace(rbNamespace).Get(context.TODO(), rbName, metav1.GetOptions{}); dynamicErr == nil {
+				// Convert unstructured to ResourceBinding
+				if convertErr := helper.ConvertToTypedObject(unstructuredRB, updated); convertErr != nil {
+					klog.Errorf("Failed to convert unstructured to ResourceBinding(%s/%s): %v", rbNamespace, rbName, convertErr)
+					return convertErr
+				}
+				rb = updated
+			} else {
+				if apierrors.IsNotFound(dynamicErr) {
+					return nil // do nothing if resource binding not exist, it might have been removed.
+				}
+				klog.Errorf("Failed to get updated ResourceBinding(%s/%s): %v", rbNamespace, rbName, dynamicErr)
+				return dynamicErr
+			}
 		}
+
 		return updateErr
 	})
 }
 
 // CleanupClusterResourceBindingClaimMetadata removes claim metadata, such as labels and annotations, from cluster resource binding.
-func (d *ResourceDetector) CleanupClusterResourceBindingClaimMetadata(crb *workv1alpha2.ClusterResourceBinding, targetClaimMetadata map[string]string) error {
+func (d *ResourceDetector) CleanupClusterResourceBindingClaimMetadata(crbName string, targetClaimMetadata map[string]string, cleanupFunc func(obj metav1.Object)) error {
+	var crb *workv1alpha2.ClusterResourceBinding
+	var err error
+
+	crb, err = d.fetchClusterResourceBinding(context.TODO(), crbName)
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			return nil // do nothing if resource binding not exist, it might have been removed.
+		}
+		return err
+	}
+
 	return retry.RetryOnConflict(retry.DefaultRetry, func() (err error) {
 		if !NeedCleanupClaimMetadata(crb, targetClaimMetadata) {
 			klog.Infof("No need to clean up the claim metadata on ClusterResourceBinding(%s) since they have changed", crb.GetName())
 			return nil
 		}
-		CleanupCPPClaimMetadata(crb)
+		cleanupFunc(crb)
 		updateErr := d.Client.Update(context.TODO(), crb)
 		if updateErr == nil {
 			return nil
 		}
 
-		updated := &workv1alpha2.ClusterResourceBinding{}
-		if err = d.Client.Get(context.TODO(), client.ObjectKey{Name: crb.GetName()}, updated); err == nil {
-			crb = updated.DeepCopy()
-		} else {
-			klog.Errorf("Failed to get updated ClusterResourceBinding(%s): %v", crb.GetName(), err)
+		if apierrors.IsConflict(updateErr) {
+			updated := &workv1alpha2.ClusterResourceBinding{}
+			gvr := workv1alpha2.SchemeGroupVersion.WithResource(workv1alpha2.ResourcePluralClusterResourceBinding)
+			if unstructuredRB, dynamicErr := d.DynamicClient.Resource(gvr).Get(context.TODO(), crbName, metav1.GetOptions{}); dynamicErr == nil {
+				// Convert unstructured to ClusterResourceBinding
+				if convertErr := helper.ConvertToTypedObject(unstructuredRB, updated); convertErr != nil {
+					klog.Errorf("Failed to convert unstructured to ClusterResourceBinding(%s): %v", crbName, convertErr)
+					return convertErr
+				}
+				crb = updated
+			} else {
+				if apierrors.IsNotFound(dynamicErr) {
+					return nil // do nothing if resource binding not exist, it might have been removed.
+				}
+				klog.Errorf("Failed to get updated ClusterResourceBinding(%s): %v", crbName, dynamicErr)
+				return dynamicErr
+			}
 		}
+
 		return updateErr
 	})
 }
diff --git a/pkg/detector/policy.go b/pkg/detector/policy.go
index a46c0b098..755433c40 100644
--- a/pkg/detector/policy.go
+++ b/pkg/detector/policy.go
@@ -110,14 +110,33 @@ func (d *ResourceDetector) getAndApplyPolicy(object *unstructured.Unstructured,
 	resourceChangeByKarmada bool, policyNamespace, policyName, claimedID string) error {
 	matchedPropagationPolicy := &policyv1alpha1.PropagationPolicy{}
 	err := d.Client.Get(context.TODO(), client.ObjectKey{Namespace: policyNamespace, Name: policyName}, matchedPropagationPolicy)
-	if err != nil {
-		if apierrors.IsNotFound(err) {
-			klog.V(4).Infof("PropagationPolicy(%s/%s) has been removed.", policyNamespace, policyName)
-			return d.HandlePropagationPolicyDeletion(claimedID)
+	if apierrors.IsNotFound(err) {
+		// The expected PropagationPolicy has been removed, indicating the cleanup process has completed.
+		// However, if the claimed data still exists in the resource template, it likely means the cleanup
+		// for the resource template was missed. This can happen if the claimed data was added to the resource
+		// template and then the PropagationPolicy was removed very shortly after.
+		// When cleaning up resource templates based on PropagationPolicy deletion, the cache may not have received
+		// the resource template update in time, resulting in a missed cleanup.
+		klog.V(4).Infof("PropagationPolicy(%s/%s) has been removed.", policyNamespace, policyName)
+		claimMetadata := labels.Set{policyv1alpha1.PropagationPolicyPermanentIDLabel: claimedID}
+		objRef := workv1alpha2.ObjectReference{
+			APIVersion: object.GetAPIVersion(),
+			Kind:       object.GetKind(),
+			Namespace:  object.GetNamespace(),
+			Name:       object.GetName(),
 		}
+		return d.handleResourceTemplateAndBindingCleanup(object, objRef, claimMetadata, CleanupPPClaimMetadata)
+	}
+	if err != nil {
 		klog.Errorf("Failed to get claimed policy(%s/%s),: %v", policyNamespace, policyName, err)
 		return err
 	}
+
+	// If the policy is being deleted, we should not apply it. Instead, waiting for the next reconcile to clean up metadata.
+	if !matchedPropagationPolicy.DeletionTimestamp.IsZero() {
+		return fmt.Errorf("policy(%s/%s) is being deleted", policyNamespace, policyName)
+	}
+
 	// Some resources are available in more than one group in the same kubernetes version.
 	// Therefore, the following scenarios occurs:
 	// In v1.21 kubernetes cluster, Ingress are available in both networking.k8s.io and extensions groups.
@@ -142,15 +161,33 @@ func (d *ResourceDetector) getAndApplyClusterPolicy(object *unstructured.Unstruc
 	resourceChangeByKarmada bool, policyName, policyID string) error {
 	matchedClusterPropagationPolicy := &policyv1alpha1.ClusterPropagationPolicy{}
 	err := d.Client.Get(context.TODO(), client.ObjectKey{Name: policyName}, matchedClusterPropagationPolicy)
-	if err != nil {
-		if apierrors.IsNotFound(err) {
-			klog.V(4).Infof("ClusterPropagationPolicy(%s) has been removed.", policyName)
-			return d.HandleClusterPropagationPolicyDeletion(policyID)
+	if apierrors.IsNotFound(err) {
+		// The expected ClusterPropagationPolicy has been removed, indicating the cleanup process has completed.
+		// However, if the claimed data still exists in the resource template, it likely means the cleanup
+		// for the resource template was missed. This can happen if the claimed data was added to the resource
+		// template and then the ClusterPropagationPolicy was removed very shortly after.
+		// When cleaning up resource templates based on ClusterPropagationPolicy deletion, the cache may not have received
+		// the resource template update in time, resulting in a missed cleanup.
+		klog.V(4).Infof("ClusterPropagationPolicy(%s) has been removed.", policyName)
+		claimMetadata := labels.Set{policyv1alpha1.ClusterPropagationPolicyPermanentIDLabel: policyID}
+		objRef := workv1alpha2.ObjectReference{
+			APIVersion: object.GetAPIVersion(),
+			Kind:       object.GetKind(),
+			Namespace:  object.GetNamespace(),
+			Name:       object.GetName(),
 		}
-
+		return d.handleResourceTemplateAndBindingCleanup(object, objRef, claimMetadata, CleanupCPPClaimMetadata)
+	}
+	if err != nil {
 		klog.Errorf("Failed to get claimed policy(%s),: %v", policyName, err)
 		return err
 	}
+
+	if !matchedClusterPropagationPolicy.DeletionTimestamp.IsZero() {
+		// If the cluster policy is being deleted, we should not apply it. Instead, waiting for the next reconcile to clean up metadata.
+		return fmt.Errorf("cluster policy(%s) is being deleted", policyName)
+	}
+
 	// Some resources are available in more than one group in the same kubernetes version.
 	// Therefore, the following scenarios occurs:
 	// In v1.21 kubernetes cluster, Ingress are available in both networking.k8s.io and extensions groups.
diff --git a/pkg/resourceinterpreter/default/thirdparty/thirdparty.go b/pkg/resourceinterpreter/default/thirdparty/thirdparty.go
index 11dbff462..bcc9b935d 100644
--- a/pkg/resourceinterpreter/default/thirdparty/thirdparty.go
+++ b/pkg/resourceinterpreter/default/thirdparty/thirdparty.go
@@ -56,6 +56,8 @@ func (p *ConfigurableInterpreter) HookEnabled(kind schema.GroupVersionKind, oper
 		script = customAccessor.GetHealthInterpretationLuaScript()
 	case configv1alpha1.InterpreterOperationInterpretReplica:
 		script = customAccessor.GetReplicaResourceLuaScript()
+	case configv1alpha1.InterpreterOperationInterpretComponent:
+		script = customAccessor.GetComponentResourceLuaScript()
 	case configv1alpha1.InterpreterOperationInterpretStatus:
 		script = customAccessor.GetStatusReflectionLuaScript()
 	case configv1alpha1.InterpreterOperationRetain:
@@ -85,6 +87,25 @@ func (p *ConfigurableInterpreter) GetReplicas(object *unstructured.Unstructured)
 	return
 }
 
+// GetComponents returns the desired components of the object.
+func (p *ConfigurableInterpreter) GetComponents(object *unstructured.Unstructured) (components []workv1alpha2.ComponentRequirements, enabled bool, err error) {
+	klog.V(4).Infof("Get components for object: %v %s/%s with thirdparty configurable interpreter.", object.GroupVersionKind(), object.GetNamespace(), object.GetName())
+
+	customAccessor, enabled := p.getCustomAccessor(object.GroupVersionKind())
+	if !enabled {
+		return
+	}
+
+	script := customAccessor.GetComponentResourceLuaScript()
+	if len(script) == 0 {
+		enabled = false
+		return
+	}
+
+	components, err = p.luaVM.GetComponents(object, script)
+	return
+}
+
 // ReviseReplica revises the replica of the given object.
 func (p *ConfigurableInterpreter) ReviseReplica(object *unstructured.Unstructured, replica int64) (revised *unstructured.Unstructured, enabled bool, err error) {
 	klog.V(4).Infof("Revise replicas for object: %v %s/%s with thirdparty configurable interpreter.", object.GroupVersionKind(), object.GetNamespace(), object.GetName())
diff --git a/pkg/resourceinterpreter/interpreter.go b/pkg/resourceinterpreter/interpreter.go
index 84ec875d5..3130a5240 100644
--- a/pkg/resourceinterpreter/interpreter.go
+++ b/pkg/resourceinterpreter/interpreter.go
@@ -216,6 +216,14 @@ func (i *customResourceInterpreterImpl) GetComponents(object *unstructured.Unstr
 		return components, nil
 	}
 
+	components, hookEnabled, err = i.thirdpartyInterpreter.GetComponents(object)
+	if err != nil {
+		return nil, err
+	}
+	if hookEnabled {
+		return components, nil
+	}
+
 	// TODO(@RainbowMango): Implement GetComponents for extracting per-component replica and resource requirements.
 	// Follow up tracked by: https://github.com/karmada-io/karmada/issues/6641
 	return nil, errors.New("interface GetComponents not implemented yet")
diff --git a/pkg/scheduler/core/assignment.go b/pkg/scheduler/core/assignment.go
index 14df16c6e..7f2619cd0 100644
--- a/pkg/scheduler/core/assignment.go
+++ b/pkg/scheduler/core/assignment.go
@@ -21,9 +21,9 @@ import (
 
 	"k8s.io/apimachinery/pkg/util/sets"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
+	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
 	"github.com/karmada-io/karmada/pkg/util"
 	"github.com/karmada-io/karmada/pkg/util/helper"
 )
@@ -67,7 +67,7 @@ const (
 
 // assignState is a wrapper of the input for assigning function.
 type assignState struct {
-	candidates []*clusterv1alpha1.Cluster
+	candidates []spreadconstraint.ClusterDetailInfo
 	strategy   *policyv1alpha1.ReplicaSchedulingStrategy
 	spec       *workv1alpha2.ResourceBindingSpec
 
@@ -86,7 +86,7 @@ type assignState struct {
 	targetReplicas int32
 }
 
-func newAssignState(candidates []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec,
+func newAssignState(candidates []spreadconstraint.ClusterDetailInfo, spec *workv1alpha2.ResourceBindingSpec,
 	status *workv1alpha2.ResourceBindingStatus) *assignState {
 	var strategyType string
 
diff --git a/pkg/scheduler/core/common.go b/pkg/scheduler/core/common.go
index 374d4d64c..05c4858a4 100644
--- a/pkg/scheduler/core/common.go
+++ b/pkg/scheduler/core/common.go
@@ -20,7 +20,6 @@ import (
 	"fmt"
 	"time"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
 	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
@@ -30,7 +29,7 @@ import (
 
 // SelectClusters selects clusters based on the placement and resource binding spec.
 func SelectClusters(clustersScore framework.ClusterScoreList,
-	placement *policyv1alpha1.Placement, spec *workv1alpha2.ResourceBindingSpec) ([]*clusterv1alpha1.Cluster, error) {
+	placement *policyv1alpha1.Placement, spec *workv1alpha2.ResourceBindingSpec) ([]spreadconstraint.ClusterDetailInfo, error) {
 	startTime := time.Now()
 	defer metrics.ScheduleStep(metrics.ScheduleStepSelect, startTime)
 
@@ -40,7 +39,7 @@ func SelectClusters(clustersScore framework.ClusterScoreList,
 
 // AssignReplicas assigns replicas to clusters based on the placement and resource binding spec.
 func AssignReplicas(
-	clusters []*clusterv1alpha1.Cluster,
+	clusters []spreadconstraint.ClusterDetailInfo,
 	spec *workv1alpha2.ResourceBindingSpec,
 	status *workv1alpha2.ResourceBindingStatus,
 ) ([]workv1alpha2.TargetCluster, error) {
@@ -69,7 +68,7 @@ func AssignReplicas(
 	// If not workload, assign all clusters without considering replicas.
 	targetClusters := make([]workv1alpha2.TargetCluster, len(clusters))
 	for i, cluster := range clusters {
-		targetClusters[i] = workv1alpha2.TargetCluster{Name: cluster.Name}
+		targetClusters[i] = workv1alpha2.TargetCluster{Name: cluster.Cluster.Name}
 	}
 	return targetClusters, nil
 }
diff --git a/pkg/scheduler/core/division_algorithm.go b/pkg/scheduler/core/division_algorithm.go
index df167bf3e..62bf2075a 100644
--- a/pkg/scheduler/core/division_algorithm.go
+++ b/pkg/scheduler/core/division_algorithm.go
@@ -20,9 +20,9 @@ import (
 	"fmt"
 	"sort"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
+	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
 	"github.com/karmada-io/karmada/pkg/scheduler/framework"
 	"github.com/karmada-io/karmada/pkg/util"
 	"github.com/karmada-io/karmada/pkg/util/helper"
@@ -35,14 +35,14 @@ func (a TargetClustersList) Len() int           { return len(a) }
 func (a TargetClustersList) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 func (a TargetClustersList) Less(i, j int) bool { return a[i].Replicas > a[j].Replicas }
 
-func getStaticWeightInfoList(clusters []*clusterv1alpha1.Cluster, weightList []policyv1alpha1.StaticClusterWeight,
+func getStaticWeightInfoList(clusters []spreadconstraint.ClusterDetailInfo, weightList []policyv1alpha1.StaticClusterWeight,
 	lastTargetClusters []workv1alpha2.TargetCluster) helper.ClusterWeightInfoList {
 	list := make(helper.ClusterWeightInfoList, 0)
 	for _, cluster := range clusters {
 		var weight int64
 		var lastReplicas int32
 		for _, staticWeightRule := range weightList {
-			if util.ClusterMatches(cluster, staticWeightRule.TargetCluster) {
+			if util.ClusterMatches(cluster.Cluster, staticWeightRule.TargetCluster) {
 				weight = util.MaxInt64(weight, staticWeightRule.Weight)
 			}
 		}
@@ -107,7 +107,7 @@ func dynamicScaleDown(state *assignState) ([]workv1alpha2.TargetCluster, error)
 	// 3. scheduledClusters and assignedReplicas are not set, which implicates we consider this action as a first schedule.
 	state.targetReplicas = state.spec.Replicas
 	state.scheduledClusters = nil
-	state.buildAvailableClusters(func(_ []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster {
+	state.buildAvailableClusters(func(_ []spreadconstraint.ClusterDetailInfo, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster {
 		availableClusters := make(TargetClustersList, len(spec.Clusters))
 		copy(availableClusters, spec.Clusters)
 		sort.Sort(availableClusters)
@@ -119,8 +119,14 @@ func dynamicScaleDown(state *assignState) ([]workv1alpha2.TargetCluster, error)
 func dynamicScaleUp(state *assignState) ([]workv1alpha2.TargetCluster, error) {
 	// Target is the extra ones.
 	state.targetReplicas = state.spec.Replicas - state.assignedReplicas
-	state.buildAvailableClusters(func(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster {
-		clusterAvailableReplicas := calAvailableReplicas(clusters, spec)
+	state.buildAvailableClusters(func(clusters []spreadconstraint.ClusterDetailInfo, _ *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster {
+		clusterAvailableReplicas := make([]workv1alpha2.TargetCluster, len(clusters))
+		for i, cluster := range clusters {
+			clusterAvailableReplicas[i] = workv1alpha2.TargetCluster{
+				Name:     cluster.Name,
+				Replicas: cluster.AllocatableReplicas,
+			}
+		}
 		sort.Sort(TargetClustersList(clusterAvailableReplicas))
 		return clusterAvailableReplicas
 	})
@@ -131,8 +137,14 @@ func dynamicScaleUp(state *assignState) ([]workv1alpha2.TargetCluster, error) {
 func dynamicFreshScale(state *assignState) ([]workv1alpha2.TargetCluster, error) {
 	// 1. targetReplicas is set to desired replicas
 	state.targetReplicas = state.spec.Replicas
-	state.buildAvailableClusters(func(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster {
-		clusterAvailableReplicas := calAvailableReplicas(clusters, spec)
+	state.buildAvailableClusters(func(clusters []spreadconstraint.ClusterDetailInfo, _ *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster {
+		clusterAvailableReplicas := make([]workv1alpha2.TargetCluster, len(clusters))
+		for i, cluster := range clusters {
+			clusterAvailableReplicas[i] = workv1alpha2.TargetCluster{
+				Name:     cluster.Name,
+				Replicas: cluster.AllocatableReplicas,
+			}
+		}
 		// 2. clusterAvailableReplicas should take into account the replicas already allocated
 		for _, scheduledCluster := range state.scheduledClusters {
 			for i, availableCluster := range clusterAvailableReplicas {
diff --git a/pkg/scheduler/core/generic_scheduler.go b/pkg/scheduler/core/generic_scheduler.go
index a3e84d35c..5f17ed0c3 100644
--- a/pkg/scheduler/core/generic_scheduler.go
+++ b/pkg/scheduler/core/generic_scheduler.go
@@ -27,6 +27,7 @@ import (
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
 	"github.com/karmada-io/karmada/pkg/scheduler/cache"
+	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
 	"github.com/karmada-io/karmada/pkg/scheduler/framework"
 	"github.com/karmada-io/karmada/pkg/scheduler/framework/runtime"
 	"github.com/karmada-io/karmada/pkg/scheduler/metrics"
@@ -94,20 +95,20 @@ func (g *genericScheduler) Schedule(
 	}
 	klog.V(4).Infof("Feasible clusters scores: %v", clustersScore)
 
-	clusters, err := g.selectClusters(clustersScore, spec.Placement, spec)
+	selectedClusters, err := g.selectClusters(clustersScore, spec.Placement, spec)
 	if err != nil {
 		return result, fmt.Errorf("failed to select clusters: %w", err)
 	}
-	klog.V(4).Infof("Selected clusters: %v", clusters)
+	klog.V(4).Infof("Selected clusters: %v", selectedClusters)
 
-	clustersWithReplicas, err := g.assignReplicas(clusters, spec, status)
+	clustersWithReplicas, err := g.assignReplicas(selectedClusters, spec, status)
 	if err != nil {
 		return result, fmt.Errorf("failed to assign replicas: %w", err)
 	}
 	klog.V(4).Infof("Assigned Replicas: %v", clustersWithReplicas)
 
 	if scheduleAlgorithmOption.EnableEmptyWorkloadPropagation {
-		clustersWithReplicas = attachZeroReplicasCluster(clusters, clustersWithReplicas)
+		clustersWithReplicas = attachZeroReplicasCluster(selectedClusters, clustersWithReplicas)
 	}
 	result.SuggestedClusters = clustersWithReplicas
 
@@ -182,11 +183,11 @@ func (g *genericScheduler) prioritizeClusters(
 }
 
 func (g *genericScheduler) selectClusters(clustersScore framework.ClusterScoreList,
-	placement *policyv1alpha1.Placement, spec *workv1alpha2.ResourceBindingSpec) ([]*clusterv1alpha1.Cluster, error) {
+	placement *policyv1alpha1.Placement, spec *workv1alpha2.ResourceBindingSpec) ([]spreadconstraint.ClusterDetailInfo, error) {
 	return SelectClusters(clustersScore, placement, spec)
 }
 
-func (g *genericScheduler) assignReplicas(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha2.ResourceBindingSpec,
+func (g *genericScheduler) assignReplicas(clusters []spreadconstraint.ClusterDetailInfo, spec *workv1alpha2.ResourceBindingSpec,
 	status *workv1alpha2.ResourceBindingStatus) ([]workv1alpha2.TargetCluster, error) {
 	return AssignReplicas(clusters, spec, status)
 }
diff --git a/pkg/scheduler/core/spreadconstraint/group_clusters.go b/pkg/scheduler/core/spreadconstraint/group_clusters.go
index b3ae19bc7..e6769e9a3 100644
--- a/pkg/scheduler/core/spreadconstraint/group_clusters.go
+++ b/pkg/scheduler/core/spreadconstraint/group_clusters.go
@@ -77,11 +77,14 @@ type ZoneInfo struct {
 
 // ClusterDetailInfo indicate the cluster information
 type ClusterDetailInfo struct {
-	Name              string
-	Score             int64
+	Name  string
+	Score int64
+	// AvailableReplicas equal to the sum of the assigned replicas and the allocatable replicas in the cluster
 	AvailableReplicas int64
 
 	Cluster *clusterv1alpha1.Cluster
+	//AllocatableReplicas the max allocatable replicas in the cluster
+	AllocatableReplicas int32
 }
 
 // GroupClustersWithScore groups cluster base provider/region/zone/cluster
@@ -344,6 +347,7 @@ func (info *GroupClustersInfo) generateClustersInfo(clustersScore framework.Clus
 	for i, clustersReplica := range clustersReplicas {
 		info.Clusters[i].AvailableReplicas = int64(clustersReplica.Replicas)
 		info.Clusters[i].AvailableReplicas += int64(rbSpec.AssignedReplicasForCluster(clustersReplica.Name))
+		info.Clusters[i].AllocatableReplicas = clustersReplica.Replicas
 	}
 
 	sortClusters(info.Clusters, func(i *ClusterDetailInfo, j *ClusterDetailInfo) *bool {
diff --git a/pkg/scheduler/core/spreadconstraint/select_clusters.go b/pkg/scheduler/core/spreadconstraint/select_clusters.go
index 7eb5abe68..108a5a9a2 100644
--- a/pkg/scheduler/core/spreadconstraint/select_clusters.go
+++ b/pkg/scheduler/core/spreadconstraint/select_clusters.go
@@ -21,19 +21,14 @@ import (
 
 	"k8s.io/klog/v2"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 )
 
 // SelectBestClusters selects the cluster set based the GroupClustersInfo and placement
-func SelectBestClusters(placement *policyv1alpha1.Placement, groupClustersInfo *GroupClustersInfo, needReplicas int32) ([]*clusterv1alpha1.Cluster, error) {
+func SelectBestClusters(placement *policyv1alpha1.Placement, groupClustersInfo *GroupClustersInfo, needReplicas int32) ([]ClusterDetailInfo, error) {
 	if len(placement.SpreadConstraints) == 0 || shouldIgnoreSpreadConstraint(placement) {
-		var clusters []*clusterv1alpha1.Cluster
-		for _, cluster := range groupClustersInfo.Clusters {
-			clusters = append(clusters, cluster.Cluster)
-		}
 		klog.V(4).Infof("Select all clusters")
-		return clusters, nil
+		return groupClustersInfo.Clusters, nil
 	}
 
 	if shouldIgnoreAvailableResource(placement) {
@@ -44,7 +39,7 @@ func SelectBestClusters(placement *policyv1alpha1.Placement, groupClustersInfo *
 }
 
 func selectBestClustersBySpreadConstraints(spreadConstraints []policyv1alpha1.SpreadConstraint,
-	groupClustersInfo *GroupClustersInfo, needReplicas int32) ([]*clusterv1alpha1.Cluster, error) {
+	groupClustersInfo *GroupClustersInfo, needReplicas int32) ([]ClusterDetailInfo, error) {
 	spreadConstraintMap := make(map[policyv1alpha1.SpreadFieldValue]policyv1alpha1.SpreadConstraint)
 	for i := range spreadConstraints {
 		spreadConstraintMap[spreadConstraints[i].SpreadByField] = spreadConstraints[i]
diff --git a/pkg/scheduler/core/spreadconstraint/select_clusters_by_cluster.go b/pkg/scheduler/core/spreadconstraint/select_clusters_by_cluster.go
index 069249bb7..43efc1ef4 100644
--- a/pkg/scheduler/core/spreadconstraint/select_clusters_by_cluster.go
+++ b/pkg/scheduler/core/spreadconstraint/select_clusters_by_cluster.go
@@ -19,12 +19,11 @@ package spreadconstraint
 import (
 	"fmt"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 )
 
 func selectBestClustersByCluster(spreadConstraint policyv1alpha1.SpreadConstraint, groupClustersInfo *GroupClustersInfo,
-	needReplicas int32) ([]*clusterv1alpha1.Cluster, error) {
+	needReplicas int32) ([]ClusterDetailInfo, error) {
 	totalClusterCnt := len(groupClustersInfo.Clusters)
 	if totalClusterCnt < spreadConstraint.MinGroups {
 		return nil, fmt.Errorf("the number of feasible clusters is less than spreadConstraint.MinGroups")
@@ -34,24 +33,17 @@ func selectBestClustersByCluster(spreadConstraint policyv1alpha1.SpreadConstrain
 	if totalClusterCnt < spreadConstraint.MaxGroups {
 		needCnt = totalClusterCnt
 	}
-
-	var clusterInfos []ClusterDetailInfo
+	var selectedClusters []ClusterDetailInfo
 
 	if needReplicas == InvalidReplicas {
-		clusterInfos = groupClustersInfo.Clusters[:needCnt]
+		selectedClusters = groupClustersInfo.Clusters[:needCnt]
 	} else {
-		clusterInfos = selectClustersByAvailableResource(groupClustersInfo.Clusters, int32(needCnt), needReplicas) // #nosec G115: integer overflow conversion int -> int32
-		if len(clusterInfos) == 0 {
+		selectedClusters = selectClustersByAvailableResource(groupClustersInfo.Clusters, int32(needCnt), needReplicas) // #nosec G115: integer overflow conversion int -> int32
+		if len(selectedClusters) == 0 {
 			return nil, fmt.Errorf("no enough resource when selecting %d clusters", needCnt)
 		}
 	}
-
-	var clusters []*clusterv1alpha1.Cluster
-	for i := range clusterInfos {
-		clusters = append(clusters, clusterInfos[i].Cluster)
-	}
-
-	return clusters, nil
+	return selectedClusters, nil
 }
 
 // if needClusterCount = 2, needReplicas = 80, member1 and member3 will be selected finally.
diff --git a/pkg/scheduler/core/spreadconstraint/select_clusters_by_region.go b/pkg/scheduler/core/spreadconstraint/select_clusters_by_region.go
index b6c739c35..8d260060d 100644
--- a/pkg/scheduler/core/spreadconstraint/select_clusters_by_region.go
+++ b/pkg/scheduler/core/spreadconstraint/select_clusters_by_region.go
@@ -21,14 +21,13 @@ import (
 
 	"k8s.io/utils/ptr"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 )
 
 func selectBestClustersByRegion(spreadConstraintMap map[policyv1alpha1.SpreadFieldValue]policyv1alpha1.SpreadConstraint,
-	groupClustersInfo *GroupClustersInfo) ([]*clusterv1alpha1.Cluster, error) {
-	var clusters []*clusterv1alpha1.Cluster
+	groupClustersInfo *GroupClustersInfo) ([]ClusterDetailInfo, error) {
 	var candidateClusters []ClusterDetailInfo
+	var selectedClusters []ClusterDetailInfo
 
 	if len(groupClustersInfo.Regions) < spreadConstraintMap[policyv1alpha1.SpreadByFieldRegion].MinGroups {
 		return nil, fmt.Errorf("the number of feasible region is less than spreadConstraint.MinGroups")
@@ -43,17 +42,17 @@ func selectBestClustersByRegion(spreadConstraintMap map[policyv1alpha1.SpreadFie
 
 	// secondly, select the clusters with the highest score in per region,
 	for i := range regions {
-		clusters = append(clusters, regions[i].Clusters[0].Cluster)
+		selectedClusters = append(selectedClusters, regions[i].Clusters[0])
 		candidateClusters = append(candidateClusters, regions[i].Clusters[1:]...)
 	}
 
-	needCnt := len(candidateClusters) + len(clusters)
+	needCnt := len(candidateClusters) + len(selectedClusters)
 	if needCnt > spreadConstraintMap[policyv1alpha1.SpreadByFieldCluster].MaxGroups {
 		needCnt = spreadConstraintMap[policyv1alpha1.SpreadByFieldCluster].MaxGroups
 	}
 
 	// thirdly, select the remaining Clusters based cluster.Score
-	restCnt := needCnt - len(clusters)
+	restCnt := needCnt - len(selectedClusters)
 	if restCnt > 0 {
 		sortClusters(candidateClusters, func(i *ClusterDetailInfo, j *ClusterDetailInfo) *bool {
 			if i.AvailableReplicas != j.AvailableReplicas {
@@ -62,11 +61,11 @@ func selectBestClustersByRegion(spreadConstraintMap map[policyv1alpha1.SpreadFie
 			return nil
 		})
 		for i := 0; i < restCnt; i++ {
-			clusters = append(clusters, candidateClusters[i].Cluster)
+			selectedClusters = append(selectedClusters, candidateClusters[i])
 		}
 	}
 
-	return clusters, nil
+	return selectedClusters, nil
 }
 
 // selectRegions is an implementation of the region selection algorithm, the purpose of
diff --git a/pkg/scheduler/core/util.go b/pkg/scheduler/core/util.go
index 1951c97a2..795d7a326 100644
--- a/pkg/scheduler/core/util.go
+++ b/pkg/scheduler/core/util.go
@@ -28,13 +28,14 @@ import (
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
 	estimatorclient "github.com/karmada-io/karmada/pkg/estimator/client"
+	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
 	"github.com/karmada-io/karmada/pkg/util"
 	"github.com/karmada-io/karmada/pkg/util/names"
 )
 
-type calculator func([]*clusterv1alpha1.Cluster, *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster
+type calculator func([]spreadconstraint.ClusterDetailInfo, *workv1alpha2.ResourceBindingSpec) []workv1alpha2.TargetCluster
 
-func getDefaultWeightPreference(clusters []*clusterv1alpha1.Cluster) *policyv1alpha1.ClusterPreferences {
+func getDefaultWeightPreference(clusters []spreadconstraint.ClusterDetailInfo) *policyv1alpha1.ClusterPreferences {
 	staticWeightLists := make([]policyv1alpha1.StaticClusterWeight, 0)
 	for _, cluster := range clusters {
 		staticWeightList := policyv1alpha1.StaticClusterWeight{
@@ -105,7 +106,8 @@ func calAvailableReplicas(clusters []*clusterv1alpha1.Cluster, spec *workv1alpha
 
 // attachZeroReplicasCluster  attach cluster in clusters into targetCluster
 // The purpose is to avoid workload not appeared in rb's spec.clusters field
-func attachZeroReplicasCluster(clusters []*clusterv1alpha1.Cluster, targetClusters []workv1alpha2.TargetCluster) []workv1alpha2.TargetCluster {
+func attachZeroReplicasCluster(clusters []spreadconstraint.ClusterDetailInfo,
+	targetClusters []workv1alpha2.TargetCluster) []workv1alpha2.TargetCluster {
 	targetClusterSet := sets.NewString()
 	for i := range targetClusters {
 		targetClusterSet.Insert(targetClusters[i].Name)
diff --git a/pkg/util/constants.go b/pkg/util/constants.go
index 969f81073..c27599cbb 100644
--- a/pkg/util/constants.go
+++ b/pkg/util/constants.go
@@ -56,9 +56,6 @@ const (
 	// managed by karmada controllers.
 	KarmadaSystemLabel = "karmada.io/system"
 
-	// EndpointSliceDispatchControllerLabelValue indicates the endpointSlice are controlled by Karmada
-	EndpointSliceDispatchControllerLabelValue = "endpointslice-dispatch-controller.karmada.io"
-
 	// RetainReplicasLabel is a reserved label to indicate whether the replicas should be retained. e.g:
 	// resourcetemplate.karmada.io/retain-replicas: true   // with value `true` indicates retain
 	// resourcetemplate.karmada.io/retain-replicas: false  // with value `false` and others, indicates not retain
@@ -71,6 +68,7 @@ const (
 	EndpointSliceWorkManagedByLabel = "endpointslice.karmada.io/managed-by"
 )
 
+// Define label values used by Karmada system.
 const (
 	// ManagedByKarmadaLabelValue indicates that these are workloads in member cluster synchronized by karmada controllers.
 	ManagedByKarmadaLabelValue = "true"
@@ -80,6 +78,12 @@ const (
 
 	// RetainReplicasValue is an optional value of RetainReplicasLabel, indicating retain
 	RetainReplicasValue = "true"
+
+	// EndpointSliceDispatchControllerLabelValue indicates the endpointSlice is controlled by Karmada endpointslice-dispatch-controller
+	EndpointSliceDispatchControllerLabelValue = "endpointslice-dispatch-controller.karmada.io"
+
+	// EndpointSliceControllerLabelValue indicates the endpointSlice is controlled by Karmada endpointslice-controller
+	EndpointSliceControllerLabelValue = "endpointslice-controller.karmada.io"
 )
 
 // Define annotations used by karmada system.
diff --git a/pkg/util/helper/mcs.go b/pkg/util/helper/mcs.go
index 672151a77..70e6eff64 100644
--- a/pkg/util/helper/mcs.go
+++ b/pkg/util/helper/mcs.go
@@ -141,3 +141,12 @@ func GetConsumerClusters(client client.Client, mcs *networkingv1alpha1.MultiClus
 	}
 	return allClusters, nil
 }
+
+// IsEndpointSliceManagedByKarmada checks if the EndpointSlice is managed by Karmada.
+func IsEndpointSliceManagedByKarmada(epsLabels map[string]string) bool {
+	switch util.GetLabelValue(epsLabels, discoveryv1.LabelManagedBy) {
+	case util.EndpointSliceDispatchControllerLabelValue, util.EndpointSliceControllerLabelValue:
+		return true
+	}
+	return false
+}
diff --git a/pkg/util/selector.go b/pkg/util/selector.go
index 83a152747..adb5a77c7 100644
--- a/pkg/util/selector.go
+++ b/pkg/util/selector.go
@@ -239,3 +239,24 @@ func matchZones(zoneMatchExpression *corev1.NodeSelectorRequirement, zones []str
 		return false
 	}
 }
+
+// ExtractUniqueNamespacedSelectors returns a new slice of ResourceSelector deduplicated by
+// APIVersion, Kind and Namespace. The returned selectors only contain APIVersion, Kind and Namespace;
+// other fields (e.g. Name, LabelSelector) are intentionally discarded.
+func ExtractUniqueNamespacedSelectors(selectors []policyv1alpha1.ResourceSelector) []policyv1alpha1.ResourceSelector {
+	var results []policyv1alpha1.ResourceSelector
+	handled := make(map[string]bool)
+	for _, selector := range selectors {
+		key := selector.APIVersion + "|" + selector.Kind + "|" + selector.Namespace
+		if handled[key] {
+			continue
+		}
+		results = append(results, policyv1alpha1.ResourceSelector{
+			APIVersion: selector.APIVersion,
+			Kind:       selector.Kind,
+			Namespace:  selector.Namespace,
+		})
+		handled[key] = true
+	}
+	return results
+}



Test patch:
diff --git a/pkg/scheduler/core/assignment_test.go b/pkg/scheduler/core/assignment_test.go
index 31e19b22d..881bc97a0 100644
--- a/pkg/scheduler/core/assignment_test.go
+++ b/pkg/scheduler/core/assignment_test.go
@@ -22,11 +22,10 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
+	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
 	"github.com/karmada-io/karmada/pkg/util"
 	"github.com/karmada-io/karmada/test/helper"
 )
@@ -48,7 +47,7 @@ var (
 func Test_assignByStaticWeightStrategy(t *testing.T) {
 	tests := []struct {
 		name             string
-		clusters         []*clusterv1alpha1.Cluster
+		clusters         []spreadconstraint.ClusterDetailInfo
 		weightPreference *policyv1alpha1.ClusterPreferences
 		replicas         int32
 		want             []workv1alpha2.TargetCluster
@@ -56,10 +55,10 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 	}{
 		{
 			name: "replica 12, weight 3:2:1",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
 			},
 			weightPreference: &policyv1alpha1.ClusterPreferences{
 				StaticWeightList: []policyv1alpha1.StaticClusterWeight{
@@ -102,10 +101,10 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 		},
 		{
 			name: "replica 12, default weight",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
 			},
 			replicas: 12,
 			want: []workv1alpha2.TargetCluster{
@@ -126,10 +125,10 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 		},
 		{
 			name: "replica 14, weight 3:2:1",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
 			},
 			weightPreference: &policyv1alpha1.ClusterPreferences{
 				StaticWeightList: []policyv1alpha1.StaticClusterWeight{
@@ -172,9 +171,9 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 		},
 		{
 			name: "insufficient replica assignment should get 0 replica",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
 			},
 			weightPreference: &policyv1alpha1.ClusterPreferences{
 				StaticWeightList: []policyv1alpha1.StaticClusterWeight{
@@ -207,9 +206,9 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 		},
 		{
 			name: "selected cluster without weight should be ignored",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
 			},
 			weightPreference: &policyv1alpha1.ClusterPreferences{
 				StaticWeightList: []policyv1alpha1.StaticClusterWeight{
@@ -232,9 +231,9 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 		},
 		{
 			name: "cluster with multiple weights",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
 			},
 			weightPreference: &policyv1alpha1.ClusterPreferences{
 				StaticWeightList: []policyv1alpha1.StaticClusterWeight{
@@ -297,23 +296,24 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 func Test_dynamicScale(t *testing.T) {
 	tests := []struct {
 		name       string
-		candidates []*clusterv1alpha1.Cluster
+		candidates []spreadconstraint.ClusterDetailInfo
 		object     *workv1alpha2.ResourceBindingSpec
 		want       []workv1alpha2.TargetCluster
 		wantErr    bool
 	}{
 		{
 			name: "replica 12 -> 6, dynamic weighted 1:1:1",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -338,16 +338,16 @@ func Test_dynamicScale(t *testing.T) {
 		},
 		{
 			name: "replica 12 -> 24, dynamic weighted 10:10:10",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 10},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 10},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 10},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -372,16 +372,16 @@ func Test_dynamicScale(t *testing.T) {
 		},
 		{
 			name: "replica 12 -> 24, dynamic weighted 1:1:1",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -401,16 +401,16 @@ func Test_dynamicScale(t *testing.T) {
 		},
 		{
 			name: "replica 12 -> 6, aggregated 1:1:1",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -432,13 +432,13 @@ func Test_dynamicScale(t *testing.T) {
 		},
 		{
 			name: "replica 12 -> 8, aggregated 100:100",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(100, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 100},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(100, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 100},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -460,16 +460,16 @@ func Test_dynamicScale(t *testing.T) {
 		},
 		{
 			name: "replica 12 -> 24, aggregated 4:6:8",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(4, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 4},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(6, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 6},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 8},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -493,16 +493,16 @@ func Test_dynamicScale(t *testing.T) {
 		},
 		{
 			name: "replica 12 -> 24, aggregated 6:6:20",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(6, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 6},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(6, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 6},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(20, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 20},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -525,16 +525,16 @@ func Test_dynamicScale(t *testing.T) {
 		},
 		{
 			name: "replica 12 -> 24, aggregated 1:1:1",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 1},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -553,16 +553,16 @@ func Test_dynamicScale(t *testing.T) {
 		},
 		{
 			name: "replica 12 -> 24, aggregated 4:8:12, with cluster2 disappeared and cluster4 appeared",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(4, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 4},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember4, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 8},
+				{Name: ClusterMember4, Cluster: helper.NewClusterWithResource(ClusterMember4, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(12, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 12},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -603,7 +603,7 @@ func Test_dynamicScale(t *testing.T) {
 func Test_dynamicScaleUp(t *testing.T) {
 	tests := []struct {
 		name       string
-		candidates []*clusterv1alpha1.Cluster
+		candidates []spreadconstraint.ClusterDetailInfo
 		object     *workv1alpha2.ResourceBindingSpec
 		// wants specifies multi possible desired result, any one got is expected
 		wants   [][]workv1alpha2.TargetCluster
@@ -611,16 +611,16 @@ func Test_dynamicScaleUp(t *testing.T) {
 	}{
 		{
 			name: "replica 12, dynamic weight 6:8:10",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(6, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 6},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 8},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 10},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -642,16 +642,16 @@ func Test_dynamicScaleUp(t *testing.T) {
 		},
 		{
 			name: "replica 12, dynamic weight 8:8:10",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 8},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 8},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 10},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -678,16 +678,16 @@ func Test_dynamicScaleUp(t *testing.T) {
 		},
 		{
 			name: "replica 12, dynamic weight 3:3:3",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 3},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 3},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 3},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -702,16 +702,16 @@ func Test_dynamicScaleUp(t *testing.T) {
 		},
 		{
 			name: "replica 12, aggregated 6:8:10",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(6, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 6},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 8},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 10},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -732,16 +732,16 @@ func Test_dynamicScaleUp(t *testing.T) {
 		},
 		{
 			name: "replica 12, aggregated 12:8:10",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(12, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 12},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 8},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 10},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -761,16 +761,16 @@ func Test_dynamicScaleUp(t *testing.T) {
 		},
 		{
 			name: "replica 12, aggregated 3:3:3",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 3},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 3},
+				{Name: ClusterMember3, Cluster: helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 3},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -785,13 +785,13 @@ func Test_dynamicScaleUp(t *testing.T) {
 		},
 		{
 			name: "replica 12, dynamic weight 3:3, with cluster3 disappeared and cluster2 appeared",
-			candidates: []*clusterv1alpha1.Cluster{
-				helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
+			candidates: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
-				helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 3},
+				{Name: ClusterMember2, Cluster: helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{
 					corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI),
-				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()),
+				}, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), AllocatableReplicas: 3},
 			},
 			object: &workv1alpha2.ResourceBindingSpec{
 				ReplicaRequirements: &workv1alpha2.ReplicaRequirements{
@@ -847,26 +847,22 @@ func Test_assignByDuplicatedStrategy(t *testing.T) {
 		{
 			name: "test with multiple cluster",
 			state: &assignState{
-				candidates: []*clusterv1alpha1.Cluster{
-					{
-						ObjectMeta: metav1.ObjectMeta{Name: "cluster1"},
-					},
-					{
-						ObjectMeta: metav1.ObjectMeta{Name: "cluster2"},
-					},
+				candidates: []spreadconstraint.ClusterDetailInfo{
+					{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+					{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
 				},
 				spec: &workv1alpha2.ResourceBindingSpec{Replicas: 2},
 			},
 			want: []workv1alpha2.TargetCluster{
-				{Name: "cluster1", Replicas: 2},
-				{Name: "cluster2", Replicas: 2},
+				{Name: ClusterMember1, Replicas: 2},
+				{Name: ClusterMember2, Replicas: 2},
 			},
 			wantErr: false,
 		},
 		{
 			name: "the target cluster is null",
 			state: &assignState{
-				candidates: []*clusterv1alpha1.Cluster{},
+				candidates: []spreadconstraint.ClusterDetailInfo{},
 				spec:       &workv1alpha2.ResourceBindingSpec{Replicas: 2},
 			},
 			want:    []workv1alpha2.TargetCluster{},
@@ -875,19 +871,15 @@ func Test_assignByDuplicatedStrategy(t *testing.T) {
 		{
 			name: "replicas is null",
 			state: &assignState{
-				candidates: []*clusterv1alpha1.Cluster{
-					{
-						ObjectMeta: metav1.ObjectMeta{Name: "cluster1"},
-					},
-					{
-						ObjectMeta: metav1.ObjectMeta{Name: "cluster2"},
-					},
+				candidates: []spreadconstraint.ClusterDetailInfo{
+					{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+					{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
 				},
 				spec: &workv1alpha2.ResourceBindingSpec{},
 			},
 			want: []workv1alpha2.TargetCluster{
-				{Name: "cluster1", Replicas: 0},
-				{Name: "cluster2", Replicas: 0},
+				{Name: ClusterMember1, Replicas: 0},
+				{Name: ClusterMember2, Replicas: 0},
 			},
 			wantErr: false,
 		},
diff --git a/pkg/scheduler/core/common_test.go b/pkg/scheduler/core/common_test.go
index 26fee0802..e15eba37a 100644
--- a/pkg/scheduler/core/common_test.go
+++ b/pkg/scheduler/core/common_test.go
@@ -25,7 +25,9 @@ import (
 	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
+	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
 	"github.com/karmada-io/karmada/pkg/scheduler/framework"
+	"github.com/karmada-io/karmada/test/helper"
 )
 
 func TestSelectClusters(t *testing.T) {
@@ -112,7 +114,7 @@ func TestSelectClusters(t *testing.T) {
 
 				actualNames := make([]string, len(result))
 				for i, cluster := range result {
-					actualNames[i] = cluster.Name
+					actualNames[i] = cluster.Cluster.Name
 				}
 
 				assert.ElementsMatch(t, expectedNames, actualNames)
@@ -124,7 +126,7 @@ func TestSelectClusters(t *testing.T) {
 func TestAssignReplicas(t *testing.T) {
 	tests := []struct {
 		name           string
-		clusters       []*clusterv1alpha1.Cluster
+		clusters       []spreadconstraint.ClusterDetailInfo
 		spec           *workv1alpha2.ResourceBindingSpec
 		status         *workv1alpha2.ResourceBindingStatus
 		expectedResult []workv1alpha2.TargetCluster
@@ -133,8 +135,8 @@ func TestAssignReplicas(t *testing.T) {
 	}{
 		{
 			name: "Assign replicas to single cluster",
-			clusters: []*clusterv1alpha1.Cluster{
-				{ObjectMeta: metav1.ObjectMeta{Name: "cluster1"}},
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
 			},
 			spec: &workv1alpha2.ResourceBindingSpec{
 				Replicas: 3,
@@ -146,12 +148,12 @@ func TestAssignReplicas(t *testing.T) {
 				},
 			},
 			status:         &workv1alpha2.ResourceBindingStatus{},
-			expectedResult: []workv1alpha2.TargetCluster{{Name: "cluster1", Replicas: 3}},
+			expectedResult: []workv1alpha2.TargetCluster{{Name: ClusterMember1, Replicas: 3}},
 			expectedError:  false,
 		},
 		{
 			name:           "No clusters available",
-			clusters:       []*clusterv1alpha1.Cluster{},
+			clusters:       []spreadconstraint.ClusterDetailInfo{},
 			spec:           &workv1alpha2.ResourceBindingSpec{Replicas: 1},
 			status:         &workv1alpha2.ResourceBindingStatus{},
 			expectedResult: nil,
@@ -160,21 +162,21 @@ func TestAssignReplicas(t *testing.T) {
 		},
 		{
 			name: "Non-workload scenario (zero replicas)",
-			clusters: []*clusterv1alpha1.Cluster{
-				{ObjectMeta: metav1.ObjectMeta{Name: "cluster1"}},
-				{ObjectMeta: metav1.ObjectMeta{Name: "cluster2"}},
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
 			},
 			spec: &workv1alpha2.ResourceBindingSpec{
 				Replicas: 0,
 			},
 			status:         &workv1alpha2.ResourceBindingStatus{},
-			expectedResult: []workv1alpha2.TargetCluster{{Name: "cluster1"}, {Name: "cluster2"}},
+			expectedResult: []workv1alpha2.TargetCluster{{Name: ClusterMember1}, {Name: ClusterMember2}},
 			expectedError:  false,
 		},
 		{
 			name: "Unsupported replica scheduling strategy",
-			clusters: []*clusterv1alpha1.Cluster{
-				{Spec: clusterv1alpha1.ClusterSpec{ID: "cluster1"}},
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
 			},
 			spec: &workv1alpha2.ResourceBindingSpec{
 				Replicas: 3,
diff --git a/pkg/scheduler/core/generic_scheduler_test.go b/pkg/scheduler/core/generic_scheduler_test.go
index 04e0906e6..e6e441569 100644
--- a/pkg/scheduler/core/generic_scheduler_test.go
+++ b/pkg/scheduler/core/generic_scheduler_test.go
@@ -21,15 +21,15 @@ import (
 	"strings"
 	"testing"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
+	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
 	"github.com/karmada-io/karmada/test/helper"
 )
 
 type testcase struct {
 	name                      string
-	clusters                  []*clusterv1alpha1.Cluster
+	clusters                  []spreadconstraint.ClusterDetailInfo
 	object                    workv1alpha2.ResourceBindingSpec
 	previousResultToNewResult map[string][]string
 	wantErr                   bool
@@ -67,9 +67,9 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		// 2. check two member cluster replicas, should be 2:1 or 1:2
 		{
 			name: "replica 3, static weighted 1:1",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 3,
@@ -98,10 +98,10 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		// 4. check three member cluster replicas, should be 2:2:1 or 2:1:2 or 1:2:2
 		{
 			name: "replica 3, static weighted 1:1:1, change replicas from 3 to 5, before change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 3,
@@ -126,10 +126,10 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		},
 		{
 			name: "replica 3, static weighted 1:1:1, change replicas from 3 to 5, after change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 5, // change replicas from 3 to 5
@@ -162,11 +162,11 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		//    * 3:1:1:2 --> 4:1:1:2
 		{
 			name: "replica 7, static weighted 2:1:1:1, change replicas from 7 to 8, before change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
-				helper.NewCluster(ClusterMember4),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
+				{Name: ClusterMember4, Cluster: helper.NewCluster(ClusterMember4)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 7,
@@ -192,11 +192,11 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		},
 		{
 			name: "replica 7, static weighted 2:1:1:1, change replicas from 7 to 8, after change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
-				helper.NewCluster(ClusterMember4),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
+				{Name: ClusterMember4, Cluster: helper.NewCluster(ClusterMember4)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 8, // change replicas from 7 to 8
@@ -232,11 +232,11 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		//    * 4:2:1:2 --> 4:1:1:2 or 4:2:1:1
 		{
 			name: "replica 9, static weighted 2:1:1:1, change replicas from 9 to 8, before change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
-				helper.NewCluster(ClusterMember4),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
+				{Name: ClusterMember4, Cluster: helper.NewCluster(ClusterMember4)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 9,
@@ -262,11 +262,11 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		},
 		{
 			name: "replica 9, static weighted 2:1:1:1, change replicas from 9 to 8, after change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
-				helper.NewCluster(ClusterMember4),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
+				{Name: ClusterMember4, Cluster: helper.NewCluster(ClusterMember4)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 8,
@@ -299,11 +299,11 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		// 4. check four member cluster replicas, the result should be 3:1:1:1
 		{
 			name: "replica 6, static weighted 1:1:1:1, change static weighted from 1:1:1:1 to 2:1:1:1, before change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
-				helper.NewCluster(ClusterMember4),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
+				{Name: ClusterMember4, Cluster: helper.NewCluster(ClusterMember4)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 6,
@@ -329,11 +329,11 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		},
 		{
 			name: "replica 6, static weighted 1:1:1:1, change static weighted from 1:1:1:1 to 2:1:1:1, after change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
-				helper.NewCluster(ClusterMember4),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
+				{Name: ClusterMember4, Cluster: helper.NewCluster(ClusterMember4)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 6,
@@ -372,10 +372,10 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		//    * 1:2:2 --> 1:1:2:1 or 1:2:1:1
 		{
 			name: "replica 5, static weighted 1:1:1, add a new cluster and change static weight to 1:1:1:1, before change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 5,
@@ -400,11 +400,11 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		},
 		{
 			name: "replica 5, static weighted 1:1:1, add a new cluster and change static weight to 1:1:1:1, after change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
-				helper.NewCluster(ClusterMember4),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
+				{Name: ClusterMember4, Cluster: helper.NewCluster(ClusterMember4)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 5,
@@ -440,11 +440,11 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		//    * 1:2:2:1 --> 2:2:2
 		{
 			name: "replica 6, static weighted 1:1:1:1, remove a cluster and change static weight to 1:1:1, before change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
-				helper.NewCluster(ClusterMember4),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
+				{Name: ClusterMember4, Cluster: helper.NewCluster(ClusterMember4)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 6,
@@ -470,10 +470,10 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 		},
 		{
 			name: "replica 6, static weighted 1:1:1:1, remove a cluster and change static weight to 1:1:1, after change",
-			clusters: []*clusterv1alpha1.Cluster{
-				helper.NewCluster(ClusterMember1),
-				helper.NewCluster(ClusterMember2),
-				helper.NewCluster(ClusterMember3),
+			clusters: []spreadconstraint.ClusterDetailInfo{
+				{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+				{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+				{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
 			},
 			object: workv1alpha2.ResourceBindingSpec{
 				Replicas: 6,
diff --git a/pkg/scheduler/core/spreadconstraint/select_clusters_by_region_test.go b/pkg/scheduler/core/spreadconstraint/select_clusters_by_region_test.go
index 71c99c755..1c0ad3e11 100644
--- a/pkg/scheduler/core/spreadconstraint/select_clusters_by_region_test.go
+++ b/pkg/scheduler/core/spreadconstraint/select_clusters_by_region_test.go
@@ -571,8 +571,12 @@ func Test_selectBestClustersByRegion(t *testing.T) {
 				t.Errorf("selectBestClustersByRegion() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			}
-			if !reflect.DeepEqual(got, tt.want) {
-				t.Errorf("selectBestClustersByRegion() = %v, want %v", got, tt.want)
+			var ret []*clusterv1alpha1.Cluster
+			for i := range got {
+				ret = append(ret, got[i].Cluster)
+			}
+			if !reflect.DeepEqual(ret, tt.want) {
+				t.Errorf("selectBestClustersByRegion() = %v, want %v", ret, tt.want)
 			}
 		})
 	}
diff --git a/pkg/scheduler/core/spreadconstraint/select_clusters_test.go b/pkg/scheduler/core/spreadconstraint/select_clusters_test.go
index 8ee457041..a8b6e885f 100644
--- a/pkg/scheduler/core/spreadconstraint/select_clusters_test.go
+++ b/pkg/scheduler/core/spreadconstraint/select_clusters_test.go
@@ -78,7 +78,7 @@ func TestSelectBestClusters(t *testing.T) {
 	tests := []struct {
 		name    string
 		args    args
-		want    []*clusterv1alpha1.Cluster
+		want    []ClusterDetailInfo
 		wantErr error
 	}{
 		{
@@ -98,9 +98,9 @@ func TestSelectBestClusters(t *testing.T) {
 				},
 				needReplicas: 100,
 			},
-			want: []*clusterv1alpha1.Cluster{
-				clusterInfos[0].Cluster,
-				clusterInfos[1].Cluster,
+			want: []ClusterDetailInfo{
+				clusterInfos[0],
+				clusterInfos[1],
 			},
 		},
 		{
@@ -123,9 +123,9 @@ func TestSelectBestClusters(t *testing.T) {
 				},
 				needReplicas: 120,
 			},
-			want: []*clusterv1alpha1.Cluster{
-				clusterInfos[0].Cluster,
-				clusterInfos[1].Cluster,
+			want: []ClusterDetailInfo{
+				clusterInfos[0],
+				clusterInfos[1],
 			},
 		},
 		{
@@ -159,11 +159,11 @@ func TestSelectBestClusters(t *testing.T) {
 				},
 				needReplicas: 120,
 			},
-			want: []*clusterv1alpha1.Cluster{
-				clusterInfos[0].Cluster,
-				clusterInfos[1].Cluster,
-				clusterInfos[2].Cluster,
-				clusterInfos[3].Cluster,
+			want: []ClusterDetailInfo{
+				clusterInfos[0],
+				clusterInfos[1],
+				clusterInfos[2],
+				clusterInfos[3],
 			},
 		},
 		{
@@ -186,9 +186,9 @@ func TestSelectBestClusters(t *testing.T) {
 				},
 				needReplicas: 120,
 			},
-			want: []*clusterv1alpha1.Cluster{
-				clusterInfos[0].Cluster,
-				clusterInfos[2].Cluster,
+			want: []ClusterDetailInfo{
+				clusterInfos[0],
+				clusterInfos[2],
 			},
 		},
 		{
diff --git a/pkg/scheduler/core/util_test.go b/pkg/scheduler/core/util_test.go
index 4b5bc63f0..a6b1d4ebc 100644
--- a/pkg/scheduler/core/util_test.go
+++ b/pkg/scheduler/core/util_test.go
@@ -20,14 +20,14 @@ import (
 	"reflect"
 	"testing"
 
-	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
 	workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
+	"github.com/karmada-io/karmada/pkg/scheduler/core/spreadconstraint"
 	"github.com/karmada-io/karmada/test/helper"
 )
 
 func Test_attachZeroReplicasCluster(t *testing.T) {
 	type args struct {
-		clusters       []*clusterv1alpha1.Cluster
+		clusters       []spreadconstraint.ClusterDetailInfo
 		targetClusters []workv1alpha2.TargetCluster
 	}
 	tests := []struct {
@@ -38,10 +38,10 @@ func Test_attachZeroReplicasCluster(t *testing.T) {
 		{
 			name: "clusters: member1,member2,member3, targetClusters:member1,member2",
 			args: args{
-				clusters: []*clusterv1alpha1.Cluster{
-					helper.NewCluster(ClusterMember1),
-					helper.NewCluster(ClusterMember2),
-					helper.NewCluster(ClusterMember3),
+				clusters: []spreadconstraint.ClusterDetailInfo{
+					{Name: ClusterMember1, Cluster: helper.NewCluster(ClusterMember1)},
+					{Name: ClusterMember2, Cluster: helper.NewCluster(ClusterMember2)},
+					{Name: ClusterMember3, Cluster: helper.NewCluster(ClusterMember3)},
 				},
 				targetClusters: []workv1alpha2.TargetCluster{
 					{
diff --git a/pkg/util/helper/mcs_test.go b/pkg/util/helper/mcs_test.go
index 8fd874f09..f21bf23a3 100644
--- a/pkg/util/helper/mcs_test.go
+++ b/pkg/util/helper/mcs_test.go
@@ -28,6 +28,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
+	"github.com/karmada-io/karmada/pkg/util"
 	"github.com/karmada-io/karmada/pkg/util/gclient"
 )
 
@@ -223,3 +224,50 @@ func TestDeleteEndpointSlice(t *testing.T) {
 		})
 	}
 }
+
+func TestIsEndpointSliceManagedByKarmada(t *testing.T) {
+	tests := []struct {
+		name   string
+		labels map[string]string
+		want   bool
+	}{
+		{
+			name: "managed by endpointslice-dispatch-controller",
+			labels: map[string]string{
+				discoveryv1.LabelManagedBy: util.EndpointSliceDispatchControllerLabelValue,
+			},
+			want: true,
+		},
+		{
+			name: "managed by endpointslice-controller",
+			labels: map[string]string{
+				discoveryv1.LabelManagedBy: util.EndpointSliceControllerLabelValue,
+			},
+			want: true,
+		},
+		{
+			name: "not managed by karmada",
+			labels: map[string]string{
+				discoveryv1.LabelManagedBy: "not-karmada",
+			},
+			want: false,
+		},
+		{
+			name:   "nil labels",
+			labels: nil,
+			want:   false,
+		},
+		{
+			name:   "empty labels",
+			labels: map[string]string{},
+			want:   false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := IsEndpointSliceManagedByKarmada(tt.labels); got != tt.want {
+				t.Errorf("IsEndpointSliceManagedByKarmada() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/util/selector_test.go b/pkg/util/selector_test.go
index a9194e492..15cad2020 100644
--- a/pkg/util/selector_test.go
+++ b/pkg/util/selector_test.go
@@ -1087,3 +1087,149 @@ func Test_matchZones(t *testing.T) {
 		})
 	}
 }
+
+func TestExtractUniqueNamespacedSelectors(t *testing.T) {
+	tests := []struct {
+		name      string
+		selectors []policyv1alpha1.ResourceSelector
+		want      []policyv1alpha1.ResourceSelector
+	}{
+		{
+			name:      "empty selectors",
+			selectors: nil,
+			want:      nil,
+		},
+		{
+			name: "single selector",
+			selectors: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "v1",
+					Kind:       "Pod",
+				},
+			},
+			want: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "v1",
+					Kind:       "Pod",
+				},
+			},
+		},
+		{
+			name: "multiple different selectors",
+			selectors: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "v1",
+					Kind:       "Pod",
+				},
+				{
+					APIVersion: "apps/v1",
+					Kind:       "Deployment",
+				},
+				{
+					APIVersion: "v1",
+					Kind:       "Service",
+				},
+			},
+			want: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "v1",
+					Kind:       "Pod",
+				},
+				{
+					APIVersion: "apps/v1",
+					Kind:       "Deployment",
+				},
+				{
+					APIVersion: "v1",
+					Kind:       "Service",
+				},
+			},
+		},
+		{
+			name: "duplicate selectors should be deduplicated",
+			selectors: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "v1",
+					Kind:       "Pod",
+				},
+				{
+					APIVersion: "apps/v1",
+					Kind:       "Deployment",
+				},
+				{
+					APIVersion: "v1",
+					Kind:       "Pod", // duplicate
+				},
+				{
+					APIVersion: "apps/v1",
+					Kind:       "Deployment", // duplicate
+				},
+			},
+			want: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "v1",
+					Kind:       "Pod",
+				},
+				{
+					APIVersion: "apps/v1",
+					Kind:       "Deployment",
+				},
+			},
+		},
+		{
+			name: "selectors with same kind but different API versions",
+			selectors: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "extensions/v1beta1",
+					Kind:       "Deployment",
+				},
+				{
+					APIVersion: "apps/v1",
+					Kind:       "Deployment",
+				},
+			},
+			want: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "extensions/v1beta1",
+					Kind:       "Deployment",
+				},
+				{
+					APIVersion: "apps/v1",
+					Kind:       "Deployment",
+				},
+			},
+		},
+		{
+			name: "selectors with custom resources",
+			selectors: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "custom.io/v1alpha1",
+					Kind:       "CustomResource",
+				},
+				{
+					APIVersion: "example.com/v1beta1",
+					Kind:       "ExampleResource",
+				},
+			},
+			want: []policyv1alpha1.ResourceSelector{
+				{
+					APIVersion: "custom.io/v1alpha1",
+					Kind:       "CustomResource",
+				},
+				{
+					APIVersion: "example.com/v1beta1",
+					Kind:       "ExampleResource",
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := ExtractUniqueNamespacedSelectors(tt.selectors)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("ExtractUniqueNamespacedSelectors() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}