Skip to content

Commit 134baa5

Browse files
committed
Convert multiple node label predicates to be a single filter plugin.
1 parent 3e5f6bd commit 134baa5

File tree

7 files changed

+127
-72
lines changed

7 files changed

+127
-72
lines changed

pkg/scheduler/algorithm/predicates/predicates.go

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -940,16 +940,18 @@ func PodFitsHost(pod *v1.Pod, meta PredicateMetadata, nodeInfo *schedulernodeinf
940940

941941
// NodeLabelChecker contains information to check node labels for a predicate.
942942
type NodeLabelChecker struct {
943-
labels []string
944-
presence bool
943+
// presentLabels should be present for the node to be considered a fit for hosting the pod
944+
presentLabels []string
945+
// absentLabels should be absent for the node to be considered a fit for hosting the pod
946+
absentLabels []string
945947
}
946948

947949
// NewNodeLabelPredicate creates a predicate which evaluates whether a pod can fit based on the
948950
// node labels which match a filter that it requests.
949-
func NewNodeLabelPredicate(labels []string, presence bool) FitPredicate {
951+
func NewNodeLabelPredicate(presentLabels []string, absentLabels []string) FitPredicate {
950952
labelChecker := &NodeLabelChecker{
951-
labels: labels,
952-
presence: presence,
953+
presentLabels: presentLabels,
954+
absentLabels: absentLabels,
953955
}
954956
return labelChecker.CheckNodeLabelPresence
955957
}
@@ -972,15 +974,21 @@ func (n *NodeLabelChecker) CheckNodeLabelPresence(pod *v1.Pod, meta PredicateMet
972974
return false, nil, fmt.Errorf("node not found")
973975
}
974976

975-
var exists bool
976977
nodeLabels := labels.Set(node.Labels)
977-
for _, label := range n.labels {
978-
exists = nodeLabels.Has(label)
979-
if (exists && !n.presence) || (!exists && n.presence) {
980-
return false, []PredicateFailureReason{ErrNodeLabelPresenceViolated}, nil
978+
check := func(labels []string, presence bool) bool {
979+
for _, label := range labels {
980+
exists := nodeLabels.Has(label)
981+
if (exists && !presence) || (!exists && presence) {
982+
return false
983+
}
981984
}
985+
return true
982986
}
983-
return true, nil, nil
987+
if check(n.presentLabels, true) && check(n.absentLabels, false) {
988+
return true, nil, nil
989+
}
990+
991+
return false, []PredicateFailureReason{ErrNodeLabelPresenceViolated}, nil
984992
}
985993

986994
// ServiceAffinity defines a struct used for creating service affinity predicates.

pkg/scheduler/algorithm/predicates/predicates_test.go

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,47 +1658,41 @@ func TestPodFitsSelector(t *testing.T) {
16581658
func TestNodeLabelPresence(t *testing.T) {
16591659
label := map[string]string{"foo": "bar", "bar": "foo"}
16601660
tests := []struct {
1661-
pod *v1.Pod
1662-
labels []string
1663-
presence bool
1664-
fits bool
1665-
name string
1661+
pod *v1.Pod
1662+
presentLabels []string
1663+
absentLabels []string
1664+
fits bool
1665+
name string
16661666
}{
16671667
{
1668-
labels: []string{"baz"},
1669-
presence: true,
1670-
fits: false,
1671-
name: "label does not match, presence true",
1668+
presentLabels: []string{"baz"},
1669+
fits: false,
1670+
name: "label does not match, presence true",
16721671
},
16731672
{
1674-
labels: []string{"baz"},
1675-
presence: false,
1676-
fits: true,
1677-
name: "label does not match, presence false",
1673+
absentLabels: []string{"baz"},
1674+
fits: true,
1675+
name: "label does not match, presence false",
16781676
},
16791677
{
1680-
labels: []string{"foo", "baz"},
1681-
presence: true,
1682-
fits: false,
1683-
name: "one label matches, presence true",
1678+
presentLabels: []string{"foo", "baz"},
1679+
fits: false,
1680+
name: "one label matches, presence true",
16841681
},
16851682
{
1686-
labels: []string{"foo", "baz"},
1687-
presence: false,
1688-
fits: false,
1689-
name: "one label matches, presence false",
1683+
absentLabels: []string{"foo", "baz"},
1684+
fits: false,
1685+
name: "one label matches, presence false",
16901686
},
16911687
{
1692-
labels: []string{"foo", "bar"},
1693-
presence: true,
1694-
fits: true,
1695-
name: "all labels match, presence true",
1688+
presentLabels: []string{"foo", "bar"},
1689+
fits: true,
1690+
name: "all labels match, presence true",
16961691
},
16971692
{
1698-
labels: []string{"foo", "bar"},
1699-
presence: false,
1700-
fits: false,
1701-
name: "all labels match, presence false",
1693+
absentLabels: []string{"foo", "bar"},
1694+
fits: false,
1695+
name: "all labels match, presence false",
17021696
},
17031697
}
17041698
expectedFailureReasons := []PredicateFailureReason{ErrNodeLabelPresenceViolated}
@@ -1709,7 +1703,7 @@ func TestNodeLabelPresence(t *testing.T) {
17091703
nodeInfo := schedulernodeinfo.NewNodeInfo()
17101704
nodeInfo.SetNode(&node)
17111705

1712-
labelChecker := NodeLabelChecker{test.labels, test.presence}
1706+
labelChecker := NodeLabelChecker{test.presentLabels, test.absentLabels}
17131707
fits, reasons, err := labelChecker.CheckNodeLabelPresence(test.pod, GetPredicateMetadata(test.pod, nil), nodeInfo)
17141708
if err != nil {
17151709
t.Errorf("unexpected error: %v", err)

pkg/scheduler/algorithm_factory.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,17 +287,23 @@ func RegisterCustomFitPredicate(policy schedulerapi.PredicatePolicy, args *plugi
287287
}
288288
} else if policy.Argument.LabelsPresence != nil {
289289
// map LabelPresence policy to ConfigProducerArgs that's used to configure the NodeLabel plugin.
290-
args.NodeLabelArgs = &nodelabel.Args{
291-
Labels: policy.Argument.LabelsPresence.Labels,
292-
Presence: policy.Argument.LabelsPresence.Presence,
290+
if args.NodeLabelArgs == nil {
291+
args.NodeLabelArgs = &nodelabel.Args{}
293292
}
294-
predicateFactory = func(args PluginFactoryArgs) predicates.FitPredicate {
293+
if policy.Argument.LabelsPresence.Presence {
294+
args.NodeLabelArgs.PresentLabels = append(args.NodeLabelArgs.PresentLabels, policy.Argument.LabelsPresence.Labels...)
295+
} else {
296+
args.NodeLabelArgs.AbsentLabels = append(args.NodeLabelArgs.AbsentLabels, policy.Argument.LabelsPresence.Labels...)
297+
}
298+
predicateFactory = func(_ PluginFactoryArgs) predicates.FitPredicate {
295299
return predicates.NewNodeLabelPredicate(
296-
policy.Argument.LabelsPresence.Labels,
297-
policy.Argument.LabelsPresence.Presence,
300+
args.NodeLabelArgs.PresentLabels,
301+
args.NodeLabelArgs.AbsentLabels,
298302
)
299303
}
300-
// We do not allow specifying the name for custom plugins, see #83472
304+
// We use the NodeLabel plugin name for all NodeLabel custom priorities.
305+
// It may get called multiple times but we essentially only register one instance of NodeLabel predicate.
306+
// This name is then used to find the registered plugin and run the plugin instead of the predicate.
301307
name = nodelabel.Name
302308
}
303309
} else if predicateFactory, ok = fitPredicateMap[policy.Name]; ok {

pkg/scheduler/factory_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func TestCreateFromConfig(t *testing.T) {
9292
"predicates" : [
9393
{"name" : "TestZoneAffinity", "argument" : {"serviceAffinity" : {"labels" : ["zone"]}}},
9494
{"name" : "TestRequireZone", "argument" : {"labelsPresence" : {"labels" : ["zone"], "presence" : true}}},
95+
{"name" : "TestNoFooLabel", "argument" : {"labelsPresence" : {"labels" : ["foo"], "presence" : false}}},
9596
{"name" : "PredicateOne"},
9697
{"name" : "PredicateTwo"}
9798
],
@@ -123,7 +124,7 @@ func TestCreateFromConfig(t *testing.T) {
123124
if err != nil {
124125
t.Errorf("Failed to marshal %+v: %v", nodeLabelConfig, err)
125126
}
126-
want := `{"Name":"NodeLabel","Args":{"labels":["zone"],"presence":true}}`
127+
want := `{"Name":"NodeLabel","Args":{"presentLabels":["zone"],"absentLabels":["foo"]}}`
127128
if string(encoding) != want {
128129
t.Errorf("Config for NodeLabel plugin mismatch. got: %v, want: %v", string(encoding), want)
129130
}

pkg/scheduler/framework/plugins/nodelabel/node_label.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package nodelabel
1818

1919
import (
2020
"context"
21+
"fmt"
2122

2223
v1 "k8s.io/api/core/v1"
2324
"k8s.io/apimachinery/pkg/runtime"
@@ -32,11 +33,24 @@ const Name = "NodeLabel"
3233

3334
// Args holds the args that are used to configure the plugin.
3435
type Args struct {
35-
// The list of labels that identify node "groups"
36-
// All of the labels should be either present (or absent) for the node to be considered a fit for hosting the pod
37-
Labels []string `json:"labels,omitempty"`
38-
// The boolean flag that indicates whether the labels should be present or absent from the node
39-
Presence bool `json:"presence,omitempty"`
36+
// PresentLabels should be present for the node to be considered a fit for hosting the pod
37+
PresentLabels []string `json:"presentLabels,omitempty"`
38+
// AbsentLabels should be absent for the node to be considered a fit for hosting the pod
39+
AbsentLabels []string `json:"absentLabels,omitempty"`
40+
}
41+
42+
// validateArgs validates that PresentLabels and AbsentLabels do not conflict.
43+
func validateArgs(args *Args) error {
44+
presentLabels := make(map[string]struct{}, len(args.PresentLabels))
45+
for _, l := range args.PresentLabels {
46+
presentLabels[l] = struct{}{}
47+
}
48+
for _, l := range args.AbsentLabels {
49+
if _, ok := presentLabels[l]; ok {
50+
return fmt.Errorf("detecting at least one label (e.g., %q) that exist in both the present and absent label list: %+v", l, args)
51+
}
52+
}
53+
return nil
4054
}
4155

4256
// New initializes a new plugin and returns it.
@@ -45,8 +59,11 @@ func New(plArgs *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin
4559
if err := framework.DecodeInto(plArgs, args); err != nil {
4660
return nil, err
4761
}
62+
if err := validateArgs(args); err != nil {
63+
return nil, err
64+
}
4865
return &NodeLabel{
49-
predicate: predicates.NewNodeLabelPredicate(args.Labels, args.Presence),
66+
predicate: predicates.NewNodeLabelPredicate(args.PresentLabels, args.AbsentLabels),
5067
}, nil
5168
}
5269

pkg/scheduler/framework/plugins/nodelabel/node_label_test.go

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,42 +27,71 @@ import (
2727
schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo"
2828
)
2929

30-
func TestNodeLabelPresence(t *testing.T) {
31-
label := map[string]string{"foo": "bar", "bar": "foo"}
30+
func TestValidateNodeLabelArgs(t *testing.T) {
31+
// "bar" exists in both present and absent labels therefore validatio should fail.
32+
args := &runtime.Unknown{Raw: []byte(`{"presentLabels" : ["foo", "bar"], "absentLabels" : ["bar", "baz"]}`)}
33+
_, err := New(args, nil)
34+
if err == nil {
35+
t.Fatal("Plugin initialization should fail.")
36+
}
37+
}
38+
39+
func TestNodeLabelFilter(t *testing.T) {
40+
label := map[string]string{"foo": "any value", "bar": "any value"}
41+
var pod *v1.Pod
3242
tests := []struct {
3343
name string
34-
pod *v1.Pod
3544
rawArgs string
3645
res framework.Code
3746
}{
3847
{
39-
name: "label does not match, presence true",
40-
rawArgs: `{"labels" : ["baz"], "presence" : true}`,
48+
name: "present label does not match",
49+
rawArgs: `{"presentLabels" : ["baz"]}`,
4150
res: framework.UnschedulableAndUnresolvable,
4251
},
4352
{
44-
name: "label does not match, presence false",
45-
rawArgs: `{"labels" : ["baz"], "presence" : false}`,
53+
name: "absent label does not match",
54+
rawArgs: `{"absentLabels" : ["baz"]}`,
4655
res: framework.Success,
4756
},
4857
{
49-
name: "one label matches, presence true",
50-
rawArgs: `{"labels" : ["foo", "baz"], "presence" : true}`,
58+
name: "one of two present labels matches",
59+
rawArgs: `{"presentLabels" : ["foo", "baz"]}`,
60+
res: framework.UnschedulableAndUnresolvable,
61+
},
62+
{
63+
name: "one of two absent labels matches",
64+
rawArgs: `{"absentLabels" : ["foo", "baz"]}`,
65+
res: framework.UnschedulableAndUnresolvable,
66+
},
67+
{
68+
name: "all present abels match",
69+
rawArgs: `{"presentLabels" : ["foo", "bar"]}`,
70+
res: framework.Success,
71+
},
72+
{
73+
name: "all absent labels match",
74+
rawArgs: `{"absentLabels" : ["foo", "bar"]}`,
75+
res: framework.UnschedulableAndUnresolvable,
76+
},
77+
{
78+
name: "both present and absent label matches",
79+
rawArgs: `{"presentLabels" : ["foo"], "absentLabels" : ["bar"]}`,
5180
res: framework.UnschedulableAndUnresolvable,
5281
},
5382
{
54-
name: "one label matches, presence false",
55-
rawArgs: `{"labels" : ["foo", "baz"], "presence" : false}`,
83+
name: "neither present nor absent label matches",
84+
rawArgs: `{"presentLabels" : ["foz"], "absentLabels" : ["baz"]}`,
5685
res: framework.UnschedulableAndUnresolvable,
5786
},
5887
{
59-
name: "all labels match, presence true",
60-
rawArgs: `{"labels" : ["foo", "bar"], "presence" : true}`,
88+
name: "present label matches and absent label doesn't match",
89+
rawArgs: `{"presentLabels" : ["foo"], "absentLabels" : ["baz"]}`,
6190
res: framework.Success,
6291
},
6392
{
64-
name: "all labels match, presence false",
65-
rawArgs: `{"labels" : ["foo", "bar"], "presence" : false}`,
93+
name: "present label doesn't match and absent label matches",
94+
rawArgs: `{"presentLabels" : ["foz"], "absentLabels" : ["bar"]}`,
6695
res: framework.UnschedulableAndUnresolvable,
6796
},
6897
}
@@ -79,7 +108,7 @@ func TestNodeLabelPresence(t *testing.T) {
79108
t.Fatalf("Failed to create plugin: %v", err)
80109
}
81110

82-
status := p.(framework.FilterPlugin).Filter(context.TODO(), nil, test.pod, nodeInfo)
111+
status := p.(framework.FilterPlugin).Filter(context.TODO(), nil, pod, nodeInfo)
83112
if status.Code() != test.res {
84113
t.Errorf("Status mismatch. got: %v, want: %v", status.Code(), test.res)
85114
}

test/integration/scheduler_perf/scheduler_bench_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ var (
4848

4949
// BenchmarkScheduling benchmarks the scheduling rate when the cluster has
5050
// various quantities of nodes and scheduled pods.
51-
func BenchmarkScheduling(b *testing.B) {
51+
func BenchmarkSchedulingV(b *testing.B) {
5252
tests := []struct{ nodes, existingPods, minPods int }{
5353
{nodes: 100, existingPods: 0, minPods: 100},
5454
{nodes: 100, existingPods: 1000, minPods: 100},

0 commit comments

Comments
 (0)