Skip to content

Commit

Permalink
operators: Add inequality operator
Browse files Browse the repository at this point in the history
Signed-off-by: Enrique Llorente <[email protected]>
  • Loading branch information
qinqon committed Jul 12, 2023
1 parent d619f00 commit a6fb4df
Show file tree
Hide file tree
Showing 14 changed files with 265 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ linters-settings:
- whyNoLint
- wrapperFunc
gocyclo:
min-complexity: 17
min-complexity: 30
goheader:
values:
regexp:
Expand Down
4 changes: 4 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ It uses the `description` field to filter between primary and secondary NIC.

{% include_relative examples/example.md example="all-ethernet-up" %}

## Turn LLDP to true at non ethernet interfaces

{% include_relative examples/example.md example="non-ethernet-lldp-enabled" %}

## Create a linux-bridge with all the interfaces matching description

{% include_relative examples/example.md example="bridge-interfaces-by-description" %}
4 changes: 4 additions & 0 deletions nmpolicy/internal/ast/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Terminal struct {
type Node struct {
Meta
EqFilter *TernaryOperator `json:"eqfilter,omitempty"`
NeFilter *TernaryOperator `json:"nefilter,omitempty"`
Replace *TernaryOperator `json:"replace,omitempty"`
Path *VariadicOperator `json:"path,omitempty"`
Terminal
Expand All @@ -43,6 +44,9 @@ func (n Node) String() string {
if n.EqFilter != nil {
return fmt.Sprintf("EqFilter(%s)", *n.EqFilter)
}
if n.NeFilter != nil {
return fmt.Sprintf("NeFilter(%s)", *n.NeFilter)
}
if n.Replace != nil {
return fmt.Sprintf("Replace(%s)", *n.Replace)
}
Expand Down
2 changes: 2 additions & 0 deletions nmpolicy/internal/lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ func (l *lexer) lexCurrentRune() (*Token, error) {
return l.lexEqualAs(REPLACE)
} else if l.isEqual() {
return l.lexEqualAs(EQFILTER)
} else if l.isExclamationMark() {
return l.lexEqualAs(NEFILTER)
} else if l.isPlus() {
return &Token{l.scn.Position(), MERGE, string(l.scn.Rune())}, nil
} else if l.isPipe() {
Expand Down
6 changes: 4 additions & 2 deletions nmpolicy/internal/lexer/lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestLexer(t *testing.T) {
testLinuxBridgeAtDefaultGwScenario(t)
}

//nolint:dupl
func testBasicExpressions(t *testing.T) {
t.Run("basic expressions", func(t *testing.T) {
runTest(t, []test{
Expand Down Expand Up @@ -97,7 +98,7 @@ func testBasicExpressions(t *testing.T) {
{47, lexer.IDENTITY, "doo3"},
{50, lexer.EOF, ""}},
}},
{" . foo1.dar1:=foo2 . dar2 ... moo3+boo3|doo3 == := :=", expected{tokens: []lexer.Token{
{" . foo1.dar1:=foo2 . dar2 ... moo3+boo3|doo3 == := := !=", expected{tokens: []lexer.Token{
{1, lexer.DOT, "."},
{3, lexer.IDENTITY, "foo1"},
{7, lexer.DOT, "."},
Expand All @@ -117,7 +118,8 @@ func testBasicExpressions(t *testing.T) {
{45, lexer.EQFILTER, "=="},
{48, lexer.REPLACE, ":="},
{51, lexer.REPLACE, ":="},
{52, lexer.EOF, ""}},
{54, lexer.NEFILTER, "!="},
{55, lexer.EOF, ""}},
}},
{"foo1.3|foo2", expected{tokens: []lexer.Token{
{0, lexer.IDENTITY, "foo1"},
Expand Down
6 changes: 5 additions & 1 deletion nmpolicy/internal/lexer/rune.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (l *lexer) isPipe() bool {
return l.scn.Rune() == '|'
}

func (l *lexer) isExclamationMark() bool {
return l.scn.Rune() == '!'
}

func (l *lexer) isDelimiter() bool {
return l.isEOF() || l.isSpace() || l.isDot() || l.isEqual() || l.isColon() || l.isPlus() || l.isPipe()
return l.isEOF() || l.isSpace() || l.isDot() || l.isEqual() || l.isColon() || l.isPlus() || l.isPipe() || l.isExclamationMark()
}
2 changes: 2 additions & 0 deletions nmpolicy/internal/lexer/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
PIPE // |
REPLACE // :=
EQFILTER // ==
NEFILTER // !=
MERGE // +
operatorsEnd
)
Expand All @@ -47,6 +48,7 @@ var tokens = []string{

REPLACE: "REPLACE",
EQFILTER: "EQFILTER",
NEFILTER: "NEFILTER",
MERGE: "MERGE",
}

Expand Down
7 changes: 7 additions & 0 deletions nmpolicy/internal/parser/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ func wrapWithInvalidEqualityFilterError(err error) *parserError {
}
}

func wrapWithInvalidInequalityFilterError(err error) *parserError {
return &parserError{
prefix: "invalid inequality filter",
inner: err,
}
}

func wrapWithInvalidReplaceError(err error) *parserError {
return &parserError{
prefix: "invalid replace",
Expand Down
16 changes: 16 additions & 0 deletions nmpolicy/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ func (p *parser) parse() (ast.Node, error) {
if err := p.parseEqFilter(); err != nil {
return ast.Node{}, err
}
} else if p.currentToken().Type == lexer.NEFILTER {
if err := p.parseNeFilter(); err != nil {
return ast.Node{}, err
}
} else if p.currentToken().Type == lexer.REPLACE {
if err := p.parseReplace(); err != nil {
return ast.Node{}, err
Expand Down Expand Up @@ -227,6 +231,18 @@ func (p *parser) parseEqFilter() error {
return nil
}

func (p *parser) parseNeFilter() error {
operator := &ast.Node{
Meta: ast.Meta{Position: p.currentToken().Position},
NeFilter: &ast.TernaryOperator{},
}
if err := p.fillInTernaryOperator(operator.NeFilter); err != nil {
return wrapWithInvalidInequalityFilterError(err)
}
p.lastNode = operator
return nil
}

func (p *parser) parseReplace() error {
operator := &ast.Node{
Meta: ast.Meta{Position: p.currentToken().Position},
Expand Down
136 changes: 136 additions & 0 deletions nmpolicy/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ import (
func TestParser(t *testing.T) {
testParsePath(t)
testParseEqFilter(t)
testParseNeFilter(t)
testParseReplace(t)
testParseReplaceWithPath(t)
testParseCapturePipeReplace(t)

testParseBasicFailures(t)
testParsePathFailures(t)
testParseEqFilterFailure(t)
testParseNeFilterFailure(t)
testParseReplaceFailure(t)

testParserReuse(t)
Expand Down Expand Up @@ -188,6 +190,59 @@ func testParseEqFilterFailure(t *testing.T) {
runTest(t, tests)
}

func testParseNeFilterFailure(t *testing.T) {
var tests = []test{
expectError(`invalid inequality filter: missing left hand argument
| !=0.0.0.0/0
| ^`,
fromTokens(
nefilter(),
str("0.0.0.0/0"),
eof(),
),
),
expectError(`invalid inequality filter: left hand argument is not a path
| foo!=0.0.0.0/0
| ...^`,
fromTokens(
str("foo"),
nefilter(),
str("0.0.0.0/0"),
eof(),
),
),
expectError(`invalid inequality filter: missing right hand argument
| routes.running.destination!=
| ...........................^`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("destination"),
nefilter(),
eof(),
),
),

expectError(`invalid inequality filter: right hand argument is not a string or identity
| routes.running.destination!=!=
| ............................^`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("destination"),
nefilter(),
nefilter(),
eof(),
),
),
}
runTest(t, tests)
}

func testParseReplaceFailure(t *testing.T) {
var tests = []test{
expectError(`invalid replace: missing left hand argument
Expand Down Expand Up @@ -344,6 +399,83 @@ eqfilter:
runTest(t, tests)
}

func testParseNeFilter(t *testing.T) {
var tests = []test{
expectAST(t, `
pos: 26
nefilter:
- pos: 0
identity: currentState
- pos: 0
path:
- pos: 0
identity: routes
- pos: 7
identity: running
- pos: 15
identity: destination
- pos: 28
string: 0.0.0.0/0`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("destination"),
nefilter(),
str("0.0.0.0/0"),
eof(),
),
),
expectAST(t, `
pos: 33
nefilter:
- pos: 0
identity: currentState
- pos: 0
path:
- pos: 0
identity: routes
- pos: 7
identity: running
- pos: 15
identity: next-hop-interface
- pos: 35
path:
- pos: 35
identity: capture
- pos: 43
identity: default-gw
- pos: 54
identity: routes
- pos: 61
number: 0
- pos: 63
identity: next-hop-interface
`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("next-hop-interface"),
nefilter(),
identity("capture"),
dot(),
identity("default-gw"),
dot(),
identity("routes"),
dot(),
number(0),
dot(),
identity("next-hop-interface"),
eof(),
),
),
}
runTest(t, tests)
}

func testParseReplace(t *testing.T) {
var tests = []test{
expectAST(t, `
Expand Down Expand Up @@ -641,6 +773,10 @@ func eqfilter() lexer.Token {
return lexer.Token{Type: lexer.EQFILTER, Literal: "=="}
}

func nefilter() lexer.Token {
return lexer.Token{Type: lexer.NEFILTER, Literal: "!="}
}

func replace() lexer.Token {
return lexer.Token{Type: lexer.REPLACE, Literal: ":="}
}
Expand Down
4 changes: 4 additions & 0 deletions nmpolicy/internal/resolver/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ func wrapWithEqFilterError(err error) error {
return fmt.Errorf("eqfilter error: %w", err)
}

func wrapWithNeFilterError(err error) error {
return fmt.Errorf("nefilter error: %w", err)
}

func replaceError(format string, a ...interface{}) error {
return wrapWithReplaceError(fmt.Errorf(format, a...))
}
Expand Down
31 changes: 27 additions & 4 deletions nmpolicy/internal/resolver/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ import (
"github.com/nmstate/nmpolicy/nmpolicy/internal/ast"
)

func filter(inputState map[string]interface{}, pathSteps ast.VariadicOperator, expectedValue interface{}) (map[string]interface{}, error) {
filtered, err := visitState(newPath(pathSteps), inputState, &filterVisitor{expectedValue: expectedValue})
func filter(
inputState map[string]interface{},
pathSteps ast.VariadicOperator,
operator func(interface{}, interface{}) bool,
expectedValue interface{}) (map[string]interface{}, error) {
filtered, err := visitState(newPath(pathSteps), inputState, &filterVisitor{
operator: operator,
expectedValue: expectedValue,
})

if err != nil {
return nil, fmt.Errorf("failed applying operation on the path: %w", err)
Expand All @@ -40,9 +47,22 @@ func filter(inputState map[string]interface{}, pathSteps ast.VariadicOperator, e
}
return filteredMap, nil
}
func eqfilter(
inputState map[string]interface{},
pathSteps ast.VariadicOperator,
expectedValue interface{}) (map[string]interface{}, error) {
return filter(inputState, pathSteps, func(lhs, rhs interface{}) bool { return lhs == rhs }, expectedValue)
}
func nefilter(
inputState map[string]interface{},
pathSteps ast.VariadicOperator,
expectedValue interface{}) (map[string]interface{}, error) {
return filter(inputState, pathSteps, func(lhs, rhs interface{}) bool { return lhs != rhs }, expectedValue)
}

type filterVisitor struct {
mergeVisitResult bool
operator func(interface{}, interface{}) bool
expectedValue interface{}
}

Expand All @@ -61,7 +81,7 @@ func (e filterVisitor) visitLastMap(p path, mapToFilter map[string]interface{})
return nil, pathError(p.currentStep, `type missmatch: the value in the path doesn't match the value to filter. `+
`"%T" != "%T" -> %+v != %+v`, obtainedValue, e.expectedValue, obtainedValue, e.expectedValue)
}
if obtainedValue == e.expectedValue {
if e.operator(obtainedValue, e.expectedValue) {
return mapToFilter, nil
}
return nil, nil
Expand Down Expand Up @@ -109,7 +129,10 @@ func (e filterVisitor) visitSlice(p path, sliceToVisit []interface{}) (interface
for _, interfaceToVisit := range sliceToVisit {
// Filter only the first slice by forcing "mergeVisitResult" to true
// for the the following ones.
visitResult, err := visitState(p, interfaceToVisit, &filterVisitor{mergeVisitResult: true, expectedValue: e.expectedValue})
visitResult, err := visitState(p, interfaceToVisit, &filterVisitor{
mergeVisitResult: true,
operator: e.operator,
expectedValue: e.expectedValue})
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit a6fb4df

Please sign in to comment.