From d01fccfd3b6b487f23dba9ae834f93126929595a Mon Sep 17 00:00:00 2001 From: Garrybest Date: Sat, 6 Nov 2021 22:39:57 +0800 Subject: [PATCH] add division algorithm test Signed-off-by: Garrybest --- pkg/scheduler/core/division_algorithm_test.go | 715 ++++++++++++++++++ test/helper/resource.go | 23 + test/helper/scheduler.go | 25 + 3 files changed, 763 insertions(+) create mode 100644 pkg/scheduler/core/division_algorithm_test.go create mode 100644 test/helper/scheduler.go diff --git a/pkg/scheduler/core/division_algorithm_test.go b/pkg/scheduler/core/division_algorithm_test.go new file mode 100644 index 000000000..c45887ec8 --- /dev/null +++ b/pkg/scheduler/core/division_algorithm_test.go @@ -0,0 +1,715 @@ +package core + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + + 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/util" + "github.com/karmada-io/karmada/test/helper" +) + +const ( + ClusterMember1 = "member1" + ClusterMember2 = "member2" + ClusterMember3 = "member3" +) + +func Test_divideReplicasByStaticWeight(t *testing.T) { + type args struct { + clusters []*clusterv1alpha1.Cluster + weightList []policyv1alpha1.StaticClusterWeight + replicas int32 + } + tests := []struct { + name string + args args + want []workv1alpha2.TargetCluster + wantErr bool + }{ + { + name: "replica 12, weight 3:2:1", + args: args{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewCluster(ClusterMember1), + helper.NewCluster(ClusterMember2), + helper.NewCluster(ClusterMember3), + }, + weightList: []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: 12, + }, + want: []workv1alpha2.TargetCluster{ + { + Name: ClusterMember1, + Replicas: 6, + }, + { + Name: ClusterMember2, + Replicas: 4, + }, + { + Name: ClusterMember3, + Replicas: 2, + }, + }, + wantErr: false, + }, + { + name: "replica 12, default weight", + args: struct { + clusters []*clusterv1alpha1.Cluster + weightList []policyv1alpha1.StaticClusterWeight + replicas int32 + }{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewCluster(ClusterMember1), + helper.NewCluster(ClusterMember2), + helper.NewCluster(ClusterMember3), + }, + replicas: 12, + }, + want: []workv1alpha2.TargetCluster{ + { + Name: ClusterMember1, + Replicas: 4, + }, + { + Name: ClusterMember2, + Replicas: 4, + }, + { + Name: ClusterMember3, + Replicas: 4, + }, + }, + wantErr: false, + }, + { + name: "replica 14, weight 3:2:1", + args: struct { + clusters []*clusterv1alpha1.Cluster + weightList []policyv1alpha1.StaticClusterWeight + replicas int32 + }{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewCluster(ClusterMember1), + helper.NewCluster(ClusterMember2), + helper.NewCluster(ClusterMember3), + }, + weightList: []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: 14, + }, + want: []workv1alpha2.TargetCluster{ + { + Name: ClusterMember1, + Replicas: 8, + }, + { + Name: ClusterMember2, + Replicas: 4, + }, + { + Name: ClusterMember3, + Replicas: 2, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := divideReplicasByStaticWeight(tt.args.clusters, tt.args.weightList, tt.args.replicas) + if (err != nil) != tt.wantErr { + t.Errorf("divideReplicasByStaticWeight() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !helper.IsScheduleResultEqual(got, tt.want) { + t.Errorf("divideReplicasByStaticWeight() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_divideReplicasByPreference(t *testing.T) { + type args struct { + clusterAvailableReplicas []workv1alpha2.TargetCluster + replicas int32 + clustersMaxReplicas int32 + preference policyv1alpha1.ReplicaDivisionPreference + preUsedClustersName []string + } + tests := []struct { + name string + args args + want []workv1alpha2.TargetCluster + wantErr bool + }{ + { + name: "replica 12, dynamic weight 18:12:6", + args: args{ + clusterAvailableReplicas: TargetClustersList{ + workv1alpha2.TargetCluster{Name: ClusterMember1, Replicas: 18}, + workv1alpha2.TargetCluster{Name: ClusterMember2, Replicas: 12}, + workv1alpha2.TargetCluster{Name: ClusterMember3, Replicas: 6}, + }, + replicas: 12, + clustersMaxReplicas: 36, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + preUsedClustersName: nil, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 6}, + {Name: ClusterMember2, Replicas: 4}, + {Name: ClusterMember3, Replicas: 2}, + }, + wantErr: false, + }, + { + name: "replica 12, dynamic weight 20:12:6", + args: args{ + clusterAvailableReplicas: TargetClustersList{ + workv1alpha2.TargetCluster{Name: ClusterMember1, Replicas: 20}, + workv1alpha2.TargetCluster{Name: ClusterMember2, Replicas: 12}, + workv1alpha2.TargetCluster{Name: ClusterMember3, Replicas: 6}, + }, + replicas: 12, + clustersMaxReplicas: 38, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + preUsedClustersName: nil, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 7}, + {Name: ClusterMember2, Replicas: 4}, + {Name: ClusterMember3, Replicas: 1}, + }, + wantErr: false, + }, + { + name: "replica 12, dynamic weight 6:12:6", + args: args{ + clusterAvailableReplicas: TargetClustersList{ + workv1alpha2.TargetCluster{Name: ClusterMember1, Replicas: 6}, + workv1alpha2.TargetCluster{Name: ClusterMember2, Replicas: 12}, + workv1alpha2.TargetCluster{Name: ClusterMember3, Replicas: 6}, + }, + replicas: 12, + clustersMaxReplicas: 24, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + preUsedClustersName: nil, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 3}, + {Name: ClusterMember2, Replicas: 6}, + {Name: ClusterMember3, Replicas: 3}, + }, + wantErr: false, + }, + { + name: "replica 12, aggregated 12:6:6", + args: args{ + clusterAvailableReplicas: TargetClustersList{ + workv1alpha2.TargetCluster{Name: ClusterMember2, Replicas: 12}, + workv1alpha2.TargetCluster{Name: ClusterMember1, Replicas: 6}, + workv1alpha2.TargetCluster{Name: ClusterMember3, Replicas: 6}, + }, + replicas: 12, + clustersMaxReplicas: 24, + preference: policyv1alpha1.ReplicaDivisionPreferenceAggregated, + preUsedClustersName: nil, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 0}, + {Name: ClusterMember2, Replicas: 12}, + {Name: ClusterMember3, Replicas: 0}, + }, + wantErr: false, + }, + { + name: "replica 12, aggregated 6:6:6", + args: args{ + clusterAvailableReplicas: TargetClustersList{ + workv1alpha2.TargetCluster{Name: ClusterMember1, Replicas: 6}, + workv1alpha2.TargetCluster{Name: ClusterMember2, Replicas: 6}, + workv1alpha2.TargetCluster{Name: ClusterMember3, Replicas: 6}, + }, + replicas: 12, + clustersMaxReplicas: 18, + preference: policyv1alpha1.ReplicaDivisionPreferenceAggregated, + preUsedClustersName: nil, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 6}, + {Name: ClusterMember2, Replicas: 6}, + {Name: ClusterMember3, Replicas: 0}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := divideReplicasByPreference(tt.args.clusterAvailableReplicas, tt.args.replicas, tt.args.preference, tt.args.preUsedClustersName...) + if (err != nil) != tt.wantErr { + t.Errorf("divideReplicasByPreference() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !helper.IsScheduleResultEqual(got, tt.want) { + t.Errorf("divideReplicasByPreference() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_divideReplicasByResource(t *testing.T) { + type args struct { + clusters []*clusterv1alpha1.Cluster + spec *workv1alpha2.ResourceBindingSpec + preference policyv1alpha1.ReplicaDivisionPreference + preUsedClustersName []string + } + tests := []struct { + name string + args args + want []workv1alpha2.TargetCluster + wantErr bool + }{ + { + name: "replica 12, dynamic weight 6:8:10", + args: args{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(6, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 12, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 3}, + {Name: ClusterMember2, Replicas: 4}, + {Name: ClusterMember3, Replicas: 5}, + }, + wantErr: false, + }, + { + name: "replica 12, dynamic weight 8:8:10", + args: args{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 12, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 4}, + {Name: ClusterMember2, Replicas: 3}, + {Name: ClusterMember3, Replicas: 5}, + }, + wantErr: false, + }, + { + name: "replica 12, dynamic weight 3:3:3", + args: args{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 12, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + }, + wantErr: true, + }, + { + name: "replica 12, aggregated 6:8:10", + args: args{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(6, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 12, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceAggregated, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 0}, + {Name: ClusterMember2, Replicas: 5}, + {Name: ClusterMember3, Replicas: 7}, + }, + wantErr: false, + }, + { + name: "replica 12, aggregated 12:8:10", + args: args{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(12, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(8, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 12, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceAggregated, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 12}, + {Name: ClusterMember2, Replicas: 0}, + {Name: ClusterMember3, Replicas: 0}, + }, + wantErr: false, + }, + { + name: "replica 12, aggregated 3:3:3", + args: args{ + clusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(3, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 12, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceAggregated, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := divideReplicasByResource(tt.args.clusters, tt.args.spec, tt.args.preference, tt.args.preUsedClustersName...) + if (err != nil) != tt.wantErr { + t.Errorf("divideReplicasByResource() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !helper.IsScheduleResultEqual(got, tt.want) { + t.Errorf("divideReplicasByResource() got = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_scaleScheduleByReplicaDivisionPreference(t *testing.T) { + type args struct { + spec *workv1alpha2.ResourceBindingSpec + preference policyv1alpha1.ReplicaDivisionPreference + preSelectedClusters []*clusterv1alpha1.Cluster + } + tests := []struct { + name string + args args + want []workv1alpha2.TargetCluster + wantErr bool + }{ + { + name: "replica 12 -> 6, dynamic weighted 2:4:6", + args: args{ + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 6, + Clusters: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 2}, + {Name: ClusterMember2, Replicas: 4}, + {Name: ClusterMember3, Replicas: 6}, + }, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + preSelectedClusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 1}, + {Name: ClusterMember2, Replicas: 2}, + {Name: ClusterMember3, Replicas: 3}, + }, + wantErr: false, + }, + { + name: "replica 12 -> 24, dynamic weighted 10:10:10", + args: args{ + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 24, + Clusters: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 2}, + {Name: ClusterMember2, Replicas: 4}, + {Name: ClusterMember3, Replicas: 6}, + }, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + preSelectedClusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 6}, + {Name: ClusterMember2, Replicas: 8}, + {Name: ClusterMember3, Replicas: 10}, + }, + wantErr: false, + }, + { + name: "replica 12 -> 24, dynamic weighted 1:1:1", + args: args{ + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 24, + Clusters: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 2}, + {Name: ClusterMember2, Replicas: 4}, + {Name: ClusterMember3, Replicas: 6}, + }, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceWeighted, + preSelectedClusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + }, + wantErr: true, + }, + { + name: "replica 12 -> 6, aggregated 2:4:6", + args: args{ + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 6, + Clusters: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 4}, + {Name: ClusterMember2, Replicas: 8}, + {Name: ClusterMember3, Replicas: 0}, + }, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceAggregated, + preSelectedClusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 2}, + {Name: ClusterMember2, Replicas: 4}, + {Name: ClusterMember3, Replicas: 0}, + }, + wantErr: false, + }, + { + name: "replica 12 -> 24, aggregated 4:6:8", + args: args{ + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 24, + Clusters: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 4}, + {Name: ClusterMember2, Replicas: 8}, + {Name: ClusterMember3, Replicas: 0}, + }, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceAggregated, + preSelectedClusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(4, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(6, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(14, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + }, + want: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 6}, + {Name: ClusterMember2, Replicas: 11}, + {Name: ClusterMember3, Replicas: 7}, + }, + wantErr: false, + }, + { + name: "replica 12 -> 24, dynamic weighted 1:1:1", + args: args{ + spec: &workv1alpha2.ResourceBindingSpec{ + ReplicaRequirements: &workv1alpha2.ReplicaRequirements{ + ResourceRequest: util.EmptyResource().ResourceList(), + }, + Replicas: 24, + Clusters: []workv1alpha2.TargetCluster{ + {Name: ClusterMember1, Replicas: 4}, + {Name: ClusterMember2, Replicas: 8}, + {Name: ClusterMember3, Replicas: 0}, + }, + }, + preference: policyv1alpha1.ReplicaDivisionPreferenceAggregated, + preSelectedClusters: []*clusterv1alpha1.Cluster{ + helper.NewClusterWithResource(ClusterMember1, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember2, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + helper.NewClusterWithResource(ClusterMember3, corev1.ResourceList{ + corev1.ResourcePods: *resource.NewQuantity(1, resource.DecimalSI), + }, util.EmptyResource().ResourceList(), util.EmptyResource().ResourceList()), + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := scaleScheduleByReplicaDivisionPreference(tt.args.spec, tt.args.preference, tt.args.preSelectedClusters) + if (err != nil) != tt.wantErr { + t.Errorf("scaleScheduleByReplicaDivisionPreference() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !helper.IsScheduleResultEqual(got, tt.want) { + t.Errorf("scaleScheduleByReplicaDivisionPreference() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/test/helper/resource.go b/test/helper/resource.go index 828969e00..1f84837f5 100644 --- a/test/helper/resource.go +++ b/test/helper/resource.go @@ -12,6 +12,8 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/pointer" + + clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1" ) // These are different resource units. @@ -379,3 +381,24 @@ func MakeNodeWithTaints(node string, milliCPU, memory, pods, ephemeralStorage in }, } } + +// NewCluster will build a Cluster. +func NewCluster(name string) *clusterv1alpha1.Cluster { + return &clusterv1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + } +} + +// NewClusterWithResource will build a Cluster with resource. +func NewClusterWithResource(name string, allocatable, allocating, allocated corev1.ResourceList) *clusterv1alpha1.Cluster { + return &clusterv1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + Status: clusterv1alpha1.ClusterStatus{ + ResourceSummary: &clusterv1alpha1.ResourceSummary{ + Allocatable: allocatable, + Allocating: allocating, + Allocated: allocated, + }, + }, + } +} diff --git a/test/helper/scheduler.go b/test/helper/scheduler.go new file mode 100644 index 000000000..8ce79ffa3 --- /dev/null +++ b/test/helper/scheduler.go @@ -0,0 +1,25 @@ +package helper + +import ( + workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2" +) + +// IsScheduleResultEqual will check whether two schedule results are equal. +func IsScheduleResultEqual(tc1, tc2 []workv1alpha2.TargetCluster) bool { + if len(tc1) != len(tc2) { + return false + } + for _, c1 := range tc1 { + found := false + for _, c2 := range tc2 { + if c1 == c2 { + found = true + break + } + } + if !found { + return false + } + } + return true +}