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},
]
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已经分析完毕,总体的代码设计可以概括为存算分离,分而治之最后得到结果。