K8s源码2-LabelSelector-Parse

标签
类型
技术
创建时间
Jan 14, 2023 20:13

使用方法

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