From 07e3bfbee68ae840680e8ad616c9e5016fde776b Mon Sep 17 00:00:00 2001 From: haoqing0110 Date: Wed, 1 Sep 2021 07:41:37 +0000 Subject: [PATCH] add resource based scheduling prioritizer Signed-off-by: haoqing0110 --- pkg/controllers/scheduling/schedule.go | 5 + pkg/controllers/scheduling/schedule_test.go | 220 ++++++++++++-- pkg/helpers/testing/builders.go | 22 ++ pkg/plugins/resource/resource.go | 181 +++++++++++ pkg/plugins/resource/resource_test.go | 237 +++++++++++++++ test/integration/placement_test.go | 320 +++++++++++++++++++- 6 files changed, 958 insertions(+), 27 deletions(-) create mode 100644 pkg/plugins/resource/resource.go create mode 100644 pkg/plugins/resource/resource_test.go diff --git a/pkg/controllers/scheduling/schedule.go b/pkg/controllers/scheduling/schedule.go index 24a174d13..e66daa219 100644 --- a/pkg/controllers/scheduling/schedule.go +++ b/pkg/controllers/scheduling/schedule.go @@ -14,6 +14,7 @@ import ( "open-cluster-management.io/placement/pkg/plugins" "open-cluster-management.io/placement/pkg/plugins/balance" "open-cluster-management.io/placement/pkg/plugins/predicate" + "open-cluster-management.io/placement/pkg/plugins/resource" "open-cluster-management.io/placement/pkg/plugins/steady" ) @@ -120,6 +121,10 @@ func NewPluginScheduler(handle plugins.Handle) *pluginScheduler { prioritizers: []plugins.Prioritizer{ balance.New(handle), steady.New(handle), + resource.NewResourcePrioritizerBuilder(handle).WithPrioritizerName("ResourceAllocatableCPU").Build(), + resource.NewResourcePrioritizerBuilder(handle).WithPrioritizerName("ResourceAllocatableMemory").Build(), + resource.NewResourcePrioritizerBuilder(handle).WithPrioritizerName("ResourceRatioCPU").Build(), + resource.NewResourcePrioritizerBuilder(handle).WithPrioritizerName("ResourceRatioMemory").Build(), }, prioritizerWeights: defaultPrioritizerConfig, } diff --git a/pkg/controllers/scheduling/schedule_test.go b/pkg/controllers/scheduling/schedule_test.go index 11b193c48..fd29dac92 100644 --- a/pkg/controllers/scheduling/schedule_test.go +++ b/pkg/controllers/scheduling/schedule_test.go @@ -59,6 +59,26 @@ func TestSchedule(t *testing.T) { Weight: 1, Scores: PrioritizerScore{"cluster1": 0}, }, + { + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, + }, }, clusters: []*clusterapiv1.ManagedCluster{ testinghelpers.NewManagedCluster("cluster1").WithLabel(clusterSetLabel, clusterSetName).Build(), @@ -96,6 +116,26 @@ func TestSchedule(t *testing.T) { Weight: 1, Scores: PrioritizerScore{"cluster1": 0}, }, + { + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, + }, }, expectedUnScheduled: 2, }, @@ -140,6 +180,26 @@ func TestSchedule(t *testing.T) { Weight: 1, Scores: PrioritizerScore{"cluster1": 100, "cluster2": 100, "cluster3": 0}, }, + { + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, + }, }, expectedUnScheduled: 0, }, @@ -171,6 +231,26 @@ func TestSchedule(t *testing.T) { Weight: 1, Scores: PrioritizerScore{"cluster1": 0}, }, + { + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, + }, }, clusters: []*clusterapiv1.ManagedCluster{ testinghelpers.NewManagedCluster("cluster1").WithLabel(clusterSetLabel, clusterSetName).Build(), @@ -179,69 +259,115 @@ func TestSchedule(t *testing.T) { }, { name: "placement with additive Prioritizer Policy", - placement: testinghelpers.NewPlacement(placementNamespace, placementName).WithPrioritizerPolicy("Additive").WithPrioritizerConfig("Steady", 3).Build(), + placement: testinghelpers.NewPlacement(placementNamespace, placementName).WithNOC(2).WithPrioritizerPolicy("Additive").WithPrioritizerConfig("Balance", 3).WithPrioritizerConfig("ResourceRatioCPU", 1).Build(), initObjs: []runtime.Object{ testinghelpers.NewClusterSet(clusterSetName), testinghelpers.NewClusterSetBinding(placementNamespace, clusterSetName), }, + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithLabel(clusterSetLabel, clusterSetName).WithResource(clusterapiv1.ResourceCPU, "10", "10").Build(), + testinghelpers.NewManagedCluster("cluster2").WithLabel(clusterSetLabel, clusterSetName).WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + testinghelpers.NewManagedCluster("cluster3").WithLabel(clusterSetLabel, clusterSetName).WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(), + }, decisions: []runtime.Object{}, expectedDecisions: []clusterapiv1alpha1.ClusterDecision{ {ClusterName: "cluster1"}, + {ClusterName: "cluster2"}, }, expectedFilterResult: []FilterResult{ { Name: "Predicate", - FilteredClusters: []string{"cluster1"}, + FilteredClusters: []string{"cluster1", "cluster2", "cluster3"}, }, }, expectedScoreResult: []PrioritizerResult{ { Name: "Balance", - Weight: 1, - Scores: PrioritizerScore{"cluster1": 100}, + Weight: 3, + Scores: PrioritizerScore{"cluster1": 100, "cluster2": 100, "cluster3": 100}, }, { Name: "Steady", - Weight: 3, - Scores: PrioritizerScore{"cluster1": 0}, + Weight: 1, + Scores: PrioritizerScore{"cluster1": 0, "cluster2": 0, "cluster3": 0}, + }, + { + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 1, + Scores: PrioritizerScore{"cluster1": 100, "cluster2": 0, "cluster3": -100}, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, }, - }, - clusters: []*clusterapiv1.ManagedCluster{ - testinghelpers.NewManagedCluster("cluster1").WithLabel(clusterSetLabel, clusterSetName).Build(), }, expectedUnScheduled: 0, }, { name: "placement with exact Prioritizer Policy", - placement: testinghelpers.NewPlacement(placementNamespace, placementName).WithPrioritizerPolicy("Exact").WithPrioritizerConfig("Steady", 3).Build(), + placement: testinghelpers.NewPlacement(placementNamespace, placementName).WithNOC(2).WithPrioritizerPolicy("Exact").WithPrioritizerConfig("Balance", 3).WithPrioritizerConfig("ResourceRatioCPU", 1).Build(), initObjs: []runtime.Object{ testinghelpers.NewClusterSet(clusterSetName), testinghelpers.NewClusterSetBinding(placementNamespace, clusterSetName), }, + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithLabel(clusterSetLabel, clusterSetName).WithResource(clusterapiv1.ResourceCPU, "10", "10").Build(), + testinghelpers.NewManagedCluster("cluster2").WithLabel(clusterSetLabel, clusterSetName).WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + testinghelpers.NewManagedCluster("cluster3").WithLabel(clusterSetLabel, clusterSetName).WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(), + }, decisions: []runtime.Object{}, expectedDecisions: []clusterapiv1alpha1.ClusterDecision{ {ClusterName: "cluster1"}, + {ClusterName: "cluster2"}, }, expectedFilterResult: []FilterResult{ { Name: "Predicate", - FilteredClusters: []string{"cluster1"}, + FilteredClusters: []string{"cluster1", "cluster2", "cluster3"}, }, }, expectedScoreResult: []PrioritizerResult{ { Name: "Balance", + Weight: 3, + Scores: PrioritizerScore{"cluster1": 100, "cluster2": 100, "cluster3": 100}, + }, + { + Name: "Steady", Weight: 0, Scores: nil, }, { - Name: "Steady", - Weight: 3, - Scores: PrioritizerScore{"cluster1": 0}, + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 1, + Scores: PrioritizerScore{"cluster1": 100, "cluster2": 0, "cluster3": -100}, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, }, - }, - clusters: []*clusterapiv1.ManagedCluster{ - testinghelpers.NewManagedCluster("cluster1").WithLabel(clusterSetLabel, clusterSetName).Build(), }, expectedUnScheduled: 0, }, @@ -285,6 +411,26 @@ func TestSchedule(t *testing.T) { Weight: 1, Scores: PrioritizerScore{"cluster1": 100, "cluster2": 0}, }, + { + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, + }, }, expectedUnScheduled: 2, }, @@ -326,6 +472,26 @@ func TestSchedule(t *testing.T) { Weight: 1, Scores: PrioritizerScore{"cluster1": 0, "cluster2": 0, "cluster3": 0}, }, + { + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, + }, }, expectedUnScheduled: 0, }, @@ -377,6 +543,26 @@ func TestSchedule(t *testing.T) { Weight: 1, Scores: PrioritizerScore{"cluster1": 0, "cluster2": 0, "cluster3": 100}, }, + { + Name: "ResourceAllocatableCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioCPU", + Weight: 0, + Scores: nil, + }, + { + Name: "ResourceRatioMemory", + Weight: 0, + Scores: nil, + }, }, expectedUnScheduled: 0, }, diff --git a/pkg/helpers/testing/builders.go b/pkg/helpers/testing/builders.go index ab7e70913..1e281c71b 100644 --- a/pkg/helpers/testing/builders.go +++ b/pkg/helpers/testing/builders.go @@ -4,6 +4,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -95,6 +96,14 @@ func (b *placementBuilder) WithSatisfiedCondition(numbOfScheduledDecisions, numb return b } +func (b *placementBuilder) WithPrioritizerConfigs(name string, weight int32) *placementBuilder { + if b.placement.Spec.PrioritizerPolicy.Configurations == nil { + b.placement.Spec.PrioritizerPolicy.Configurations = []clusterapiv1alpha1.PrioritizerConfig{} + } + b.placement.Spec.PrioritizerPolicy.Configurations = append(b.placement.Spec.PrioritizerPolicy.Configurations, clusterapiv1alpha1.PrioritizerConfig{Name: name, Weight: weight}) + return b +} + func (b *placementBuilder) Build() *clusterapiv1alpha1.Placement { return b.placement } @@ -209,6 +218,19 @@ func (b *managedClusterBuilder) WithClaim(name, value string) *managedClusterBui return b } +func (b *managedClusterBuilder) WithResource(resourceName clusterapiv1.ResourceName, allocatable, capacity string) *managedClusterBuilder { + if b.cluster.Status.Allocatable == nil { + b.cluster.Status.Allocatable = make(map[clusterapiv1.ResourceName]resource.Quantity) + } + if b.cluster.Status.Capacity == nil { + b.cluster.Status.Capacity = make(map[clusterapiv1.ResourceName]resource.Quantity) + } + + b.cluster.Status.Allocatable[resourceName], _ = resource.ParseQuantity(allocatable) + b.cluster.Status.Capacity[resourceName], _ = resource.ParseQuantity(capacity) + return b +} + func (b *managedClusterBuilder) Build() *clusterapiv1.ManagedCluster { return b.cluster } diff --git a/pkg/plugins/resource/resource.go b/pkg/plugins/resource/resource.go new file mode 100644 index 000000000..07a24d980 --- /dev/null +++ b/pkg/plugins/resource/resource.go @@ -0,0 +1,181 @@ +package resource + +import ( + "context" + "fmt" + "regexp" + "sort" + + clusterapiv1 "open-cluster-management.io/api/cluster/v1" + clusterapiv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1" + "open-cluster-management.io/placement/pkg/plugins" +) + +const ( + placementLabel = clusterapiv1alpha1.PlacementLabel + description = ` + ResourceRatio[ResourceType] and ResourceAllocatable[ResourceType] prioritizer makes the scheduling + decisions based on the resource allocatable to capacity ratio or allocatable of managed clusters. + The [ResourceType] could be CPU or Memory. + The clusters that has the most allocatable to capacity ratio or allocatable are given the highest score, + while the least is given the lowest score. + ` +) + +var _ plugins.Prioritizer = &ResourcePrioritizer{} + +var resourceMap = map[string]clusterapiv1.ResourceName{ + "CPU": clusterapiv1.ResourceCPU, + "Memory": clusterapiv1.ResourceMemory, +} + +type ResourcePrioritizer struct { + handle plugins.Handle + prioritizerName string + algorithm string + resource clusterapiv1.ResourceName +} + +type ResourcePrioritizerBuilder struct { + resourcePrioritizer *ResourcePrioritizer +} + +func NewResourcePrioritizerBuilder(handle plugins.Handle) *ResourcePrioritizerBuilder { + return &ResourcePrioritizerBuilder{ + resourcePrioritizer: &ResourcePrioritizer{ + handle: handle, + }, + } +} + +func (r *ResourcePrioritizerBuilder) WithPrioritizerName(name string) *ResourcePrioritizerBuilder { + r.resourcePrioritizer.prioritizerName = name + return r +} + +func (r *ResourcePrioritizerBuilder) Build() *ResourcePrioritizer { + algorithm, resource := parsePrioritizerName(r.resourcePrioritizer.prioritizerName) + r.resourcePrioritizer.algorithm = algorithm + r.resourcePrioritizer.resource = resource + return r.resourcePrioritizer +} + +// parese prioritizerName to algorithm and resource. +// For example, prioritizerName ResourceAllocatableCPU will return Allocatable, CPU. +func parsePrioritizerName(prioritizerName string) (algorithm string, resource clusterapiv1.ResourceName) { + s := regexp.MustCompile("[A-Z]+[a-z]*").FindAllString(prioritizerName, -1) + if len(s) == 3 { + return s[1], resourceMap[s[2]] + } + return "", "" +} + +func (r *ResourcePrioritizer) Name() string { + return r.prioritizerName +} + +func (r *ResourcePrioritizer) Description() string { + return description +} + +func (r *ResourcePrioritizer) Score(ctx context.Context, placement *clusterapiv1alpha1.Placement, clusters []*clusterapiv1.ManagedCluster) (map[string]int64, error) { + switch r.algorithm { + case "Ratio": + return mostResourceRatioScores(r.resource, clusters) + case "Allocatable": + return mostResourceAllocatableScores(r.resource, clusters) + } + return nil, nil +} + +// Calculate clusters scores based on the resource allocatable to capacity ratio. +// The clusters that has the most allocatable to capacity ratio are given the highest score, while the least is given the lowest score. +// The score range is from -100 to 100. +func mostResourceRatioScores(resourceName clusterapiv1.ResourceName, clusters []*clusterapiv1.ManagedCluster) (map[string]int64, error) { + scores := map[string]int64{} + + for _, cluster := range clusters { + // get cluster resourceName's allocatable and capacity + allocatable, capacity, err := getClusterResource(cluster, resourceName) + if err != nil { + continue + } + + // score = (resource_x_allocatable / resource_x_capacity - 0.5) * 2 * 100 + if capacity != 0 { + ratio := float64(allocatable) / float64(capacity) + scores[cluster.Name] = int64((ratio - 0.5) * 2.0 * 100.0) + } + } + + return scores, nil +} + +// Calculate clusters scores based on the resource allocatable. +// The clusters that has the most allocatable are given the highest score, while the least is given the lowest score. +// The score range is from -100 to 100. +func mostResourceAllocatableScores(resourceName clusterapiv1.ResourceName, clusters []*clusterapiv1.ManagedCluster) (map[string]int64, error) { + scores := map[string]int64{} + + // get resourceName's min and max allocatable among all the clusters + minAllocatable, maxAllocatable, err := getClustersMinMaxAllocatableResource(clusters, resourceName) + if err != nil { + return scores, nil + } + + for _, cluster := range clusters { + // get one cluster resourceName's allocatable + allocatable, _, err := getClusterResource(cluster, resourceName) + if err != nil { + continue + } + + // score = ((resource_x_allocatable - min(resource_x_allocatable)) / (max(resource_x_allocatable) - min(resource_x_allocatable)) - 0.5) * 2 * 100 + if (maxAllocatable - minAllocatable) != 0 { + ratio := float64(allocatable-minAllocatable) / float64(maxAllocatable-minAllocatable) + scores[cluster.Name] = int64((ratio - 0.5) * 2.0 * 100.0) + } else { + scores[cluster.Name] = 100.0 + } + } + + return scores, nil +} + +// Go through one cluster resources and return the allocatable and capacity of the resourceName. +func getClusterResource(cluster *clusterapiv1.ManagedCluster, resourceName clusterapiv1.ResourceName) (allocatable, capacity float64, err error) { + if v, exist := cluster.Status.Allocatable[resourceName]; exist { + allocatable = v.AsApproximateFloat64() + } else { + return allocatable, capacity, fmt.Errorf("no allocatable %s found in cluster %s", resourceName, cluster.ObjectMeta.Name) + } + + if v, exist := cluster.Status.Capacity[resourceName]; exist { + capacity = v.AsApproximateFloat64() + } else { + return allocatable, capacity, fmt.Errorf("no capacity %s found in cluster %s", resourceName, cluster.ObjectMeta.Name) + } + + return allocatable, capacity, nil +} + +// Go through all the cluster resources and return the min and max allocatable value of the resourceName. +func getClustersMinMaxAllocatableResource(clusters []*clusterapiv1.ManagedCluster, resourceName clusterapiv1.ResourceName) (minAllocatable, maxAllocatable float64, err error) { + allocatable := sort.Float64Slice{} + + // get allocatable resources + for _, cluster := range clusters { + if alloc, _, err := getClusterResource(cluster, resourceName); err == nil { + allocatable = append(allocatable, alloc) + } + } + + // return err if no allocatable resource + if len(allocatable) == 0 { + return 0, 0, fmt.Errorf("no allocatable %s found in clusters", resourceName) + } + + // sort to get min and max + sort.Float64s(allocatable) + return allocatable[0], allocatable[len(allocatable)-1], nil +} diff --git a/pkg/plugins/resource/resource_test.go b/pkg/plugins/resource/resource_test.go new file mode 100644 index 000000000..aec5889c7 --- /dev/null +++ b/pkg/plugins/resource/resource_test.go @@ -0,0 +1,237 @@ +package resource + +import ( + "context" + "testing" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/runtime" + clusterapiv1 "open-cluster-management.io/api/cluster/v1" + clusterapiv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1" + testinghelpers "open-cluster-management.io/placement/pkg/helpers/testing" +) + +func TestScoreClusterWithResource(t *testing.T) { + cases := []struct { + name string + resource clusterapiv1.ResourceName + algorithm string + placement *clusterapiv1alpha1.Placement + clusters []*clusterapiv1.ManagedCluster + existingDecisions []runtime.Object + expectedScores map[string]int64 + }{ + { + name: "scores of ResourceRatioMemory", + resource: clusterapiv1.ResourceMemory, + algorithm: "Ratio", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "20", "100").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "100", "100").Build(), + }, + existingDecisions: []runtime.Object{}, + expectedScores: map[string]int64{"cluster1": -60, "cluster2": 0, "cluster3": 100}, + }, + { + name: "scores of ResourceRatioMemory with same resource value", + resource: clusterapiv1.ResourceMemory, + algorithm: "Ratio", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(), + }, + expectedScores: map[string]int64{"cluster1": 0, "cluster2": 0, "cluster3": 0}, + }, + { + name: "scores of ResourceRatioMemory with zero resource value", + resource: clusterapiv1.ResourceMemory, + algorithm: "Ratio", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(), + }, + expectedScores: map[string]int64{"cluster1": -100, "cluster2": -100, "cluster3": -100}, + }, + { + name: "scores of ResourceRatioMemory with no cluster resource", + resource: clusterapiv1.ResourceMemory, + algorithm: "Ratio", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").Build(), + testinghelpers.NewManagedCluster("cluster2").Build(), + testinghelpers.NewManagedCluster("cluster3").Build(), + }, + expectedScores: map[string]int64{}, + }, + { + name: "scores of ResourceRatioCPU", + resource: clusterapiv1.ResourceCPU, + algorithm: "Ratio", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "10", "10").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "2", "10").Build(), + }, + expectedScores: map[string]int64{"cluster1": 100, "cluster2": 0, "cluster3": -60}, + }, + { + name: "scores of ResourceRatioCPU with same resource value", + resource: clusterapiv1.ResourceCPU, + algorithm: "Ratio", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + }, + expectedScores: map[string]int64{"cluster1": 0, "cluster2": 0, "cluster3": 0}, + }, + { + name: "scores of ResourceRatioCPU with zero resource value", + resource: clusterapiv1.ResourceCPU, + algorithm: "Ratio", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(), + }, + expectedScores: map[string]int64{"cluster1": -100, "cluster2": -100, "cluster3": -100}, + }, + { + name: "scores of ResourceRatioCPU with no cluster resource", + resource: clusterapiv1.ResourceCPU, + algorithm: "Ratio", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").Build(), + testinghelpers.NewManagedCluster("cluster2").Build(), + testinghelpers.NewManagedCluster("cluster3").Build(), + }, + expectedScores: map[string]int64{}, + }, + { + name: "scores of ResourceAllocatableMemory", + resource: clusterapiv1.ResourceMemory, + algorithm: "Allocatable", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "20", "100").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "60", "100").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "100", "100").Build(), + }, + expectedScores: map[string]int64{"cluster1": -100, "cluster2": 0, "cluster3": 100}, + }, + { + name: "scores of ResourceAllocatableMemory with same resource value", + resource: clusterapiv1.ResourceMemory, + algorithm: "Allocatable", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(), + }, + expectedScores: map[string]int64{"cluster1": 100, "cluster2": 100, "cluster3": 100}, + }, + { + name: "scores of ResourceAllocatableMemory with zero resource value", + resource: clusterapiv1.ResourceMemory, + algorithm: "Allocatable", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(), + }, + expectedScores: map[string]int64{"cluster1": 100, "cluster2": 100, "cluster3": 100}, + }, + { + name: "scores of ResourceAllocatableMemory with no cluster resource", + resource: clusterapiv1.ResourceMemory, + algorithm: "Allocatable", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").Build(), + testinghelpers.NewManagedCluster("cluster2").Build(), + testinghelpers.NewManagedCluster("cluster3").Build(), + }, + expectedScores: map[string]int64{}, + }, + { + name: "scores of ResourceAllocatableCPU", + resource: clusterapiv1.ResourceCPU, + algorithm: "Allocatable", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "10", "10").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "6", "10").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "2", "10").Build(), + }, + expectedScores: map[string]int64{"cluster1": 100, "cluster2": 0, "cluster3": -100}, + }, + { + name: "scores of ResourceAllocatableCPU with same resource value", + resource: clusterapiv1.ResourceCPU, + algorithm: "Allocatable", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(), + }, + expectedScores: map[string]int64{"cluster1": 100, "cluster2": 100, "cluster3": 100}, + }, + { + name: "scores of ResourceAllocatableCPU with zero resource value", + resource: clusterapiv1.ResourceCPU, + algorithm: "Allocatable", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(), + testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(), + testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(), + }, + expectedScores: map[string]int64{"cluster1": 100, "cluster2": 100, "cluster3": 100}, + }, + { + name: "scores of ResourceAllocatableCPU with no cluster resource", + resource: clusterapiv1.ResourceCPU, + algorithm: "Allocatable", + placement: testinghelpers.NewPlacement("test", "test").Build(), + clusters: []*clusterapiv1.ManagedCluster{ + testinghelpers.NewManagedCluster("cluster1").Build(), + testinghelpers.NewManagedCluster("cluster2").Build(), + testinghelpers.NewManagedCluster("cluster3").Build(), + }, + expectedScores: map[string]int64{}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + resource := &ResourcePrioritizer{ + handle: testinghelpers.NewFakePluginHandle(t, nil, c.existingDecisions...), + resource: c.resource, + algorithm: c.algorithm, + } + + scores, err := resource.Score(context.TODO(), c.placement, c.clusters) + if err != nil { + t.Errorf("Expect no error, but got %v", err) + } + + if !apiequality.Semantic.DeepEqual(scores, c.expectedScores) { + t.Errorf("Expect score %v, but got %v", c.expectedScores, scores) + } + }) + } +} diff --git a/test/integration/placement_test.go b/test/integration/placement_test.go index 501d64341..30bd50e09 100644 --- a/test/integration/placement_test.go +++ b/test/integration/placement_test.go @@ -9,9 +9,11 @@ import ( "github.com/openshift/library-go/pkg/controller/controllercmd" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/rand" + apiequality "k8s.io/apimachinery/pkg/api/equality" clusterapiv1 "open-cluster-management.io/api/cluster/v1" clusterapiv1alpha1 "open-cluster-management.io/api/cluster/v1alpha1" controllers "open-cluster-management.io/placement/pkg/controllers" @@ -28,6 +30,7 @@ var _ = ginkgo.Describe("Placement", func() { var cancel context.CancelFunc var namespace string var placementName string + var clusterName string var clusterSet1Name, clusterSet2Name string var suffix string var err error @@ -36,6 +39,7 @@ var _ = ginkgo.Describe("Placement", func() { suffix = rand.String(5) namespace = fmt.Sprintf("ns-%s", suffix) placementName = fmt.Sprintf("placement-%s", suffix) + clusterName = fmt.Sprintf("cluster-%s", suffix) clusterSet1Name = fmt.Sprintf("clusterset-%s", suffix) clusterSet2Name = fmt.Sprintf("clusterset-%s", rand.String(5)) @@ -86,6 +90,31 @@ var _ = ginkgo.Describe("Placement", func() { }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) } + assertCreatingPlacementDecision := func(name string, clusterNames []string) { + ginkgo.By(fmt.Sprintf("Create placementdecision %s", name)) + placementDecision := &clusterapiv1alpha1.PlacementDecision{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + Labels: map[string]string{ + placementLabel: name, + }, + }, + } + placementDecision, err = clusterClient.ClusterV1alpha1().PlacementDecisions(namespace).Create(context.Background(), placementDecision, metav1.CreateOptions{}) + + clusterDecisions := []clusterapiv1alpha1.ClusterDecision{} + for _, clusterName := range clusterNames { + clusterDecisions = append(clusterDecisions, clusterapiv1alpha1.ClusterDecision{ + ClusterName: clusterName, + }) + } + + placementDecision.Status.Decisions = clusterDecisions + placementDecision, err = clusterClient.ClusterV1alpha1().PlacementDecisions(namespace).UpdateStatus(context.Background(), placementDecision, metav1.UpdateOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + } + assertPlacementDeleted := func(placementName string) { ginkgo.By("Check if placement is gone") gomega.Eventually(func() bool { @@ -121,6 +150,26 @@ var _ = ginkgo.Describe("Placement", func() { }, eventuallyTimeout, eventuallyInterval).Should(gomega.BeTrue()) } + assertClusterNamesOfDecisions := func(placementName string, desiredClusters []string) { + ginkgo.By(fmt.Sprintf("Check the cluster names of placementdecisions %s", placementName)) + gomega.Eventually(func() bool { + pdl, err := clusterClient.ClusterV1alpha1().PlacementDecisions(namespace).List(context.Background(), metav1.ListOptions{ + LabelSelector: placementLabel + "=" + placementName, + }) + if err != nil { + return false + } + actualClusters := []string{} + for _, pd := range pdl.Items { + for _, d := range pd.Status.Decisions { + actualClusters = append(actualClusters, d.ClusterName) + } + } + ginkgo.By(fmt.Sprintf("Expect %v, but got %v", desiredClusters, actualClusters)) + return apiequality.Semantic.DeepEqual(desiredClusters, actualClusters) + }, eventuallyTimeout*2, eventuallyInterval).Should(gomega.BeTrue()) + } + assertPlacementStatus := func(placementName string, numOfSelectedClusters int, satisfied bool) { ginkgo.By("Check the status of placement") gomega.Eventually(func() bool { @@ -190,7 +239,52 @@ var _ = ginkgo.Describe("Placement", func() { } } - assertCreatingPlacement := func(name string, noc *int32, nod int) { + assertCreatingClustersWithNames := func(clusterSetName string, managedClusterNames []string) { + ginkgo.By(fmt.Sprintf("Create %d clusters", len(managedClusterNames))) + for _, name := range managedClusterNames { + cluster := &clusterapiv1.ManagedCluster{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cluster-", + Labels: map[string]string{ + clusterSetLabel: clusterSetName, + }, + Name: name, + }, + } + ginkgo.By(fmt.Sprintf("Create cluster %s", name)) + _, err = clusterClient.ClusterV1().ManagedClusters().Create(context.Background(), cluster, metav1.CreateOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + } + } + + assertUpdatingClusterWithClusterResources := func(managedClusterName string, res []string) { + ginkgo.By(fmt.Sprintf("Updating ManagedClusters %s cluster resources", managedClusterName)) + + mc, err := clusterClient.ClusterV1().ManagedClusters().Get(context.Background(), managedClusterName, metav1.GetOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + allocatable := map[clusterapiv1.ResourceName]resource.Quantity{} + capacity := map[clusterapiv1.ResourceName]resource.Quantity{} + + allocatable[clusterapiv1.ResourceCPU], err = resource.ParseQuantity(res[0]) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + allocatable[clusterapiv1.ResourceMemory], err = resource.ParseQuantity(res[2]) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + capacity[clusterapiv1.ResourceCPU], err = resource.ParseQuantity(res[1]) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + capacity[clusterapiv1.ResourceMemory], err = resource.ParseQuantity(res[3]) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + + mc.Status = clusterapiv1.ManagedClusterStatus{ + Allocatable: allocatable, + Capacity: capacity, + Conditions: []metav1.Condition{}, + } + _, err = clusterClient.ClusterV1().ManagedClusters().UpdateStatus(context.Background(), mc, metav1.UpdateOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + } + + assertCreatingPlacement := func(name string, noc *int32, nod int, prioritizerPolicy clusterapiv1alpha1.PrioritizerPolicy) { ginkgo.By("Create placement") placement := &clusterapiv1alpha1.Placement{ ObjectMeta: metav1.ObjectMeta{ @@ -198,7 +292,8 @@ var _ = ginkgo.Describe("Placement", func() { Name: name, }, Spec: clusterapiv1alpha1.PlacementSpec{ - NumberOfClusters: noc, + NumberOfClusters: noc, + PrioritizerPolicy: prioritizerPolicy, }, } placement, err = clusterClient.ClusterV1alpha1().Placements(namespace).Create(context.Background(), placement, metav1.CreateOptions{}) @@ -222,7 +317,7 @@ var _ = ginkgo.Describe("Placement", func() { ginkgo.It("Should re-create placementdecisions successfully once placementdecisions are deleted", func() { assertBindingClusterSet(clusterSet1Name) assertCreatingClusters(clusterSet1Name, 5) - assertCreatingPlacement(placementName, noc(10), 5) + assertCreatingPlacement(placementName, noc(10), 5, clusterapiv1alpha1.PrioritizerPolicy{}) ginkgo.By("Delete placementdecisions") placementDecisions, err := clusterClient.ClusterV1alpha1().PlacementDecisions(namespace).List(context.Background(), metav1.ListOptions{ @@ -246,7 +341,7 @@ var _ = ginkgo.Describe("Placement", func() { assertBindingClusterSet(clusterSet2Name) assertCreatingClusters(clusterSet1Name, 2) assertCreatingClusters(clusterSet2Name, 3) - assertCreatingPlacement(placementName, noc(10), 5) + assertCreatingPlacement(placementName, noc(10), 5, clusterapiv1alpha1.PrioritizerPolicy{}) // update ClusterSets placement, err := clusterClient.ClusterV1alpha1().Placements(namespace).Get(context.Background(), placementName, metav1.GetOptions{}) @@ -262,7 +357,7 @@ var _ = ginkgo.Describe("Placement", func() { assertBindingClusterSet(clusterSet1Name) assertCreatingClusters(clusterSet1Name, 2) assertCreatingClusters(clusterSet1Name, 3, "cloud", "Amazon") - assertCreatingPlacement(placementName, noc(10), 5) + assertCreatingPlacement(placementName, noc(10), 5, clusterapiv1alpha1.PrioritizerPolicy{}) // add a predicates placement, err := clusterClient.ClusterV1alpha1().Placements(namespace).Get(context.Background(), placementName, metav1.GetOptions{}) @@ -287,7 +382,7 @@ var _ = ginkgo.Describe("Placement", func() { ginkgo.It("Should schedule successfully once spec.NumberOfClusters is reduced", func() { assertBindingClusterSet(clusterSet1Name) assertCreatingClusters(clusterSet1Name, 5) - assertCreatingPlacement(placementName, noc(10), 5) + assertCreatingPlacement(placementName, noc(10), 5, clusterapiv1alpha1.PrioritizerPolicy{}) ginkgo.By("Reduce NOC of the placement") placement, err := clusterClient.ClusterV1alpha1().Placements(namespace).Get(context.Background(), placementName, metav1.GetOptions{}) @@ -305,7 +400,7 @@ var _ = ginkgo.Describe("Placement", func() { ginkgo.It("Should schedule successfully once spec.NumberOfClusters is increased", func() { assertBindingClusterSet(clusterSet1Name) assertCreatingClusters(clusterSet1Name, 10) - assertCreatingPlacement(placementName, noc(5), 5) + assertCreatingPlacement(placementName, noc(5), 5, clusterapiv1alpha1.PrioritizerPolicy{}) ginkgo.By("Increase NOC of the placement") placement, err := clusterClient.ClusterV1alpha1().Placements(namespace).Get(context.Background(), placementName, metav1.GetOptions{}) @@ -323,7 +418,7 @@ var _ = ginkgo.Describe("Placement", func() { ginkgo.It("Should be satisfied once new clusters are added", func() { assertBindingClusterSet(clusterSet1Name) assertCreatingClusters(clusterSet1Name, 5) - assertCreatingPlacement(placementName, noc(10), 5) + assertCreatingPlacement(placementName, noc(10), 5, clusterapiv1alpha1.PrioritizerPolicy{}) // add more clusters assertCreatingClusters(clusterSet1Name, 5) @@ -336,7 +431,7 @@ var _ = ginkgo.Describe("Placement", func() { ginkgo.It("Should schedule successfully once new clusterset is bound", func() { assertBindingClusterSet(clusterSet1Name) assertCreatingClusters(clusterSet1Name, 5) - assertCreatingPlacement(placementName, noc(10), 5) + assertCreatingPlacement(placementName, noc(10), 5, clusterapiv1alpha1.PrioritizerPolicy{}) ginkgo.By("Bind one more clusterset to the placement namespace") assertBindingClusterSet(clusterSet2Name) @@ -350,12 +445,217 @@ var _ = ginkgo.Describe("Placement", func() { ginkgo.It("Should create multiple placementdecisions once scheduled", func() { assertBindingClusterSet(clusterSet1Name) assertCreatingClusters(clusterSet1Name, 101) - assertCreatingPlacement(placementName, nil, 101) + assertCreatingPlacement(placementName, nil, 101, clusterapiv1alpha1.PrioritizerPolicy{}) nod := 101 assertNumberOfDecisions(placementName, nod) assertPlacementStatus(placementName, nod, true) }) + + ginkgo.It("Should schedule successfully with default SchedulePolicy", func() { + // cluster settings + clusterNames := []string{ + clusterName + "-1", + clusterName + "-2", + clusterName + "-3", + } + clusterResources := make([][]string, len(clusterNames)) + clusterResources[0] = []string{"10", "10", "50", "100"} + clusterResources[1] = []string{"7", "10", "90", "100"} + clusterResources[2] = []string{"9", "10", "80", "100"} + + // placement settings + prioritizerPolicy := clusterapiv1alpha1.PrioritizerPolicy{} + + //Creating the clusters with resources + assertBindingClusterSet(clusterSet1Name) + assertCreatingClustersWithNames(clusterSet1Name, clusterNames) + for i, name := range clusterNames { + assertUpdatingClusterWithClusterResources(name, clusterResources[i]) + } + + //Checking the result of the placement + assertCreatingPlacement(placementName, noc(2), 2, prioritizerPolicy) + assertClusterNamesOfDecisions(placementName, []string{clusterNames[0], clusterNames[1]}) + }) + + ginkgo.It("Should schedule successfully based on SchedulePolicy ResourceRatioCPU and ResourceRatioCPU", func() { + // cluster settings + clusterNames := []string{ + clusterName + "-1", + clusterName + "-2", + clusterName + "-3", + } + clusterResources := make([][]string, len(clusterNames)) + clusterResources[0] = []string{"10", "10", "50", "100"} + clusterResources[1] = []string{"7", "10", "90", "100"} + clusterResources[2] = []string{"9", "10", "80", "100"} + + // placement settings + prioritizerPolicy := clusterapiv1alpha1.PrioritizerPolicy{ + Mode: clusterapiv1alpha1.PrioritizerPolicyModeExact, + Configurations: []clusterapiv1alpha1.PrioritizerConfig{ + { + Name: "ResourceRatioCPU", + Weight: 1, + }, + { + Name: "ResourceRatioMemory", + Weight: 1, + }, + }, + } + + //Creating the clusters with resources + assertBindingClusterSet(clusterSet1Name) + assertCreatingClustersWithNames(clusterSet1Name, clusterNames) + for i, name := range clusterNames { + assertUpdatingClusterWithClusterResources(name, clusterResources[i]) + } + + //Checking the result of the placement + assertCreatingPlacement(placementName, noc(2), 2, prioritizerPolicy) + assertClusterNamesOfDecisions(placementName, []string{clusterNames[1], clusterNames[2]}) + + }) + + ginkgo.It("Should schedule successfully based on default SchedulePolicy ResourceAllocatableCPU & ResourceAllocatableMemory", func() { + // cluster settings + clusterNames := []string{ + clusterName + "-1", + clusterName + "-2", + clusterName + "-3", + } + clusterResources := make([][]string, len(clusterNames)) + clusterResources[0] = []string{"10", "10", "50", "100"} + clusterResources[1] = []string{"7", "10", "90", "100"} + clusterResources[2] = []string{"9", "10", "80", "100"} + + // placement settings + prioritizerPolicy := clusterapiv1alpha1.PrioritizerPolicy{ + Mode: clusterapiv1alpha1.PrioritizerPolicyModeExact, + Configurations: []clusterapiv1alpha1.PrioritizerConfig{ + { + Name: "ResourceAllocatableCPU", + Weight: 1, + }, + { + Name: "ResourceAllocatableMemory", + Weight: 1, + }, + }, + } + + //Creating the clusters with resources + assertBindingClusterSet(clusterSet1Name) + assertCreatingClustersWithNames(clusterSet1Name, clusterNames) + for i, name := range clusterNames { + assertUpdatingClusterWithClusterResources(name, clusterResources[i]) + } + + //Checking the result of the placement + assertCreatingPlacement(placementName, noc(2), 2, prioritizerPolicy) + assertClusterNamesOfDecisions(placementName, []string{clusterNames[0], clusterNames[2]}) + + }) + + ginkgo.It("Should re-schedule successfully successfully once a new cluster added", func() { + // cluster settings + clusterNames := []string{ + clusterName + "-1", + clusterName + "-2", + clusterName + "-3", + } + clusterResources := make([][]string, len(clusterNames)) + clusterResources[0] = []string{"10", "10", "50", "100"} + clusterResources[1] = []string{"7", "10", "90", "100"} + clusterResources[2] = []string{"9", "10", "80", "100"} + + // placement settings + prioritizerPolicy := clusterapiv1alpha1.PrioritizerPolicy{ + Mode: clusterapiv1alpha1.PrioritizerPolicyModeExact, + Configurations: []clusterapiv1alpha1.PrioritizerConfig{ + { + Name: "ResourceRatioCPU", + Weight: 1, + }, + { + Name: "ResourceRatioMemory", + Weight: 1, + }, + }, + } + + //Creating the clusters with resources + assertBindingClusterSet(clusterSet1Name) + assertCreatingClustersWithNames(clusterSet1Name, clusterNames) + for i, name := range clusterNames { + assertUpdatingClusterWithClusterResources(name, clusterResources[i]) + } + + //Checking the result of the placement + assertCreatingPlacement(placementName, noc(2), 2, prioritizerPolicy) + assertClusterNamesOfDecisions(placementName, []string{clusterNames[1], clusterNames[2]}) + + ginkgo.By("Adding a new cluster with resources") + clusterNames = append(clusterNames, clusterName+"-4") + newClusterResources := []string{"10", "10", "100", "100"} + assertCreatingClustersWithNames(clusterSet1Name, clusterNames[3:4]) + assertUpdatingClusterWithClusterResources(clusterNames[3], newClusterResources) + + //Checking the result of the placement + assertClusterNamesOfDecisions(placementName, []string{clusterNames[2], clusterNames[3]}) + + }) + + ginkgo.It("Should keep steady successfully even placementdecisions' balance and cluster resource changes", func() { + // cluster settings + clusterNames := []string{ + clusterName + "-1", + clusterName + "-2", + clusterName + "-3", + } + clusterResources := make([][]string, len(clusterNames)) + clusterResources[0] = []string{"10", "10", "50", "100"} + clusterResources[1] = []string{"7", "10", "90", "100"} + clusterResources[2] = []string{"9", "10", "80", "100"} + + // placement settings + prioritizerPolicy := clusterapiv1alpha1.PrioritizerPolicy{ + Mode: clusterapiv1alpha1.PrioritizerPolicyModeAdditive, + Configurations: []clusterapiv1alpha1.PrioritizerConfig{ + { + Name: "Steady", + Weight: 3, + }, + { + Name: "ResourceRatioCPU", + Weight: 1, + }, + { + Name: "ResourceRatioMemory", + Weight: 1, + }, + }, + } + //Creating the clusters with resources + assertBindingClusterSet(clusterSet1Name) + assertCreatingClustersWithNames(clusterSet1Name, clusterNames) + for i, name := range clusterNames { + assertUpdatingClusterWithClusterResources(name, clusterResources[i]) + } + + //Checking the result of the placement + assertCreatingPlacement(placementName, noc(2), 2, prioritizerPolicy) + assertClusterNamesOfDecisions(placementName, []string{clusterNames[1], clusterNames[2]}) + + ginkgo.By("Adding fake placement decisions and update cluster resources") + assertCreatingPlacementDecision(placementName+"-1", []string{clusterNames[1]}) + assertUpdatingClusterWithClusterResources(clusterNames[0], []string{"10", "10", "10", "100"}) + + //Checking the result of the placement + assertClusterNamesOfDecisions(placementName, []string{clusterNames[1], clusterNames[2]}) + }) }) })