Merge pull request #81614 from liu-cong/score-refactor

Move RunNormalizeScorePlugins and ApplyScoreWeights into RunScorePlugins; Also add unit tests for RunScorePlugins.
This commit is contained in:
Kubernetes Prow Robot 2019-08-21 10:37:29 -07:00 committed by GitHub
commit 90df64b75b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 202 additions and 602 deletions

View File

@ -784,18 +784,6 @@ func PrioritizeNodes(
return schedulerapi.HostPriorityList{}, scoreStatus.AsError() return schedulerapi.HostPriorityList{}, scoreStatus.AsError()
} }
// Run the Normalize Score plugins.
status := framework.RunNormalizeScorePlugins(pluginContext, pod, scoresMap)
if !status.IsSuccess() {
return schedulerapi.HostPriorityList{}, status.AsError()
}
// Apply weights for scores.
status = framework.ApplyScoreWeights(pluginContext, pod, scoresMap)
if !status.IsSuccess() {
return schedulerapi.HostPriorityList{}, status.AsError()
}
// Summarize all scores. // Summarize all scores.
result := make(schedulerapi.HostPriorityList, 0, len(nodes)) result := make(schedulerapi.HostPriorityList, 0, len(nodes))

View File

@ -48,6 +48,7 @@ go_test(
deps = [ deps = [
"//pkg/scheduler/apis/config:go_default_library", "//pkg/scheduler/apis/config:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
], ],
) )

View File

@ -360,6 +360,8 @@ func (f *framework) RunScorePlugins(pc *PluginContext, pod *v1.Pod, nodes []*v1.
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
errCh := schedutil.NewErrorChannel() errCh := schedutil.NewErrorChannel()
// Run Score method for each node in parallel.
workqueue.ParallelizeUntil(ctx, 16, len(nodes), func(index int) { workqueue.ParallelizeUntil(ctx, 16, len(nodes), func(index int) {
for _, pl := range f.scorePlugins { for _, pl := range f.scorePlugins {
nodeName := nodes[index].Name nodeName := nodes[index].Name
@ -374,31 +376,16 @@ func (f *framework) RunScorePlugins(pc *PluginContext, pod *v1.Pod, nodes []*v1.
} }
} }
}) })
if err := errCh.ReceiveError(); err != nil { if err := errCh.ReceiveError(); err != nil {
msg := fmt.Sprintf("error while running score plugin for pod %q: %v", pod.Name, err) msg := fmt.Sprintf("error while running score plugin for pod %q: %v", pod.Name, err)
klog.Error(msg) klog.Error(msg)
return nil, NewStatus(Error, msg) return nil, NewStatus(Error, msg)
} }
return pluginToNodeScores, nil // Run NormalizeScore method for each ScoreWithNormalizePlugin in parallel.
}
// RunNormalizeScorePlugins runs the NormalizeScore function of Score plugins.
// It should be called after RunScorePlugins with the PluginToNodeScores result.
// It then modifies the list with normalized scores. It returns a non-success Status
// if any of the NormalizeScore functions returns a non-success status.
func (f *framework) RunNormalizeScorePlugins(pc *PluginContext, pod *v1.Pod, scores PluginToNodeScores) *Status {
ctx, cancel := context.WithCancel(context.Background())
errCh := schedutil.NewErrorChannel()
workqueue.ParallelizeUntil(ctx, 16, len(f.scoreWithNormalizePlugins), func(index int) { workqueue.ParallelizeUntil(ctx, 16, len(f.scoreWithNormalizePlugins), func(index int) {
pl := f.scoreWithNormalizePlugins[index] pl := f.scoreWithNormalizePlugins[index]
nodeScoreList, ok := scores[pl.Name()] nodeScoreList := pluginToNodeScores[pl.Name()]
if !ok {
err := fmt.Errorf("normalize score plugin %q has no corresponding scores in the PluginToNodeScores", pl.Name())
errCh.SendErrorWithCancel(err, cancel)
return
}
status := pl.NormalizeScore(pc, pod, nodeScoreList) status := pl.NormalizeScore(pc, pod, nodeScoreList)
if !status.IsSuccess() { if !status.IsSuccess() {
err := fmt.Errorf("normalize score plugin %q failed with error %v", pl.Name(), status.Message()) err := fmt.Errorf("normalize score plugin %q failed with error %v", pl.Name(), status.Message())
@ -406,51 +393,36 @@ func (f *framework) RunNormalizeScorePlugins(pc *PluginContext, pod *v1.Pod, sco
return return
} }
}) })
if err := errCh.ReceiveError(); err != nil { if err := errCh.ReceiveError(); err != nil {
msg := fmt.Sprintf("error while running normalize score plugin for pod %q: %v", pod.Name, err) msg := fmt.Sprintf("error while running normalize score plugin for pod %q: %v", pod.Name, err)
klog.Error(msg) klog.Error(msg)
return NewStatus(Error, msg) return nil, NewStatus(Error, msg)
} }
return nil // Apply score defaultWeights for each ScorePlugin in parallel.
}
// ApplyScoreWeights applies weights to the score results. It should be called after
// RunNormalizeScorePlugins.
func (f *framework) ApplyScoreWeights(pc *PluginContext, pod *v1.Pod, scores PluginToNodeScores) *Status {
ctx, cancel := context.WithCancel(context.Background())
errCh := schedutil.NewErrorChannel()
workqueue.ParallelizeUntil(ctx, 16, len(f.scorePlugins), func(index int) { workqueue.ParallelizeUntil(ctx, 16, len(f.scorePlugins), func(index int) {
pl := f.scorePlugins[index] pl := f.scorePlugins[index]
// Score plugins' weight has been checked when they are initialized. // Score plugins' weight has been checked when they are initialized.
weight := f.pluginNameToWeightMap[pl.Name()] weight := f.pluginNameToWeightMap[pl.Name()]
nodeScoreList, ok := scores[pl.Name()] nodeScoreList := pluginToNodeScores[pl.Name()]
if !ok {
err := fmt.Errorf("score plugin %q has no corresponding scores in the PluginToNodeScores", pl.Name())
errCh.SendErrorWithCancel(err, cancel)
return
}
for i, nodeScore := range nodeScoreList { for i, nodeScore := range nodeScoreList {
// return error if score plugin returns invalid score. // return error if score plugin returns invalid score.
if nodeScore.Score > MaxNodeScore || nodeScore.Score < MinNodeScore { if nodeScore.Score > MaxNodeScore || nodeScore.Score < MinNodeScore {
err := fmt.Errorf("score plugin %q returns an invalid score %q, it should in the range of [MinNodeScore, MaxNodeScore] after normalizing", pl.Name(), nodeScore.Score) err := fmt.Errorf("score plugin %q returns an invalid score %v, it should in the range of [%v, %v] after normalizing", pl.Name(), nodeScore.Score, MinNodeScore, MaxNodeScore)
errCh.SendErrorWithCancel(err, cancel) errCh.SendErrorWithCancel(err, cancel)
return return
} }
nodeScoreList[i].Score = nodeScore.Score * weight nodeScoreList[i].Score = nodeScore.Score * weight
} }
}) })
if err := errCh.ReceiveError(); err != nil { if err := errCh.ReceiveError(); err != nil {
msg := fmt.Sprintf("error while applying score weights for pod %q: %v", pod.Name, err) msg := fmt.Sprintf("error while applying score defaultWeights for pod %q: %v", pod.Name, err)
klog.Error(msg) klog.Error(msg)
return NewStatus(Error, msg) return nil, NewStatus(Error, msg)
} }
return nil return pluginToNodeScores, nil
} }
// RunPrebindPlugins runs the set of configured prebind plugins. It returns a // RunPrebindPlugins runs the set of configured prebind plugins. It returns a

View File

@ -20,160 +20,98 @@ import (
"reflect" "reflect"
"testing" "testing"
v1 "k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/apis/config"
) )
const ( const (
scoreWithNormalizePlugin1 = "score-with-normalize-plugin-1"
scoreWithNormalizePlugin2 = "score-with-normalize-plugin-2"
scorePlugin1 = "score-plugin-1" scorePlugin1 = "score-plugin-1"
scorePlugin2 = "score-plugin-2"
scorePlugin3 = "score-plugin-3"
pluginNotImplementingScore = "plugin-not-implementing-score" pluginNotImplementingScore = "plugin-not-implementing-score"
weight1 = 2
weight2 = 3
weight3 = 4
) )
// TestScorePlugin1 and 2 implements ScoreWithNormalizePlugin interface, // TestScoreWithNormalizePlugin implements ScoreWithNormalizePlugin interface.
// TestScorePlugin3 only implements ScorePlugin interface. // TestScorePlugin only implements ScorePlugin interface.
var _ = ScoreWithNormalizePlugin(&TestScorePlugin1{}) var _ = ScoreWithNormalizePlugin(&TestScoreWithNormalizePlugin{})
var _ = ScoreWithNormalizePlugin(&TestScorePlugin2{}) var _ = ScorePlugin(&TestScorePlugin{})
var _ = ScorePlugin(&TestScorePlugin3{})
type TestScorePlugin1 struct { func newScoreWithNormalizePlugin1(inj injectedResult) Plugin {
// If fail is true, NormalizeScore will return error status. return &TestScoreWithNormalizePlugin{scoreWithNormalizePlugin1, inj}
fail bool }
func newScoreWithNormalizePlugin2(inj injectedResult) Plugin {
return &TestScoreWithNormalizePlugin{scoreWithNormalizePlugin2, inj}
}
func newScorePlugin1(inj injectedResult) Plugin {
return &TestScorePlugin{scorePlugin1, inj}
}
func newPluginNotImplementingScore(injectedResult) Plugin {
return &PluginNotImplementingScore{}
} }
// NewScorePlugin1 is the factory for NormalizeScore plugin 1. type TestScoreWithNormalizePlugin struct {
func NewScorePlugin1(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) { name string
return &TestScorePlugin1{}, nil inj injectedResult
} }
// NewScorePlugin1InjectFailure creates a new TestScorePlugin1 which will func (pl *TestScoreWithNormalizePlugin) Name() string {
// return an error status for NormalizeScore. return pl.name
func NewScorePlugin1InjectFailure(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
return &TestScorePlugin1{fail: true}, nil
} }
func (pl *TestScorePlugin1) Name() string { func (pl *TestScoreWithNormalizePlugin) NormalizeScore(pc *PluginContext, pod *v1.Pod, scores NodeScoreList) *Status {
return scorePlugin1 return injectNormalizeRes(pl.inj, scores)
} }
func (pl *TestScorePlugin1) NormalizeScore(pc *PluginContext, pod *v1.Pod, scores NodeScoreList) *Status { func (pl *TestScoreWithNormalizePlugin) Score(pc *PluginContext, p *v1.Pod, nodeName string) (int, *Status) {
if pl.fail { return setScoreRes(pl.inj)
return NewStatus(Error, "injecting failure.")
}
// Simply decrease each node score by 1.
for i := range scores {
scores[i].Score = scores[i].Score - 1
}
return nil
} }
func (pl *TestScorePlugin1) Score(pc *PluginContext, p *v1.Pod, nodeName string) (int, *Status) { // TestScorePlugin only implements ScorePlugin interface.
// Score is currently not used in the tests so just return some dummy value. type TestScorePlugin struct {
return 0, nil name string
inj injectedResult
} }
type TestScorePlugin2 struct{} func (pl *TestScorePlugin) Name() string {
return pl.name
// NewScorePlugin2 is the factory for NormalizeScore plugin 2.
func NewScorePlugin2(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
return &TestScorePlugin2{}, nil
} }
func (pl *TestScorePlugin2) Name() string { func (pl *TestScorePlugin) Score(pc *PluginContext, p *v1.Pod, nodeName string) (int, *Status) {
return scorePlugin2 return setScoreRes(pl.inj)
}
func (pl *TestScorePlugin2) NormalizeScore(pc *PluginContext, pod *v1.Pod, scores NodeScoreList) *Status {
// Simply force each node score to 5.
for i := range scores {
scores[i].Score = 5
}
return nil
}
func (pl *TestScorePlugin2) Score(pc *PluginContext, p *v1.Pod, nodeName string) (int, *Status) {
// Score is currently not used in the tests so just return some dummy value.
return 0, nil
}
// TestScorePlugin3 only implements ScorePlugin interface.
type TestScorePlugin3 struct{}
// NewScorePlugin3 is the factory for Score plugin 3.
func NewScorePlugin3(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
return &TestScorePlugin3{}, nil
}
func (pl *TestScorePlugin3) Name() string {
return scorePlugin3
}
func (pl *TestScorePlugin3) Score(pc *PluginContext, p *v1.Pod, nodeName string) (int, *Status) {
// Score is currently not used in the tests so just return some dummy value.
return 0, nil
} }
// PluginNotImplementingScore doesn't implement the ScorePlugin interface. // PluginNotImplementingScore doesn't implement the ScorePlugin interface.
type PluginNotImplementingScore struct{} type PluginNotImplementingScore struct{}
// NewPluginNotImplementingScore is the factory for PluginNotImplementingScore.
func NewPluginNotImplementingScore(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
return &PluginNotImplementingScore{}, nil
}
func (pl *PluginNotImplementingScore) Name() string { func (pl *PluginNotImplementingScore) Name() string {
return pluginNotImplementingScore return pluginNotImplementingScore
} }
var registry = Registry{ var defaultConstructors = map[string]func(injectedResult) Plugin{
scorePlugin1: NewScorePlugin1, scoreWithNormalizePlugin1: newScoreWithNormalizePlugin1,
scorePlugin2: NewScorePlugin2, scoreWithNormalizePlugin2: newScoreWithNormalizePlugin2,
scorePlugin3: NewScorePlugin3, scorePlugin1: newScorePlugin1,
pluginNotImplementingScore: NewPluginNotImplementingScore, pluginNotImplementingScore: newPluginNotImplementingScore,
} }
var plugin1 = &config.Plugins{ var defaultWeights = map[string]int32{
Score: &config.PluginSet{ scoreWithNormalizePlugin1: 1,
Enabled: []config.Plugin{ scoreWithNormalizePlugin2: 2,
{Name: scorePlugin1, Weight: weight1}, scorePlugin1: 1,
},
},
}
var plugin3 = &config.Plugins{
Score: &config.PluginSet{
Enabled: []config.Plugin{
{Name: scorePlugin3, Weight: weight3},
},
},
}
var plugin1And2 = &config.Plugins{
Score: &config.PluginSet{
Enabled: []config.Plugin{
{Name: scorePlugin1, Weight: weight1},
{Name: scorePlugin2, Weight: weight2},
},
},
}
var plugin1And3 = &config.Plugins{
Score: &config.PluginSet{
Enabled: []config.Plugin{
{Name: scorePlugin1, Weight: weight1},
{Name: scorePlugin3, Weight: weight3},
},
},
} }
// No specific config required. // No specific config required.
var args = []config.PluginConfig{} var args []config.PluginConfig
var pc = &PluginContext{} var pc = &PluginContext{}
// Pod is only used for logging errors. // Pod is only used for logging errors.
var pod = &v1.Pod{} var pod = &v1.Pod{}
var nodes = []*v1.Node{
{ObjectMeta: metav1.ObjectMeta{Name: "node1"}},
{ObjectMeta: metav1.ObjectMeta{Name: "node2"}},
}
func TestInitFrameworkWithScorePlugins(t *testing.T) { func TestInitFrameworkWithScorePlugins(t *testing.T) {
tests := []struct { tests := []struct {
@ -184,24 +122,12 @@ func TestInitFrameworkWithScorePlugins(t *testing.T) {
}{ }{
{ {
name: "enabled Score plugin doesn't exist in registry", name: "enabled Score plugin doesn't exist in registry",
plugins: &config.Plugins{ plugins: buildConfigDefaultWeights("notExist"),
Score: &config.PluginSet{
Enabled: []config.Plugin{
{Name: "notExist"},
},
},
},
initErr: true, initErr: true,
}, },
{ {
name: "enabled Score plugin doesn't extend the ScorePlugin interface", name: "enabled Score plugin doesn't extend the ScorePlugin interface",
plugins: &config.Plugins{ plugins: buildConfigDefaultWeights(pluginNotImplementingScore),
Score: &config.PluginSet{
Enabled: []config.Plugin{
{Name: pluginNotImplementingScore},
},
},
},
initErr: true, initErr: true,
}, },
{ {
@ -210,37 +136,21 @@ func TestInitFrameworkWithScorePlugins(t *testing.T) {
}, },
{ {
name: "enabled Score plugin list is empty", name: "enabled Score plugin list is empty",
plugins: &config.Plugins{ plugins: buildConfigDefaultWeights(),
Score: &config.PluginSet{
Enabled: []config.Plugin{},
},
},
}, },
{ {
name: "enabled plugin only implements ScorePlugin interface", name: "enabled plugin only implements ScorePlugin interface",
plugins: &config.Plugins{ plugins: buildConfigDefaultWeights(scorePlugin1),
Score: &config.PluginSet{
Enabled: []config.Plugin{
{Name: scorePlugin3},
},
},
},
}, },
{ {
name: "enabled plugin implements ScoreWithNormalizePlugin interface", name: "enabled plugin implements ScoreWithNormalizePlugin interface",
plugins: &config.Plugins{ plugins: buildConfigDefaultWeights(scoreWithNormalizePlugin1),
Score: &config.PluginSet{
Enabled: []config.Plugin{
{Name: scorePlugin1},
},
},
},
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := NewFramework(registry, tt.plugins, args) _, err := NewFramework(toRegistry(defaultConstructors, make(map[string]injectedResult)), tt.plugins, args)
if tt.initErr && err == nil { if tt.initErr && err == nil {
t.Fatal("Framework initialization should fail") t.Fatal("Framework initialization should fail")
} }
@ -251,410 +161,106 @@ func TestInitFrameworkWithScorePlugins(t *testing.T) {
} }
} }
func TestRunNormalizeScorePlugins(t *testing.T) { func TestRunScorePlugins(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
registry Registry registry Registry
plugins *config.Plugins plugins *config.Plugins
input PluginToNodeScores injectedRes map[string]injectedResult
want PluginToNodeScores want PluginToNodeScores
// If err is true, we expect RunNormalizeScorePlugin to fail. // If err is true, we expect RunScorePlugin to fail.
err bool err bool
}{ }{
{ {
name: "no NormalizeScore plugins", name: "no Score plugins",
plugins: plugin3, plugins: buildConfigDefaultWeights(),
registry: registry, want: PluginToNodeScores{},
input: PluginToNodeScores{
scorePlugin1: {
{
Name: "node1",
Score: 2,
}, },
{ {
Name: "node2", name: "single Score plugin",
Score: 3, plugins: buildConfigDefaultWeights(scorePlugin1),
injectedRes: map[string]injectedResult{
scorePlugin1: {scoreRes: 1},
}, },
}, // scorePlugin1 Score returns 1, weight=1, so want=1.
},
// No NormalizeScore plugin, map should be untouched.
want: PluginToNodeScores{ want: PluginToNodeScores{
scorePlugin1: { scorePlugin1: {{Name: "node1", Score: 1}, {Name: "node2", Score: 1}},
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
}, },
}, },
{ {
name: "single Score plugin, single NormalizeScore plugin", name: "single ScoreWithNormalize plugin",
registry: registry, //registry: registry,
plugins: plugin1, plugins: buildConfigDefaultWeights(scoreWithNormalizePlugin1),
input: PluginToNodeScores{ injectedRes: map[string]injectedResult{
scorePlugin1: { scoreWithNormalizePlugin1: {scoreRes: 10, normalizeRes: 5},
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
}, },
// scoreWithNormalizePlugin1 Score returns 10, but NormalizeScore overrides to 5, weight=1, so want=5
want: PluginToNodeScores{ want: PluginToNodeScores{
// For plugin1, want=input-1. scoreWithNormalizePlugin1: {{Name: "node1", Score: 5}, {Name: "node2", Score: 5}},
scorePlugin1: {
{
Name: "node1",
Score: 1,
},
{
Name: "node2",
Score: 2,
},
},
}, },
}, },
{ {
name: "2 Score plugins, 2 NormalizeScore plugins", name: "2 Score plugins, 2 NormalizeScore plugins",
registry: registry, plugins: buildConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1, scoreWithNormalizePlugin2),
plugins: plugin1And2, injectedRes: map[string]injectedResult{
input: PluginToNodeScores{ scorePlugin1: {scoreRes: 1},
scorePlugin1: { scoreWithNormalizePlugin1: {scoreRes: 3, normalizeRes: 4},
{ scoreWithNormalizePlugin2: {scoreRes: 4, normalizeRes: 5},
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
scorePlugin2: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 4,
},
},
}, },
// scorePlugin1 Score returns 1, weight =1, so want=1.
// scoreWithNormalizePlugin1 Score returns 3, but NormalizeScore overrides to 4, weight=1, so want=4.
// scoreWithNormalizePlugin2 Score returns 4, but NormalizeScore overrides to 5, weight=2, so want=10.
want: PluginToNodeScores{ want: PluginToNodeScores{
// For plugin1, want=input-1. scorePlugin1: {{Name: "node1", Score: 1}, {Name: "node2", Score: 1}},
scorePlugin1: { scoreWithNormalizePlugin1: {{Name: "node1", Score: 4}, {Name: "node2", Score: 4}},
{ scoreWithNormalizePlugin2: {{Name: "node1", Score: 10}, {Name: "node2", Score: 10}},
Name: "node1",
Score: 1,
},
{
Name: "node2",
Score: 2,
},
},
// For plugin2, want=5.
scorePlugin2: {
{
Name: "node1",
Score: 5,
},
{
Name: "node2",
Score: 5,
},
},
}, },
}, },
{ {
name: "2 Score plugins, 1 NormalizeScore plugin", name: "score fails",
registry: registry, injectedRes: map[string]injectedResult{
plugins: plugin1And3, scoreWithNormalizePlugin1: {scoreErr: true},
input: PluginToNodeScores{
scorePlugin1: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
scorePlugin2: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 4,
},
},
},
want: PluginToNodeScores{
// For plugin1, want=input-1.
scorePlugin1: {
{
Name: "node1",
Score: 1,
},
{
Name: "node2",
Score: 2,
},
},
// No NormalizeScore for plugin 3. The node scores are untouched.
scorePlugin2: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 4,
},
},
},
},
{
name: "score map contains both test plugin 1 and 2 but plugin 1 fails",
registry: Registry{
scorePlugin1: NewScorePlugin1InjectFailure,
scorePlugin2: NewScorePlugin2,
},
plugins: plugin1And2,
input: PluginToNodeScores{
scorePlugin1: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
scorePlugin2: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 4,
},
},
}, },
plugins: buildConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1),
err: true, err: true,
}, },
{ {
name: "2 plugins but score map only contains plugin1", name: "normalize fails",
registry: registry, injectedRes: map[string]injectedResult{
plugins: plugin1And2, scoreWithNormalizePlugin1: {normalizeErr: true},
input: PluginToNodeScores{
scorePlugin1: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
},
err: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := NewFramework(tt.registry, tt.plugins, args)
if err != nil {
t.Fatalf("Failed to create framework for testing: %v", err)
}
status := f.RunNormalizeScorePlugins(pc, pod, tt.input)
if tt.err {
if status.IsSuccess() {
t.Errorf("Expected status to be non-success.")
}
} else {
if !status.IsSuccess() {
t.Errorf("Expected status to be success.")
}
if !reflect.DeepEqual(tt.input, tt.want) {
t.Errorf("Score map after RunNormalizeScorePlugin: %+v, want: %+v.", tt.input, tt.want)
}
}
})
}
}
func TestApplyScoreWeights(t *testing.T) {
tests := []struct {
name string
plugins *config.Plugins
input PluginToNodeScores
want PluginToNodeScores
// If err is true, we expect ApplyScoreWeights to fail.
err bool
}{
{
name: "single Score plugin, single nodeScoreList",
plugins: plugin1,
input: PluginToNodeScores{
scorePlugin1: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
},
want: PluginToNodeScores{
// For plugin1, want=input*weight1.
scorePlugin1: {
{
Name: "node1",
Score: 4,
},
{
Name: "node2",
Score: 6,
},
},
},
},
{
name: "2 Score plugins, 2 nodeScoreLists in scoreMap",
plugins: plugin1And2,
input: PluginToNodeScores{
scorePlugin1: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
scorePlugin2: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 4,
},
},
},
want: PluginToNodeScores{
// For plugin1, want=input*weight1.
scorePlugin1: {
{
Name: "node1",
Score: 4,
},
{
Name: "node2",
Score: 6,
},
},
// For plugin2, want=input*weight2.
scorePlugin2: {
{
Name: "node1",
Score: 6,
},
{
Name: "node2",
Score: 12,
},
},
},
},
{
name: "2 Score plugins, 1 without corresponding nodeScoreList in the score map",
plugins: plugin1And2,
input: PluginToNodeScores{
scorePlugin1: {
{
Name: "node1",
Score: 2,
},
{
Name: "node2",
Score: 3,
},
},
}, },
plugins: buildConfigDefaultWeights(scorePlugin1, scoreWithNormalizePlugin1),
err: true, err: true,
}, },
{ {
name: "Score plugin return score greater than MaxNodeScore", name: "Score plugin return score greater than MaxNodeScore",
plugins: plugin1And2, plugins: buildConfigDefaultWeights(scorePlugin1),
input: PluginToNodeScores{ injectedRes: map[string]injectedResult{
scorePlugin1: { scorePlugin1: {scoreRes: MaxNodeScore + 1},
{
Name: "node1",
Score: MaxNodeScore + 1,
},
{
Name: "node2",
Score: 3,
},
},
scorePlugin2: {
{
Name: "node1",
Score: MinNodeScore,
},
{
Name: "node2",
Score: 5,
},
},
}, },
err: true, err: true,
}, },
{ {
name: "Score plugin return score less than MinNodeScore", name: "Score plugin return score less than MinNodeScore",
plugins: plugin1And2, plugins: buildConfigDefaultWeights(scorePlugin1),
input: PluginToNodeScores{ injectedRes: map[string]injectedResult{
scorePlugin1: { scorePlugin1: {scoreRes: MinNodeScore - 1},
{ },
Name: "node1", err: true,
Score: MaxNodeScore,
}, },
{ {
Name: "node2", name: "ScoreWithNormalize plugin return score greater than MaxNodeScore",
Score: 3, plugins: buildConfigDefaultWeights(scoreWithNormalizePlugin1),
injectedRes: map[string]injectedResult{
scoreWithNormalizePlugin1: {normalizeRes: MaxNodeScore + 1},
}, },
}, err: true,
scorePlugin2: {
{
Name: "node1",
Score: MinNodeScore - 1,
}, },
{ {
Name: "node2", name: "ScoreWithNormalize plugin return score less than MinNodeScore",
Score: 5, plugins: buildConfigDefaultWeights(scoreWithNormalizePlugin1),
}, injectedRes: map[string]injectedResult{
}, scoreWithNormalizePlugin1: {normalizeRes: MinNodeScore - 1},
}, },
err: true, err: true,
}, },
@ -662,25 +268,76 @@ func TestApplyScoreWeights(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
// Inject the results for each plugin.
registry := toRegistry(defaultConstructors, tt.injectedRes)
f, err := NewFramework(registry, tt.plugins, args) f, err := NewFramework(registry, tt.plugins, args)
if err != nil { if err != nil {
t.Fatalf("Failed to create framework for testing: %v", err) t.Fatalf("Failed to create framework for testing: %v", err)
} }
status := f.ApplyScoreWeights(pc, pod, tt.input) res, status := f.RunScorePlugins(pc, pod, nodes)
if tt.err { if tt.err {
if status.IsSuccess() { if status.IsSuccess() {
t.Errorf("Expected status to be non-success.") t.Error("Expected status to be non-success.")
} }
} else { return
}
if !status.IsSuccess() { if !status.IsSuccess() {
t.Errorf("Expected status to be success.") t.Errorf("Expected status to be success.")
} }
if !reflect.DeepEqual(tt.input, tt.want) { if !reflect.DeepEqual(res, tt.want) {
t.Errorf("Score map after RunNormalizeScorePlugin: %+v, want: %+v.", tt.input, tt.want) t.Errorf("Score map after RunScorePlugin: %+v, want: %+v.", res, tt.want)
}
} }
}) })
} }
} }
func toRegistry(constructors map[string]func(injectedResult) Plugin, injectedRes map[string]injectedResult) Registry {
registry := make(Registry)
for pl, f := range constructors {
npl := pl
nf := f
registry[pl] = func(_ *runtime.Unknown, _ FrameworkHandle) (Plugin, error) {
return nf(injectedRes[npl]), nil
}
}
return registry
}
func buildConfigDefaultWeights(ps ...string) *config.Plugins {
return buildConfigWithWeights(defaultWeights, ps...)
}
func buildConfigWithWeights(weights map[string]int32, ps ...string) *config.Plugins {
var plugins []config.Plugin
for _, p := range ps {
plugins = append(plugins, config.Plugin{Name: p, Weight: weights[p]})
}
return &config.Plugins{Score: &config.PluginSet{Enabled: plugins}}
}
type injectedResult struct {
scoreRes int
normalizeRes int
scoreErr bool
normalizeErr bool
}
func setScoreRes(inj injectedResult) (int, *Status) {
if inj.scoreErr {
return 0, NewStatus(Error, "injecting failure.")
}
return inj.scoreRes, nil
}
func injectNormalizeRes(inj injectedResult, scores NodeScoreList) *Status {
if inj.normalizeErr {
return NewStatus(Error, "injecting failure.")
}
for i := range scores {
scores[i].Score = inj.normalizeRes
}
return nil
}

View File

@ -311,16 +311,6 @@ type Framework interface {
// a non-success status. // a non-success status.
RunScorePlugins(pc *PluginContext, pod *v1.Pod, nodes []*v1.Node) (PluginToNodeScores, *Status) RunScorePlugins(pc *PluginContext, pod *v1.Pod, nodes []*v1.Node) (PluginToNodeScores, *Status)
// RunNormalizeScorePlugins runs the normalize score plugins. It should be called after
// RunScorePlugins with the PluginToNodeScores result. It then modifies the map with
// normalized scores. It returns a non-success Status if any of the normalize score plugins
// returns a non-success status.
RunNormalizeScorePlugins(pc *PluginContext, pod *v1.Pod, scores PluginToNodeScores) *Status
// ApplyScoreWeights applies weights to the score results. It should be called after
// RunNormalizeScorePlugins.
ApplyScoreWeights(pc *PluginContext, pod *v1.Pod, scores PluginToNodeScores) *Status
// RunPrebindPlugins runs the set of configured prebind plugins. It returns // RunPrebindPlugins runs the set of configured prebind plugins. It returns
// *Status and its code is set to non-success if any of the plugins returns // *Status and its code is set to non-success if any of the plugins returns
// anything but Success. If the Status code is "Unschedulable", it is // anything but Success. If the Status code is "Unschedulable", it is

View File

@ -179,14 +179,6 @@ func (*fakeFramework) RunScorePlugins(pc *framework.PluginContext, pod *v1.Pod,
return nil, nil return nil, nil
} }
func (*fakeFramework) RunNormalizeScorePlugins(pc *framework.PluginContext, pod *v1.Pod, scores framework.PluginToNodeScores) *framework.Status {
return nil
}
func (*fakeFramework) ApplyScoreWeights(pc *framework.PluginContext, pod *v1.Pod, scores framework.PluginToNodeScores) *framework.Status {
return nil
}
func (*fakeFramework) RunPrebindPlugins(pc *framework.PluginContext, pod *v1.Pod, nodeName string) *framework.Status { func (*fakeFramework) RunPrebindPlugins(pc *framework.PluginContext, pod *v1.Pod, nodeName string) *framework.Status {
return nil return nil
} }