Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions pkg/scheduler/framework/score.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

package framework

import (
fleetv1 "go.goms.io/fleet/apis/v1"
)

// ClusterScore is the scores the scheduler assigns to a cluster.
type ClusterScore struct {
// TopologySpreadScore determines how much a binding would satisfy the topology spread
// constraints specified by the user.
TopologySpreadScore int
// AffinityScore determines how much a binding would satisfy the affinity terms
// specified by the user.
AffinityScore int
}

// Add adds a ClusterScore to another ClusterScore.
func (s1 *ClusterScore) Add(s2 ClusterScore) {
s1.TopologySpreadScore += s2.TopologySpreadScore
s1.AffinityScore += s2.AffinityScore
}

// Less returns true if a ClusterScore is less than another.
func (s1 *ClusterScore) Less(s2 *ClusterScore) bool {
if s1.TopologySpreadScore != s2.TopologySpreadScore {
return s1.TopologySpreadScore < s2.TopologySpreadScore
}

return s1.AffinityScore < s2.AffinityScore
}

// ScoredCluster is a cluster with a score.
type ScoredCluster struct {
Cluster *fleetv1.MemberCluster
Score *ClusterScore
}

// ScoredClusters is a list of ScoredClusters; this type implements the sort.Interface.
type ScoredClusters []*ScoredCluster

// Len returns the length of a ScoredClusters; it implemented sort.Interface.Len().
func (sc ScoredClusters) Len() int { return len(sc) }

// Less returns true if a ScoredCluster is of a lower score than another; it implemented sort.Interface.Less().
func (sc ScoredClusters) Less(i, j int) bool {
return sc[i].Score.Less(sc[j].Score)
}

// Swap swaps two ScoredClusters in the list; it implemented sort.Interface.Swap().
func (sc ScoredClusters) Swap(i, j int) {
sc[i], sc[j] = sc[j], sc[i]
}
310 changes: 310 additions & 0 deletions pkg/scheduler/framework/score_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

package framework

import (
"sort"
"testing"

"github.com/google/go-cmp/cmp"

fleetv1 "go.goms.io/fleet/apis/v1"
)

func TestClusterScoreAdd(t *testing.T) {
s1 := &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 0,
}

s2 := &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 5,
}

s1.Add(*s2)
want := &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 5,
}
if !cmp.Equal(s1, want) {
t.Fatalf("Add() = %v, want %v", s1, want)
}
}

func TestClusterScoreLess(t *testing.T) {
testCases := []struct {
name string
s1 *ClusterScore
s2 *ClusterScore
want bool
}{
{
name: "s1 is less than s2 in topology spread score",
s1: &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 10,
},
s2: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 20,
},
want: true,
},
{
name: "s1 is less than s2 in affinity score",
s1: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 10,
},
s2: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 20,
},
want: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if tc.s1.Less(tc.s2) != tc.want {
t.Fatalf("Less(%v, %v) = %t, want %t", tc.s1, tc.s2, !tc.want, tc.want)
}

if tc.s2.Less(tc.s1) != !tc.want {
t.Fatalf("Less(%v, %v) = %t, want %t", tc.s2, tc.s1, tc.want, !tc.want)
}
})
}
}

func TestClusterScoreEqual(t *testing.T) {
s1 := &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 0,
}

s2 := &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 0,
}

if s1.Less(s2) || s2.Less(s1) {
t.Fatalf("Less(%v, %v) = %v, Less(%v, %v) = %v, want both to be false", s1, s2, s1.Less(s2), s2, s1, s2.Less(s1))
}
}

func TestScoredClustersSort(t *testing.T) {
clusterA := &fleetv1.MemberCluster{}
clusterB := &fleetv1.MemberCluster{}
clusterC := &fleetv1.MemberCluster{}
clusterD := &fleetv1.MemberCluster{}

testCases := []struct {
name string
scs ScoredClusters
want ScoredClusters
}{
{
name: "sort asc values",
scs: ScoredClusters{
{
Cluster: clusterA,
Score: &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 10,
},
},
{
Cluster: clusterB,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 10,
},
},
{
Cluster: clusterC,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 20,
},
},
{
Cluster: clusterD,
Score: &ClusterScore{
TopologySpreadScore: 2,
AffinityScore: 30,
},
},
},
want: ScoredClusters{
{
Cluster: clusterD,
Score: &ClusterScore{
TopologySpreadScore: 2,
AffinityScore: 30,
},
},
{
Cluster: clusterC,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 20,
},
},
{
Cluster: clusterB,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 10,
},
},
{
Cluster: clusterA,
Score: &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 10,
},
},
},
},
{
name: "sort desc values",
scs: ScoredClusters{
{
Cluster: clusterD,
Score: &ClusterScore{
TopologySpreadScore: 2,
AffinityScore: 30,
},
},
{
Cluster: clusterC,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 20,
},
},
{
Cluster: clusterB,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 10,
},
},
{
Cluster: clusterA,
Score: &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 10,
},
},
},
want: ScoredClusters{
{
Cluster: clusterD,
Score: &ClusterScore{
TopologySpreadScore: 2,
AffinityScore: 30,
},
},
{
Cluster: clusterC,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 20,
},
},
{
Cluster: clusterB,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 10,
},
},
{
Cluster: clusterA,
Score: &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 10,
},
},
},
},
{
name: "sort values in random",
scs: ScoredClusters{
{
Cluster: clusterC,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 20,
},
},
{
Cluster: clusterD,
Score: &ClusterScore{
TopologySpreadScore: 2,
AffinityScore: 30,
},
},
{
Cluster: clusterA,
Score: &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 10,
},
},
{
Cluster: clusterB,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 10,
},
},
},
want: ScoredClusters{
{
Cluster: clusterD,
Score: &ClusterScore{
TopologySpreadScore: 2,
AffinityScore: 30,
},
},
{
Cluster: clusterC,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 20,
},
},
{
Cluster: clusterB,
Score: &ClusterScore{
TopologySpreadScore: 1,
AffinityScore: 10,
},
},
{
Cluster: clusterA,
Score: &ClusterScore{
TopologySpreadScore: 0,
AffinityScore: 10,
},
},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
sort.Sort(sort.Reverse(tc.scs))
if !cmp.Equal(tc.scs, tc.want) {
t.Fatalf("Sort() = %v, want %v", tc.scs, tc.want)
}
})
}
}