K8s源码2-LabelSelector-Parse

Selector Parse

使用方法

lq, err := Parse("x=y,a=b")

源码分析

Parse函数调用parse,构造Parser对象,之后调用Parser.parse()方法,返回一个实现了Selector接口的internalSelector对象。

func Parse(selector string, opts ...field.PathOption) (Selector, error) {
	parsedSelector, err := parse(selector, field.ToPath(opts...))
	// ...省略
}
func parse(selector string, path *field.Path) (internalSelector, error) {
	p := &Parser{l: &Lexer{s: selector, pos: 0}, path: path}
	items, err := p.parse()
}

那么主要就看一下Parser.parse()做了什么

type Parser struct {
	l            *Lexer
	scannedItems []ScannedItem
	position     int
	path         *field.Path
}

func (p *Parser) parse() (internalSelector, error) {
	p.scan() // init scannedItems
  // ...省略
}

scan()方法将输入的字符串,解析成一个个scanItems[]。

大体思路是通过一个游标pos,读取String的每一个Byte并存入buffer,如果遇到特殊字符,则说明刚刚读取的是一个Key或者Value。

读取特殊字符,则是使用了另一个方法scanSpecialSymbol(),大体思路如此,不再说明。

// 扫描特殊符号in,not exists或者是key和value
func (l *Lexer) scanIDOrKeyword() (tok Token, lit string) {
	var buffer []byte
IdentifierLoop:
	for {
		switch ch := l.read(); {
		case ch == 0:
			break IdentifierLoop
		case isSpecialSymbol(ch) || isWhitespace(ch):
			l.unread()
			break IdentifierLoop
		default:
			buffer = append(buffer, ch)
		}
	}
	s := string(buffer)
	if val, ok := string2token[s]; ok { // is a literal token?
		return val, s
	}
	return IdentifierToken, s // otherwise is an identifier
}

 // 扫描'=', '!', '(', ')', ',', '>', '<'
func (l *Lexer) scanSpecialSymbol() (Token, string) {...}

最终scanItem如下:

[
	{IdentifierToken x},
	{EqualsToken =},
 	{IdentifierToken y},
]

image-20230115110047001

IdentifierToken用来标志Key和Value,EqualsToken标志等于号=。

接下来通过for循环,开始构造Selector中的Requirement数组

func (p *Parser) parse() (internalSelector, error) {
	p.scan() // init scannedItems

	var requirements internalSelector
	for {
		tok, lit := p.lookahead(Values)
		switch tok {
		case IdentifierToken, DoesNotExistToken:
			r, err := p.parseRequirement()
			if err != nil {
				return nil, fmt.Errorf("unable to parse requirement: %v", err)
			}
			requirements = append(requirements, *r)
			t, l := p.consume(Values)
      // ... 省略部份代码
     }
  }
}

这里通过parseRequirement()方法构造Requirement数组,具体思路是,先取Key,再取Op,再取Value,利用Parser.Position ++来当作游标。

每一次从scanItem[]中取3个scanItem。

func (p *Parser) parseRequirement() (*Requirement, error) {
  
 	// 先取key
	key, operator, err := p.parseKeyAndInferOperator()
	if err != nil {
		return nil, err
	}
  // ...
  //  再取op运算符
	operator, err = p.parseOperator()
	if err != nil {
		return nil, err
	}
  // 最后取value
	var values sets.String
	switch operator {
	case selection.In, selection.NotIn:
		values, err = p.parseValues()
	case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.GreaterThan, selection.LessThan:
		values, err = p.parseExactValue()
	}
	if err != nil {
		return nil, err
	}
	return NewRequirement(key, operator, values.List(), field.WithPath(p.path))

}

最终得到一个Requirement数组,即Selector。

总结

大体思路是,将输入的"x=y,a=b"依次遍历,遇到特殊符号就停一下,将已经遍历的Byte打上标签并储存成scanItem[],之后再依次遍历scanItem[],构造成Requirement[],即Selector,方便之后匹配。存算分离,先存,再算,计算时根据不同的运算符,构造不同的Requirement。

至此K8s中LabelSelector已经分析完毕,总体的代码设计可以概括为存算分离,分而治之最后得到结果。

这个系列的帖子