diff --git a/staging/src/k8s.io/apimachinery/pkg/labels/selector.go b/staging/src/k8s.io/apimachinery/pkg/labels/selector.go index 1f4e796fbd0..067bcac0a0d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/labels/selector.go +++ b/staging/src/k8s.io/apimachinery/pkg/labels/selector.go @@ -23,11 +23,12 @@ import ( "strconv" "strings" + "k8s.io/klog/v2" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/klog/v2" ) var ( @@ -115,6 +116,15 @@ func Nothing() Selector { return sharedNothingSelector } +// MatchesNothing only returns true for selectors which are definitively determined to match no objects. +// This currently only detects the `labels.Nothing()` selector, but may change over time to detect more selectors that match no objects. +// +// Note: The current implementation does not check for selector conflict scenarios (e.g., a=a,a!=a). +// Support for detecting such cases can be added in the future. +func MatchesNothing(selector Selector) bool { + return selector == sharedNothingSelector +} + // NewSelector returns a nil selector func NewSelector() Selector { return internalSelector(nil) diff --git a/staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go b/staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go index be956909a13..91dfd51d56c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go @@ -1099,3 +1099,114 @@ func TestRequirementEqual(t *testing.T) { }) } } + +func TestMatchesNothing(t *testing.T) { + tests := []struct { + name string + selector string + set map[string]string + labelSelector Selector + want bool + }{ + { + name: "MatchNothing should match Nothing()", + labelSelector: Nothing(), + want: true, + }, + { + name: "MatchNothing should match sharedNothingSelector", + labelSelector: sharedNothingSelector, + want: true, + }, + { + name: "MatchNothing should not match Everything()", + labelSelector: Everything(), + want: false, + }, + { + name: "MatchNothing should not match sharedEverythingSelector", + labelSelector: sharedEverythingSelector, + want: false, + }, + { + name: "MatchNothing should not match empty set", + set: map[string]string{}, + want: false, + }, + { + name: "MatchNothing should not match non-empty set", + set: map[string]string{"key": "value"}, + want: false, + }, + { + name: "MatchNothing should not match empty selector", + selector: "", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - exists", + selector: "a", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - not exists", + selector: "!a", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - equals", + selector: "a=b", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - not equals", + selector: "a!=b", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - in", + selector: "a in (b)", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - notin", + selector: "a notin (b)", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - conflict exists and not exists", + selector: "a,!a", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - conflict equals and not equals", + selector: "a=b,a!=b", + want: false, + }, + { + name: "MatchNothing should not match non-empty selector - conflict in and notin", + selector: "a in (b),a notin (b)", + want: false, + }, + } + + for i := 0; i < len(tests); i++ { + if tests[i].labelSelector != nil { + expectMatchNothing(t, tests[i].labelSelector, tests[i].want) + } else if tests[i].set != nil { + expectMatchNothing(t, SelectorFromSet(tests[i].set), tests[i].want) + } else { + selector, err := Parse(tests[i].selector) + if err != nil { + t.Errorf("Unable to parse %v as a selector.\n", selector) + } + expectMatchNothing(t, selector, tests[i].want) + } + } +} + +func expectMatchNothing(t *testing.T, selector Selector, want bool) { + if MatchesNothing(selector) != want { + t.Errorf("Wanted %s to MatchNothing '%t', but it did not.\n", selector, want) + } +} diff --git a/staging/src/k8s.io/client-go/tools/cache/listers.go b/staging/src/k8s.io/client-go/tools/cache/listers.go index 9e050ff45e7..dc4dfd2cd48 100644 --- a/staging/src/k8s.io/client-go/tools/cache/listers.go +++ b/staging/src/k8s.io/client-go/tools/cache/listers.go @@ -32,6 +32,10 @@ type AppendFunc func(interface{}) // ListAll lists items in the store matching the given selector, calling appendFn on each one. func ListAll(store Store, selector labels.Selector, appendFn AppendFunc) error { + if labels.MatchesNothing(selector) { + return nil + } + selectAll := selector.Empty() for _, m := range store.List() { if selectAll { @@ -55,6 +59,10 @@ func ListAll(store Store, selector labels.Selector, appendFn AppendFunc) error { // calling appendFn on each one. // If a blank namespace (NamespaceAll) is specified, this delegates to ListAll(). func ListAllByNamespace(indexer Indexer, namespace string, selector labels.Selector, appendFn AppendFunc) error { + if labels.MatchesNothing(selector) { + return nil + } + if namespace == metav1.NamespaceAll { return ListAll(indexer, selector, appendFn) } diff --git a/staging/src/k8s.io/client-go/tools/cache/listers_test.go b/staging/src/k8s.io/client-go/tools/cache/listers_test.go index 730807f6e38..18f3ee35d8e 100644 --- a/staging/src/k8s.io/client-go/tools/cache/listers_test.go +++ b/staging/src/k8s.io/client-go/tools/cache/listers_test.go @@ -138,3 +138,23 @@ func BenchmarkLister_Match_100k_100(b *testing.B) { func BenchmarkLister_Match_1M_100(b *testing.B) { benchmarkLister(b, 1000000, 100, map[string]string{"match": "true"}) } + +func benchmarkNothingLister(b *testing.B, numObjects int, numMatching int, labelSelector *metav1.LabelSelector) { + store := mustCreateStore(numObjects, numMatching, nil) + selector, err := metav1.LabelSelectorAsSelector(labelSelector) + if err != nil { + b.Fatalf("LabelSelectorAsSelector returned an error: %v", err) + } + b.ResetTimer() + for n := 0; n < b.N; n++ { + err := ListAll(store, selector, func(m interface{}) { + }) + if err != nil { + b.Fatalf("ListAll returned an error: %v", err) + } + } +} + +func BenchmarkLister_Match_1M_0(b *testing.B) { + benchmarkNothingLister(b, 1000000, 0, nil) +}