Commit 8959e0cc authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #15509 from bgrant0607/labels

Auto commit by PR queue bot
parents 3e3833b0 1bac67f9
...@@ -52,6 +52,7 @@ func Everything() Selector { ...@@ -52,6 +52,7 @@ func Everything() Selector {
type Operator string type Operator string
const ( const (
DoesNotExistOperator Operator = "!"
EqualsOperator Operator = "=" EqualsOperator Operator = "="
DoubleEqualsOperator Operator = "==" DoubleEqualsOperator Operator = "=="
InOperator Operator = "in" InOperator Operator = "in"
...@@ -85,10 +86,11 @@ type Requirement struct { ...@@ -85,10 +86,11 @@ type Requirement struct {
// NewRequirement is the constructor for a Requirement. // NewRequirement is the constructor for a Requirement.
// If any of these rules is violated, an error is returned: // If any of these rules is violated, an error is returned:
// (1) The operator can only be In, NotIn or Exists. // (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 // (2) If the operator is In or NotIn, the values set must be non-empty.
// be non-empty. // (3) If the operator is Equals, DoubleEquals, or NotEquals, the values set must contain one value.
// (3) The key is invalid due to its length, or sequence // (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. // of characters. See validateLabelKey for more details.
// //
// The empty string is a valid value in the input values set. // 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 ...@@ -103,9 +105,12 @@ func NewRequirement(key string, op Operator, vals sets.String) (*Requirement, er
} }
case EqualsOperator, DoubleEqualsOperator, NotEqualsOperator: case EqualsOperator, DoubleEqualsOperator, NotEqualsOperator:
if len(vals) != 1 { 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: default:
return nil, fmt.Errorf("operator '%v' is not recognized", op) 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 ...@@ -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. // value for that key is in Requirement's value set.
// (3) The operator is NotIn, Labels has the Requirement's key and // (3) The operator is NotIn, Labels has the Requirement's key and
// Labels' value for that key is not in Requirement's value set. // 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. // Requirement's key.
func (r *Requirement) Matches(ls Labels) bool { func (r *Requirement) Matches(ls Labels) bool {
switch r.operator { switch r.operator {
...@@ -141,6 +146,8 @@ func (r *Requirement) Matches(ls Labels) bool { ...@@ -141,6 +146,8 @@ func (r *Requirement) Matches(ls Labels) bool {
return !r.strValues.Has(ls.Get(r.key)) return !r.strValues.Has(ls.Get(r.key))
case ExistsOperator: case ExistsOperator:
return ls.Has(r.key) return ls.Has(r.key)
case DoesNotExistOperator:
return !ls.Has(r.key)
default: default:
return false return false
} }
...@@ -173,6 +180,9 @@ func (lsel LabelSelector) Empty() bool { ...@@ -173,6 +180,9 @@ func (lsel LabelSelector) Empty() bool {
// returned. See NewRequirement for creating a valid Requirement. // returned. See NewRequirement for creating a valid Requirement.
func (r *Requirement) String() string { func (r *Requirement) String() string {
var buffer bytes.Buffer var buffer bytes.Buffer
if r.operator == DoesNotExistOperator {
buffer.WriteString("!")
}
buffer.WriteString(r.key) buffer.WriteString(r.key)
switch r.operator { switch r.operator {
...@@ -186,7 +196,7 @@ func (r *Requirement) String() string { ...@@ -186,7 +196,7 @@ func (r *Requirement) String() string {
buffer.WriteString(" in ") buffer.WriteString(" in ")
case NotInOperator: case NotInOperator:
buffer.WriteString(" notin ") buffer.WriteString(" notin ")
case ExistsOperator: case ExistsOperator, DoesNotExistOperator:
return buffer.String() return buffer.String()
} }
...@@ -249,6 +259,7 @@ const ( ...@@ -249,6 +259,7 @@ const (
EndOfStringToken EndOfStringToken
ClosedParToken ClosedParToken
CommaToken CommaToken
DoesNotExistToken
DoubleEqualsToken DoubleEqualsToken
EqualsToken EqualsToken
IdentifierToken // to represent keys and values IdentifierToken // to represent keys and values
...@@ -263,6 +274,7 @@ const ( ...@@ -263,6 +274,7 @@ const (
var string2token = map[string]Token{ var string2token = map[string]Token{
")": ClosedParToken, ")": ClosedParToken,
",": CommaToken, ",": CommaToken,
"!": DoesNotExistToken,
"==": DoubleEqualsToken, "==": DoubleEqualsToken,
"=": EqualsToken, "=": EqualsToken,
"in": InToken, "in": InToken,
...@@ -457,7 +469,7 @@ func (p *Parser) parse() ([]Requirement, error) { ...@@ -457,7 +469,7 @@ func (p *Parser) parse() ([]Requirement, error) {
for { for {
tok, lit := p.lookahead(Values) tok, lit := p.lookahead(Values)
switch tok { switch tok {
case IdentifierToken: case IdentifierToken, DoesNotExistToken:
r, err := p.parseRequirement() r, err := p.parseRequirement()
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse requirement: %v", err) return nil, fmt.Errorf("unable to parse requirement: %v", err)
...@@ -469,7 +481,7 @@ func (p *Parser) parse() ([]Requirement, error) { ...@@ -469,7 +481,7 @@ func (p *Parser) parse() ([]Requirement, error) {
return requirements, nil return requirements, nil
case CommaToken: case CommaToken:
t2, l2 := p.lookahead(Values) t2, l2 := p.lookahead(Values)
if t2 != IdentifierToken { if t2 != IdentifierToken && t2 != DoesNotExistToken {
return nil, fmt.Errorf("found '%s', expected: identifier after ','", l2) return nil, fmt.Errorf("found '%s', expected: identifier after ','", l2)
} }
default: default:
...@@ -478,7 +490,7 @@ func (p *Parser) parse() ([]Requirement, error) { ...@@ -478,7 +490,7 @@ func (p *Parser) parse() ([]Requirement, error) {
case EndOfStringToken: case EndOfStringToken:
return requirements, nil return requirements, nil
default: 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) { ...@@ -488,7 +500,7 @@ func (p *Parser) parseRequirement() (*Requirement, error) {
if err != nil { if err != nil {
return nil, err 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) return NewRequirement(key, operator, nil)
} }
operator, err = p.parseOperator() operator, err = p.parseOperator()
...@@ -510,10 +522,15 @@ func (p *Parser) parseRequirement() (*Requirement, error) { ...@@ -510,10 +522,15 @@ func (p *Parser) parseRequirement() (*Requirement, error) {
} }
// parseKeyAndInferOperator parse literals. // parseKeyAndInferOperator parse literals.
// in case of no operator 'in, notin, ==, =, !=' are found // in case of no operator '!, in, notin, ==, =, !=' are found
// the 'exists' operattor is inferred // the 'exists' operator is inferred
func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) { func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) {
var operator Operator
tok, literal := p.consume(Values) tok, literal := p.consume(Values)
if tok == DoesNotExistToken {
operator = DoesNotExistOperator
tok, literal = p.consume(Values)
}
if tok != IdentifierToken { if tok != IdentifierToken {
err := fmt.Errorf("found '%s', expected: identifier", literal) err := fmt.Errorf("found '%s', expected: identifier", literal)
return "", "", err return "", "", err
...@@ -521,10 +538,11 @@ func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) { ...@@ -521,10 +538,11 @@ func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) {
if err := validateLabelKey(literal); err != nil { if err := validateLabelKey(literal); err != nil {
return "", "", err return "", "", err
} }
var operator Operator
if t, _ := p.lookahead(Values); t == EndOfStringToken || t == CommaToken { if t, _ := p.lookahead(Values); t == EndOfStringToken || t == CommaToken {
if operator != DoesNotExistOperator {
operator = ExistsOperator operator = ExistsOperator
} }
}
return literal, operator, nil return literal, operator, nil
} }
...@@ -533,6 +551,7 @@ func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) { ...@@ -533,6 +551,7 @@ func (p *Parser) parseKeyAndInferOperator() (string, Operator, error) {
func (p *Parser) parseOperator() (op Operator, err error) { func (p *Parser) parseOperator() (op Operator, err error) {
tok, lit := p.consume(KeyAndOperator) tok, lit := p.consume(KeyAndOperator)
switch tok { switch tok {
// DoesNotExistToken shouldn't be here because it's a unary operator, not a binary operator
case InToken: case InToken:
op = InOperator op = InOperator
case EqualsToken: case EqualsToken:
...@@ -633,7 +652,7 @@ func (p *Parser) parseExactValue() (sets.String, error) { ...@@ -633,7 +652,7 @@ func (p *Parser) parseExactValue() (sets.String, error) {
// The input will cause an error if it does not follow this form: // The input will cause an error if it does not follow this form:
// //
// <selector-syntax> ::= <requirement> | <requirement> "," <selector-syntax> ] // <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> // <set-based-restriction> ::= "" | <inclusion-exclusion> <value-set>
// <inclusion-exclusion> ::= <inclusion> | <exclusion> // <inclusion-exclusion> ::= <inclusion> | <exclusion>
// <exclusion> ::= "notin" // <exclusion> ::= "notin"
...@@ -648,13 +667,14 @@ func (p *Parser) parseExactValue() (sets.String, error) { ...@@ -648,13 +667,14 @@ func (p *Parser) parseExactValue() (sets.String, error) {
// "x in (foo,,baz),y,z notin ()" // "x in (foo,,baz),y,z notin ()"
// //
// Note: // 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 // VALUEs in its requirement
// (2) Exclusion - " notin " - denotes that the KEY is not equal to any // (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 // (3) The empty string is a valid VALUE
// (4) A requirement with just a KEY - as in "y" above - denotes that // (4) A requirement with just a KEY - as in "y" above - denotes that
// the KEY exists and can be any VALUE. // 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) { func Parse(selector string) (Selector, error) {
p := &Parser{l: &Lexer{s: selector, pos: 0}} p := &Parser{l: &Lexer{s: selector, pos: 0}}
......
...@@ -33,10 +33,12 @@ func TestSelectorParse(t *testing.T) { ...@@ -33,10 +33,12 @@ func TestSelectorParse(t *testing.T) {
"x= ", "x= ",
"x=,z= ", "x=,z= ",
"x= ,z= ", "x= ,z= ",
"!x",
} }
testBadStrings := []string{ testBadStrings := []string{
"x=a||y=b", "x=a||y=b",
"x==a==b", "x==a==b",
"!x=a",
} }
for _, test := range testGoodStrings { for _, test := range testGoodStrings {
lq, err := Parse(test) lq, err := Parse(test)
...@@ -103,10 +105,14 @@ func TestSelectorMatches(t *testing.T) { ...@@ -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": "y", "z": "w"})
expectMatch(t, "x!=y,z!=w", Set{"x": "z", "z": "a"}) 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, "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=z", Set{})
expectNoMatch(t, "x=y", Set{"x": "z"}) 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": "w", "z": "w"})
expectNoMatch(t, "x!=y,z!=w", Set{"x": "z", "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{ labelset := Set{
"foo": "bar", "foo": "bar",
...@@ -178,11 +184,14 @@ func TestLexer(t *testing.T) { ...@@ -178,11 +184,14 @@ func TestLexer(t *testing.T) {
{"in", InToken}, {"in", InToken},
{"=", EqualsToken}, {"=", EqualsToken},
{"==", DoubleEqualsToken}, {"==", DoubleEqualsToken},
//Note that Lex returns the longest valid token found
{"!", DoesNotExistToken},
{"!=", NotEqualsToken}, {"!=", NotEqualsToken},
{"(", OpenParToken}, {"(", OpenParToken},
{")", ClosedParToken}, {")", ClosedParToken},
//Non-"special" characters are considered part of an identifier
{"~", IdentifierToken},
{"||", IdentifierToken}, {"||", IdentifierToken},
{"!", ErrorToken},
} }
for _, v := range testcases { for _, v := range testcases {
l := &Lexer{s: v.s, pos: 0} l := &Lexer{s: v.s, pos: 0}
...@@ -213,6 +222,7 @@ func TestLexerSequence(t *testing.T) { ...@@ -213,6 +222,7 @@ func TestLexerSequence(t *testing.T) {
{"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken}}, {"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken}},
{"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken}}, {"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken}},
{"key", []Token{IdentifierToken}}, {"key", []Token{IdentifierToken}},
{"!key", []Token{DoesNotExistToken, IdentifierToken}},
{"()", []Token{OpenParToken, ClosedParToken}}, {"()", []Token{OpenParToken, ClosedParToken}},
{"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken}}, {"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken}},
{"== != (), = notin", []Token{DoubleEqualsToken, NotEqualsToken, OpenParToken, ClosedParToken, CommaToken, EqualsToken, NotInToken}}, {"== != (), = notin", []Token{DoubleEqualsToken, NotEqualsToken, OpenParToken, ClosedParToken, CommaToken, EqualsToken, NotInToken}},
...@@ -248,6 +258,7 @@ func TestParserLookahead(t *testing.T) { ...@@ -248,6 +258,7 @@ func TestParserLookahead(t *testing.T) {
{"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken, EndOfStringToken}}, {"key notin ( value )", []Token{IdentifierToken, NotInToken, OpenParToken, IdentifierToken, ClosedParToken, EndOfStringToken}},
{"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken, EndOfStringToken}}, {"key in ( value1, value2 )", []Token{IdentifierToken, InToken, OpenParToken, IdentifierToken, CommaToken, IdentifierToken, ClosedParToken, EndOfStringToken}},
{"key", []Token{IdentifierToken, EndOfStringToken}}, {"key", []Token{IdentifierToken, EndOfStringToken}},
{"!key", []Token{DoesNotExistToken, IdentifierToken, EndOfStringToken}},
{"()", []Token{OpenParToken, ClosedParToken, EndOfStringToken}}, {"()", []Token{OpenParToken, ClosedParToken, EndOfStringToken}},
{"", []Token{EndOfStringToken}}, {"", []Token{EndOfStringToken}},
{"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken, EndOfStringToken}}, {"x in (),y", []Token{IdentifierToken, InToken, OpenParToken, ClosedParToken, CommaToken, IdentifierToken, EndOfStringToken}},
...@@ -285,6 +296,7 @@ func TestRequirementConstructor(t *testing.T) { ...@@ -285,6 +296,7 @@ func TestRequirementConstructor(t *testing.T) {
{"x", InOperator, sets.NewString("foo"), true}, {"x", InOperator, sets.NewString("foo"), true},
{"x", NotInOperator, sets.NewString("foo"), true}, {"x", NotInOperator, sets.NewString("foo"), true},
{"x", ExistsOperator, nil, true}, {"x", ExistsOperator, nil, true},
{"x", DoesNotExistOperator, nil, true},
{"1foo", InOperator, sets.NewString("bar"), true}, {"1foo", InOperator, sets.NewString("bar"), true},
{"1234", 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 {strings.Repeat("a", 254), ExistsOperator, nil, false}, //breaks DNS rule that len(key) <= 253
...@@ -311,6 +323,11 @@ func TestToString(t *testing.T) { ...@@ -311,6 +323,11 @@ func TestToString(t *testing.T) {
getRequirement("z", ExistsOperator, nil, t)}, getRequirement("z", ExistsOperator, nil, t)},
"x in (abc,def),y notin (jkl),z", true}, "x in (abc,def),y notin (jkl),z", true},
{&LabelSelector{ {&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), getRequirement("x", InOperator, sets.NewString("abc", "def"), t),
req}, // adding empty req for the trailing ',' req}, // adding empty req for the trailing ','
"x in (abc,def),", false}, "x in (abc,def),", false},
...@@ -322,8 +339,9 @@ func TestToString(t *testing.T) { ...@@ -322,8 +339,9 @@ func TestToString(t *testing.T) {
{&LabelSelector{ {&LabelSelector{
getRequirement("x", EqualsOperator, sets.NewString("abc"), t), getRequirement("x", EqualsOperator, sets.NewString("abc"), t),
getRequirement("y", DoubleEqualsOperator, sets.NewString("jkl"), t), getRequirement("y", DoubleEqualsOperator, sets.NewString("jkl"), t),
getRequirement("z", NotEqualsOperator, sets.NewString("a"), t)}, getRequirement("z", NotEqualsOperator, sets.NewString("a"), t),
"x=abc,y==jkl,z!=a", true}, getRequirement("z", ExistsOperator, nil, t)},
"x=abc,y==jkl,z!=a,z", true},
} }
for _, ts := range toStringTests { for _, ts := range toStringTests {
if out := ts.In.String(); out == "" && ts.Valid { if out := ts.In.String(); out == "" && ts.Valid {
...@@ -356,6 +374,14 @@ func TestRequirementLabelSelectorMatching(t *testing.T) { ...@@ -356,6 +374,14 @@ func TestRequirementLabelSelectorMatching(t *testing.T) {
getRequirement("x", NotInOperator, sets.NewString(""), t), getRequirement("x", NotInOperator, sets.NewString(""), t),
getRequirement("y", ExistsOperator, nil, t), getRequirement("y", ExistsOperator, nil, t),
}, true}, }, 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{ {Set{"y": "baz"}, &LabelSelector{
getRequirement("x", InOperator, sets.NewString(""), t), getRequirement("x", InOperator, sets.NewString(""), t),
}, false}, }, false},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment