From 4edbecc85dac29816b3991ad93eebaa550887cec Mon Sep 17 00:00:00 2001 From: Jan Chaloupka Date: Sun, 9 May 2021 11:56:38 +0200 Subject: [PATCH] Define NodeSelectorsEqual predicate --- pkg/utils/predicates.go | 111 +++++++ pkg/utils/predicates_test.go | 603 +++++++++++++++++++++++++++++++++++ 2 files changed, 714 insertions(+) diff --git a/pkg/utils/predicates.go b/pkg/utils/predicates.go index a705f4314..bc7b59a2f 100644 --- a/pkg/utils/predicates.go +++ b/pkg/utils/predicates.go @@ -18,6 +18,7 @@ package utils import ( "fmt" + "reflect" "sort" v1 "k8s.io/api/core/v1" @@ -81,6 +82,116 @@ func podMatchesNodeLabels(pod *v1.Pod, node *v1.Node) bool { return true } +func uniqueSortNodeSelectorTerms(srcTerms []v1.NodeSelectorTerm) []v1.NodeSelectorTerm { + terms := append([]v1.NodeSelectorTerm{}, srcTerms...) + for i := range terms { + if terms[i].MatchExpressions != nil { + terms[i].MatchExpressions = uniqueSortNodeSelectorRequirements(terms[i].MatchExpressions) + } + if terms[i].MatchFields != nil { + terms[i].MatchFields = uniqueSortNodeSelectorRequirements(terms[i].MatchFields) + } + } + + if len(terms) < 2 { + return terms + } + + lastTerm := terms[0] + uniqueTerms := append([]v1.NodeSelectorTerm{}, terms[0]) + + for _, term := range terms[1:] { + if reflect.DeepEqual(term, lastTerm) { + continue + } + lastTerm = term + uniqueTerms = append(uniqueTerms, term) + } + + return uniqueTerms +} + +// sort NodeSelectorRequirement in (key, operator, values) order +func uniqueSortNodeSelectorRequirements(srcReqs []v1.NodeSelectorRequirement) []v1.NodeSelectorRequirement { + reqs := append([]v1.NodeSelectorRequirement{}, srcReqs...) + + // unique sort Values + for i := range reqs { + sort.Strings(reqs[i].Values) + if len(reqs[i].Values) > 1 { + lastString := reqs[i].Values[0] + values := []string{lastString} + for _, val := range reqs[i].Values { + if val == lastString { + continue + } + lastString = val + values = append(values, val) + } + reqs[i].Values = values + } + } + + if len(reqs) < 2 { + return reqs + } + + // unique sort reqs + sort.Slice(reqs, func(i, j int) bool { + if reqs[i].Key < reqs[j].Key { + return true + } + if reqs[i].Key > reqs[j].Key { + return false + } + if reqs[i].Operator < reqs[j].Operator { + return true + } + if reqs[i].Operator > reqs[j].Operator { + return false + } + if len(reqs[i].Values) < len(reqs[j].Values) { + return true + } + if len(reqs[i].Values) > len(reqs[j].Values) { + return false + } + for k := range reqs[i].Values { + if reqs[i].Values[k] < reqs[j].Values[k] { + return true + } + if reqs[i].Values[k] > reqs[j].Values[k] { + return false + } + } + return true + }) + + lastReq := reqs[0] + uniqueReqs := append([]v1.NodeSelectorRequirement{}, lastReq) + for _, req := range reqs[1:] { + if reflect.DeepEqual(req, lastReq) { + continue + } + lastReq = req + uniqueReqs = append(uniqueReqs, req) + } + return uniqueReqs +} + +func NodeSelectorsEqual(n1, n2 *v1.NodeSelector) bool { + if n1 == nil && n2 == nil { + return true + } + if n1 == nil || n2 == nil { + return false + } + return reflect.DeepEqual( + uniqueSortNodeSelectorTerms(n1.NodeSelectorTerms), + uniqueSortNodeSelectorTerms(n2.NodeSelectorTerms), + ) +} + // TolerationsTolerateTaint checks if taint is tolerated by any of the tolerations. func TolerationsTolerateTaint(tolerations []v1.Toleration, taint *v1.Taint) bool { for i := range tolerations { diff --git a/pkg/utils/predicates_test.go b/pkg/utils/predicates_test.go index 691da26ea..defef78af 100644 --- a/pkg/utils/predicates_test.go +++ b/pkg/utils/predicates_test.go @@ -335,3 +335,606 @@ func TestTolerationsEqual(t *testing.T) { }) } } + +func TestUniqueSortNodeSelectorRequirements(t *testing.T) { + tests := []struct { + name string + requirements []v1.NodeSelectorRequirement + expectedRequirements []v1.NodeSelectorRequirement + }{ + { + name: "Identical requirements", + requirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + expectedRequirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + { + name: "Sorted requirements", + requirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v2"}, + }, + }, + expectedRequirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v2"}, + }, + }, + }, + { + name: "Sort values", + requirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v2", "v1"}, + }, + }, + expectedRequirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + }, + }, + { + name: "Sort by key", + requirements: []v1.NodeSelectorRequirement{ + { + Key: "k3", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + }, + expectedRequirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k3", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + }, + }, + { + name: "Sort by operator", + requirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpExists, + Values: []string{"v1", "v2"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpGt, + Values: []string{"v1", "v2"}, + }, + }, + expectedRequirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpExists, + Values: []string{"v1", "v2"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpGt, + Values: []string{"v1", "v2"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + }, + }, + { + name: "Sort by values", + requirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v6", "v5"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v2", "v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v4", "v1"}, + }, + }, + expectedRequirements: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v4"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v5", "v6"}, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resultRequirements := uniqueSortNodeSelectorRequirements(test.requirements) + if !reflect.DeepEqual(resultRequirements, test.expectedRequirements) { + t.Errorf("Requirements not sorted as expected, \n\tgot: %#v, \n\texpected: %#v", resultRequirements, test.expectedRequirements) + } + }) + } +} + +func TestUniqueSortNodeSelectorTerms(t *testing.T) { + tests := []struct { + name string + terms []v1.NodeSelectorTerm + expectedTerms []v1.NodeSelectorTerm + }{ + { + name: "Identical terms", + terms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + expectedTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + { + name: "Sorted terms", + terms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + expectedTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + MatchFields: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + { + name: "Sort terms", + terms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v2", "v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v3", "v1"}, + }, + }, + }, + }, + expectedTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v3"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + { + name: "Unique sort terms", + terms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v2", "v1"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v2", "v1"}, + }, + }, + }, + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v2", "v1"}, + }, + }, + }, + }, + expectedTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + resultTerms := uniqueSortNodeSelectorTerms(test.terms) + if !reflect.DeepEqual(resultTerms, test.expectedTerms) { + t.Errorf("Terms not sorted as expected, \n\tgot: %#v, \n\texpected: %#v", resultTerms, test.expectedTerms) + } + }) + } +} + +func TestNodeSelectorTermsEqual(t *testing.T) { + tests := []struct { + name string + leftSelector, rightSelector v1.NodeSelector + equal bool + }{ + { + name: "identical selectors", + leftSelector: v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + rightSelector: v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + equal: true, + }, + { + name: "equal selectors", + leftSelector: v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + }, + }, + }, + }, + rightSelector: v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + equal: true, + }, + { + name: "non-equal selectors in values", + leftSelector: v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + rightSelector: v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + }, + }, + }, + }, + equal: false, + }, + { + name: "non-equal selectors in keys", + leftSelector: v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k3", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1"}, + }, + }, + }, + }, + }, + rightSelector: v1.NodeSelector{ + NodeSelectorTerms: []v1.NodeSelectorTerm{ + { + MatchExpressions: []v1.NodeSelectorRequirement{ + { + Key: "k1", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + { + Key: "k2", + Operator: v1.NodeSelectorOpIn, + Values: []string{"v1", "v2"}, + }, + }, + }, + }, + }, + equal: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + equal := NodeSelectorsEqual(&test.leftSelector, &test.rightSelector) + if equal != test.equal { + t.Errorf("NodeSelectorsEqual expected to be %v, got %v", test.equal, equal) + } + }) + } +}