Merge pull request #84816 from liu-cong/nodelabel

Aggregate mulitple NodePreference custom priorities to a single score plugin.
This commit is contained in:
Kubernetes Prow Robot 2019-11-05 22:19:30 -08:00 committed by GitHub
commit 3d0f737cd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 247 additions and 168 deletions

View File

@ -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",

View File

@ -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,

View File

@ -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)
}
})
}
}

View File

@ -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{

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}

View File

@ -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",

View File

@ -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
}

View File

@ -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)
}
})
}
}