Repository: karmada-io/karmada

Issue:
StaticWeightList/TakeByWeight may reduce replicas in some clusters when total replicas increase
### Description

When distributing replicas across clusters using [TakeByWeight](https://github.com/karmada-io/karmada/blob/b0b0bb482e6a963d10850b8b72723079c83588af/pkg/util/helper/binding.go#L112), the rounding and remainder handling logic can cause temporary inconsistencies in the total number of active replicas.

Specifically, after rounding down fractional allocations (`int32` cast), the remaining replicas are redistributed sequentially by incrementing each cluster’s replica count. However, this can lead to scenarios where one cluster’s replicas are reduced compared to the previous iteration, causing the overall system to run with fewer replicas than intended until all clusters catch up.

This is particularly problematic for us because we deploy **LLM servers across multiple regions**, and **a new server can take up to \~10 minutes to become ready**. During that time, the system may run noticeably below the requested replica count, affecting availability.

### Weights

The following examples use our production weights:

* `southcentralus = 170`
* `westus = 110`
* `eastus2 = 60`
  (sum = 340)

### Example

* **Total replicas = 5**

  * Ideal (weight/sum × total):

    * `southcentralus` = 5 × 170/340 = **2.5** → 2
    * `westus`        = 5 × 110/340 = **1.6176…** → 1
    * `eastus2`       = 5 × 60/340  = **0.8823…** → 0
  * After remainder pass (current logic: +1 in order): **3, 2, 0**

* **Total replicas = 6**

  * Ideal:

    * `southcentralus` = 6 × 170/340 = **3.0** → 3
    * `westus`        = 6 × 110/340 = **1.9411…** → 1
    * `eastus2`       = 6 × 60/340  = **1.0588…** → 1
  * After remainder pass: **4, 1, 1**
  * **Issue:** Compared to the 5-replica case, `westus` drops from **2 → 1**. Given \~10 minutes startup for new LLM servers, we effectively run at only **4 active replicas** for a noticeable period.

### Expected Behavior

* When the global total increases, **no cluster’s replica count should decrease**

Discussion:
@RainbowMango: > When the global total increases, no cluster’s replica count should decrease

I totally agree. During scale-up, the replica on the existing cluster should never be reduced. 

I will try to reproduce it on my side.

@akakakakakaa by the way, do you have an idea how to fix it?
@akakakakakaa: I am not very familiar with how this kind of issue is usually solved. To explore possible approaches, I asked GPT for some ideas. One of the suggestions was to switch from the current rounding logic to a divisor method (e.g., D’Hondt or Webster). After reviewing the idea, it seems like a reasonable solution.

---
## Divisor Method

A divisor method assigns replicas incrementally, always giving the “next replica” to the cluster with the highest priority score.

* **D’Hondt (Jefferson):** favors larger weights slightly.
* **Webster (Sainte-Laguë):** more neutral / proportional.
* Both guarantee that when total replicas increase, **no cluster’s allocation decreases**.
* Because this approach is implemented with a **priority queue**, replicas in each cluster grow **linearly with total replicas**, and allocations remain consistent:

  * if two clusters have the same weight, the one with fewer replicas will always be prioritized until they balance.
---
```go
package main

import (
	"container/heap"
	"fmt"
)

// ==== Data structures ====

// Cluster allocation target
type TargetCluster struct {
	Name     string
	Replicas int32
}

// Item used in the priority queue
type Item struct {
	name   string
	weight float64
	alloc  int
	method string
}

// Score calculation (priority)
func (it Item) score() float64 {
	switch it.method {
	case "webster": // Webster (Sainte-Laguë): divisors 1,3,5,...
		return it.weight / float64(2*it.alloc+1)
	default: // D'Hondt (Jefferson): divisors 1,2,3,...
		return it.weight / float64(it.alloc+1)
	}
}

// PriorityQueue implements heap.Interface
type PriorityQueue []Item

func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool {
	// max-heap by score
	si, sj := pq[i].score(), pq[j].score()
	if si != sj {
		return si > sj
	}
	// tie-break by weight, then name
	if pq[i].weight != pq[j].weight {
		return pq[i].weight > pq[j].weight
	}
	return pq[i].name < pq[j].name
}
func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] }

func (pq *PriorityQueue) Push(x interface{}) {
	*pq = append(*pq, x.(Item))
}
func (pq *PriorityQueue) Pop() interface{} {
	old := *pq
	n := len(old)
	item := old[n-1]
	*pq = old[0 : n-1]
	return item
}

// ==== Divisor allocation ====

// TakeByWeightDivisor allocates replicas using D’Hondt or Webster method.
func TakeByWeightDivisor(weights map[string]int, total int, method string) []TargetCluster {
	// Build priority queue
	h := &PriorityQueue{}
	for name, w := range weights {
		*h = append(*h, Item{name: name, weight: float64(w), alloc: 0, method: method})
	}
	heap.Init(h)

	// Assign replicas one by one
	for i := 0; i < total; i++ {
		it := heap.Pop(h).(Item)
		it.alloc++
		heap.Push(h, it)
	}

	// Collect result
	result := make([]TargetCluster, 0, len(*h))
	for h.Len() > 0 {
		it := heap.Pop(h).(Item)
		result = append(result, TargetCluster{
			Name:     it.name,
			Replicas: int32(it.alloc),
		})
	}
	return result
}
```
@RainbowMango: Interesting, I will put this in my queue and explore it further.

@akakakakakaa are you using Karmada now? Or in the phase of evaluation? I'm interested in the use case of running inferencing workloads with Karmada.@akakakakakaa: A couple of months ago we ran into GPU supply issues, which pushed us to move from a single-cluster setup to a multi-region one. Our main requirements were:

1. It should run on Kubernetes for operational ease.
2. Traffic should be easily distributed across regions.
3. Because GPU quotas differ by region, we needed to deploy with different weights per region.

After evaluating different options, we found Karmada to be the best fit and adopted it. We’ve been running it in production for about two months now without major issues. Combined with Istio multicluster, it works very well, even if pods are deployed unevenly across regions, traffic naturally shifts to match capacity.@RainbowMango: That's awesome! I'm glad to hear that. 

Since you are already running Karmada in production, we recommend joining [the Karmada Adopter Group](https://github.com/karmada-io/community/tree/main/adopter-group). This way, you'll receive important updates(like critical bugfixes) and stay connected with the community.

Joining is easy ---  just leave a comment on the https://github.com/karmada-io/karmada/issues/4540, then the community member will help you get onboard. @RainbowMango: I reproduced this on my side: 

**1: Create a Deployment with `5` replicas as follows:**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 5
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
```

**2: Create a PropagationPolicy with cluster weight `170:110:60` (member1:member2:member3):**
```yaml
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
  name: nginx-propagation
spec:
  resourceSelectors:
    - apiVersion: apps/v1
      kind: Deployment
      name: nginx
  placement:
    clusterAffinity:
      clusterNames:
        - member1
        - member2
        - member3
    replicaScheduling:
      replicaDivisionPreference: Weighted
      replicaSchedulingType: Divided
      weightPreference:
        staticWeightList:
          - targetCluster:
              clusterNames:
                - member1
            weight: 170
          - targetCluster:
              clusterNames:
                - member2
            weight: 110
          - targetCluster:
              clusterNames:
                - member3
            weight: 60
    spreadConstraints:
      - spreadByField: cluster
        maxGroups: 3
        minGroups: 3
```

**3: Check schedule result: `member1: 3`, `member2:2`**
```
I0909 09:01:51.748566       1 generic_scheduler.go:108] Assigned Replicas: [{member1 3} {member2 2}]
```

**4: Scale deployment replicas from `5` to `6`**
```
-bash-5.0# kubectl scale deployment nginx --replicas 6
deployment.apps/nginx scaled
```

**5: Check the schedule result: `member1: 4`, `member2:1`, `member3: 1`**
```
I0909 09:07:06.500411       1 generic_scheduler.go:108] Assigned Replicas: [{member1 4} {member2 1} {member3 1}]
```

This test shows the replicas in `member2` reduced from `2` to `1` during the scale operation. This is the unexpected behavior. @RainbowMango: @akakakakakaa Now I can confirm that the root cause is exactly as you mentioned, the [TakeByWeight](https://github.com/karmada-io/karmada/blob/b0b0bb482e6a963d10850b8b72723079c83588af/pkg/util/helper/binding.go#L112) uses an unstable algorithm. 
I will continue to investigate how to fix it. (will look at the solution you provided above)@RainbowMango: @akakakakakaa The `D’Hondt` algorithm you provided above looks pretty good, but I need to explore it further, as [another scenario](https://github.com/karmada-io/karmada/issues/6424) requires each cluster to have at least one replica assigned. 

By the way, what do you think of [the adopter thing](https://github.com/karmada-io/karmada/issues/6735#issuecomment-3264943038) I mentioned above?@FYLSunghwan: Hi @RainbowMango,
I’m Sunghwan Kim, an ML Engineer working together with @akakakakakaa.

Our team is positive about joining the Adopter Group, and as you suggested, we’ve already created an [issue comment](https://github.com/karmada-io/karmada/issues/4540#issuecomment-3279110408) to apply.
We look forward to work with the Karmada community to address challenges in serving LLMs across multi-cluster environments :)@RainbowMango: Awesome! Thank you @FYLSunghwan and @akakakakakaa. We'd love to cooperate with every user. I believe this issue would be a good start.

I just created https://github.com/karmada-io/community/issues/160 to get you onboard.@RainbowMango: A quick update on this issue: I've been working on it over the past few days. I have almost finished the new algorithm, but given that this feature is widely used, I just sent #6757 to build the test base, so that we can clearly identify the effect of adopting the new algorithm.@RainbowMango: Another quick update:

I am trying to adopt [the Webster method](https://en.wikipedia.org/wiki/Sainte-Lagu%C3%AB_method) to refactor the [TakeByWeight](https://github.com/karmada-io/karmada/blob/b0b0bb482e6a963d10850b8b72723079c83588af/pkg/util/helper/binding.go#L112), it breaks quite a lot of unit tests. I've fixed most of them. But some of the tests were introduced by [another feature](https://github.com/karmada-io/karmada/issues/4220), which needs more time to evaluate the effect.

Here is the code snapshot: https://github.com/RainbowMango/karmada/commit/f87985d892b74737770b20107428a7f0cd68b5e2
And failing tests are:
```
-bash-5.0# go test ./pkg/scheduler/core/... -run=Test_EvenDistributionOfReplicas
I0918 17:19:53.939512 3249861 defaults.go:54] default Karmada Image Version: v0.0.0-master
--- FAIL: Test_EvenDistributionOfReplicas (0.00s)
    --- FAIL: Test_EvenDistributionOfReplicas/replica_7,_static_weighted_2:1:1:1,_change_replicas_from_7_to_8,_after_change (0.00s)
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 3} {member2 2} {member3 1} {member4 2}], wants 3:2:1:2 --> [4:2:1:1]
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 3} {member2 2} {member3 1} {member4 2}], wants 3:2:1:2 --> [4:1:2:1]
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 3} {member2 2} {member3 1} {member4 2}], wants 3:2:1:2 --> [4:1:1:2]
    --- FAIL: Test_EvenDistributionOfReplicas/replica_9,_static_weighted_2:1:1:1,_change_replicas_from_9_to_8,_before_change (0.00s)
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 3} {member2 2} {member3 2} {member4 2}], wants 3:2:2:2 --> [4:2:2:1 4:1:2:2 4:2:1:2]
    --- FAIL: Test_EvenDistributionOfReplicas/replica_9,_static_weighted_2:1:1:1,_change_replicas_from_9_to_8,_after_change (0.00s)
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 3} {member2 2} {member3 1} {member4 2}], wants 3:2:1:2 --> [4:1:1:2 4:1:2:1]
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 3} {member2 2} {member3 1} {member4 2}], wants 3:2:1:2 --> [4:1:1:2 4:2:1:1]
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 3} {member2 2} {member3 1} {member4 2}], wants 3:2:1:2 --> [4:2:1:1 4:1:2:1]
    --- FAIL: Test_EvenDistributionOfReplicas/replica_5,_static_weighted_1:1:1,_add_a_new_cluster_and_change_static_weight_to_1:1:1:1,_after_change (0.00s)
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 1} {member2 1} {member3 1} {member4 2}], wants 1:1:1:2 --> [1:2:1:1 2:1:1:1]
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 1} {member2 1} {member3 1} {member4 2}], wants 1:1:1:2 --> [1:1:2:1 2:1:1:1]
        generic_scheduler_test.go:527: AssignReplicas() got = [{member1 1} {member2 1} {member3 1} {member4 2}], wants 1:1:1:2 --> [1:1:2:1 1:2:1:1]
FAIL
FAIL	github.com/karmada-io/karmada/pkg/scheduler/core	0.023s
```
@RainbowMango: @akakakakakaa I've sent another PR #6772 to get the priority queue onboard. And after that, the [TakeByWeight](https://github.com/karmada-io/karmada/blob/b0b0bb482e6a963d10850b8b72723079c83588af/pkg/util/helper/binding.go#L112) will be refactored by follow-up PRs iteratively.  To ensure the changes won't break anything else, just fix the bug. All the tasks are tracked by #6739.  (By the way, if you want to join the iteration, just leave a comment and claim which task you'd like to take.)@RainbowMango: @akakakakakaa @FYLSunghwan 
This issue has been resolved by #6793. And the patch is included in [the Karmada v1.15.1 release](https://github.com/karmada-io/karmada/releases/tag/v1.15.1), which was available on Sep 30, 2025.  Please feel free to let us know if you need any help, like upgrading your Karmada deployment. 

Solution patch:
diff --git a/pkg/scheduler/core/assignment.go b/pkg/scheduler/core/assignment.go
index 7f2619cd0..94e397c9c 100644
--- a/pkg/scheduler/core/assignment.go
+++ b/pkg/scheduler/core/assignment.go
@@ -199,8 +199,8 @@ func assignByStaticWeightStrategy(state *assignState) ([]workv1alpha2.TargetClus
 	}
 	weightList := getStaticWeightInfoList(state.candidates, state.strategy.WeightPreference.StaticWeightList, state.spec.Clusters)
 
-	disp := helper.NewDispenser(state.spec.Replicas, nil)
-	disp.TakeByWeight(weightList)
+	disp := helper.NewDispenser(state.spec.Replicas, nil, state.spec.Resource.UID)
+	disp.AllocateByWeight(weightList)
 
 	return disp.Result, nil
 }
diff --git a/pkg/util/helper/binding.go b/pkg/util/helper/binding.go
index 6cde1398a..167f3a3ef 100644
--- a/pkg/util/helper/binding.go
+++ b/pkg/util/helper/binding.go
@@ -19,6 +19,7 @@ package helper
 import (
 	"context"
 	"crypto/rand"
+	"hash/fnv"
 	"math/big"
 	"sort"
 
@@ -30,6 +31,7 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/errors"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/dynamic"
@@ -65,6 +67,8 @@ func (p ClusterWeightInfoList) Less(i, j int) bool {
 	if p[i].Weight != p[j].Weight {
 		return p[i].Weight > p[j].Weight
 	}
+
+	// TODO(@zhzhuang-zju): remove this part after we remove the legacy TakeByWeight method.
 	// when weights is equal, sort by last scheduling replicas result,
 	// more last scheduling replicas means the remainders of the last scheduling were randomized to such clusters,
 	// so in order to keep the inertia in this scheduling, such clusters should also be prioritized
@@ -94,13 +98,16 @@ type Dispenser struct {
 	NumReplicas int32
 	// Final result.
 	Result []workv1alpha2.TargetCluster
+	// TieBreaker is used to break the tie.
+	TieBreaker func(a, b Party) bool
 }
 
 // NewDispenser will construct a dispenser with target replicas and a prescribed initial result.
-func NewDispenser(numReplicas int32, init []workv1alpha2.TargetCluster) *Dispenser {
+// uuid is used to generate a random but stable tiebreaker function, which is used to break the tie when the parties have the same priority.
+func NewDispenser(numReplicas int32, init []workv1alpha2.TargetCluster, uuid types.UID) *Dispenser {
 	cp := make([]workv1alpha2.TargetCluster, len(init))
 	copy(cp, init)
-	return &Dispenser{NumReplicas: numReplicas, Result: cp}
+	return &Dispenser{NumReplicas: numReplicas, Result: cp, TieBreaker: tieBreakerByUID(uuid)}
 }
 
 // Done indicates whether finish dispensing.
@@ -108,7 +115,11 @@ func (a *Dispenser) Done() bool {
 	return a.NumReplicas == 0 && len(a.Result) != 0
 }
 
-// TakeByWeight divide replicas by a weight list and merge the result into previous result.
+// TakeByWeight divides replicas according to a weight list and merges the result into the previous result.
+// Legacy method; will be removed in the future. This algorithm suffers from the Alabama Paradox,
+// where increasing the total number of replicas can cause some clusters to receive fewer replicas.
+// For details, see: https://github.com/karmada-io/karmada/issues/6735.
+// TODO(@zhzhuang-zju): remove this method and use AllocateByWeight instead.
 func (a *Dispenser) TakeByWeight(w ClusterWeightInfoList) {
 	if a.Done() {
 		return
@@ -143,6 +154,70 @@ func (a *Dispenser) TakeByWeight(w ClusterWeightInfoList) {
 	a.Result = util.MergeTargetClusters(a.Result, result)
 }
 
+// AllocateByWeight divides replicas by Webster method.
+func (a *Dispenser) AllocateByWeight(w ClusterWeightInfoList) {
+	if a.Done() {
+		return
+	}
+	sum := w.GetWeightSum()
+	if sum == 0 {
+		return
+	}
+
+	var initialAssignments = make(map[string]int32, len(a.Result))
+	for _, cluster := range a.Result {
+		initialAssignments[cluster.Name] = cluster.Replicas
+	}
+
+	partyVotes := make(map[string]int64, len(w))
+	for _, info := range w {
+		partyVotes[info.ClusterName] = info.Weight
+	}
+
+	a.Result = convertPartiesToTargetClusters(AllocateWebsterSeats(a.NumReplicas, partyVotes, initialAssignments, a.TieBreaker))
+	a.NumReplicas = 0
+}
+
+func tieBreakerByUID(uid types.UID) func(a, b Party) bool {
+	// sortNameDescending gives higher priority to parties with lexicographically larger names in case of a tie.
+	sortNameDescending := func(a, b Party) bool {
+		// The party with fewer seats wins the tie.
+		if a.Seats != b.Seats {
+			return a.Seats < b.Seats
+		}
+		return a.Name > b.Name
+	}
+	// sortNameAscending gives higher priority to parties with lexicographically smaller names in case of a tie.
+	sortNameAscending := func(a, b Party) bool {
+		// The party with fewer seats wins the tie.
+		if a.Seats != b.Seats {
+			return a.Seats < b.Seats
+		}
+		return a.Name < b.Name
+	}
+	if len(uid) == 0 {
+		return sortNameAscending
+	}
+
+	h := fnv.New32a()
+	h.Write([]byte(uid))
+	if h.Sum32()&1 == 1 {
+		return sortNameDescending
+	}
+	return sortNameAscending
+}
+
+func convertPartiesToTargetClusters(parties []Party) []workv1alpha2.TargetCluster {
+	result := make([]workv1alpha2.TargetCluster, 0, len(parties))
+	for _, party := range parties {
+		result = append(result, workv1alpha2.TargetCluster{
+			Name:     party.Name,
+			Replicas: party.Seats,
+		})
+	}
+	return result
+}
+
 // GetStaticWeightInfoListByTargetClusters constructs a weight list by target cluster slice.
 func GetStaticWeightInfoListByTargetClusters(tcs, scheduled []workv1alpha2.TargetCluster) ClusterWeightInfoList {
 	weightList := make(ClusterWeightInfoList, 0, len(tcs))
@@ -166,7 +241,7 @@ func GetStaticWeightInfoListByTargetClusters(tcs, scheduled []workv1alpha2.Targe
 // SpreadReplicasByTargetClusters divides replicas by the weight of a target cluster list.
 func SpreadReplicasByTargetClusters(numReplicas int32, tcs, init []workv1alpha2.TargetCluster) []workv1alpha2.TargetCluster {
 	weightList := GetStaticWeightInfoListByTargetClusters(tcs, init)
-	disp := NewDispenser(numReplicas, init)
+	disp := NewDispenser(numReplicas, init, "")
 	disp.TakeByWeight(weightList)
 	return disp.Result
 }



Test patch:
diff --git a/pkg/scheduler/core/assignment_test.go b/pkg/scheduler/core/assignment_test.go
index 881bc97a0..45b087a50 100644
--- a/pkg/scheduler/core/assignment_test.go
+++ b/pkg/scheduler/core/assignment_test.go
@@ -123,6 +123,52 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 			},
 			wantErr: false,
 		},
+		{
+			name: "replica 13, weight 3:2:1",
+			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{
+					{
+						TargetCluster: policyv1alpha1.ClusterAffinity{
+							ClusterNames: []string{ClusterMember1},
+						},
+						Weight: 3,
+					},
+					{
+						TargetCluster: policyv1alpha1.ClusterAffinity{
+							ClusterNames: []string{ClusterMember2},
+						},
+						Weight: 2,
+					},
+					{
+						TargetCluster: policyv1alpha1.ClusterAffinity{
+							ClusterNames: []string{ClusterMember3},
+						},
+						Weight: 1,
+					},
+				},
+			},
+			replicas: 13,
+			want: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 7,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 4,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 2,
+				},
+			},
+			wantErr: false,
+		},
 		{
 			name: "replica 14, weight 3:2:1",
 			clusters: []spreadconstraint.ClusterDetailInfo{
@@ -156,11 +202,11 @@ func Test_assignByStaticWeightStrategy(t *testing.T) {
 			want: []workv1alpha2.TargetCluster{
 				{
 					Name:     ClusterMember1,
-					Replicas: 8,
+					Replicas: 7,
 				},
 				{
 					Name:     ClusterMember2,
-					Replicas: 4,
+					Replicas: 5,
 				},
 				{
 					Name:     ClusterMember3,
diff --git a/pkg/scheduler/core/generic_scheduler_test.go b/pkg/scheduler/core/generic_scheduler_test.go
index e6e441569..54ca4ba9e 100644
--- a/pkg/scheduler/core/generic_scheduler_test.go
+++ b/pkg/scheduler/core/generic_scheduler_test.go
@@ -17,8 +17,7 @@ limitations under the License.
 package core
 
 import (
-	"strconv"
-	"strings"
+	"reflect"
 	"testing"
 
 	policyv1alpha1 "github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1"
@@ -28,43 +27,14 @@ import (
 )
 
 type testcase struct {
-	name                      string
-	clusters                  []spreadconstraint.ClusterDetailInfo
-	object                    workv1alpha2.ResourceBindingSpec
-	previousResultToNewResult map[string][]string
-	wantErr                   bool
+	name     string
+	clusters []spreadconstraint.ClusterDetailInfo
+	object   workv1alpha2.ResourceBindingSpec
+	result   []workv1alpha2.TargetCluster
 }
 
-var clusterToIndex = map[string]int{ClusterMember1: 0, ClusterMember2: 1, ClusterMember3: 2, ClusterMember4: 3}
-var indexToCluster = []string{ClusterMember1, ClusterMember2, ClusterMember3, ClusterMember4}
-
-func targetClusterToString(tcs []workv1alpha2.TargetCluster) string {
-	res := make([]string, len(tcs))
-	for _, cluster := range tcs {
-		idx := clusterToIndex[cluster.Name]
-		res[idx] = strconv.Itoa(int(cluster.Replicas))
-	}
-	return strings.Join(res, ":")
-}
-
-func stringToTargetCluster(str string) []workv1alpha2.TargetCluster {
-	arr := strings.Split(str, ":")
-	tcs := make([]workv1alpha2.TargetCluster, len(arr))
-	for i, replicas := range arr {
-		num, _ := strconv.Atoi(replicas)
-		tcs[i].Replicas = int32(num) // #nosec G109,G115: integer overflow conversion int -> int32
-		tcs[i].Name = indexToCluster[i]
-	}
-	return tcs
-}
-
-// These are acceptance test cases given by QA for requirement: dividing replicas by static weight evenly
-// https://github.com/karmada-io/karmada/issues/4220
-func Test_EvenDistributionOfReplicas(t *testing.T) {
+func Test_DistributionOfReplicas(t *testing.T) {
 	tests := []testcase{
-		// Test Case No.1 of even distribution of replicas: random assignment of static weight remainder
-		// 1. create deployment (replicas=3), weight=1:1
-		// 2. check two member cluster replicas, should be 2:1 or 1:2
 		{
 			name: "replica 3, static weighted 1:1",
 			clusters: []spreadconstraint.ClusterDetailInfo{
@@ -86,16 +56,17 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"": {"1:2", "2:1"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
-		// Test Case No.2 of even distribution of replicas: random assignment of static weight remainder in expanding replicas scenarios
-		// 1. create deployment (replicas=3), weight=1:1:1
-		// 2. check three member cluster replicas, should be 1:1:1
-		// 3. update replicas from 3 to 5
-		// 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: []spreadconstraint.ClusterDetailInfo{
@@ -119,10 +90,20 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"": {"1:1:1"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
 		{
 			name: "replica 3, static weighted 1:1:1, change replicas from 3 to 5, after change",
@@ -147,19 +128,21 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"1:1:1": {"2:2:1", "2:1:2", "1:2:2"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
-		// Test Case No.3 of even distribution of replicas: ensure as much inertia as possible in expanding replicas scenarios
-		// 1. create deployment (replicas=7), weight=2:1:1:1
-		// 2. check four member cluster replicas, can be 3:2:1:1、3:1:2:1、3:1:1:2
-		// 3. update replicas from 7 to 8
-		// 4. check four member cluster replicas, the added replica should lie in first cluster, i.e:
-		//    * 3:2:1:1 --> 4:2:1:1
-		//    * 3:1:2:1 --> 4:1:2:1
-		//    * 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: []spreadconstraint.ClusterDetailInfo{
@@ -185,10 +168,24 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"": {"3:2:1:1", "3:1:2:1", "3:1:1:2"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 3,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember4,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
 		{
 			name: "replica 7, static weighted 2:1:1:1, change replicas from 7 to 8, after change",
@@ -215,21 +212,25 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"3:2:1:1": {"4:2:1:1"},
-				"3:1:2:1": {"4:1:2:1"},
-				"3:1:1:2": {"4:1:1:2"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 3,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember4,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
-		// Test Case No.4 of even distribution of replicas: ensure as much inertia as possible in reducing replicas scenarios
-		// 1. create deployment (replicas=9), weight=2:1:1:1
-		// 2. check four member cluster replicas, can be 4:2:2:1、4:1:2:2、4:2:1:2
-		// 3. update replicas from 9 to 8
-		// 4. check four member cluster replicas, the reduced replica should be scaled down from cluster with 2 replicas previously, i.e:
-		//    * 4:2:2:1 --> 4:2:1:1 or 4:1:2:1
-		//    * 4:1:2:2 --> 4:1:1:2 or 4:1:2:1
-		//    * 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: []spreadconstraint.ClusterDetailInfo{
@@ -255,10 +256,24 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"": {"4:2:2:1", "4:1:2:2", "4:2:1:2"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 3,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember4,
+					Replicas: 2,
+				},
 			},
-			wantErr: false,
 		},
 		{
 			name: "replica 9, static weighted 2:1:1:1, change replicas from 9 to 8, after change",
@@ -285,18 +300,25 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"4:2:2:1": {"4:2:1:1", "4:1:2:1"},
-				"4:1:2:2": {"4:1:1:2", "4:1:2:1"},
-				"4:2:1:2": {"4:1:1:2", "4:2:1:1"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 3,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember4,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
-		// Test Case No.5 of even distribution of replicas: ensure as much inertia as possible in modifying static weight scenarios
-		// 1. create deployment (replicas=6), weight=1:1:1:1
-		// 2. check four member cluster replicas, can be 2:2:1:1、2:1:2:1、2:1:1:2、1:2:2:1、1:2:1:2、1:1:2:2
-		// 3. change static weight from 1:1:1:1 to 2:1:1:1
-		// 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: []spreadconstraint.ClusterDetailInfo{
@@ -322,10 +344,24 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"": {"2:2:1:1", "2:1:2:1", "2:1:1:2", "1:2:2:1", "1:2:1:2", "1:1:2:2"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember4,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
 		{
 			name: "replica 6, static weighted 1:1:1:1, change static weighted from 1:1:1:1 to 2:1:1:1, after change",
@@ -352,24 +388,25 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"2:2:1:1": {"3:1:1:1"},
-				"2:1:2:1": {"3:1:1:1"},
-				"2:1:1:2": {"3:1:1:1"},
-				"1:2:2:1": {"3:1:1:1"},
-				"1:2:1:2": {"3:1:1:1"},
-				"1:1:2:2": {"3:1:1:1"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 3,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember4,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
-		// Test Case No.6 of even distribution of replicas: ensure as much inertia as possible in adding cluster scenarios
-		// 1. create deployment (replicas=5), weight=1:1:1
-		// 2. check three member cluster replicas, can be 2:2:1、2:1:2、1:2:2
-		// 3. add a new cluster, and change static weight to 1:1:1:1
-		// 4. check four member cluster replicas, the replica in newly add cluster will be scale from cluster with 2 replicas previously, just like:
-		//    * 2:2:1 --> 1:2:1:1 or 2:1:1:1
-		//    * 2:1:2 --> 1:1:2:1 or 2:1:1:1
-		//    * 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: []spreadconstraint.ClusterDetailInfo{
@@ -393,10 +430,20 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"": {"2:2:1", "2:1:2", "1:2:2"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
 		{
 			name: "replica 5, static weighted 1:1:1, add a new cluster and change static weight to 1:1:1:1, after change",
@@ -423,21 +470,25 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"2:2:1": {"1:2:1:1", "2:1:1:1"},
-				"2:1:2": {"1:1:2:1", "2:1:1:1"},
-				"1:2:2": {"1:1:2:1", "1:2:1:1"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember4,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
-		// Test Case No.7 of even distribution of replicas: ensure as much inertia as possible in removing cluster scenarios
-		// 1. create deployment (replicas=6), weight=1:1:1:1
-		// 2. check four member cluster replicas, can be 2:2:1:1、2:1:2:1、2:1:1:2、1:2:2:1、1:2:1:2、1:1:2:2
-		// 3. delete the member4 cluster in case of only one replica has been allocated to the member4 cluster
-		// 4. check three left member cluster replicas, the original replica in member4 cluster will be scaled to another cluster with one replica, just like:
-		//    * 2:2:1:1 --> 2:2:2
-		//    * 2:1:2:1 --> 2:2:2
-		//    * 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: []spreadconstraint.ClusterDetailInfo{
@@ -463,10 +514,24 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"": {"2:2:1:1", "2:1:2:1", "2:1:1:2", "1:2:2:1", "1:2:1:2", "1:1:2:2"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 1,
+				},
+				{
+					Name:     ClusterMember4,
+					Replicas: 1,
+				},
 			},
-			wantErr: false,
 		},
 		{
 			name: "replica 6, static weighted 1:1:1:1, remove a cluster and change static weight to 1:1:1, after change",
@@ -491,40 +556,37 @@ func Test_EvenDistributionOfReplicas(t *testing.T) {
 					},
 				},
 			},
-			previousResultToNewResult: map[string][]string{
-				"2:2:1:1": {"2:2:2"},
-				"2:1:2:1": {"2:2:2"},
-				"1:2:2:1": {"2:2:2"},
+			result: []workv1alpha2.TargetCluster{
+				{
+					Name:     ClusterMember1,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember2,
+					Replicas: 2,
+				},
+				{
+					Name:     ClusterMember3,
+					Replicas: 2,
+				},
 			},
-			wantErr: false,
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			var g = &genericScheduler{}
-			for previous, expect := range tt.previousResultToNewResult {
-				obj := tt.object
-
-				// 1. get previous schedule result, if previous == "" means no previous schedule result
-				if previous != "" {
-					obj.Clusters = stringToTargetCluster(previous)
-				}
+			obj := tt.object
 
-				// 2. schedule basing on previous schedule result
-				got, err := g.assignReplicas(tt.clusters, &obj, &workv1alpha2.ResourceBindingStatus{})
-				if (err != nil) != tt.wantErr {
-					t.Errorf("AssignReplicas() error = %v, wantErr %v", err, tt.wantErr)
-					return
-				}
+			// 2. schedule basing on previous schedule result
+			got, err := g.assignReplicas(tt.clusters, &obj, &workv1alpha2.ResourceBindingStatus{})
+			if err != nil {
+				t.Errorf("AssignReplicas() error = %v", err)
+				return
+			}
 
-				// 3. check if schedule result got match to expected
-				gotResult := targetClusterToString(got)
-				for _, expectResult := range expect {
-					if gotResult == expectResult {
-						return
-					}
-				}
-				t.Errorf("AssignReplicas() got = %v, wants %s --> %v", got, gotResult, expect)
+			// 3. check if schedule result got match to expected
+			if !reflect.DeepEqual(got, tt.result) {
+				t.Errorf("AssignReplicas() got = %v, wants %v", got, tt.result)
 			}
 		})
 	}
diff --git a/pkg/util/helper/binding_test.go b/pkg/util/helper/binding_test.go
index 6363030d9..dc6df0b0e 100644
--- a/pkg/util/helper/binding_test.go
+++ b/pkg/util/helper/binding_test.go
@@ -30,6 +30,7 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/client-go/dynamic"
 	dynamicfake "k8s.io/client-go/dynamic/fake"
@@ -52,6 +53,9 @@ const (
 	ClusterMember1 = "member1"
 	ClusterMember2 = "member2"
 	ClusterMember3 = "member3"
+	emptyUID       = ""
+	evenUID        = "4858ca61-3ff8-4095-8267-f3d059d8074c"
+	oddUID         = "d43fdeeb-d750-4e2b-bee3-64eb94534f4c"
 )
 
 func Test_dispenser_takeByWeight(t *testing.T) {
@@ -191,7 +195,7 @@ func Test_dispenser_takeByWeight(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			a := NewDispenser(tt.newReplicas, tt.initialAssignment)
+			a := NewDispenser(tt.newReplicas, tt.initialAssignment, emptyUID)
 			a.TakeByWeight(tt.weightList)
 			if !a.Done() {
 				t.Errorf("dispensing unexpectedly not finished")
@@ -203,6 +207,192 @@ func Test_dispenser_takeByWeight(t *testing.T) {
 	}
 }
 
+func Test_dispenser_AllocateByWeight(t *testing.T) {
+	tests := []struct {
+		name              string
+		newReplicas       int32
+		initialAssignment []workv1alpha2.TargetCluster
+		weightList        ClusterWeightInfoList
+		desired           []workv1alpha2.TargetCluster
+		uuid              types.UID
+	}{
+		{
+			name:        "Scale up 6 replicas",
+			newReplicas: 6,
+			uuid:        emptyUID,
+			initialAssignment: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 1},
+				{Name: "B", Replicas: 2},
+				{Name: "C", Replicas: 3},
+			},
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 1},
+				{ClusterName: "B", Weight: 2},
+				{ClusterName: "C", Weight: 3},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 2},
+				{Name: "B", Replicas: 4},
+				{Name: "C", Replicas: 6},
+			},
+		},
+		{
+			name:        "Scale up 3 replicas",
+			newReplicas: 3,
+			uuid:        emptyUID,
+			initialAssignment: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 1},
+				{Name: "B", Replicas: 2},
+				{Name: "C", Replicas: 3},
+			},
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 1},
+				{ClusterName: "B", Weight: 2},
+				{ClusterName: "C", Weight: 3},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 2},
+				{Name: "B", Replicas: 3},
+				{Name: "C", Replicas: 4},
+			},
+		},
+		{
+			name:        "Scale up 2 replicas",
+			newReplicas: 2,
+			uuid:        emptyUID,
+			initialAssignment: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 1},
+				{Name: "B", Replicas: 2},
+				{Name: "C", Replicas: 3},
+			},
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 1},
+				{ClusterName: "B", Weight: 2},
+				{ClusterName: "C", Weight: 3},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 1},
+				{Name: "B", Replicas: 3},
+				{Name: "C", Replicas: 4},
+			},
+		},
+		{
+			name:              "HA scenario with weight 2:1: assign 1 replica",
+			newReplicas:       1,
+			uuid:              emptyUID,
+			initialAssignment: nil,
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 2},
+				{ClusterName: "B", Weight: 1},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 1},
+				{Name: "B", Replicas: 0},
+			},
+		},
+		{
+			name:              "HA scenario with weight 2:1: assign 2 replicas",
+			newReplicas:       2,
+			uuid:              emptyUID,
+			initialAssignment: nil,
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 2},
+				{ClusterName: "B", Weight: 1},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 1},
+				{Name: "B", Replicas: 1},
+			},
+		},
+		{
+			name:              "HA scenario with weight 2:1: assign 3 replicas",
+			newReplicas:       3,
+			uuid:              emptyUID,
+			initialAssignment: nil,
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 2},
+				{ClusterName: "B", Weight: 1},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 2},
+				{Name: "B", Replicas: 1},
+			},
+		},
+		{
+			name:              "brand new assign 5 replicas",
+			newReplicas:       5,
+			uuid:              emptyUID,
+			initialAssignment: nil,
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 170},
+				{ClusterName: "B", Weight: 110},
+				{ClusterName: "C", Weight: 60},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 2},
+				{Name: "B", Replicas: 2},
+				{Name: "C", Replicas: 1},
+			},
+		},
+		{
+			name:              "brand new assign 6 replicas",
+			newReplicas:       6,
+			uuid:              emptyUID,
+			initialAssignment: nil,
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 170},
+				{ClusterName: "B", Weight: 110},
+				{ClusterName: "C", Weight: 60},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 3},
+				{Name: "B", Replicas: 2},
+				{Name: "C", Replicas: 1},
+			},
+		},
+		{
+			name:              "brand new assign 5 replicas with odd uuid",
+			newReplicas:       5,
+			uuid:              oddUID,
+			initialAssignment: nil,
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 1},
+				{ClusterName: "B", Weight: 1},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 2},
+				{Name: "B", Replicas: 3},
+			},
+		},
+		{
+			name:              "brand new assign 5 replicas with even uuid",
+			newReplicas:       5,
+			uuid:              evenUID,
+			initialAssignment: nil,
+			weightList: []ClusterWeightInfo{
+				{ClusterName: "A", Weight: 1},
+				{ClusterName: "B", Weight: 1},
+			},
+			desired: []workv1alpha2.TargetCluster{
+				{Name: "A", Replicas: 3},
+				{Name: "B", Replicas: 2},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := NewDispenser(tt.newReplicas, tt.initialAssignment, tt.uuid)
+			a.AllocateByWeight(tt.weightList)
+			if !a.Done() {
+				t.Errorf("dispensing unexpectedly not finished")
+			}
+			if !testhelper.IsScheduleResultEqual(a.Result, tt.desired) {
+				t.Errorf("expected result after AllocateByWeight: %v, but got: %v", tt.desired, a.Result)
+			}
+		})
+	}
+}
+
 func TestDispenseReplicasByTargetClusters(t *testing.T) {
 	type args struct {
 		clusters []workv1alpha2.TargetCluster