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 {
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}}
......
......@@ -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
......@@ -311,6 +323,11 @@ func TestToString(t *testing.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 ','
"x in (abc,def),", false},
......@@ -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},
......
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