mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-14 11:57:51 +00:00
apis/nfd: split rule processing into a separate package
This patch tidies up the nfdv1alpha1 API package by refactoring out the implementation of (NodeFeature)Rule evaluation into a separate package.
This commit is contained in:
parent
e162540e54
commit
97bf841140
8 changed files with 687 additions and 651 deletions
|
@ -1,457 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
api "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
type BoolAssertionFunc func(assert.TestingT, bool, ...interface{}) bool
|
||||
|
||||
type ValueAssertionFunc func(assert.TestingT, interface{}, ...interface{}) bool
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
type V = api.MatchValue
|
||||
type TC struct {
|
||||
name string
|
||||
op api.MatchOp
|
||||
values V
|
||||
input interface{}
|
||||
valid bool
|
||||
result BoolAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "MatchAny-1", op: api.MatchAny, result: assert.True},
|
||||
{name: "MatchAny-2", op: api.MatchAny, input: "2", valid: false, result: assert.True},
|
||||
|
||||
{name: "MatchIn-1", op: api.MatchIn, values: V{"1"}, input: "2", valid: false, result: assert.False},
|
||||
{name: "MatchIn-2", op: api.MatchIn, values: V{"1"}, input: "2", valid: true, result: assert.False},
|
||||
{name: "MatchIn-3", op: api.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.False},
|
||||
{name: "MatchIn-4", op: api.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchNotIn-1", op: api.MatchNotIn, values: V{"2"}, input: 2, valid: false, result: assert.False},
|
||||
{name: "MatchNotIn-2", op: api.MatchNotIn, values: V{"1"}, input: 2, valid: true, result: assert.True},
|
||||
{name: "MatchNotIn-3", op: api.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.False},
|
||||
{name: "MatchNotIn-4", op: api.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.False},
|
||||
|
||||
{name: "MatchInRegexp-1", op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: false, result: assert.False},
|
||||
{name: "MatchInRegexp-2", op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: true, result: assert.True},
|
||||
{name: "MatchInRegexp-3", op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-12", valid: true, result: assert.False},
|
||||
{name: "MatchInRegexp-4", op: api.MatchInRegexp, values: V{"val-[0-9]$", "al-[1-9]"}, input: "val-12", valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchExists-1", op: api.MatchExists, input: nil, valid: false, result: assert.False},
|
||||
{name: "MatchExists-2", op: api.MatchExists, input: nil, valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchDoesNotExist-1", op: api.MatchDoesNotExist, input: false, valid: false, result: assert.True},
|
||||
{name: "MatchDoesNotExist-2", op: api.MatchDoesNotExist, input: false, valid: true, result: assert.False},
|
||||
|
||||
{name: "MatchGt-1", op: api.MatchGt, values: V{"2"}, input: 3, valid: false, result: assert.False},
|
||||
{name: "MatchGt-2", op: api.MatchGt, values: V{"2"}, input: 2, valid: true, result: assert.False},
|
||||
{name: "MatchGt-3", op: api.MatchGt, values: V{"2"}, input: 3, valid: true, result: assert.True},
|
||||
{name: "MatchGt-4", op: api.MatchGt, values: V{"-10"}, input: -3, valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchLt-1", op: api.MatchLt, values: V{"2"}, input: "1", valid: false, result: assert.False},
|
||||
{name: "MatchLt-2", op: api.MatchLt, values: V{"2"}, input: "2", valid: true, result: assert.False},
|
||||
{name: "MatchLt-3", op: api.MatchLt, values: V{"-10"}, input: -3, valid: true, result: assert.False},
|
||||
{name: "MatchLt-4", op: api.MatchLt, values: V{"2"}, input: "1", valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchGtLt-1", op: api.MatchGtLt, values: V{"1", "10"}, input: "1", valid: false, result: assert.False},
|
||||
{name: "MatchGtLt-2", op: api.MatchGtLt, values: V{"1", "10"}, input: "1", valid: true, result: assert.False},
|
||||
{name: "MatchGtLt-3", op: api.MatchGtLt, values: V{"1", "10"}, input: "10", valid: true, result: assert.False},
|
||||
{name: "MatchGtLt-4", op: api.MatchGtLt, values: V{"1", "10"}, input: "2", valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchIsTrue-1", op: api.MatchIsTrue, input: true, valid: false, result: assert.False},
|
||||
{name: "MatchIsTrue-2", op: api.MatchIsTrue, input: true, valid: true, result: assert.True},
|
||||
{name: "MatchIsTrue-3", op: api.MatchIsTrue, input: false, valid: true, result: assert.False},
|
||||
|
||||
{name: "MatchIsFalse-1", op: api.MatchIsFalse, input: "false", valid: false, result: assert.False},
|
||||
{name: "MatchIsFalse-2", op: api.MatchIsFalse, input: "false", valid: true, result: assert.True},
|
||||
{name: "MatchIsFalse-3", op: api.MatchIsFalse, input: "true", valid: true, result: assert.False},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
me := api.MatchExpression{Op: tc.op, Value: tc.values}
|
||||
res, err := me.Match(tc.valid, tc.input)
|
||||
tc.result(t, res)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// Error cases
|
||||
tcs = []TC{
|
||||
{name: "MatchAny-err-1", op: api.MatchAny, values: V{"1"}, input: "val"},
|
||||
|
||||
{name: "MatchIn-err-1", op: api.MatchIn, input: "val"},
|
||||
|
||||
{name: "MatchNotIn-err-1", op: api.MatchNotIn, input: "val"},
|
||||
|
||||
{name: "MatchInRegexp-err-1", op: api.MatchInRegexp, input: "val"},
|
||||
{name: "MatchInRegexp-err-2", op: api.MatchInRegexp, values: V{"("}, input: "val"},
|
||||
|
||||
{name: "MatchExists-err-1", op: api.MatchExists, values: V{"1"}},
|
||||
|
||||
{name: "MatchDoesNotExist-err-1", op: api.MatchDoesNotExist, values: V{"1"}},
|
||||
|
||||
{name: "MatchGt-err-1", op: api.MatchGt, input: "1"},
|
||||
{name: "MatchGt-err-2", op: api.MatchGt, values: V{"1", "2"}, input: "1"},
|
||||
{name: "MatchGt-err-3", op: api.MatchGt, values: V{""}, input: "1"},
|
||||
{name: "MatchGt-err-4", op: api.MatchGt, values: V{"2"}, input: "3a"},
|
||||
|
||||
{name: "MatchLt-err-1", op: api.MatchLt, input: "1"},
|
||||
{name: "MatchLt-err-2", op: api.MatchLt, values: V{"1", "2", "3"}, input: "1"},
|
||||
{name: "MatchLt-err-3", op: api.MatchLt, values: V{"a"}, input: "1"},
|
||||
{name: "MatchLt-err-4", op: api.MatchLt, values: V{"2"}, input: "1.0"},
|
||||
|
||||
{name: "MatchGtLt-err-1", op: api.MatchGtLt, input: "1"},
|
||||
{name: "MatchGtLt-err-2", op: api.MatchGtLt, values: V{"1"}, input: "1"},
|
||||
{name: "MatchGtLt-err-3", op: api.MatchGtLt, values: V{"2", "1"}, input: "1"},
|
||||
{name: "MatchGtLt-err-4", op: api.MatchGtLt, values: V{"1", "2", "3"}, input: "1"},
|
||||
{name: "MatchGtLt-err-5", op: api.MatchGtLt, values: V{"a", "2"}, input: "1"},
|
||||
{name: "MatchGtLt-err-6", op: api.MatchGtLt, values: V{"1", "10"}, input: "1.0"},
|
||||
|
||||
{name: "MatchIsTrue-err-1", op: api.MatchIsTrue, values: V{"1"}, input: "true"},
|
||||
|
||||
{name: "MatchIsFalse-err-1", op: api.MatchIsFalse, values: V{"1", "2"}, input: "false"},
|
||||
|
||||
{name: "invalid-op-err", op: "non-existent-op", values: V{"1"}, input: 1},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
me := api.MatchExpression{Op: tc.op, Value: tc.values}
|
||||
res, err := me.Match(true, tc.input)
|
||||
assert.False(t, res)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchKeys(t *testing.T) {
|
||||
type V = api.MatchValue
|
||||
type I = map[string]api.Nil
|
||||
type TC struct {
|
||||
name string
|
||||
key string
|
||||
op api.MatchOp
|
||||
values V
|
||||
input I
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "1", op: api.MatchAny, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "2", op: api.MatchExists, key: "foo", input: nil, result: assert.False, err: assert.Nil},
|
||||
{name: "3", op: api.MatchExists, key: "foo", input: I{"bar": {}}, result: assert.False, err: assert.Nil},
|
||||
{name: "4", op: api.MatchExists, key: "foo", input: I{"bar": {}, "foo": {}}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "5", op: api.MatchDoesNotExist, key: "foo", input: nil, result: assert.True, err: assert.Nil},
|
||||
{name: "6", op: api.MatchDoesNotExist, key: "foo", input: I{}, result: assert.True, err: assert.Nil},
|
||||
{name: "7", op: api.MatchDoesNotExist, key: "foo", input: I{"bar": {}}, result: assert.True, err: assert.Nil},
|
||||
{name: "8", op: api.MatchDoesNotExist, key: "foo", input: I{"bar": {}, "foo": {}}, result: assert.False, err: assert.Nil},
|
||||
|
||||
// All other ops should return an error
|
||||
{name: "9", op: api.MatchIn, values: V{"foo"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "10", op: api.MatchNotIn, values: V{"foo"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "11", op: api.MatchInRegexp, values: V{"foo"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "12", op: api.MatchGt, values: V{"1"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "13", op: api.MatchLt, values: V{"1"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "14", op: api.MatchGtLt, values: V{"1", "10"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "15", op: api.MatchIsTrue, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "16", op: api.MatchIsFalse, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
me := api.MatchExpression{Op: tc.op, Value: tc.values}
|
||||
res, err := me.MatchKeys(tc.key, tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchValues(t *testing.T) {
|
||||
type V = []string
|
||||
type I = map[string]string
|
||||
|
||||
type TC struct {
|
||||
name string
|
||||
op api.MatchOp
|
||||
values V
|
||||
key string
|
||||
input I
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "1", op: api.MatchAny, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "2", op: api.MatchIn, values: V{"1", "2"}, key: "foo", input: I{"bar": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "3", op: api.MatchIn, values: V{"1", "2"}, key: "foo", input: I{"foo": "3"}, result: assert.False, err: assert.Nil},
|
||||
{name: "4", op: api.MatchIn, values: V{"1", "2"}, key: "foo", input: I{"foo": "2"}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "5", op: api.MatchNotIn, values: V{"1", "2"}, key: "foo", input: I{"bar": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "6", op: api.MatchNotIn, values: V{"1", "2"}, key: "foo", input: I{"foo": "3"}, result: assert.True, err: assert.Nil},
|
||||
{name: "7", op: api.MatchNotIn, values: V{"1", "2"}, key: "foo", input: I{"foo": "2"}, result: assert.False, err: assert.Nil},
|
||||
|
||||
{name: "8", op: api.MatchInRegexp, values: V{"1", "2"}, key: "foo", input: I{"bar": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "9", op: api.MatchInRegexp, values: V{"1", "[0-8]"}, key: "foo", input: I{"foo": "9"}, result: assert.False, err: assert.Nil},
|
||||
{name: "10", op: api.MatchInRegexp, values: V{"1", "[0-8]"}, key: "foo", input: I{"foo": "2"}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "11", op: api.MatchExists, key: "foo", input: I{"bar": "1"}, result: assert.False, err: assert.Nil},
|
||||
{name: "12", op: api.MatchExists, key: "foo", input: I{"foo": "1"}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "13", op: api.MatchDoesNotExist, key: "foo", input: nil, result: assert.True, err: assert.Nil},
|
||||
{name: "14", op: api.MatchDoesNotExist, key: "foo", input: I{"foo": "1"}, result: assert.False, err: assert.Nil},
|
||||
|
||||
{name: "15", op: api.MatchGt, values: V{"2"}, key: "foo", input: I{"bar": "3"}, result: assert.False, err: assert.Nil},
|
||||
{name: "16", op: api.MatchGt, values: V{"2"}, key: "foo", input: I{"bar": "3", "foo": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "17", op: api.MatchGt, values: V{"2"}, key: "foo", input: I{"bar": "3", "foo": "3"}, result: assert.True, err: assert.Nil},
|
||||
{name: "18", op: api.MatchGt, values: V{"2"}, key: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.False, err: assert.NotNil},
|
||||
|
||||
{name: "19", op: api.MatchLt, values: V{"2"}, key: "foo", input: I{"bar": "1"}, result: assert.False, err: assert.Nil},
|
||||
{name: "20", op: api.MatchLt, values: V{"2"}, key: "foo", input: I{"bar": "1", "foo": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "21", op: api.MatchLt, values: V{"2"}, key: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.True, err: assert.Nil},
|
||||
{name: "22", op: api.MatchLt, values: V{"2"}, key: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.False, err: assert.NotNil},
|
||||
|
||||
{name: "23", op: api.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "1"}, result: assert.False, err: assert.Nil},
|
||||
{name: "24", op: api.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "1", "foo": "11"}, result: assert.False, err: assert.Nil},
|
||||
{name: "25", op: api.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "1", "foo": "-11"}, result: assert.False, err: assert.Nil},
|
||||
{name: "26", op: api.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.True, err: assert.Nil},
|
||||
{name: "27", op: api.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.False, err: assert.NotNil},
|
||||
|
||||
{name: "28", op: api.MatchIsTrue, key: "foo", result: assert.False, err: assert.Nil},
|
||||
{name: "29", op: api.MatchIsTrue, key: "foo", input: I{"foo": "1"}, result: assert.False, err: assert.Nil},
|
||||
{name: "30", op: api.MatchIsTrue, key: "foo", input: I{"foo": "true"}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "31", op: api.MatchIsFalse, key: "foo", input: I{"foo": "true"}, result: assert.False, err: assert.Nil},
|
||||
{name: "32", op: api.MatchIsFalse, key: "foo", input: I{"foo": "false"}, result: assert.True, err: assert.Nil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
me := api.MatchExpression{Op: tc.op, Value: tc.values}
|
||||
res, err := me.MatchValues(tc.key, tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMESMatchKeys(t *testing.T) {
|
||||
type I = map[string]api.Nil
|
||||
type O = []api.MatchedElement
|
||||
type TC struct {
|
||||
name string
|
||||
mes string
|
||||
input I
|
||||
output O
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{input: I{}, output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{input: I{"foo": {}}, output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{mes: `
|
||||
foo: { op: DoesNotExist }
|
||||
bar: { op: Exists }
|
||||
`,
|
||||
input: I{"bar": {}, "baz": {}, "buzz": {}},
|
||||
output: O{{"Name": "bar"}, {"Name": "foo"}},
|
||||
result: assert.True, err: assert.Nil},
|
||||
|
||||
{mes: `
|
||||
foo: { op: DoesNotExist }
|
||||
bar: { op: Exists }
|
||||
`,
|
||||
input: I{"foo": {}, "bar": {}, "baz": {}},
|
||||
output: nil,
|
||||
result: assert.False, err: assert.Nil},
|
||||
|
||||
{mes: `
|
||||
foo: { op: In, value: ["bar"] }
|
||||
bar: { op: Exists }
|
||||
`,
|
||||
input: I{"bar": {}, "baz": {}},
|
||||
output: nil,
|
||||
result: assert.False, err: assert.NotNil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mes := &api.MatchExpressionSet{}
|
||||
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
|
||||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
res, out, err := mes.MatchGetKeys(tc.input)
|
||||
tc.result(t, res)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err = mes.MatchKeys(tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMESMatchValues(t *testing.T) {
|
||||
type I = map[string]string
|
||||
type O = []api.MatchedElement
|
||||
type TC struct {
|
||||
name string
|
||||
mes string
|
||||
input I
|
||||
output O
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "1", output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "2", input: I{}, output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "3", input: I{"foo": "bar"}, output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "4",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: In, value: ["val", "wal"] }
|
||||
baz: { op: Gt, value: ["10"] }
|
||||
`,
|
||||
input: I{"bar": "val"},
|
||||
result: assert.False, err: assert.Nil},
|
||||
|
||||
{name: "5",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: In, value: ["val", "wal"] }
|
||||
baz: { op: Gt, value: ["10"] }
|
||||
`,
|
||||
input: I{"foo": "1", "bar": "val", "baz": "123", "buzz": "light"},
|
||||
output: O{{"Name": "bar", "Value": "val"}, {"Name": "baz", "Value": "123"}, {"Name": "foo", "Value": "1"}},
|
||||
result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "5",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: In, value: ["val"] }
|
||||
baz: { op: Gt, value: ["10"] }
|
||||
`,
|
||||
input: I{"foo": "1", "bar": "val", "baz": "123.0"},
|
||||
result: assert.False, err: assert.NotNil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mes := &api.MatchExpressionSet{}
|
||||
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
|
||||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
res, out, err := mes.MatchGetValues(tc.input)
|
||||
tc.result(t, res)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err = mes.MatchValues(tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMESMatchInstances(t *testing.T) {
|
||||
type I = api.InstanceFeature
|
||||
type O = []api.MatchedElement
|
||||
type A = map[string]string
|
||||
type TC struct {
|
||||
name string
|
||||
mes string
|
||||
input []I
|
||||
output O
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "1", output: O{}, result: assert.False, err: assert.Nil}, // nil instances -> false
|
||||
|
||||
{name: "2", input: []I{}, output: O{}, result: assert.False, err: assert.Nil}, // zero instances -> false
|
||||
|
||||
{name: "3", input: []I{I{Attributes: A{}}}, output: O{A{}}, result: assert.True, err: assert.Nil}, // one "empty" instance
|
||||
|
||||
{name: "4",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: Lt, value: ["10"] }
|
||||
`,
|
||||
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "1"}}},
|
||||
output: O{},
|
||||
result: assert.False, err: assert.Nil},
|
||||
|
||||
{name: "5",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: Lt, value: ["10"] }
|
||||
`,
|
||||
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"foo": "2", "bar": "1"}}},
|
||||
output: O{A{"foo": "2", "bar": "1"}},
|
||||
result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "6",
|
||||
mes: `
|
||||
bar: { op: Lt, value: ["10"] }
|
||||
`,
|
||||
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "0x1"}}},
|
||||
result: assert.False, err: assert.NotNil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mes := &api.MatchExpressionSet{}
|
||||
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
|
||||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
out, err := mes.MatchGetInstances(tc.input)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err := mes.MatchInstances(tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
225
pkg/apis/nfd/v1alpha1/nodefeaturerule/expression-api_test.go
Normal file
225
pkg/apis/nfd/v1alpha1/nodefeaturerule/expression-api_test.go
Normal file
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nodefeaturerule_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
|
||||
api "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1/nodefeaturerule"
|
||||
)
|
||||
|
||||
type BoolAssertionFunc func(assert.TestingT, bool, ...interface{}) bool
|
||||
|
||||
type ValueAssertionFunc func(assert.TestingT, interface{}, ...interface{}) bool
|
||||
|
||||
func TestMatchKeys(t *testing.T) {
|
||||
type I = map[string]nfdv1alpha1.Nil
|
||||
type O = []api.MatchedElement
|
||||
type TC struct {
|
||||
name string
|
||||
mes string
|
||||
input I
|
||||
output O
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{input: I{}, output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{input: I{"foo": {}}, output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{mes: `
|
||||
foo: { op: DoesNotExist }
|
||||
bar: { op: Exists }
|
||||
`,
|
||||
input: I{"bar": {}, "baz": {}, "buzz": {}},
|
||||
output: O{{"Name": "bar"}, {"Name": "foo"}},
|
||||
result: assert.True, err: assert.Nil},
|
||||
|
||||
{mes: `
|
||||
foo: { op: DoesNotExist }
|
||||
bar: { op: Exists }
|
||||
`,
|
||||
input: I{"foo": {}, "bar": {}, "baz": {}},
|
||||
output: nil,
|
||||
result: assert.False, err: assert.Nil},
|
||||
|
||||
{mes: `
|
||||
foo: { op: In, value: ["bar"] }
|
||||
bar: { op: Exists }
|
||||
`,
|
||||
input: I{"bar": {}, "baz": {}},
|
||||
output: nil,
|
||||
result: assert.False, err: assert.NotNil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mes := &nfdv1alpha1.MatchExpressionSet{}
|
||||
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
|
||||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
res, out, err := api.MatchGetKeys(mes, tc.input)
|
||||
tc.result(t, res)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err = api.MatchKeys(mes, tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchValues(t *testing.T) {
|
||||
type I = map[string]string
|
||||
type O = []api.MatchedElement
|
||||
type TC struct {
|
||||
name string
|
||||
mes string
|
||||
input I
|
||||
output O
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "1", output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "2", input: I{}, output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "3", input: I{"foo": "bar"}, output: O{}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "4",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: In, value: ["val", "wal"] }
|
||||
baz: { op: Gt, value: ["10"] }
|
||||
`,
|
||||
input: I{"bar": "val"},
|
||||
result: assert.False, err: assert.Nil},
|
||||
|
||||
{name: "5",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: In, value: ["val", "wal"] }
|
||||
baz: { op: Gt, value: ["10"] }
|
||||
`,
|
||||
input: I{"foo": "1", "bar": "val", "baz": "123", "buzz": "light"},
|
||||
output: O{{"Name": "bar", "Value": "val"}, {"Name": "baz", "Value": "123"}, {"Name": "foo", "Value": "1"}},
|
||||
result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "5",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: In, value: ["val"] }
|
||||
baz: { op: Gt, value: ["10"] }
|
||||
`,
|
||||
input: I{"foo": "1", "bar": "val", "baz": "123.0"},
|
||||
result: assert.False, err: assert.NotNil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mes := &nfdv1alpha1.MatchExpressionSet{}
|
||||
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
|
||||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
res, out, err := api.MatchGetValues(mes, tc.input)
|
||||
tc.result(t, res)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err = api.MatchValues(mes, tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchInstances(t *testing.T) {
|
||||
type I = nfdv1alpha1.InstanceFeature
|
||||
type O = []api.MatchedElement
|
||||
type A = map[string]string
|
||||
type TC struct {
|
||||
name string
|
||||
mes string
|
||||
input []I
|
||||
output O
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "1", output: O{}, result: assert.False, err: assert.Nil}, // nil instances -> false
|
||||
|
||||
{name: "2", input: []I{}, output: O{}, result: assert.False, err: assert.Nil}, // zero instances -> false
|
||||
|
||||
{name: "3", input: []I{I{Attributes: A{}}}, output: O{A{}}, result: assert.True, err: assert.Nil}, // one "empty" instance
|
||||
|
||||
{name: "4",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: Lt, value: ["10"] }
|
||||
`,
|
||||
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "1"}}},
|
||||
output: O{},
|
||||
result: assert.False, err: assert.Nil},
|
||||
|
||||
{name: "5",
|
||||
mes: `
|
||||
foo: { op: Exists }
|
||||
bar: { op: Lt, value: ["10"] }
|
||||
`,
|
||||
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"foo": "2", "bar": "1"}}},
|
||||
output: O{A{"foo": "2", "bar": "1"}},
|
||||
result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "6",
|
||||
mes: `
|
||||
bar: { op: Lt, value: ["10"] }
|
||||
`,
|
||||
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "0x1"}}},
|
||||
result: assert.False, err: assert.NotNil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mes := &nfdv1alpha1.MatchExpressionSet{}
|
||||
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
|
||||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
out, err := api.MatchGetInstances(mes, tc.input)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err := api.MatchInstances(mes, tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
package nodefeaturerule
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -25,40 +25,42 @@ import (
|
|||
|
||||
"golang.org/x/exp/maps"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
var matchOps = map[MatchOp]struct{}{
|
||||
MatchAny: {},
|
||||
MatchIn: {},
|
||||
MatchNotIn: {},
|
||||
MatchInRegexp: {},
|
||||
MatchExists: {},
|
||||
MatchDoesNotExist: {},
|
||||
MatchGt: {},
|
||||
MatchLt: {},
|
||||
MatchGtLt: {},
|
||||
MatchIsTrue: {},
|
||||
MatchIsFalse: {},
|
||||
var matchOps = map[nfdv1alpha1.MatchOp]struct{}{
|
||||
nfdv1alpha1.MatchAny: {},
|
||||
nfdv1alpha1.MatchIn: {},
|
||||
nfdv1alpha1.MatchNotIn: {},
|
||||
nfdv1alpha1.MatchInRegexp: {},
|
||||
nfdv1alpha1.MatchExists: {},
|
||||
nfdv1alpha1.MatchDoesNotExist: {},
|
||||
nfdv1alpha1.MatchGt: {},
|
||||
nfdv1alpha1.MatchLt: {},
|
||||
nfdv1alpha1.MatchGtLt: {},
|
||||
nfdv1alpha1.MatchIsTrue: {},
|
||||
nfdv1alpha1.MatchIsFalse: {},
|
||||
}
|
||||
|
||||
// Match evaluates the MatchExpression against a single input value.
|
||||
func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
||||
// evaluateMatchExpression evaluates the MatchExpression against a single input value.
|
||||
func evaluateMatchExpression(m *nfdv1alpha1.MatchExpression, valid bool, value interface{}) (bool, error) {
|
||||
if _, ok := matchOps[m.Op]; !ok {
|
||||
return false, fmt.Errorf("invalid Op %q", m.Op)
|
||||
}
|
||||
|
||||
switch m.Op {
|
||||
case MatchAny:
|
||||
case nfdv1alpha1.MatchAny:
|
||||
if len(m.Value) != 0 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
||||
}
|
||||
return true, nil
|
||||
case MatchExists:
|
||||
case nfdv1alpha1.MatchExists:
|
||||
if len(m.Value) != 0 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
||||
}
|
||||
return valid, nil
|
||||
case MatchDoesNotExist:
|
||||
case nfdv1alpha1.MatchDoesNotExist:
|
||||
if len(m.Value) != 0 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
||||
}
|
||||
|
@ -68,7 +70,7 @@ func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
|||
if valid {
|
||||
value := fmt.Sprintf("%v", value)
|
||||
switch m.Op {
|
||||
case MatchIn:
|
||||
case nfdv1alpha1.MatchIn:
|
||||
if len(m.Value) == 0 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op)
|
||||
}
|
||||
|
@ -77,7 +79,7 @@ func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
}
|
||||
case MatchNotIn:
|
||||
case nfdv1alpha1.MatchNotIn:
|
||||
if len(m.Value) == 0 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op)
|
||||
}
|
||||
|
@ -87,7 +89,7 @@ func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
|||
}
|
||||
}
|
||||
return true, nil
|
||||
case MatchInRegexp:
|
||||
case nfdv1alpha1.MatchInRegexp:
|
||||
if len(m.Value) == 0 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op)
|
||||
}
|
||||
|
@ -104,7 +106,7 @@ func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
}
|
||||
case MatchGt, MatchLt:
|
||||
case nfdv1alpha1.MatchGt, nfdv1alpha1.MatchLt:
|
||||
if len(m.Value) != 1 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must contain exactly one element for Op %q (have %v)", m.Op, m.Value)
|
||||
}
|
||||
|
@ -118,10 +120,10 @@ func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
|||
return false, fmt.Errorf("not a number %q in %v", m.Value[0], m)
|
||||
}
|
||||
|
||||
if (l < r && m.Op == MatchLt) || (l > r && m.Op == MatchGt) {
|
||||
if (l < r && m.Op == nfdv1alpha1.MatchLt) || (l > r && m.Op == nfdv1alpha1.MatchGt) {
|
||||
return true, nil
|
||||
}
|
||||
case MatchGtLt:
|
||||
case nfdv1alpha1.MatchGtLt:
|
||||
if len(m.Value) != 2 {
|
||||
return false, fmt.Errorf("invalid expression, value' field must contain exactly two elements for Op %q (have %v)", m.Op, m.Value)
|
||||
}
|
||||
|
@ -140,12 +142,12 @@ func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
|||
return false, fmt.Errorf("invalid expression, value[0] must be less than Value[1] for Op %q (have %v)", m.Op, m.Value)
|
||||
}
|
||||
return v > lr[0] && v < lr[1], nil
|
||||
case MatchIsTrue:
|
||||
case nfdv1alpha1.MatchIsTrue:
|
||||
if len(m.Value) != 0 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
||||
}
|
||||
return value == "true", nil
|
||||
case MatchIsFalse:
|
||||
case nfdv1alpha1.MatchIsFalse:
|
||||
if len(m.Value) != 0 {
|
||||
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
||||
}
|
||||
|
@ -157,17 +159,17 @@ func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
// MatchKeys evaluates the MatchExpression against a set of keys.
|
||||
func (m *MatchExpression) MatchKeys(name string, keys map[string]Nil) (bool, error) {
|
||||
// evaluateMatchExpressionKeys evaluates the MatchExpression against a set of keys.
|
||||
func evaluateMatchExpressionKeys(m *nfdv1alpha1.MatchExpression, name string, keys map[string]nfdv1alpha1.Nil) (bool, error) {
|
||||
matched := false
|
||||
|
||||
_, ok := keys[name]
|
||||
switch m.Op {
|
||||
case MatchAny:
|
||||
case nfdv1alpha1.MatchAny:
|
||||
matched = true
|
||||
case MatchExists:
|
||||
case nfdv1alpha1.MatchExists:
|
||||
matched = ok
|
||||
case MatchDoesNotExist:
|
||||
case nfdv1alpha1.MatchDoesNotExist:
|
||||
matched = !ok
|
||||
default:
|
||||
return false, fmt.Errorf("invalid Op %q when matching keys", m.Op)
|
||||
|
@ -183,10 +185,10 @@ func (m *MatchExpression) MatchKeys(name string, keys map[string]Nil) (bool, err
|
|||
return matched, nil
|
||||
}
|
||||
|
||||
// MatchValues evaluates the MatchExpression against a set of key-value pairs.
|
||||
func (m *MatchExpression) MatchValues(name string, values map[string]string) (bool, error) {
|
||||
// evaluateMatchExpressionValues evaluates the MatchExpression against a set of key-value pairs.
|
||||
func evaluateMatchExpressionValues(m *nfdv1alpha1.MatchExpression, name string, values map[string]string) (bool, error) {
|
||||
v, ok := values[name]
|
||||
matched, err := m.Match(ok, v)
|
||||
matched, err := evaluateMatchExpression(m, ok, v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -201,11 +203,11 @@ func (m *MatchExpression) MatchValues(name string, values map[string]string) (bo
|
|||
}
|
||||
|
||||
// MatchKeyNames evaluates the MatchExpression against names of a set of key features.
|
||||
func (m *MatchExpression) MatchKeyNames(keys map[string]Nil) (bool, []MatchedElement, error) {
|
||||
func MatchKeyNames(m *nfdv1alpha1.MatchExpression, keys map[string]nfdv1alpha1.Nil) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for k := range keys {
|
||||
if match, err := m.Match(true, k); err != nil {
|
||||
if match, err := evaluateMatchExpression(m, true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k})
|
||||
|
@ -237,11 +239,11 @@ func (m *MatchExpression) MatchKeyNames(keys map[string]Nil) (bool, []MatchedEle
|
|||
}
|
||||
|
||||
// MatchValueNames evaluates the MatchExpression against names of a set of value features.
|
||||
func (m *MatchExpression) MatchValueNames(values map[string]string) (bool, []MatchedElement, error) {
|
||||
func MatchValueNames(m *nfdv1alpha1.MatchExpression, values map[string]string) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for k, v := range values {
|
||||
if match, err := m.Match(true, k); err != nil {
|
||||
if match, err := evaluateMatchExpression(m, true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k, "Value": v})
|
||||
|
@ -269,11 +271,11 @@ func (m *MatchExpression) MatchValueNames(values map[string]string) (bool, []Mat
|
|||
|
||||
// MatchInstanceAttributeNames evaluates the MatchExpression against a set of
|
||||
// instance features, matching against the names of their attributes.
|
||||
func (m *MatchExpression) MatchInstanceAttributeNames(instances []InstanceFeature) ([]MatchedElement, error) {
|
||||
func MatchInstanceAttributeNames(m *nfdv1alpha1.MatchExpression, instances []nfdv1alpha1.InstanceFeature) ([]MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for _, i := range instances {
|
||||
if match, _, err := m.MatchValueNames(i.Attributes); err != nil {
|
||||
if match, _, err := MatchValueNames(m, i.Attributes); err != nil {
|
||||
return nil, err
|
||||
} else if match {
|
||||
ret = append(ret, i.Attributes)
|
||||
|
@ -283,23 +285,22 @@ func (m *MatchExpression) MatchInstanceAttributeNames(instances []InstanceFeatur
|
|||
}
|
||||
|
||||
// MatchKeys evaluates the MatchExpressionSet against a set of keys.
|
||||
func (m *MatchExpressionSet) MatchKeys(keys map[string]Nil) (bool, error) {
|
||||
matched, _, err := m.MatchGetKeys(keys)
|
||||
func MatchKeys(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil) (bool, error) {
|
||||
matched, _, err := MatchGetKeys(m, keys)
|
||||
return matched, err
|
||||
}
|
||||
|
||||
// MatchedElement holds one matched Instance.
|
||||
// +k8s:deepcopy-gen=false
|
||||
type MatchedElement map[string]string
|
||||
|
||||
// MatchGetKeys evaluates the MatchExpressionSet against a set of keys and
|
||||
// returns all matched keys or nil if no match was found. Note that an empty
|
||||
// MatchExpressionSet returns a match with an empty slice of matched features.
|
||||
func (m *MatchExpressionSet) MatchGetKeys(keys map[string]Nil) (bool, []MatchedElement, error) {
|
||||
func MatchGetKeys(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil) (bool, []MatchedElement, error) {
|
||||
ret := make([]MatchedElement, 0, len(*m))
|
||||
|
||||
for n, e := range *m {
|
||||
match, err := e.MatchKeys(n, keys)
|
||||
match, err := evaluateMatchExpressionKeys(e, n, keys)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
@ -314,19 +315,19 @@ func (m *MatchExpressionSet) MatchGetKeys(keys map[string]Nil) (bool, []MatchedE
|
|||
}
|
||||
|
||||
// MatchValues evaluates the MatchExpressionSet against a set of key-value pairs.
|
||||
func (m *MatchExpressionSet) MatchValues(values map[string]string) (bool, error) {
|
||||
matched, _, err := m.MatchGetValues(values)
|
||||
func MatchValues(m *nfdv1alpha1.MatchExpressionSet, values map[string]string) (bool, error) {
|
||||
matched, _, err := MatchGetValues(m, values)
|
||||
return matched, err
|
||||
}
|
||||
|
||||
// MatchGetValues evaluates the MatchExpressionSet against a set of key-value
|
||||
// pairs and returns all matched key-value pairs. Note that an empty
|
||||
// MatchExpressionSet returns a match with an empty slice of matched features.
|
||||
func (m *MatchExpressionSet) MatchGetValues(values map[string]string) (bool, []MatchedElement, error) {
|
||||
func MatchGetValues(m *nfdv1alpha1.MatchExpressionSet, values map[string]string) (bool, []MatchedElement, error) {
|
||||
ret := make([]MatchedElement, 0, len(*m))
|
||||
|
||||
for n, e := range *m {
|
||||
match, err := e.MatchValues(n, values)
|
||||
match, err := evaluateMatchExpressionValues(e, n, values)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
@ -343,8 +344,8 @@ func (m *MatchExpressionSet) MatchGetValues(values map[string]string) (bool, []M
|
|||
// MatchInstances evaluates the MatchExpressionSet against a set of instance
|
||||
// features, each of which is an individual set of key-value pairs
|
||||
// (attributes).
|
||||
func (m *MatchExpressionSet) MatchInstances(instances []InstanceFeature) (bool, error) {
|
||||
v, err := m.MatchGetInstances(instances)
|
||||
func MatchInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, error) {
|
||||
v, err := MatchGetInstances(m, instances)
|
||||
return len(v) > 0, err
|
||||
}
|
||||
|
||||
|
@ -352,11 +353,11 @@ func (m *MatchExpressionSet) MatchInstances(instances []InstanceFeature) (bool,
|
|||
// features, each of which is an individual set of key-value pairs
|
||||
// (attributes). A slice containing all matching instances is returned. An
|
||||
// empty (non-nil) slice is returned if no matching instances were found.
|
||||
func (m *MatchExpressionSet) MatchGetInstances(instances []InstanceFeature) ([]MatchedElement, error) {
|
||||
func MatchGetInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) ([]MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for _, i := range instances {
|
||||
if match, err := m.MatchValues(i.Attributes); err != nil {
|
||||
if match, err := MatchValues(m, i.Attributes); err != nil {
|
||||
return nil, err
|
||||
} else if match {
|
||||
ret = append(ret, i.Attributes)
|
262
pkg/apis/nfd/v1alpha1/nodefeaturerule/expression_test.go
Normal file
262
pkg/apis/nfd/v1alpha1/nodefeaturerule/expression_test.go
Normal file
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nodefeaturerule
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
type BoolAssertionFunc func(assert.TestingT, bool, ...interface{}) bool
|
||||
|
||||
type ValueAssertionFunc func(assert.TestingT, interface{}, ...interface{}) bool
|
||||
|
||||
func TestEvaluateMatchExpression(t *testing.T) {
|
||||
type V = nfdv1alpha1.MatchValue
|
||||
type TC struct {
|
||||
name string
|
||||
op nfdv1alpha1.MatchOp
|
||||
values V
|
||||
input interface{}
|
||||
valid bool
|
||||
result BoolAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "MatchAny-1", op: nfdv1alpha1.MatchAny, result: assert.True},
|
||||
{name: "MatchAny-2", op: nfdv1alpha1.MatchAny, input: "2", valid: false, result: assert.True},
|
||||
|
||||
{name: "MatchIn-1", op: nfdv1alpha1.MatchIn, values: V{"1"}, input: "2", valid: false, result: assert.False},
|
||||
{name: "MatchIn-2", op: nfdv1alpha1.MatchIn, values: V{"1"}, input: "2", valid: true, result: assert.False},
|
||||
{name: "MatchIn-3", op: nfdv1alpha1.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.False},
|
||||
{name: "MatchIn-4", op: nfdv1alpha1.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchNotIn-1", op: nfdv1alpha1.MatchNotIn, values: V{"2"}, input: 2, valid: false, result: assert.False},
|
||||
{name: "MatchNotIn-2", op: nfdv1alpha1.MatchNotIn, values: V{"1"}, input: 2, valid: true, result: assert.True},
|
||||
{name: "MatchNotIn-3", op: nfdv1alpha1.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.False},
|
||||
{name: "MatchNotIn-4", op: nfdv1alpha1.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.False},
|
||||
|
||||
{name: "MatchInRegexp-1", op: nfdv1alpha1.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: false, result: assert.False},
|
||||
{name: "MatchInRegexp-2", op: nfdv1alpha1.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: true, result: assert.True},
|
||||
{name: "MatchInRegexp-3", op: nfdv1alpha1.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-12", valid: true, result: assert.False},
|
||||
{name: "MatchInRegexp-4", op: nfdv1alpha1.MatchInRegexp, values: V{"val-[0-9]$", "al-[1-9]"}, input: "val-12", valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchExists-1", op: nfdv1alpha1.MatchExists, input: nil, valid: false, result: assert.False},
|
||||
{name: "MatchExists-2", op: nfdv1alpha1.MatchExists, input: nil, valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchDoesNotExist-1", op: nfdv1alpha1.MatchDoesNotExist, input: false, valid: false, result: assert.True},
|
||||
{name: "MatchDoesNotExist-2", op: nfdv1alpha1.MatchDoesNotExist, input: false, valid: true, result: assert.False},
|
||||
|
||||
{name: "MatchGt-1", op: nfdv1alpha1.MatchGt, values: V{"2"}, input: 3, valid: false, result: assert.False},
|
||||
{name: "MatchGt-2", op: nfdv1alpha1.MatchGt, values: V{"2"}, input: 2, valid: true, result: assert.False},
|
||||
{name: "MatchGt-3", op: nfdv1alpha1.MatchGt, values: V{"2"}, input: 3, valid: true, result: assert.True},
|
||||
{name: "MatchGt-4", op: nfdv1alpha1.MatchGt, values: V{"-10"}, input: -3, valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchLt-1", op: nfdv1alpha1.MatchLt, values: V{"2"}, input: "1", valid: false, result: assert.False},
|
||||
{name: "MatchLt-2", op: nfdv1alpha1.MatchLt, values: V{"2"}, input: "2", valid: true, result: assert.False},
|
||||
{name: "MatchLt-3", op: nfdv1alpha1.MatchLt, values: V{"-10"}, input: -3, valid: true, result: assert.False},
|
||||
{name: "MatchLt-4", op: nfdv1alpha1.MatchLt, values: V{"2"}, input: "1", valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchGtLt-1", op: nfdv1alpha1.MatchGtLt, values: V{"1", "10"}, input: "1", valid: false, result: assert.False},
|
||||
{name: "MatchGtLt-2", op: nfdv1alpha1.MatchGtLt, values: V{"1", "10"}, input: "1", valid: true, result: assert.False},
|
||||
{name: "MatchGtLt-3", op: nfdv1alpha1.MatchGtLt, values: V{"1", "10"}, input: "10", valid: true, result: assert.False},
|
||||
{name: "MatchGtLt-4", op: nfdv1alpha1.MatchGtLt, values: V{"1", "10"}, input: "2", valid: true, result: assert.True},
|
||||
|
||||
{name: "MatchIsTrue-1", op: nfdv1alpha1.MatchIsTrue, input: true, valid: false, result: assert.False},
|
||||
{name: "MatchIsTrue-2", op: nfdv1alpha1.MatchIsTrue, input: true, valid: true, result: assert.True},
|
||||
{name: "MatchIsTrue-3", op: nfdv1alpha1.MatchIsTrue, input: false, valid: true, result: assert.False},
|
||||
|
||||
{name: "MatchIsFalse-1", op: nfdv1alpha1.MatchIsFalse, input: "false", valid: false, result: assert.False},
|
||||
{name: "MatchIsFalse-2", op: nfdv1alpha1.MatchIsFalse, input: "false", valid: true, result: assert.True},
|
||||
{name: "MatchIsFalse-3", op: nfdv1alpha1.MatchIsFalse, input: "true", valid: true, result: assert.False},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
me := &nfdv1alpha1.MatchExpression{Op: tc.op, Value: tc.values}
|
||||
res, err := evaluateMatchExpression(me, tc.valid, tc.input)
|
||||
tc.result(t, res)
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// Error cases
|
||||
tcs = []TC{
|
||||
{name: "MatchAny-err-1", op: nfdv1alpha1.MatchAny, values: V{"1"}, input: "val"},
|
||||
|
||||
{name: "MatchIn-err-1", op: nfdv1alpha1.MatchIn, input: "val"},
|
||||
|
||||
{name: "MatchNotIn-err-1", op: nfdv1alpha1.MatchNotIn, input: "val"},
|
||||
|
||||
{name: "MatchInRegexp-err-1", op: nfdv1alpha1.MatchInRegexp, input: "val"},
|
||||
{name: "MatchInRegexp-err-2", op: nfdv1alpha1.MatchInRegexp, values: V{"("}, input: "val"},
|
||||
|
||||
{name: "MatchExists-err-1", op: nfdv1alpha1.MatchExists, values: V{"1"}},
|
||||
|
||||
{name: "MatchDoesNotExist-err-1", op: nfdv1alpha1.MatchDoesNotExist, values: V{"1"}},
|
||||
|
||||
{name: "MatchGt-err-1", op: nfdv1alpha1.MatchGt, input: "1"},
|
||||
{name: "MatchGt-err-2", op: nfdv1alpha1.MatchGt, values: V{"1", "2"}, input: "1"},
|
||||
{name: "MatchGt-err-3", op: nfdv1alpha1.MatchGt, values: V{""}, input: "1"},
|
||||
{name: "MatchGt-err-4", op: nfdv1alpha1.MatchGt, values: V{"2"}, input: "3a"},
|
||||
|
||||
{name: "MatchLt-err-1", op: nfdv1alpha1.MatchLt, input: "1"},
|
||||
{name: "MatchLt-err-2", op: nfdv1alpha1.MatchLt, values: V{"1", "2", "3"}, input: "1"},
|
||||
{name: "MatchLt-err-3", op: nfdv1alpha1.MatchLt, values: V{"a"}, input: "1"},
|
||||
{name: "MatchLt-err-4", op: nfdv1alpha1.MatchLt, values: V{"2"}, input: "1.0"},
|
||||
|
||||
{name: "MatchGtLt-err-1", op: nfdv1alpha1.MatchGtLt, input: "1"},
|
||||
{name: "MatchGtLt-err-2", op: nfdv1alpha1.MatchGtLt, values: V{"1"}, input: "1"},
|
||||
{name: "MatchGtLt-err-3", op: nfdv1alpha1.MatchGtLt, values: V{"2", "1"}, input: "1"},
|
||||
{name: "MatchGtLt-err-4", op: nfdv1alpha1.MatchGtLt, values: V{"1", "2", "3"}, input: "1"},
|
||||
{name: "MatchGtLt-err-5", op: nfdv1alpha1.MatchGtLt, values: V{"a", "2"}, input: "1"},
|
||||
{name: "MatchGtLt-err-6", op: nfdv1alpha1.MatchGtLt, values: V{"1", "10"}, input: "1.0"},
|
||||
|
||||
{name: "MatchIsTrue-err-1", op: nfdv1alpha1.MatchIsTrue, values: V{"1"}, input: "true"},
|
||||
|
||||
{name: "MatchIsFalse-err-1", op: nfdv1alpha1.MatchIsFalse, values: V{"1", "2"}, input: "false"},
|
||||
|
||||
{name: "invalid-op-err", op: "non-existent-op", values: V{"1"}, input: 1},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
me := &nfdv1alpha1.MatchExpression{Op: tc.op, Value: tc.values}
|
||||
res, err := evaluateMatchExpression(me, true, tc.input)
|
||||
assert.False(t, res)
|
||||
assert.NotNil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateMatchExpressionKeys(t *testing.T) {
|
||||
type V = nfdv1alpha1.MatchValue
|
||||
type I = map[string]nfdv1alpha1.Nil
|
||||
type TC struct {
|
||||
name string
|
||||
key string
|
||||
op nfdv1alpha1.MatchOp
|
||||
values V
|
||||
input I
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "1", op: nfdv1alpha1.MatchAny, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "2", op: nfdv1alpha1.MatchExists, key: "foo", input: nil, result: assert.False, err: assert.Nil},
|
||||
{name: "3", op: nfdv1alpha1.MatchExists, key: "foo", input: I{"bar": {}}, result: assert.False, err: assert.Nil},
|
||||
{name: "4", op: nfdv1alpha1.MatchExists, key: "foo", input: I{"bar": {}, "foo": {}}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "5", op: nfdv1alpha1.MatchDoesNotExist, key: "foo", input: nil, result: assert.True, err: assert.Nil},
|
||||
{name: "6", op: nfdv1alpha1.MatchDoesNotExist, key: "foo", input: I{}, result: assert.True, err: assert.Nil},
|
||||
{name: "7", op: nfdv1alpha1.MatchDoesNotExist, key: "foo", input: I{"bar": {}}, result: assert.True, err: assert.Nil},
|
||||
{name: "8", op: nfdv1alpha1.MatchDoesNotExist, key: "foo", input: I{"bar": {}, "foo": {}}, result: assert.False, err: assert.Nil},
|
||||
|
||||
// All other ops should return an error
|
||||
{name: "9", op: nfdv1alpha1.MatchIn, values: V{"foo"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "10", op: nfdv1alpha1.MatchNotIn, values: V{"foo"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "11", op: nfdv1alpha1.MatchInRegexp, values: V{"foo"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "12", op: nfdv1alpha1.MatchGt, values: V{"1"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "13", op: nfdv1alpha1.MatchLt, values: V{"1"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "14", op: nfdv1alpha1.MatchGtLt, values: V{"1", "10"}, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "15", op: nfdv1alpha1.MatchIsTrue, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
{name: "16", op: nfdv1alpha1.MatchIsFalse, key: "foo", result: assert.False, err: assert.NotNil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
me := &nfdv1alpha1.MatchExpression{Op: tc.op, Value: tc.values}
|
||||
res, err := evaluateMatchExpressionKeys(me, tc.key, tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateMatchExpressionValues(t *testing.T) {
|
||||
type V = []string
|
||||
type I = map[string]string
|
||||
|
||||
type TC struct {
|
||||
name string
|
||||
op nfdv1alpha1.MatchOp
|
||||
values V
|
||||
key string
|
||||
input I
|
||||
result BoolAssertionFunc
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{name: "1", op: nfdv1alpha1.MatchAny, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "2", op: nfdv1alpha1.MatchIn, values: V{"1", "2"}, key: "foo", input: I{"bar": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "3", op: nfdv1alpha1.MatchIn, values: V{"1", "2"}, key: "foo", input: I{"foo": "3"}, result: assert.False, err: assert.Nil},
|
||||
{name: "4", op: nfdv1alpha1.MatchIn, values: V{"1", "2"}, key: "foo", input: I{"foo": "2"}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "5", op: nfdv1alpha1.MatchNotIn, values: V{"1", "2"}, key: "foo", input: I{"bar": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "6", op: nfdv1alpha1.MatchNotIn, values: V{"1", "2"}, key: "foo", input: I{"foo": "3"}, result: assert.True, err: assert.Nil},
|
||||
{name: "7", op: nfdv1alpha1.MatchNotIn, values: V{"1", "2"}, key: "foo", input: I{"foo": "2"}, result: assert.False, err: assert.Nil},
|
||||
|
||||
{name: "8", op: nfdv1alpha1.MatchInRegexp, values: V{"1", "2"}, key: "foo", input: I{"bar": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "9", op: nfdv1alpha1.MatchInRegexp, values: V{"1", "[0-8]"}, key: "foo", input: I{"foo": "9"}, result: assert.False, err: assert.Nil},
|
||||
{name: "10", op: nfdv1alpha1.MatchInRegexp, values: V{"1", "[0-8]"}, key: "foo", input: I{"foo": "2"}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "11", op: nfdv1alpha1.MatchExists, key: "foo", input: I{"bar": "1"}, result: assert.False, err: assert.Nil},
|
||||
{name: "12", op: nfdv1alpha1.MatchExists, key: "foo", input: I{"foo": "1"}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "13", op: nfdv1alpha1.MatchDoesNotExist, key: "foo", input: nil, result: assert.True, err: assert.Nil},
|
||||
{name: "14", op: nfdv1alpha1.MatchDoesNotExist, key: "foo", input: I{"foo": "1"}, result: assert.False, err: assert.Nil},
|
||||
|
||||
{name: "15", op: nfdv1alpha1.MatchGt, values: V{"2"}, key: "foo", input: I{"bar": "3"}, result: assert.False, err: assert.Nil},
|
||||
{name: "16", op: nfdv1alpha1.MatchGt, values: V{"2"}, key: "foo", input: I{"bar": "3", "foo": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "17", op: nfdv1alpha1.MatchGt, values: V{"2"}, key: "foo", input: I{"bar": "3", "foo": "3"}, result: assert.True, err: assert.Nil},
|
||||
{name: "18", op: nfdv1alpha1.MatchGt, values: V{"2"}, key: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.False, err: assert.NotNil},
|
||||
|
||||
{name: "19", op: nfdv1alpha1.MatchLt, values: V{"2"}, key: "foo", input: I{"bar": "1"}, result: assert.False, err: assert.Nil},
|
||||
{name: "20", op: nfdv1alpha1.MatchLt, values: V{"2"}, key: "foo", input: I{"bar": "1", "foo": "2"}, result: assert.False, err: assert.Nil},
|
||||
{name: "21", op: nfdv1alpha1.MatchLt, values: V{"2"}, key: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.True, err: assert.Nil},
|
||||
{name: "22", op: nfdv1alpha1.MatchLt, values: V{"2"}, key: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.False, err: assert.NotNil},
|
||||
|
||||
{name: "23", op: nfdv1alpha1.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "1"}, result: assert.False, err: assert.Nil},
|
||||
{name: "24", op: nfdv1alpha1.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "1", "foo": "11"}, result: assert.False, err: assert.Nil},
|
||||
{name: "25", op: nfdv1alpha1.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "1", "foo": "-11"}, result: assert.False, err: assert.Nil},
|
||||
{name: "26", op: nfdv1alpha1.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.True, err: assert.Nil},
|
||||
{name: "27", op: nfdv1alpha1.MatchGtLt, values: V{"-10", "10"}, key: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.False, err: assert.NotNil},
|
||||
|
||||
{name: "28", op: nfdv1alpha1.MatchIsTrue, key: "foo", result: assert.False, err: assert.Nil},
|
||||
{name: "29", op: nfdv1alpha1.MatchIsTrue, key: "foo", input: I{"foo": "1"}, result: assert.False, err: assert.Nil},
|
||||
{name: "30", op: nfdv1alpha1.MatchIsTrue, key: "foo", input: I{"foo": "true"}, result: assert.True, err: assert.Nil},
|
||||
|
||||
{name: "31", op: nfdv1alpha1.MatchIsFalse, key: "foo", input: I{"foo": "true"}, result: assert.False, err: assert.Nil},
|
||||
{name: "32", op: nfdv1alpha1.MatchIsFalse, key: "foo", input: I{"foo": "false"}, result: assert.True, err: assert.Nil},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
me := &nfdv1alpha1.MatchExpression{Op: tc.op, Value: tc.values}
|
||||
res, err := evaluateMatchExpressionValues(me, tc.key, tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
package nodefeaturerule
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -27,6 +27,7 @@ import (
|
|||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -41,7 +42,7 @@ type RuleOutput struct {
|
|||
}
|
||||
|
||||
// Execute the rule against a set of input features.
|
||||
func (r *Rule) Execute(features *Features) (RuleOutput, error) {
|
||||
func Execute(r *nfdv1alpha1.Rule, features *nfdv1alpha1.Features) (RuleOutput, error) {
|
||||
labels := make(map[string]string)
|
||||
vars := make(map[string]string)
|
||||
|
||||
|
@ -49,7 +50,7 @@ func (r *Rule) Execute(features *Features) (RuleOutput, error) {
|
|||
// Logical OR over the matchAny matchers
|
||||
matched := false
|
||||
for _, matcher := range r.MatchAny {
|
||||
if isMatch, matches, err := matcher.match(features); err != nil {
|
||||
if isMatch, matches, err := evaluateMatchAnyElem(&matcher, features); err != nil {
|
||||
return RuleOutput{}, err
|
||||
} else if isMatch {
|
||||
matched = true
|
||||
|
@ -62,10 +63,10 @@ func (r *Rule) Execute(features *Features) (RuleOutput, error) {
|
|||
break
|
||||
}
|
||||
|
||||
if err := r.executeLabelsTemplate(matches, labels); err != nil {
|
||||
if err := executeLabelsTemplate(r, matches, labels); err != nil {
|
||||
return RuleOutput{}, err
|
||||
}
|
||||
if err := r.executeVarsTemplate(matches, vars); err != nil {
|
||||
if err := executeVarsTemplate(r, matches, vars); err != nil {
|
||||
return RuleOutput{}, err
|
||||
}
|
||||
}
|
||||
|
@ -77,17 +78,17 @@ func (r *Rule) Execute(features *Features) (RuleOutput, error) {
|
|||
}
|
||||
|
||||
if len(r.MatchFeatures) > 0 {
|
||||
if isMatch, matches, err := r.MatchFeatures.match(features); err != nil {
|
||||
if isMatch, matches, err := evaluateFeatureMatcher(&r.MatchFeatures, features); err != nil {
|
||||
return RuleOutput{}, err
|
||||
} else if !isMatch {
|
||||
klog.V(2).InfoS("rule did not match", "ruleName", r.Name)
|
||||
return RuleOutput{}, nil
|
||||
} else {
|
||||
klog.V(4).InfoS("matchFeatures matched", "ruleName", r.Name, "matchedFeatures", utils.DelayedDumper(matches))
|
||||
if err := r.executeLabelsTemplate(matches, labels); err != nil {
|
||||
if err := executeLabelsTemplate(r, matches, labels); err != nil {
|
||||
return RuleOutput{}, err
|
||||
}
|
||||
if err := r.executeVarsTemplate(matches, vars); err != nil {
|
||||
if err := executeVarsTemplate(r, matches, vars); err != nil {
|
||||
return RuleOutput{}, err
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +108,7 @@ func (r *Rule) Execute(features *Features) (RuleOutput, error) {
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *Rule) executeLabelsTemplate(in matchedFeatures, out map[string]string) error {
|
||||
func executeLabelsTemplate(r *nfdv1alpha1.Rule, in matchedFeatures, out map[string]string) error {
|
||||
if r.LabelsTemplate == "" {
|
||||
return nil
|
||||
}
|
||||
|
@ -127,7 +128,7 @@ func (r *Rule) executeLabelsTemplate(in matchedFeatures, out map[string]string)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *Rule) executeVarsTemplate(in matchedFeatures, out map[string]string) error {
|
||||
func executeVarsTemplate(r *nfdv1alpha1.Rule, in matchedFeatures, out map[string]string) error {
|
||||
if r.VarsTemplate == "" {
|
||||
return nil
|
||||
}
|
||||
|
@ -151,11 +152,11 @@ type matchedFeatures map[string]domainMatchedFeatures
|
|||
|
||||
type domainMatchedFeatures map[string][]MatchedElement
|
||||
|
||||
func (e *MatchAnyElem) match(features *Features) (bool, matchedFeatures, error) {
|
||||
return e.MatchFeatures.match(features)
|
||||
func evaluateMatchAnyElem(e *nfdv1alpha1.MatchAnyElem, features *nfdv1alpha1.Features) (bool, matchedFeatures, error) {
|
||||
return evaluateFeatureMatcher(&e.MatchFeatures, features)
|
||||
}
|
||||
|
||||
func (m *FeatureMatcher) match(features *Features) (bool, matchedFeatures, error) {
|
||||
func evaluateFeatureMatcher(m *nfdv1alpha1.FeatureMatcher, features *nfdv1alpha1.Features) (bool, matchedFeatures, error) {
|
||||
matches := make(matchedFeatures, len(*m))
|
||||
|
||||
// Logical AND over the terms
|
||||
|
@ -180,30 +181,30 @@ func (m *FeatureMatcher) match(features *Features) (bool, matchedFeatures, error
|
|||
var err error
|
||||
if f, ok := features.Flags[featureName]; ok {
|
||||
if term.MatchExpressions != nil {
|
||||
isMatch, matchedElems, err = term.MatchExpressions.MatchGetKeys(f.Elements)
|
||||
isMatch, matchedElems, err = MatchGetKeys(term.MatchExpressions, f.Elements)
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
isMatch, meTmp, err = term.MatchName.MatchKeyNames(f.Elements)
|
||||
isMatch, meTmp, err = MatchKeyNames(term.MatchName, f.Elements)
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
}
|
||||
} else if f, ok := features.Attributes[featureName]; ok {
|
||||
if term.MatchExpressions != nil {
|
||||
isMatch, matchedElems, err = term.MatchExpressions.MatchGetValues(f.Elements)
|
||||
isMatch, matchedElems, err = MatchGetValues(term.MatchExpressions, f.Elements)
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
isMatch, meTmp, err = term.MatchName.MatchValueNames(f.Elements)
|
||||
isMatch, meTmp, err = MatchValueNames(term.MatchName, f.Elements)
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
}
|
||||
} else if f, ok := features.Instances[featureName]; ok {
|
||||
if term.MatchExpressions != nil {
|
||||
matchedElems, err = term.MatchExpressions.MatchGetInstances(f.Elements)
|
||||
matchedElems, err = MatchGetInstances(term.MatchExpressions, f.Elements)
|
||||
isMatch = len(matchedElems) > 0
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
meTmp, err = term.MatchName.MatchInstanceAttributeNames(f.Elements)
|
||||
meTmp, err = MatchInstanceAttributeNames(term.MatchName, f.Elements)
|
||||
isMatch = len(meTmp) > 0
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
|
|
@ -14,217 +14,219 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
package nodefeaturerule
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
// newMatchExpression returns a new MatchExpression instance.
|
||||
func newMatchExpression(op MatchOp, values ...string) *MatchExpression {
|
||||
return &MatchExpression{
|
||||
func newMatchExpression(op nfdv1alpha1.MatchOp, values ...string) *nfdv1alpha1.MatchExpression {
|
||||
return &nfdv1alpha1.MatchExpression{
|
||||
Op: op,
|
||||
Value: values,
|
||||
}
|
||||
}
|
||||
|
||||
func TestRule(t *testing.T) {
|
||||
f := &Features{}
|
||||
r1 := Rule{Labels: map[string]string{"label-1": "", "label-2": "true"}}
|
||||
r2 := Rule{
|
||||
f := &nfdv1alpha1.Features{}
|
||||
r1 := &nfdv1alpha1.Rule{Labels: map[string]string{"label-1": "", "label-2": "true"}}
|
||||
r2 := &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"label-1": "label-val-1"},
|
||||
Vars: map[string]string{"var-1": "var-val-1"},
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain-1.kf-1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchExists),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-1": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test totally empty features
|
||||
m, err := r1.Execute(f)
|
||||
m, err := Execute(r1, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
|
||||
|
||||
_, err = r2.Execute(f)
|
||||
_, err = Execute(r2, f)
|
||||
assert.Error(t, err, "matching against a missing feature should have returned an error")
|
||||
|
||||
// Test properly initialized empty features
|
||||
f = NewFeatures()
|
||||
f = nfdv1alpha1.NewFeatures()
|
||||
|
||||
m, err = r1.Execute(f)
|
||||
m, err = Execute(r1, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
|
||||
assert.Empty(t, r1.Vars, "vars should be empty")
|
||||
|
||||
_, err = r2.Execute(f)
|
||||
_, err = Execute(r2, f)
|
||||
assert.Error(t, err, "matching against a missing feature type should have returned an error")
|
||||
|
||||
// Test empty feature sets
|
||||
f.Flags["domain-1.kf-1"] = NewFlagFeatures()
|
||||
f.Attributes["domain-1.vf-1"] = NewAttributeFeatures(nil)
|
||||
f.Instances["domain-1.if-1"] = NewInstanceFeatures(nil)
|
||||
f.Flags["domain-1.kf-1"] = nfdv1alpha1.NewFlagFeatures()
|
||||
f.Attributes["domain-1.vf-1"] = nfdv1alpha1.NewAttributeFeatures(nil)
|
||||
f.Instances["domain-1.if-1"] = nfdv1alpha1.NewInstanceFeatures(nil)
|
||||
|
||||
m, err = r1.Execute(f)
|
||||
m, err = Execute(r1, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
|
||||
|
||||
m, err = r2.Execute(f)
|
||||
m, err = Execute(r2, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "unexpected match")
|
||||
|
||||
// Test non-empty feature sets
|
||||
f.Flags["domain-1.kf-1"].Elements["key-x"] = Nil{}
|
||||
f.Flags["domain-1.kf-1"].Elements["key-x"] = nfdv1alpha1.Nil{}
|
||||
f.Attributes["domain-1.vf-1"].Elements["key-1"] = "val-x"
|
||||
f.Instances["domain-1.if-1"] = NewInstanceFeatures([]InstanceFeature{
|
||||
*NewInstanceFeature(map[string]string{"attr-1": "val-x"})})
|
||||
f.Instances["domain-1.if-1"] = nfdv1alpha1.NewInstanceFeatures([]nfdv1alpha1.InstanceFeature{
|
||||
*nfdv1alpha1.NewInstanceFeature(map[string]string{"attr-1": "val-x"})})
|
||||
|
||||
m, err = r1.Execute(f)
|
||||
m, err = Execute(r1, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
|
||||
|
||||
// Test empty MatchExpressions
|
||||
r1.MatchFeatures = FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
r1.MatchFeatures = nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain-1.kf-1",
|
||||
MatchExpressions: &MatchExpressionSet{},
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{},
|
||||
},
|
||||
}
|
||||
m, err = r1.Execute(f)
|
||||
m, err = Execute(r1, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty match expression set mathces anything")
|
||||
|
||||
// Match "key" features
|
||||
m, err = r2.Execute(f)
|
||||
m, err = Execute(r2, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "keys should not have matched")
|
||||
|
||||
f.Flags["domain-1.kf-1"].Elements["key-1"] = Nil{}
|
||||
m, err = r2.Execute(f)
|
||||
f.Flags["domain-1.kf-1"].Elements["key-1"] = nfdv1alpha1.Nil{}
|
||||
m, err = Execute(r2, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r2.Labels, m.Labels, "keys should have matched")
|
||||
assert.Equal(t, r2.Vars, m.Vars, "vars should be present")
|
||||
|
||||
// Match "value" features
|
||||
r3 := Rule{
|
||||
r3 := &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"label-3": "label-val-3", "empty": ""},
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain-1.vf-1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchIn, "val-1"),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-1": newMatchExpression(nfdv1alpha1.MatchIn, "val-1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = r3.Execute(f)
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "values should not have matched")
|
||||
|
||||
f.Attributes["domain-1.vf-1"].Elements["key-1"] = "val-1"
|
||||
m, err = r3.Execute(f)
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "values should have matched")
|
||||
|
||||
// Match "instance" features
|
||||
r4 := Rule{
|
||||
r4 := &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"label-4": "label-val-4"},
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain-1.if-1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(MatchIn, "val-1"),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(nfdv1alpha1.MatchIn, "val-1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = r4.Execute(f)
|
||||
m, err = Execute(r4, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
f.Instances["domain-1.if-1"].Elements[0].Attributes["attr-1"] = "val-1"
|
||||
m, err = r4.Execute(f)
|
||||
m, err = Execute(r4, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r4.Labels, m.Labels, "instances should have matched")
|
||||
|
||||
// Test multiple feature matchers
|
||||
r5 := Rule{
|
||||
r5 := &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"label-5": "label-val-5"},
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain-1.vf-1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchIn, "val-x"),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-1": newMatchExpression(nfdv1alpha1.MatchIn, "val-x"),
|
||||
},
|
||||
},
|
||||
FeatureMatcherTerm{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain-1.if-1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(MatchIn, "val-1"),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(nfdv1alpha1.MatchIn, "val-1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = r5.Execute(f)
|
||||
m, err = Execute(r5, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(MatchIn, "val-1")
|
||||
m, err = r5.Execute(f)
|
||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||
m, err = Execute(r5, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
|
||||
|
||||
// Test MatchAny
|
||||
r5.MatchAny = []MatchAnyElem{
|
||||
r5.MatchAny = []nfdv1alpha1.MatchAnyElem{
|
||||
{
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain-1.kf-1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-na": newMatchExpression(MatchExists),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-na": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = r5.Execute(f)
|
||||
m, err = Execute(r5, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
r5.MatchAny = append(r5.MatchAny,
|
||||
MatchAnyElem{
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
nfdv1alpha1.MatchAnyElem{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain-1.kf-1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchExists),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-1": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(MatchIn, "val-1")
|
||||
m, err = r5.Execute(f)
|
||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||
m, err = Execute(r5, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
|
||||
}
|
||||
|
||||
func TestTemplating(t *testing.T) {
|
||||
f := &Features{
|
||||
Flags: map[string]FlagFeatureSet{
|
||||
f := &nfdv1alpha1.Features{
|
||||
Flags: map[string]nfdv1alpha1.FlagFeatureSet{
|
||||
"domain_1.kf_1": {
|
||||
Elements: map[string]Nil{
|
||||
Elements: map[string]nfdv1alpha1.Nil{
|
||||
"key-a": {},
|
||||
"key-b": {},
|
||||
"key-c": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
Attributes: map[string]AttributeFeatureSet{
|
||||
Attributes: map[string]nfdv1alpha1.AttributeFeatureSet{
|
||||
"domain_1.vf_1": {
|
||||
Elements: map[string]string{
|
||||
"key-1": "val-1",
|
||||
|
@ -234,9 +236,9 @@ func TestTemplating(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Instances: map[string]InstanceFeatureSet{
|
||||
Instances: map[string]nfdv1alpha1.InstanceFeatureSet{
|
||||
"domain_1.if_1": {
|
||||
Elements: []InstanceFeature{
|
||||
Elements: []nfdv1alpha1.InstanceFeature{
|
||||
{
|
||||
Attributes: map[string]string{
|
||||
"attr-1": "1",
|
||||
|
@ -267,7 +269,7 @@ func TestTemplating(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
r1 := Rule{
|
||||
r1 := &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"label-1": "label-val-1"},
|
||||
LabelsTemplate: `
|
||||
label-1=will-be-overridden
|
||||
|
@ -284,34 +286,34 @@ var-1=value-will-be-overridden-by-vars
|
|||
var-2=
|
||||
{{range .domain_1.kf_1}}kf-{{.Name}}=true
|
||||
{{end}}`,
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain_1.kf_1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-a": newMatchExpression(MatchExists),
|
||||
"key-c": newMatchExpression(MatchExists),
|
||||
"foo": newMatchExpression(MatchDoesNotExist),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-a": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
"key-c": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
"foo": newMatchExpression(nfdv1alpha1.MatchDoesNotExist),
|
||||
},
|
||||
},
|
||||
FeatureMatcherTerm{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain_1.vf_1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchIn, "val-1", "val-2"),
|
||||
"bar": newMatchExpression(MatchDoesNotExist),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-1": newMatchExpression(nfdv1alpha1.MatchIn, "val-1", "val-2"),
|
||||
"bar": newMatchExpression(nfdv1alpha1.MatchDoesNotExist),
|
||||
},
|
||||
},
|
||||
FeatureMatcherTerm{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain_1.if_1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(MatchLt, "100"),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(nfdv1alpha1.MatchLt, "100"),
|
||||
},
|
||||
},
|
||||
FeatureMatcherTerm{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain_1.if_1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(MatchExists),
|
||||
"attr-2": newMatchExpression(MatchExists),
|
||||
"attr-3": newMatchExpression(MatchExists),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
"attr-2": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
"attr-3": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -319,7 +321,7 @@ var-2=
|
|||
|
||||
// test with empty MatchFeatures, but with MatchAny
|
||||
r3 := r1.DeepCopy()
|
||||
r3.MatchAny = []MatchAnyElem{{MatchFeatures: r3.MatchFeatures}}
|
||||
r3.MatchAny = []nfdv1alpha1.MatchAnyElem{{MatchFeatures: r3.MatchFeatures}}
|
||||
r3.MatchFeatures = nil
|
||||
|
||||
expectedLabels := map[string]string{
|
||||
|
@ -346,12 +348,12 @@ var-2=
|
|||
"kf-foo": "true",
|
||||
}
|
||||
|
||||
m, err := r1.Execute(f)
|
||||
m, err := Execute(r1, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
|
||||
|
||||
m, err = r3.Execute(f)
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
|
||||
|
@ -359,60 +361,60 @@ var-2=
|
|||
//
|
||||
// Test error cases
|
||||
//
|
||||
r2 := Rule{
|
||||
MatchFeatures: FeatureMatcher{
|
||||
r2 := &nfdv1alpha1.Rule{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
// We need at least one matcher to match to execute the template.
|
||||
// Use a simple empty matchexpression set to match anything.
|
||||
FeatureMatcherTerm{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain_1.kf_1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-a": newMatchExpression(MatchExists),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-a": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r2.LabelsTemplate = "foo=bar"
|
||||
m, err = r2.Execute(f)
|
||||
m, err = Execute(r2, f)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, map[string]string{"foo": "bar"}, m.Labels, "instances should have matched")
|
||||
assert.Empty(t, m.Vars)
|
||||
|
||||
r2.LabelsTemplate = "foo"
|
||||
_, err = r2.Execute(f)
|
||||
_, err = Execute(r2, f)
|
||||
assert.Error(t, err)
|
||||
|
||||
r2.LabelsTemplate = "{{"
|
||||
_, err = r2.Execute(f)
|
||||
_, err = Execute(r2, f)
|
||||
assert.Error(t, err)
|
||||
|
||||
r2.LabelsTemplate = ""
|
||||
r2.VarsTemplate = "bar=baz"
|
||||
m, err = r2.Execute(f)
|
||||
m, err = Execute(r2, f)
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, m.Labels)
|
||||
assert.Equal(t, map[string]string{"bar": "baz"}, m.Vars, "instances should have matched")
|
||||
|
||||
r2.VarsTemplate = "bar"
|
||||
_, err = r2.Execute(f)
|
||||
_, err = Execute(r2, f)
|
||||
assert.Error(t, err)
|
||||
|
||||
r2.VarsTemplate = "{{"
|
||||
_, err = r2.Execute(f)
|
||||
_, err = Execute(r2, f)
|
||||
assert.Error(t, err)
|
||||
|
||||
//
|
||||
// Test matchName
|
||||
//
|
||||
r4 := Rule{
|
||||
r4 := &nfdv1alpha1.Rule{
|
||||
LabelsTemplate: "{{range .domain_1.vf_1}}{{.Name}}={{.Value}}\n{{end}}",
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain_1.vf_1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-5": newMatchExpression(MatchDoesNotExist),
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-5": newMatchExpression(nfdv1alpha1.MatchDoesNotExist),
|
||||
},
|
||||
MatchName: newMatchExpression(MatchIn, "key-1", "key-4"),
|
||||
MatchName: newMatchExpression(nfdv1alpha1.MatchIn, "key-1", "key-4"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -422,21 +424,21 @@ var-2=
|
|||
"key-5": "",
|
||||
}
|
||||
|
||||
m, err = r4.Execute(f)
|
||||
m, err = Execute(r4, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
|
||||
r4 = Rule{
|
||||
r4 = &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"should-not-match": "true"},
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain_1.vf_1",
|
||||
MatchName: newMatchExpression(MatchIn, "key-not-exists"),
|
||||
MatchName: newMatchExpression(nfdv1alpha1.MatchIn, "key-not-exists"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m, err = r4.Execute(f)
|
||||
m, err = Execute(r4, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, map[string]string(nil), m.Labels, "instances should have matched")
|
||||
}
|
|
@ -54,6 +54,7 @@ import (
|
|||
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1/nodefeaturerule"
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
|
||||
pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/utils"
|
||||
|
@ -970,7 +971,7 @@ func (m *nfdMaster) processNodeFeatureRule(nodeName string, features *nfdv1alpha
|
|||
klog.InfoS("executing NodeFeatureRule", "nodefeaturerule", klog.KObj(spec), "nodeName", nodeName)
|
||||
}
|
||||
for _, rule := range spec.Spec.Rules {
|
||||
ruleOut, err := rule.Execute(features)
|
||||
ruleOut, err := nodefeaturerule.Execute(&rule, features)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to process rule", "ruleName", rule.Name, "nodefeaturerule", klog.KObj(spec), "nodeName", nodeName)
|
||||
nfrProcessingErrors.Inc()
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"k8s.io/klog/v2"
|
||||
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
|
||||
nodefeaturerule "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1/nodefeaturerule"
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/utils"
|
||||
"sigs.k8s.io/node-feature-discovery/source"
|
||||
api "sigs.k8s.io/node-feature-discovery/source/custom/api"
|
||||
|
@ -92,7 +93,7 @@ func (s *customSource) GetLabels() (source.FeatureLabels, error) {
|
|||
klog.V(2).InfoS("resolving custom features", "configuration", utils.DelayedDumper(allFeatureConfig))
|
||||
// Iterate over features
|
||||
for _, rule := range allFeatureConfig {
|
||||
ruleOut, err := rule.Execute(features)
|
||||
ruleOut, err := nodefeaturerule.Execute(&rule, features)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to execute rule")
|
||||
continue
|
||||
|
|
Loading…
Reference in a new issue