Aggregate mulitple NodePreference custom priorities to a single score plugin.
This commit is contained in:
		@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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{
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user