From 97d641b129fd99ea8846e978874f551f46066a30 Mon Sep 17 00:00:00 2001 From: Cong Liu Date: Tue, 5 Nov 2019 15:09:18 -0500 Subject: [PATCH] Aggregate mulitple NodePreference custom priorities to a single score plugin. --- pkg/scheduler/algorithm/priorities/BUILD | 1 - .../algorithm/priorities/node_label.go | 25 +++- .../algorithm/priorities/node_label_test.go | 127 ---------------- pkg/scheduler/algorithm_factory.go | 28 +++- .../apis/config/testing/compatibility_test.go | 7 +- pkg/scheduler/factory_test.go | 25 +++- .../framework/plugins/default_registry.go | 6 + .../framework/plugins/nodelabel/BUILD | 2 + .../framework/plugins/nodelabel/node_label.go | 55 +++++-- .../plugins/nodelabel/node_label_test.go | 139 +++++++++++++++++- 10 files changed, 247 insertions(+), 168 deletions(-) delete mode 100644 pkg/scheduler/algorithm/priorities/node_label_test.go diff --git a/pkg/scheduler/algorithm/priorities/BUILD b/pkg/scheduler/algorithm/priorities/BUILD index 9511de44b9b..b5b6cfdfbf9 100644 --- a/pkg/scheduler/algorithm/priorities/BUILD +++ b/pkg/scheduler/algorithm/priorities/BUILD @@ -64,7 +64,6 @@ go_test( "metadata_test.go", "most_requested_test.go", "node_affinity_test.go", - "node_label_test.go", "node_prefer_avoid_pods_test.go", "requested_to_capacity_ratio_test.go", "resource_limits_test.go", diff --git a/pkg/scheduler/algorithm/priorities/node_label.go b/pkg/scheduler/algorithm/priorities/node_label.go index cda69a3cbc0..a9bedd51c6e 100644 --- a/pkg/scheduler/algorithm/priorities/node_label.go +++ b/pkg/scheduler/algorithm/priorities/node_label.go @@ -27,15 +27,15 @@ import ( // NodeLabelPrioritizer contains information to calculate node label priority. type NodeLabelPrioritizer struct { - label string - presence bool + presentLabelsPreference []string + absentLabelsPreference []string } // NewNodeLabelPriority creates a NodeLabelPrioritizer. -func NewNodeLabelPriority(label string, presence bool) (PriorityMapFunction, PriorityReduceFunction) { +func NewNodeLabelPriority(presentLabelsPreference []string, absentLabelsPreference []string) (PriorityMapFunction, PriorityReduceFunction) { labelPrioritizer := &NodeLabelPrioritizer{ - label: label, - presence: presence, + presentLabelsPreference: presentLabelsPreference, + absentLabelsPreference: absentLabelsPreference, } return labelPrioritizer.CalculateNodeLabelPriorityMap, nil } @@ -49,11 +49,20 @@ func (n *NodeLabelPrioritizer) CalculateNodeLabelPriorityMap(pod *v1.Pod, meta i return framework.NodeScore{}, fmt.Errorf("node not found") } - exists := labels.Set(node.Labels).Has(n.label) score := int64(0) - if (exists && n.presence) || (!exists && !n.presence) { - score = framework.MaxNodeScore + for _, label := range n.presentLabelsPreference { + if labels.Set(node.Labels).Has(label) { + score += framework.MaxNodeScore + } } + for _, label := range n.absentLabelsPreference { + if !labels.Set(node.Labels).Has(label) { + score += framework.MaxNodeScore + } + } + // Take average score for each label to ensure the score doesn't exceed MaxNodeScore. + score /= int64(len(n.presentLabelsPreference) + len(n.absentLabelsPreference)) + return framework.NodeScore{ Name: node.Name, Score: score, diff --git a/pkg/scheduler/algorithm/priorities/node_label_test.go b/pkg/scheduler/algorithm/priorities/node_label_test.go deleted file mode 100644 index ded6ad12b0b..00000000000 --- a/pkg/scheduler/algorithm/priorities/node_label_test.go +++ /dev/null @@ -1,127 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package priorities - -import ( - "reflect" - "testing" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1" - nodeinfosnapshot "k8s.io/kubernetes/pkg/scheduler/nodeinfo/snapshot" -) - -func TestNewNodeLabelPriority(t *testing.T) { - label1 := map[string]string{"foo": "bar"} - label2 := map[string]string{"bar": "foo"} - label3 := map[string]string{"bar": "baz"} - tests := []struct { - nodes []*v1.Node - label string - presence bool - expectedList framework.NodeScoreList - name string - }{ - { - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, - }, - expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}}, - label: "baz", - presence: true, - name: "no match found, presence true", - }, - { - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, - }, - expectedList: []framework.NodeScore{{Name: "machine1", Score: framework.MaxNodeScore}, {Name: "machine2", Score: framework.MaxNodeScore}, {Name: "machine3", Score: framework.MaxNodeScore}}, - label: "baz", - presence: false, - name: "no match found, presence false", - }, - { - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, - }, - expectedList: []framework.NodeScore{{Name: "machine1", Score: framework.MaxNodeScore}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}}, - label: "foo", - presence: true, - name: "one match found, presence true", - }, - { - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, - }, - expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: framework.MaxNodeScore}, {Name: "machine3", Score: framework.MaxNodeScore}}, - label: "foo", - presence: false, - name: "one match found, presence false", - }, - { - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, - }, - expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: framework.MaxNodeScore}, {Name: "machine3", Score: framework.MaxNodeScore}}, - label: "bar", - presence: true, - name: "two matches found, presence true", - }, - { - nodes: []*v1.Node{ - {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, - {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, - }, - expectedList: []framework.NodeScore{{Name: "machine1", Score: framework.MaxNodeScore}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}}, - label: "bar", - presence: false, - name: "two matches found, presence false", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - snapshot := nodeinfosnapshot.NewSnapshot(nil, test.nodes) - labelPrioritizer := &NodeLabelPrioritizer{ - label: test.label, - presence: test.presence, - } - list, err := priorityFunction(labelPrioritizer.CalculateNodeLabelPriorityMap, nil, nil)(nil, snapshot, test.nodes) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - // sort the two lists to avoid failures on account of different ordering - sortNodeScoreList(test.expectedList) - sortNodeScoreList(list) - if !reflect.DeepEqual(test.expectedList, list) { - t.Errorf("expected %#v, got %#v", test.expectedList, list) - } - }) - } -} diff --git a/pkg/scheduler/algorithm_factory.go b/pkg/scheduler/algorithm_factory.go index 90096aba6f6..53444958494 100644 --- a/pkg/scheduler/algorithm_factory.go +++ b/pkg/scheduler/algorithm_factory.go @@ -400,15 +400,35 @@ func RegisterCustomPriorityFunction(policy schedulerapi.PriorityPolicy, args *pl Weight: policy.Weight, } } else if policy.Argument.LabelPreference != nil { + // We use the NodeLabel plugin name for all NodeLabel custom priorities. + // It may get called multiple times but we essentially only register one instance of NodeLabel priority. + // This name is then used to find the registered plugin and run the plugin instead of the priority. + name = nodelabel.Name + if args.NodeLabelArgs == nil { + args.NodeLabelArgs = &nodelabel.Args{} + } + if policy.Argument.LabelPreference.Presence { + args.NodeLabelArgs.PresentLabelsPreference = append(args.NodeLabelArgs.PresentLabelsPreference, policy.Argument.LabelPreference.Label) + } else { + args.NodeLabelArgs.AbsentLabelsPreference = append(args.NodeLabelArgs.AbsentLabelsPreference, policy.Argument.LabelPreference.Label) + } + schedulerFactoryMutex.RLock() + weight := policy.Weight + if existing, ok := priorityFunctionMap[name]; ok { + // If there are n NodeLabel priority configured in the policy, the weight for the corresponding + // priority is n*(weight of each priority in policy). + weight += existing.Weight + } pcf = &PriorityConfigFactory{ - MapReduceFunction: func(args PluginFactoryArgs) (priorities.PriorityMapFunction, priorities.PriorityReduceFunction) { + MapReduceFunction: func(_ PluginFactoryArgs) (priorities.PriorityMapFunction, priorities.PriorityReduceFunction) { return priorities.NewNodeLabelPriority( - policy.Argument.LabelPreference.Label, - policy.Argument.LabelPreference.Presence, + args.NodeLabelArgs.PresentLabelsPreference, + args.NodeLabelArgs.AbsentLabelsPreference, ) }, - Weight: policy.Weight, + Weight: weight, } + schedulerFactoryMutex.RUnlock() } else if policy.Argument.RequestedToCapacityRatioArguments != nil { scoringFunctionShape, resources := buildScoringFunctionShapeFromRequestedToCapacityRatioArguments(policy.Argument.RequestedToCapacityRatioArguments) args.RequestedToCapacityRatioArgs = &requestedtocapacityratio.Args{ diff --git a/pkg/scheduler/apis/config/testing/compatibility_test.go b/pkg/scheduler/apis/config/testing/compatibility_test.go index 94db0feb8d7..1ac540bbf6b 100644 --- a/pkg/scheduler/apis/config/testing/compatibility_test.go +++ b/pkg/scheduler/apis/config/testing/compatibility_test.go @@ -103,7 +103,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { wantPrioritizers: sets.NewString( "ServiceSpreadingPriority", "TestServiceAntiAffinity", - "TestLabelPreference", ), wantPlugins: map[string][]config.Plugin{ "FilterPlugin": { @@ -116,6 +115,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { }, "ScorePlugin": { {Name: "NodeResourcesLeastAllocated", Weight: 1}, + {Name: "NodeLabel", Weight: 4}, }, }, }, @@ -151,7 +151,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "EqualPriority", "SelectorSpreadPriority", "TestServiceAntiAffinity", - "TestLabelPreference", ), wantPlugins: map[string][]config.Plugin{ "FilterPlugin": { @@ -167,6 +166,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "ScorePlugin": { {Name: "NodeResourcesBalancedAllocation", Weight: 2}, {Name: "NodeResourcesLeastAllocated", Weight: 2}, + {Name: "NodeLabel", Weight: 4}, }, }, }, @@ -207,7 +207,6 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "EqualPriority", "SelectorSpreadPriority", "TestServiceAntiAffinity", - "TestLabelPreference", ), wantPlugins: map[string][]config.Plugin{ "FilterPlugin": { @@ -229,6 +228,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { {Name: "ImageLocality", Weight: 2}, {Name: "NodeResourcesLeastAllocated", Weight: 2}, {Name: "NodeAffinity", Weight: 2}, + {Name: "NodeLabel", Weight: 4}, }, }, }, @@ -1263,6 +1263,7 @@ func TestCompatibility_v1_Scheduler(t *testing.T) { "NodeResourcesBalancedAllocation": "BalancedResourceAllocation", "NodeResourcesMostAllocated": "MostRequestedPriority", "RequestedToCapacityRatio": "RequestedToCapacityRatioPriority", + "NodeLabel": "TestLabelPreference", } for _, tc := range testcases { diff --git a/pkg/scheduler/factory_test.go b/pkg/scheduler/factory_test.go index 7c8fcdb0ce6..ccbf7f93bd8 100644 --- a/pkg/scheduler/factory_test.go +++ b/pkg/scheduler/factory_test.go @@ -98,6 +98,8 @@ func TestCreateFromConfig(t *testing.T) { ], "priorities" : [ {"name" : "RackSpread", "weight" : 3, "argument" : {"serviceAntiAffinity" : {"label" : "rack"}}}, + {"name" : "LabelPreference1", "weight" : 3, "argument" : {"labelPreference" : {"label" : "l1", "presence": true}}}, + {"name" : "LabelPreference2", "weight" : 3, "argument" : {"labelPreference" : {"label" : "l2", "presence": false}}}, {"name" : "PriorityOne", "weight" : 2}, {"name" : "PriorityTwo", "weight" : 1} ] }`) @@ -114,29 +116,36 @@ func TestCreateFromConfig(t *testing.T) { t.Errorf("Wrong hardPodAffinitySymmetricWeight, ecpected: %d, got: %d", v1.DefaultHardPodAffinitySymmetricWeight, hpa) } - // Verify that custom predicates are converted to framework plugins. - if !pluginExists(nodelabel.Name, "FilterPlugin", conf) { - t.Error("NodeLabel plugin not exist in framework.") + // Verify that node label predicate/priority are converted to framework plugins. + if _, ok := findPlugin(nodelabel.Name, "FilterPlugin", conf); !ok { + t.Fatalf("NodeLabel plugin not exist in framework.") } - // Verify that the policy config is converted to plugin config for custom predicates. + nodeLabelScorePlugin, ok := findPlugin(nodelabel.Name, "ScorePlugin", conf) + if !ok { + t.Fatalf("NodeLabel plugin not exist in framework.") + } + if nodeLabelScorePlugin.Weight != 6 { + t.Errorf("Wrong weight. Got: %v, want: 6", nodeLabelScorePlugin.Weight) + } + // Verify that the policy config is converted to plugin config for node label predicate/priority. nodeLabelConfig := findPluginConfig(nodelabel.Name, conf) encoding, err := json.Marshal(nodeLabelConfig) if err != nil { t.Errorf("Failed to marshal %+v: %v", nodeLabelConfig, err) } - want := `{"Name":"NodeLabel","Args":{"presentLabels":["zone"],"absentLabels":["foo"]}}` + want := `{"Name":"NodeLabel","Args":{"presentLabels":["zone"],"absentLabels":["foo"],"presentLabelsPreference":["l1"],"absentLabelsPreference":["l2"]}}` if string(encoding) != want { t.Errorf("Config for NodeLabel plugin mismatch. got: %v, want: %v", string(encoding), want) } } -func pluginExists(name, extensionPoint string, schedConf *Config) bool { +func findPlugin(name, extensionPoint string, schedConf *Config) (schedulerapi.Plugin, bool) { for _, pl := range schedConf.Framework.ListPlugins()[extensionPoint] { if pl.Name == name { - return true + return pl, true } } - return false + return schedulerapi.Plugin{}, false } func findPluginConfig(name string, schedConf *Config) schedulerapi.PluginConfig { diff --git a/pkg/scheduler/framework/plugins/default_registry.go b/pkg/scheduler/framework/plugins/default_registry.go index 337a9fc80af..8a470cb125e 100644 --- a/pkg/scheduler/framework/plugins/default_registry.go +++ b/pkg/scheduler/framework/plugins/default_registry.go @@ -256,6 +256,12 @@ func NewDefaultConfigProducerRegistry() *ConfigProducerRegistry { return }) + registry.RegisterPriority(nodelabel.Name, + func(args ConfigProducerArgs) (plugins config.Plugins, pluginConfig []config.PluginConfig) { + plugins.Score = appendToPluginSet(plugins.Score, nodelabel.Name, &args.Weight) + pluginConfig = append(pluginConfig, makePluginConfig(nodelabel.Name, args.NodeLabelArgs)) + return + }) return registry } diff --git a/pkg/scheduler/framework/plugins/nodelabel/BUILD b/pkg/scheduler/framework/plugins/nodelabel/BUILD index 7f68b26586f..cc70641a259 100644 --- a/pkg/scheduler/framework/plugins/nodelabel/BUILD +++ b/pkg/scheduler/framework/plugins/nodelabel/BUILD @@ -7,6 +7,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/scheduler/algorithm/predicates:go_default_library", + "//pkg/scheduler/algorithm/priorities:go_default_library", "//pkg/scheduler/framework/plugins/migration:go_default_library", "//pkg/scheduler/framework/v1alpha1:go_default_library", "//pkg/scheduler/nodeinfo:go_default_library", @@ -22,6 +23,7 @@ go_test( deps = [ "//pkg/scheduler/framework/v1alpha1:go_default_library", "//pkg/scheduler/nodeinfo:go_default_library", + "//pkg/scheduler/nodeinfo/snapshot: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", diff --git a/pkg/scheduler/framework/plugins/nodelabel/node_label.go b/pkg/scheduler/framework/plugins/nodelabel/node_label.go index 65da4622fee..1a91bd5e46d 100644 --- a/pkg/scheduler/framework/plugins/nodelabel/node_label.go +++ b/pkg/scheduler/framework/plugins/nodelabel/node_label.go @@ -23,6 +23,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" + "k8s.io/kubernetes/pkg/scheduler/algorithm/priorities" "k8s.io/kubernetes/pkg/scheduler/framework/plugins/migration" framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1" "k8s.io/kubernetes/pkg/scheduler/nodeinfo" @@ -37,42 +38,56 @@ type Args struct { PresentLabels []string `json:"presentLabels,omitempty"` // AbsentLabels should be absent for the node to be considered a fit for hosting the pod AbsentLabels []string `json:"absentLabels,omitempty"` + // Nodes that have labels in the list will get a higher score. + PresentLabelsPreference []string `json:"presentLabelsPreference,omitempty"` + // Nodes that don't have labels in the list will get a higher score. + AbsentLabelsPreference []string `json:"absentLabelsPreference,omitempty"` } -// validateArgs validates that PresentLabels and AbsentLabels do not conflict. -func validateArgs(args *Args) error { - presentLabels := make(map[string]struct{}, len(args.PresentLabels)) - for _, l := range args.PresentLabels { - presentLabels[l] = struct{}{} +// validateArgs validates that presentLabels and absentLabels do not conflict. +func validateNoConflict(presentLabels []string, absentLabels []string) error { + m := make(map[string]struct{}, len(presentLabels)) + for _, l := range presentLabels { + m[l] = struct{}{} } - for _, l := range args.AbsentLabels { - if _, ok := presentLabels[l]; ok { - return fmt.Errorf("detecting at least one label (e.g., %q) that exist in both the present and absent label list: %+v", l, args) + for _, l := range absentLabels { + if _, ok := m[l]; ok { + return fmt.Errorf("detecting at least one label (e.g., %q) that exist in both the present(%+v) and absent(%+v) label list", l, presentLabels, absentLabels) } } return nil } // New initializes a new plugin and returns it. -func New(plArgs *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) { +func New(plArgs *runtime.Unknown, handle framework.FrameworkHandle) (framework.Plugin, error) { args := &Args{} if err := framework.DecodeInto(plArgs, args); err != nil { return nil, err } - if err := validateArgs(args); err != nil { + if err := validateNoConflict(args.PresentLabels, args.AbsentLabels); err != nil { return nil, err } + if err := validateNoConflict(args.PresentLabelsPreference, args.AbsentLabelsPreference); err != nil { + return nil, err + } + // Note that the reduce function is always nil therefore it's ignored. + prioritize, _ := priorities.NewNodeLabelPriority(args.PresentLabelsPreference, args.AbsentLabelsPreference) return &NodeLabel{ - predicate: predicates.NewNodeLabelPredicate(args.PresentLabels, args.AbsentLabels), + handle: handle, + predicate: predicates.NewNodeLabelPredicate(args.PresentLabels, args.AbsentLabels), + prioritize: prioritize, }, nil } // NodeLabel checks whether a pod can fit based on the node labels which match a filter that it requests. type NodeLabel struct { - predicate predicates.FitPredicate + handle framework.FrameworkHandle + predicate predicates.FitPredicate + prioritize priorities.PriorityMapFunction } var _ framework.FilterPlugin = &NodeLabel{} +var _ framework.ScorePlugin = &NodeLabel{} // Name returns name of the plugin. It is used in logs, etc. func (pl *NodeLabel) Name() string { @@ -85,3 +100,19 @@ func (pl *NodeLabel) Filter(ctx context.Context, _ *framework.CycleState, pod *v _, reasons, err := pl.predicate(pod, nil, nodeInfo) return migration.PredicateResultToFrameworkStatus(reasons, err) } + +// Score invoked at the score extension point. +func (pl *NodeLabel) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { + nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) + if err != nil { + return 0, framework.NewStatus(framework.Error, fmt.Sprintf("getting node %q from Snapshot: %v", nodeName, err)) + } + // Note that node label priority function doesn't use metadata, hence passing nil here. + s, err := pl.prioritize(pod, nil, nodeInfo) + return s.Score, migration.ErrorToFrameworkStatus(err) +} + +// ScoreExtensions of the Score plugin. +func (pl *NodeLabel) ScoreExtensions() framework.ScoreExtensions { + return nil +} diff --git a/pkg/scheduler/framework/plugins/nodelabel/node_label_test.go b/pkg/scheduler/framework/plugins/nodelabel/node_label_test.go index 71764ac8d3a..66078984a6b 100644 --- a/pkg/scheduler/framework/plugins/nodelabel/node_label_test.go +++ b/pkg/scheduler/framework/plugins/nodelabel/node_label_test.go @@ -25,14 +25,48 @@ import ( "k8s.io/apimachinery/pkg/runtime" framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1" schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo" + nodeinfosnapshot "k8s.io/kubernetes/pkg/scheduler/nodeinfo/snapshot" ) func TestValidateNodeLabelArgs(t *testing.T) { - // "bar" exists in both present and absent labels therefore validatio should fail. - args := &runtime.Unknown{Raw: []byte(`{"presentLabels" : ["foo", "bar"], "absentLabels" : ["bar", "baz"]}`)} - _, err := New(args, nil) - if err == nil { - t.Fatal("Plugin initialization should fail.") + tests := []struct { + name string + args string + err bool + }{ + { + name: "happy case", + args: `{"presentLabels" : ["foo", "bar"], "absentLabels" : ["baz"], "presentLabelsPreference" : ["foo", "bar"], "absentLabelsPreference" : ["baz"]}`, + }, + { + name: "label presence conflict", + // "bar" exists in both present and absent labels therefore validation should fail. + args: `{"presentLabels" : ["foo", "bar"], "absentLabels" : ["bar", "baz"], "presentLabelsPreference" : ["foo", "bar"], "absentLabelsPreference" : ["baz"]}`, + err: true, + }, + { + name: "label preference conflict", + // "bar" exists in both present and absent labels preferences therefore validation should fail. + args: `{"presentLabels" : ["foo", "bar"], "absentLabels" : ["baz"], "presentLabelsPreference" : ["foo", "bar"], "absentLabelsPreference" : ["bar", "baz"]}`, + err: true, + }, + { + name: "both label presence and preference conflict", + args: `{"presentLabels" : ["foo", "bar"], "absentLabels" : ["bar", "baz"], "presentLabelsPreference" : ["foo", "bar"], "absentLabelsPreference" : ["bar", "baz"]}`, + err: true, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + args := &runtime.Unknown{Raw: []byte(test.args)} + _, err := New(args, nil) + if test.err && err == nil { + t.Fatal("Plugin initialization should fail.") + } + if !test.err && err != nil { + t.Fatalf("Plugin initialization shouldn't fail: %v", err) + } + }) } } @@ -115,3 +149,98 @@ func TestNodeLabelFilter(t *testing.T) { }) } } + +func TestNodeLabelScore(t *testing.T) { + tests := []struct { + rawArgs string + want int64 + name string + }{ + { + want: framework.MaxNodeScore, + rawArgs: `{"presentLabelsPreference" : ["foo"]}`, + name: "one present label match", + }, + { + want: 0, + rawArgs: `{"presentLabelsPreference" : ["somelabel"]}`, + name: "one present label mismatch", + }, + { + want: framework.MaxNodeScore, + rawArgs: `{"presentLabelsPreference" : ["foo", "bar"]}`, + name: "two present labels match", + }, + { + want: 0, + rawArgs: `{"presentLabelsPreference" : ["somelabel1", "somelabel2"]}`, + name: "two present labels mismatch", + }, + { + want: framework.MaxNodeScore / 2, + rawArgs: `{"presentLabelsPreference" : ["foo", "somelabel"]}`, + name: "two present labels only one matches", + }, + { + want: 0, + rawArgs: `{"absentLabelsPreference" : ["foo"]}`, + name: "one absent label match", + }, + { + want: framework.MaxNodeScore, + rawArgs: `{"absentLabelsPreference" : ["somelabel"]}`, + name: "one absent label mismatch", + }, + { + want: 0, + rawArgs: `{"absentLabelsPreference" : ["foo", "bar"]}`, + name: "two absent labels match", + }, + { + want: framework.MaxNodeScore, + rawArgs: `{"absentLabelsPreference" : ["somelabel1", "somelabel2"]}`, + name: "two absent labels mismatch", + }, + { + want: framework.MaxNodeScore / 2, + rawArgs: `{"absentLabelsPreference" : ["foo", "somelabel"]}`, + name: "two absent labels only one matches", + }, + { + want: framework.MaxNodeScore, + rawArgs: `{"presentLabelsPreference" : ["foo", "bar"], "absentLabelsPreference" : ["somelabel1", "somelabel2"]}`, + name: "two present labels match, two absent labels mismatch", + }, + { + want: 0, + rawArgs: `{"absentLabelsPreference" : ["foo", "bar"], "presentLabelsPreference" : ["somelabel1", "somelabel2"]}`, + name: "two present labels both mismatch, two absent labels both match", + }, + { + want: 3 * framework.MaxNodeScore / 4, + rawArgs: `{"presentLabelsPreference" : ["foo", "somelabel"], "absentLabelsPreference" : ["somelabel1", "somelabel2"]}`, + name: "two present labels one matches, two absent labels mismatch", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + state := framework.NewCycleState() + node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: map[string]string{"foo": "", "bar": ""}}} + fh, _ := framework.NewFramework(nil, nil, nil, framework.WithNodeInfoSnapshot(nodeinfosnapshot.NewSnapshot(nil, []*v1.Node{node}))) + args := &runtime.Unknown{Raw: []byte(test.rawArgs)} + p, err := New(args, fh) + if err != nil { + t.Fatalf("Failed to create plugin: %+v", err) + } + nodeName := node.ObjectMeta.Name + score, status := p.(framework.ScorePlugin).Score(context.Background(), state, nil, nodeName) + if !status.IsSuccess() { + t.Errorf("unexpected error: %v", status) + } + if test.want != score { + t.Errorf("Wrong score. got %#v, want %#v", score, test.want) + } + }) + } +}