Skip to content

Commit

Permalink
parser, replace: Support path as replace value
Browse files Browse the repository at this point in the history
One of the feature is replacing with the captured state from a capture
entry path. This change add support for it at the parser and create a
unit test for the resolver since resolver has the feature already.

Signed-off-by: Quique Llorente <[email protected]>
  • Loading branch information
qinqon committed Jan 3, 2022
1 parent ae49c34 commit 4787e16
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 41 deletions.
8 changes: 4 additions & 4 deletions nmpolicy/internal/parser/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,17 @@ func invalidPathError(msg string) *parserError {
}
}

func invalidEqualityFilterError(msg string) *parserError {
func wrapWithInvalidEqualityFilterError(err error) *parserError {
return &parserError{
prefix: "invalid equality filter",
msg: msg,
inner: err,
}
}

func invalidReplaceError(msg string) *parserError {
func wrapWithInvalidReplaceError(err error) *parserError {
return &parserError{
prefix: "invalid replace",
msg: msg,
inner: err,
}
}

Expand Down
59 changes: 23 additions & 36 deletions nmpolicy/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,34 +200,8 @@ func (p *parser) parseEqFilter() error {
Meta: ast.Meta{Position: p.currentToken().Position},
EqFilter: &ast.TernaryOperator{},
}
if p.lastNode == nil {
return invalidEqualityFilterError("missing left hand argument")
}
if p.lastNode.Path == nil {
return invalidEqualityFilterError("left hand argument is not a path")
}

p.fillInPipedInOrCurrentState(&operator.EqFilter[0])

operator.EqFilter[1] = *p.lastNode

p.nextToken()

if p.currentToken().Type == lexer.STRING {
if err := p.parseString(); err != nil {
return err
}
operator.EqFilter[2] = *p.lastNode
} else if p.currentToken().Type == lexer.IDENTITY {
err := p.parsePath()
if err != nil {
return err
}
operator.EqFilter[2] = *p.lastNode
} else if p.currentToken().Type == lexer.EOF {
return invalidEqualityFilterError("missing right hand argument")
} else {
return invalidEqualityFilterError("right hand argument is not a string or identity")
if err := p.fillInTernaryOperator(operator.EqFilter); err != nil {
return wrapWithInvalidEqualityFilterError(err)
}
p.lastNode = operator
return nil
Expand All @@ -238,29 +212,42 @@ func (p *parser) parseReplace() error {
Meta: ast.Meta{Position: p.currentToken().Position},
Replace: &ast.TernaryOperator{},
}
if err := p.fillInTernaryOperator(operator.Replace); err != nil {
return wrapWithInvalidReplaceError(err)
}
p.lastNode = operator
return nil
}

func (p *parser) fillInTernaryOperator(operator *ast.TernaryOperator) error {
if p.lastNode == nil {
return invalidReplaceError("missing left hand argument")
return fmt.Errorf("missing left hand argument")
}
if p.lastNode.Path == nil {
return invalidReplaceError("left hand argument is not a path")
return fmt.Errorf("left hand argument is not a path")
}

p.fillInPipedInOrCurrentState(&operator.Replace[0])
p.fillInPipedInOrCurrentState(&operator[0])

operator.Replace[1] = *p.lastNode
operator[1] = *p.lastNode

p.nextToken()
if p.currentToken().Type == lexer.STRING {
if err := p.parseString(); err != nil {
return err
}
operator.Replace[2] = *p.lastNode
operator[2] = *p.lastNode
} else if p.currentToken().Type == lexer.IDENTITY {
err := p.parsePath()
if err != nil {
return err
}
operator[2] = *p.lastNode
} else if p.currentToken().Type == lexer.EOF {
return invalidReplaceError("missing right hand argument")
return fmt.Errorf("missing right hand argument")
} else {
return invalidReplaceError("right hand argument is not a string")
return fmt.Errorf("right hand argument is not a string or identity")
}
p.lastNode = operator
return nil
}

Expand Down
54 changes: 53 additions & 1 deletion nmpolicy/internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func TestParser(t *testing.T) {
testParsePath(t)
testParseEqFilter(t)
testParseReplace(t)
testParseReplaceWithPath(t)
testParseCapturePipeReplace(t)

testParseBasicFailures(t)
Expand Down Expand Up @@ -222,7 +223,7 @@ func testParseReplaceFailure(t *testing.T) {
),
),

expectError(`invalid replace: right hand argument is not a string
expectError(`invalid replace: right hand argument is not a string or identity
| routes.running.destination:=:=
| ............................^`,
fromTokens(
Expand Down Expand Up @@ -376,6 +377,57 @@ replace:
runTest(t, tests)
}

func testParseReplaceWithPath(t *testing.T) {
var tests = []test{
expectAST(t, `
pos: 33
replace:
- 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: primary-nic
- pos: 55
identity: interfaces
- pos: 66
number: 0
- pos: 68
identity: name
`,
fromTokens(
identity("routes"),
dot(),
identity("running"),
dot(),
identity("next-hop-interface"),
replace(),
identity("capture"),
dot(),
identity("primary-nic"),
dot(),
identity("interfaces"),
dot(),
number(0),
dot(),
identity("name"),
eof(),
),
),
}
runTest(t, tests)
}

func testParseCapturePipeReplace(t *testing.T) {
var tests = []test{
expectAST(t, `
Expand Down
96 changes: 96 additions & 0 deletions nmpolicy/internal/resolver/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,10 @@ func TestFilter(t *testing.T) {
testFilterInvalidTypeOnPath(t)
testFilterOptionalField(t)
testFilterNonCaptureRefPathAtThirdArg(t)

testReplaceCurrentState(t)
testReplaceCapturedState(t)
testReplaceWithCaptureRef(t)
})
}

Expand Down Expand Up @@ -727,3 +729,97 @@ bridge-routes:
runTest(t, &testToRun)
})
}

func testReplaceWithCaptureRef(t *testing.T) {
t.Run("Replace list of structs field from capture reference with capture reference value", func(t *testing.T) {
testToRun := test{
capturedStatesCache: `
default-gw:
state:
routes:
running:
- destination: 0.0.0.0/0
next-hop-address: 192.168.100.1
next-hop-interface: br1
table-id: 254
br1-bridge:
state:
interfaces:
- name: br1
type: linux-bridge
bridge:
port:
- name: eth3
`,

captureASTPool: `
default-gw-br1-first-port:
pos: 1
replace:
- pos: 2
path:
- pos: 3
identity: capture
- pos: 4
identity: default-gw
- pos: 3
path:
- pos: 4
identity: routes
- pos: 5
identity: running
- pos: 6
identity: next-hop-interface
- pos: 7
path:
- pos: 8
identity: capture
- pos: 9
identity: br1-bridge
- pos: 10
identity: interfaces
- pos: 11
number: 0
- pos: 12
identity: bridge
- pos: 13
identity: port
- pos: 14
number: 0
- pos: 15
identity: name
`,

expectedCapturedStates: `
default-gw:
state:
routes:
running:
- destination: 0.0.0.0/0
next-hop-address: 192.168.100.1
next-hop-interface: br1
table-id: 254
br1-bridge:
state:
interfaces:
- name: br1
type: linux-bridge
bridge:
port:
- name: eth3
default-gw-br1-first-port:
state:
routes:
running:
- destination: 0.0.0.0/0
next-hop-address: 192.168.100.1
next-hop-interface: eth3
table-id: 254
`,
}
runTest(t, &testToRun)
})
}

0 comments on commit 4787e16

Please sign in to comment.