Add DoesNotExist label operator.

This commit is contained in:
Brian Grant 2015-10-12 23:13:41 +00:00
parent 69a8dc64c7
commit 1bac67f9e2
3 changed files with 128 additions and 58 deletions

View File

@ -33,6 +33,23 @@ Documentation for other releases can be found at
# Labels
**Table of Contents**
<!-- BEGIN MUNGE: GENERATED_TOC -->
- [Labels](#labels)
- [Motivation](#motivation)
- [Syntax and character set](#syntax-and-character-set)
- [Label selectors](#label-selectors)
- [_Equality-based_ requirement](#equality-based-requirement)
- [_Set-based_ requirement](#set-based-requirement)
- [API](#api)
- [LIST and WATCH filtering](#list-and-watch-filtering)
- [Set references in API objects](#set-references-in-api-objects)
- [Service and ReplicationController](#service-and-replicationcontroller)
- [Job and other new resources](#job-and-other-new-resources)
<!-- END MUNGE: GENERATED_TOC -->
_Labels_ are key/value pairs that are attached to objects, such as pods.
Labels are intended to be used to specify identifying attributes of objects that are meaningful and relevant to users, but which do not directly imply semantics to the core system.
Labels can be used to organize and to select subsets of objects. Labels can be attached to objects at creation time and subsequently added and modified at any time.
@ -56,15 +73,14 @@ Service deployments and batch processing pipelines are often multi-dimensional e
Example labels:
* `"release" : "stable"`, `"release" : "canary"`, ...
* `"release" : "stable"`, `"release" : "canary"`
* `"environment" : "dev"`, `"environment" : "qa"`, `"environment" : "production"`
* `"tier" : "frontend"`, `"tier" : "backend"`, `"tier" : "middleware"`
* `"partition" : "customerA"`, `"partition" : "customerB"`, ...
* `"tier" : "frontend"`, `"tier" : "backend"`, `"tier" : "cache"`
* `"partition" : "customerA"`, `"partition" : "customerB"`
* `"track" : "daily"`, `"track" : "weekly"`
These are just examples; you are free to develop your own conventions.
## Syntax and character set
_Labels_ are key value pairs. Valid label keys have two segments: an optional prefix and name, separated by a slash (`/`). The name segment is required and must be 63 characters or less, beginning and ending with an alphanumeric character (`[a-z0-9A-Z]`) with dashes (`-`), underscores (`_`), dots (`.`), and alphanumerics between. The prefix is optional. If specified, the prefix must be a DNS subdomain: a series of DNS labels separated by dots (`.`), not longer than 253 characters in total, followed by a slash (`/`).
@ -83,10 +99,12 @@ A label selector can be made of multiple _requirements_ which are comma-separate
An empty label selector (that is, one with zero requirements) selects every object in the collection.
A null label selector (which is only possible for optional selector fields) selects no objects.
### _Equality-based_ requirement
_Equality-_ or _inequality-based_ requirements allow filtering by label keys and values. Matching objects must have all of the specified labels (both keys and values), though they may have additional labels as well.
Three kinds of operators are admitted `=`,`==`,`!=`. The first two represent _equality_ and are simply synonyms. While the latter represents _inequality_. For example:
_Equality-_ or _inequality-based_ requirements allow filtering by label keys and values. Matching objects must satisfy all of the specified label constraints, though they may have additional labels as well.
Three kinds of operators are admitted `=`,`==`,`!=`. The first two represent _equality_ (and are simply synonyms), while the latter represents _inequality_. For example:
```
environment = production
@ -94,23 +112,25 @@ tier != frontend
```
The former selects all resources with key equal to `environment` and value equal to `production`.
The latter selects all resources with key equal to `tier` and value distinct from `frontend`.
The latter selects all resources with key equal to `tier` and value distinct from `frontend`, and all resources with no labels with the `tier` key.
One could filter for resources in `production` excluding `frontend` using the comma operator: `environment=production,tier!=frontend`
### _Set-based_ requirement
_Set-based_ label requirements allow filtering keys according to a set of values. Matching objects must have all of the specified labels (i.e. all keys and at least one of the values specified for each key). Three kinds of operators are supported: `in`,`notin` and exists (only the key identifier). For example:
_Set-based_ label requirements allow filtering keys according to a set of values. Three kinds of operators are supported: `in`,`notin` and exists (only the key identifier). For example:
```
environment in (production, qa)
tier notin (frontend, backend)
partition
!partition
```
The first example selects all resources with key equal to `environment` and value equal to `production` or `qa`.
The second example selects all resources with key equal to `tier` and values other than `frontend` and `backend`.
The second example selects all resources with key equal to `tier` and values other than `frontend` and `backend`, and all resources with no labels with the `tier` key.
The third example selects all resources including a label with key `partition`; no values are checked.
The fourth example selects all resources without a label with key `partition`; no values are checked.
Similarly the comma separator acts as an _AND_ operator. So filtering resources with a `partition` key (no matter the value) and with `environment` different than  `qa` can be achieved using `partition,environment notin (qa)`.
The _set-based_ label selector is a general form of equality since `environment=production` is equivalent to `environment in (production)`; similarly for `!=` and `notin`.
@ -119,6 +139,8 @@ _Set-based_ requirements can be mixed with _equality-based_ requirements. For ex
## API
### LIST and WATCH filtering
LIST and WATCH operations may specify label selectors to filter the sets of objects returned using a query parameter. Both requirements are permitted:
* _equality-based_ requirements: `?labelSelector=environment%3Dproduction,tier%3Dfrontend`
@ -126,38 +148,41 @@ LIST and WATCH operations may specify label selectors to filter the sets of obje
Both label selector styles can be used to list or watch resources via a REST client. For example targetting `apiserver` with `kubectl` and using _equality-based_ one may write:
```shell
```console
$ kubectl get pods -l environment=production,tier=frontend
```
or using _set-based_ requirements:
```
$kubectl get pods -l 'environment in (production),tier in (frontend)'
```console
$ kubectl get pods -l 'environment in (production),tier in (frontend)'
```
As already mentioned _set-based_ requirements are more expressive.  For instance, they can implement the _OR_ operator:
As already mentioned _set-based_ requirements are more expressive.  For instance, they can implement the _OR_ operator on values:
```shell
$kubectl get pods -l 'environment in (production, qa)'
```console
$ kubectl get pods -l 'environment in (production, qa)'
```
or restricting negative matching via _exists_ operator:
```shell
$kubectl get pods -l 'environment,environment notin (frontend)'
```console
$ kubectl get pods -l 'environment,environment notin (frontend)'
```
Kubernetes also supports two objects that use label selectors to keep track of their members, `service`s and `replicationcontroller`s:
### Set references in API objects
* `service`: A [service](services.md) is a configuration unit for the proxies that run on every worker node.  It is named and points to one or more pods.
* `replicationcontroller`: A [replication controller](replication-controller.md) ensures that a specified number of pod "replicas" are running at any one time.
Some Kubernetes objects, such as [`service`s](services.md) and [`replicationcontroller`s](replication-controller.md), also use label selectors to specify sets of other resources, such as [pods](pods.md).
The set of pods that a `service` targets is defined with a label selector. Similarly, the population of pods that a `replicationcontroller` is monitoring is also defined with a label selector. For management convenience and consistency, `services` and `replicationcontrollers` may themselves have labels and would generally carry the labels their corresponding pods have in common. Labels selectors for both objects are defined in `json` or `yaml` files using `map` and only _equality-based_ requirement selectors are supported:
#### Service and ReplicationController
The set of pods that a `service` targets is defined with a label selector. Similarly, the population of pods that a `replicationcontroller` should manage is also defined with a label selector.
Labels selectors for both objects are defined in `json` or `yaml` files using maps, and only _equality-based_ requirement selectors are supported:
```json
"selector": {
"name" : "redis",
"component" : "redis",
}
```
@ -165,26 +190,25 @@ or
```yaml
selector:
name: redis
component: redis
```
this selector (respectively in `json` or `yaml` format) is equivalent to `name=redis` or `name in (redis)`. At the moment in `json` or `yaml` format there is no way to represent inequalities or existence operators see [#341](https://github.com/kubernetes/kubernetes/issues/341).
this selector (respectively in `json` or `yaml` format) is equivalent to `component=redis` or `component in (redis)`.
For example [redis-controller.yaml](../../examples/redis/redis-controller.yaml)`redis` `replicationcontroller` will monitor pods selected by the `name=redis` _equality-based_ requirement.
#### Job and other new resources
Similarly for [redis-sentinel-service.yaml](../../examples/redis/redis-sentinel-service.yaml) `redis-sentinel` `service` will target pods selected by the `redis-sentinel=true` _equality-based_ requirement.
Newer resources, such as [job](jobs.md), support _set-based_ requirements as well.
Sets identified by labels could be overlapping (think Venn diagrams). For instance, a service might target all pods with `"tier": "frontend"` and `"environment" : "prod"`. Now say you have 10 replicated pods that make up this tier. But you want to be able to 'canary' a new version of this component. You could set up a `replicationcontroller` (with `replicas` set to 9) for the bulk of the replicas with labels `"tier" : "frontend"` and `"environment" : "prod"` and `"track" : "stable"` and another `replicationcontroller` (with `replicas` set to 1) for the canary with labels `"tier" : "frontend"` and `"environment" : "prod"` and `"track" : "canary"`. Now the service is covering both the canary and non-canary pods. But you can mess with the `replicationcontrollers` separately to test things out, monitor the results, etc.
Note that the superset described in the previous example is also heterogeneous. In long-lived, highly available, horizontally scaled, distributed, continuously evolving service applications, heterogeneity is inevitable, due to canaries, incremental rollouts, live reconfiguration, simultaneous updates and auto-scaling, hardware upgrades, and so on.
Pods (and other objects) may belong to multiple sets simultaneously, which enables representation of service substructure and/or superstructure. In particular, labels are intended to facilitate the creation of non-hierarchical, multi-dimensional deployment structures. They are useful for a variety of management purposes (e.g., configuration, deployment) and for application introspection and analysis (e.g., logging, monitoring, alerting, analytics). Without the ability to form sets by intersecting labels, many implicitly related, overlapping flat sets would need to be created, for each subset and/or superset desired, which would lose semantic information and be difficult to keep consistent. Purely hierarchically nested sets wouldn't readily support slicing sets across different dimensions.
## Future developments
Concerning API: we may extend such filtering to DELETE operations in the future.
```yaml
selector:
matchLabels:
component: redis
matchExpressions:
- {key: tier, operator: In, values: [cache]}
- {key: environment, operator: NotIn, values: [dev]}
```
`matchLabels` is a map of `{key,value}` pairs. A single `{key,value}` in the `matchLabels` map is equivalent to an element of `matchExpressions`, whose `key` field is "key", the `operator` is "In", and the `values` array contains only "value". `matchExpressions` is a list of pod selector requirements. Valid operators include In, NotIn, Exists, and DoesNotExist. The values set must be non-empty in the case of In and NotIn. All of the requirements, from both `matchLabels` and `matchExpressions` are ANDed together -- they must all be satisfied in order to match.
<!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/labels.md?pixel)]()

View File

@ -52,6 +52,7 @@ func Everything() Selector {
type Operator string
const (
DoesNotExistOperator Operator = "!"
EqualsOperator Operator = "="
DoubleEqualsOperator Operator = "=="
InOperator Operator = "in"
@ -85,10 +86,11 @@ type Requirement struct {
// NewRequirement is the constructor for a Requirement.
// If any of these rules is violated, an error is returned:
// (1) The operator can only be In, NotIn or Exists.
// (2) If the operator is In or NotIn, the values set must
// be non-empty.
// (3) The key is invalid due to its length, or sequence
// (1) The operator can only be In, NotIn, Equals, DoubleEquals, NotEquals, Exists, or DoesNotExist.
// (2) If the operator is In or NotIn, the values set must be non-empty.
// (3) If the operator is Equals, DoubleEquals, or NotEquals, the values set must contain one value.
// (4) If the operator is Exists or DoesNotExist, the value set must be empty.
// (5) The key is invalid due to its length, or sequence
// of characters. See validateLabelKey for more details.
//
// The empty string is a valid value in the input values set.
@ -103,9 +105,12 @@ func NewRequirement(key string, op Operator, vals sets.String) (*Requirement, er
}
case EqualsOperator, DoubleEqualsOperator, NotEqualsOperator:
if len(vals) != 1 {
return nil, fmt.Errorf("exact match compatibility requires one single value")
return nil, fmt.Errorf("exact-match compatibility requires one single value")
}
case ExistsOperator, DoesNotExistOperator:
if len(vals) != 0 {
return nil, fmt.Errorf("values set must be empty for exists and does not exist")
}
case ExistsOperator:
default:
return nil, fmt.Errorf("operator '%v' is not recognized", op)
}
@ -125,7 +130,7 @@ func NewRequirement(key string, op Operator, vals sets.String) (*Requirement, er
// value for that key is in Requirement's value set.
// (3) The operator is NotIn, Labels has the Requirement's key and
// Labels' value for that key is not in Requirement's value set.
// (4) The operator is NotIn and Labels does not have the
// (4) The operator is DoesNotExist or NotIn and Labels does not have the
// Requirement's key.
func (r *Requirement) Matches(ls Labels) bool {
switch r.operator {
@ -141,6 +146,8 @@ func (r *Requirement) Matches(ls Labels) bool {
return !r.strValues.Has(ls.Get(r.key))
case ExistsOperator:
return ls.Has(r.key)
case DoesNotExistOperator:
return !ls.Has(r.key)
default:
return false
}
@ -173,6 +180,9 @@ func (lsel LabelSelector) Empty() bool {
// returned. See NewRequirement for creating a valid Requirement.
func (r *Requirement) String() string {
var buffer bytes.Buffer
if r.operator == DoesNotExistOperator {
buffer.WriteString("!")
}
buffer.WriteString(r.key)
switch r.operator {
@ -186,7 +196,7 @@ func (r *Requirement) String() string {
buffer.WriteString(" in ")
case NotInOperator:
buffer.WriteString(" notin ")
case ExistsOperator:
case ExistsOperator, DoesNotExistOperator:
return buffer.String()
}
@ -249,6 +259,7 @@ const (
EndOfStringToken
ClosedParToken
CommaToken
DoesNotExistToken
DoubleEqualsToken
EqualsToken
IdentifierToken // to represent keys and values
@ -263,6 +274,7 @@ const (
var string2token = map[string]Token{
")": ClosedParToken,
",": CommaToken,
"!": DoesNotExistToken,
"==": DoubleEqualsToken,
"=": EqualsToken,
"in": InToken,
@ -457,7 +469,7 @@ func (p *Parser) parse() ([]Requirement, error) {
for {
tok, lit := p.lookahead(Values)
switch tok {
case IdentifierToken:
case IdentifierToken, DoesNotExistToken:
r, err := p.parseRequirement()
if err != nil {
return nil, fmt.Errorf("unable to parse requirement: %v", err)
@ -469,7 +481,7 @@ func (p *Parser) parse() ([]Requirement, error) {
return requirements, nil
case CommaToken:
t2, l2 := p.lookahead(Values)
if t2 != IdentifierToken {
if t2 != IdentifierToken && t2 != DoesNotExistToken {
return nil, fmt.Errorf("found '%s', expected: identifier after ','", l2)
}
default:
@ -478,7 +490,7 @@ func (p *Parser) parse() ([]Requirement, error) {
case EndOfStringToken:
return requirements, nil
default:
return nil, fmt.Errorf("found '%s', expected: identifier or 'end of string'", lit)
return nil, fmt.Errorf("found '%s', expected: !, identifier, or 'end of string'", lit)
}
}
}
@ -488,7 +500,7 @@ func (p *Parser) parseRequirement() (*Requirement, error) {
if err != nil {
return nil, err
}
if operator == ExistsOperator { // operator Exists found lookahead set checked
if operator == ExistsOperator || operator == DoesNotExistOperator { // operator found lookahead set checked
return NewRequirement(key, operator, nil)
}
operator, err = p.parseOperator()
@ -510,10 +522,15 @@ func (p *Parser) parseRequirement() (*Requirement, error) {
}
// parseKeyAndInferOperator parse literals.
// in case of no operator 'in, notin, ==, =, !=' are found
// the 'exists' operattor is inferred
// in case of no operator '!, in, notin, ==, =, !=' are found
// the 'exists' operator is inferred
func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) {
var operator Operator
tok, literal := p.consume(Values)
if tok == DoesNotExistToken {
operator = DoesNotExistOperator
tok, literal = p.consume(Values)
}
if tok != IdentifierToken {
err := fmt.Errorf("found '%s', expected: identifier", literal)
return "", "", err
@ -521,9 +538,10 @@ func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) {
if err := validateLabelKey(literal); err != nil {
return "", "", err
}
var operator Operator
if t, _ := p.lookahead(Values); t == EndOfStringToken || t == CommaToken {
operator = ExistsOperator
if operator != DoesNotExistOperator {
operator = ExistsOperator
}
}
return literal, operator, nil
}
@ -533,6 +551,7 @@ func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) {
func (p *Parser) parseOperator() (op Operator, err error) {
tok, lit := p.consume(KeyAndOperator)
switch tok {
// DoesNotExistToken shouldn't be here because it's a unary operator, not a binary operator
case InToken:
op = InOperator
case EqualsToken:
@ -633,7 +652,7 @@ func (p *Parser) parseExactValue() (sets.String, error) {
// The input will cause an error if it does not follow this form:
//
// <selector-syntax> ::= <requirement> | <requirement> "," <selector-syntax> ]
// <requirement> ::= KEY [ <set-based-restriction> | <exact-match-restriction>
// <requirement> ::= [!] KEY [ <set-based-restriction> | <exact-match-restriction> ]
// <set-based-restriction> ::= "" | <inclusion-exclusion> <value-set>
// <inclusion-exclusion> ::= <inclusion> | <exclusion>
// <exclusion> ::= "notin"
@ -648,13 +667,14 @@ func (p *Parser) parseExactValue() (sets.String, error) {
// "x in (foo,,baz),y,z notin ()"
//
// Note:
// (1) Inclusion - " in " - denotes that the KEY is equal to any of the
// (1) Inclusion - " in " - denotes that the KEY exists and is equal to any of the
// VALUEs in its requirement
// (2) Exclusion - " notin " - denotes that the KEY is not equal to any
// of the VALUEs in its requirement
// of the VALUEs in its requirement or does not exist
// (3) The empty string is a valid VALUE
// (4) A requirement with just a KEY - as in "y" above - denotes that
// the KEY exists and can be any VALUE.
// (5) A requirement with just !KEY requires that the KEY not exist.
//
func Parse(selector string) (Selector, error) {
p := &Parser{l: &Lexer{s: selector, pos: 0}}

View File

@ -33,10 +33,12 @@ func TestSelectorParse(t *testing.T) {
"x= ",
"x=,z= ",
"x= ,z= ",
"!x",
}
testBadStrings := []string{
"x=a||y=b",
"x==a==b",
"!x=a",
}
for _, test := range testGoodStrings {
lq, err := Parse(test)
@ -103,10 +105,14 @@ func TestSelectorMatches(t *testing.T) {
expectMatch(t, "x=y,z=w", Set{"x": "y", "z": "w"})
expectMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "a"})
expectMatch(t, "notin=in", Set{"notin": "in"}) // in and notin in exactMatch
expectMatch(t, "x", Set{"x": "z"})
expectMatch(t, "!x", Set{"y": "z"})
expectNoMatch(t, "x=z", Set{})
expectNoMatch(t, "x=y", Set{"x": "z"})
expectNoMatch(t, "x=y,z=w", Set{"x": "w", "z": "w"})
expectNoMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "w"})
expectNoMatch(t, "x", Set{"y": "z"})
expectNoMatch(t, "!x", Set{"x": "z"})
labelset := Set{
"foo": "bar",
@ -178,11 +184,14 @@ func TestLexer(t *testing.T) {
{"in", InToken},
{"=", EqualsToken},
{"==", DoubleEqualsToken},
//Note that Lex returns the longest valid token found
{"!", DoesNotExistToken},
{"!=", NotEqualsToken},
{"(", OpenParToken},
{")", ClosedParToken},
//Non-"special" characters are considered part of an identifier
{"~", IdentifierToken},
{"||", IdentifierToken},
{"!", ErrorToken},
}
for _, v := range testcases {
l := &Lexer{s: v.s, pos: 0}
@ -213,6 +222,7 @@ func TestLexerSequence(t *testing.T) {
{"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken}},
{"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken}},
{"key", []Token{IdentifierToken}},
{"!key", []Token{DoesNotExistToken, IdentifierToken}},
{"()", []Token{OpenParToken, ClosedParToken}},
{"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken}},
{"== != (), = notin", []Token{DoubleEqualsToken, NotEqualsToken, OpenParToken, ClosedParToken, CommaToken, EqualsToken, NotInToken}},
@ -248,6 +258,7 @@ func TestParserLookahead(t *testing.T) {
{"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken, EndOfStringToken}},
{"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken, EndOfStringToken}},
{"key", []Token{IdentifierToken, EndOfStringToken}},
{"!key", []Token{DoesNotExistToken, IdentifierToken, EndOfStringToken}},
{"()", []Token{OpenParToken, ClosedParToken, EndOfStringToken}},
{"", []Token{EndOfStringToken}},
{"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken, EndOfStringToken}},
@ -285,6 +296,7 @@ func TestRequirementConstructor(t *testing.T) {
{"x", InOperator, sets.NewString("foo"), true},
{"x", NotInOperator, sets.NewString("foo"), true},
{"x", ExistsOperator, nil, true},
{"x", DoesNotExistOperator, nil, true},
{"1foo", InOperator, sets.NewString("bar"), true},
{"1234", InOperator, sets.NewString("bar"), true},
{strings.Repeat("a", 254), ExistsOperator, nil, false}, //breaks DNS rule that len(key) <= 253
@ -310,6 +322,11 @@ func TestToString(t *testing.T) {
getRequirement("y", NotInOperator, sets.NewString("jkl"), t),
getRequirement("z", ExistsOperator, nil, t)},
"x in (abc,def),y notin (jkl),z", true},
{&LabelSelector{
getRequirement("x", NotInOperator, sets.NewString("abc", "def"), t),
getRequirement("y", NotEqualsOperator, sets.NewString("jkl"), t),
getRequirement("z", DoesNotExistOperator, nil, t)},
"x notin (abc,def),y!=jkl,!z", true},
{&LabelSelector{
getRequirement("x", InOperator, sets.NewString("abc", "def"), t),
req}, // adding empty req for the trailing ','
@ -322,8 +339,9 @@ func TestToString(t *testing.T) {
{&LabelSelector{
getRequirement("x", EqualsOperator, sets.NewString("abc"), t),
getRequirement("y", DoubleEqualsOperator, sets.NewString("jkl"), t),
getRequirement("z", NotEqualsOperator, sets.NewString("a"), t)},
"x=abc,y==jkl,z!=a", true},
getRequirement("z", NotEqualsOperator, sets.NewString("a"), t),
getRequirement("z", ExistsOperator, nil, t)},
"x=abc,y==jkl,z!=a,z", true},
}
for _, ts := range toStringTests {
if out := ts.In.String(); out == "" && ts.Valid {
@ -356,6 +374,14 @@ func TestRequirementLabelSelectorMatching(t *testing.T) {
getRequirement("x", NotInOperator, sets.NewString(""), t),
getRequirement("y", ExistsOperator, nil, t),
}, true},
{Set{"y": ""}, &LabelSelector{
getRequirement("x", DoesNotExistOperator, nil, t),
getRequirement("y", ExistsOperator, nil, t),
}, true},
{Set{"y": ""}, &LabelSelector{
getRequirement("x", NotInOperator, sets.NewString(""), t),
getRequirement("y", DoesNotExistOperator, nil, t),
}, false},
{Set{"y": "baz"}, &LabelSelector{
getRequirement("x", InOperator, sets.NewString(""), t),
}, false},