mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
Merge pull request #84816 from liu-cong/nodelabel
Aggregate mulitple NodePreference custom priorities to a single score plugin.
This commit is contained in:
commit
3d0f737cd9
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user