1
0
Fork 0
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:
Markus Lehtonen 2023-11-29 14:32:33 +02:00
parent e162540e54
commit 97bf841140
8 changed files with 687 additions and 651 deletions

View file

@ -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)
})
}
}

View 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)
})
}
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package v1alpha1 package nodefeaturerule
import ( import (
"fmt" "fmt"
@ -25,40 +25,42 @@ import (
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"k8s.io/klog/v2" "k8s.io/klog/v2"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
) )
var matchOps = map[MatchOp]struct{}{ var matchOps = map[nfdv1alpha1.MatchOp]struct{}{
MatchAny: {}, nfdv1alpha1.MatchAny: {},
MatchIn: {}, nfdv1alpha1.MatchIn: {},
MatchNotIn: {}, nfdv1alpha1.MatchNotIn: {},
MatchInRegexp: {}, nfdv1alpha1.MatchInRegexp: {},
MatchExists: {}, nfdv1alpha1.MatchExists: {},
MatchDoesNotExist: {}, nfdv1alpha1.MatchDoesNotExist: {},
MatchGt: {}, nfdv1alpha1.MatchGt: {},
MatchLt: {}, nfdv1alpha1.MatchLt: {},
MatchGtLt: {}, nfdv1alpha1.MatchGtLt: {},
MatchIsTrue: {}, nfdv1alpha1.MatchIsTrue: {},
MatchIsFalse: {}, nfdv1alpha1.MatchIsFalse: {},
} }
// Match evaluates the MatchExpression against a single input value. // evaluateMatchExpression evaluates the MatchExpression against a single input value.
func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) { func evaluateMatchExpression(m *nfdv1alpha1.MatchExpression, valid bool, value interface{}) (bool, error) {
if _, ok := matchOps[m.Op]; !ok { if _, ok := matchOps[m.Op]; !ok {
return false, fmt.Errorf("invalid Op %q", m.Op) return false, fmt.Errorf("invalid Op %q", m.Op)
} }
switch m.Op { switch m.Op {
case MatchAny: case nfdv1alpha1.MatchAny:
if len(m.Value) != 0 { 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 false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
} }
return true, nil return true, nil
case MatchExists: case nfdv1alpha1.MatchExists:
if len(m.Value) != 0 { 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 false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
} }
return valid, nil return valid, nil
case MatchDoesNotExist: case nfdv1alpha1.MatchDoesNotExist:
if len(m.Value) != 0 { 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 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 { if valid {
value := fmt.Sprintf("%v", value) value := fmt.Sprintf("%v", value)
switch m.Op { switch m.Op {
case MatchIn: case nfdv1alpha1.MatchIn:
if len(m.Value) == 0 { if len(m.Value) == 0 {
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op) 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 return true, nil
} }
} }
case MatchNotIn: case nfdv1alpha1.MatchNotIn:
if len(m.Value) == 0 { if len(m.Value) == 0 {
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op) 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 return true, nil
case MatchInRegexp: case nfdv1alpha1.MatchInRegexp:
if len(m.Value) == 0 { if len(m.Value) == 0 {
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op) 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 return true, nil
} }
} }
case MatchGt, MatchLt: case nfdv1alpha1.MatchGt, nfdv1alpha1.MatchLt:
if len(m.Value) != 1 { 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) 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) 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 return true, nil
} }
case MatchGtLt: case nfdv1alpha1.MatchGtLt:
if len(m.Value) != 2 { 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) 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 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 return v > lr[0] && v < lr[1], nil
case MatchIsTrue: case nfdv1alpha1.MatchIsTrue:
if len(m.Value) != 0 { 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 false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
} }
return value == "true", nil return value == "true", nil
case MatchIsFalse: case nfdv1alpha1.MatchIsFalse:
if len(m.Value) != 0 { 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 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 return false, nil
} }
// MatchKeys evaluates the MatchExpression against a set of keys. // evaluateMatchExpressionKeys evaluates the MatchExpression against a set of keys.
func (m *MatchExpression) MatchKeys(name string, keys map[string]Nil) (bool, error) { func evaluateMatchExpressionKeys(m *nfdv1alpha1.MatchExpression, name string, keys map[string]nfdv1alpha1.Nil) (bool, error) {
matched := false matched := false
_, ok := keys[name] _, ok := keys[name]
switch m.Op { switch m.Op {
case MatchAny: case nfdv1alpha1.MatchAny:
matched = true matched = true
case MatchExists: case nfdv1alpha1.MatchExists:
matched = ok matched = ok
case MatchDoesNotExist: case nfdv1alpha1.MatchDoesNotExist:
matched = !ok matched = !ok
default: default:
return false, fmt.Errorf("invalid Op %q when matching keys", m.Op) 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 return matched, nil
} }
// MatchValues evaluates the MatchExpression against a set of key-value pairs. // evaluateMatchExpressionValues evaluates the MatchExpression against a set of key-value pairs.
func (m *MatchExpression) MatchValues(name string, values map[string]string) (bool, error) { func evaluateMatchExpressionValues(m *nfdv1alpha1.MatchExpression, name string, values map[string]string) (bool, error) {
v, ok := values[name] v, ok := values[name]
matched, err := m.Match(ok, v) matched, err := evaluateMatchExpression(m, ok, v)
if err != nil { if err != nil {
return false, err 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. // 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{} ret := []MatchedElement{}
for k := range keys { 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 return false, nil, err
} else if match { } else if match {
ret = append(ret, MatchedElement{"Name": k}) 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. // 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{} ret := []MatchedElement{}
for k, v := range values { 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 return false, nil, err
} else if match { } else if match {
ret = append(ret, MatchedElement{"Name": k, "Value": v}) 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 // MatchInstanceAttributeNames evaluates the MatchExpression against a set of
// instance features, matching against the names of their attributes. // 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{} ret := []MatchedElement{}
for _, i := range instances { 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 return nil, err
} else if match { } else if match {
ret = append(ret, i.Attributes) ret = append(ret, i.Attributes)
@ -283,23 +285,22 @@ func (m *MatchExpression) MatchInstanceAttributeNames(instances []InstanceFeatur
} }
// MatchKeys evaluates the MatchExpressionSet against a set of keys. // MatchKeys evaluates the MatchExpressionSet against a set of keys.
func (m *MatchExpressionSet) MatchKeys(keys map[string]Nil) (bool, error) { func MatchKeys(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil) (bool, error) {
matched, _, err := m.MatchGetKeys(keys) matched, _, err := MatchGetKeys(m, keys)
return matched, err return matched, err
} }
// MatchedElement holds one matched Instance. // MatchedElement holds one matched Instance.
// +k8s:deepcopy-gen=false
type MatchedElement map[string]string type MatchedElement map[string]string
// MatchGetKeys evaluates the MatchExpressionSet against a set of keys and // 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 // 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. // 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)) ret := make([]MatchedElement, 0, len(*m))
for n, e := range *m { for n, e := range *m {
match, err := e.MatchKeys(n, keys) match, err := evaluateMatchExpressionKeys(e, n, keys)
if err != nil { if err != nil {
return false, nil, err 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. // MatchValues evaluates the MatchExpressionSet against a set of key-value pairs.
func (m *MatchExpressionSet) MatchValues(values map[string]string) (bool, error) { func MatchValues(m *nfdv1alpha1.MatchExpressionSet, values map[string]string) (bool, error) {
matched, _, err := m.MatchGetValues(values) matched, _, err := MatchGetValues(m, values)
return matched, err return matched, err
} }
// MatchGetValues evaluates the MatchExpressionSet against a set of key-value // MatchGetValues evaluates the MatchExpressionSet against a set of key-value
// pairs and returns all matched key-value pairs. Note that an empty // pairs and returns all matched key-value pairs. Note that an empty
// MatchExpressionSet returns a match with an empty slice of matched features. // 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)) ret := make([]MatchedElement, 0, len(*m))
for n, e := range *m { for n, e := range *m {
match, err := e.MatchValues(n, values) match, err := evaluateMatchExpressionValues(e, n, values)
if err != nil { if err != nil {
return false, nil, err 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 // MatchInstances evaluates the MatchExpressionSet against a set of instance
// features, each of which is an individual set of key-value pairs // features, each of which is an individual set of key-value pairs
// (attributes). // (attributes).
func (m *MatchExpressionSet) MatchInstances(instances []InstanceFeature) (bool, error) { func MatchInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, error) {
v, err := m.MatchGetInstances(instances) v, err := MatchGetInstances(m, instances)
return len(v) > 0, err 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 // features, each of which is an individual set of key-value pairs
// (attributes). A slice containing all matching instances is returned. An // (attributes). A slice containing all matching instances is returned. An
// empty (non-nil) slice is returned if no matching instances were found. // 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{} ret := []MatchedElement{}
for _, i := range instances { 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 return nil, err
} else if match { } else if match {
ret = append(ret, i.Attributes) ret = append(ret, i.Attributes)

View 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)
})
}
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package v1alpha1 package nodefeaturerule
import ( import (
"bytes" "bytes"
@ -27,6 +27,7 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/klog/v2" "k8s.io/klog/v2"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/pkg/utils"
) )
@ -41,7 +42,7 @@ type RuleOutput struct {
} }
// Execute the rule against a set of input features. // 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) labels := make(map[string]string)
vars := 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 // Logical OR over the matchAny matchers
matched := false matched := false
for _, matcher := range r.MatchAny { 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 return RuleOutput{}, err
} else if isMatch { } else if isMatch {
matched = true matched = true
@ -62,10 +63,10 @@ func (r *Rule) Execute(features *Features) (RuleOutput, error) {
break break
} }
if err := r.executeLabelsTemplate(matches, labels); err != nil { if err := executeLabelsTemplate(r, matches, labels); err != nil {
return RuleOutput{}, err return RuleOutput{}, err
} }
if err := r.executeVarsTemplate(matches, vars); err != nil { if err := executeVarsTemplate(r, matches, vars); err != nil {
return RuleOutput{}, err return RuleOutput{}, err
} }
} }
@ -77,17 +78,17 @@ func (r *Rule) Execute(features *Features) (RuleOutput, error) {
} }
if len(r.MatchFeatures) > 0 { 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 return RuleOutput{}, err
} else if !isMatch { } else if !isMatch {
klog.V(2).InfoS("rule did not match", "ruleName", r.Name) klog.V(2).InfoS("rule did not match", "ruleName", r.Name)
return RuleOutput{}, nil return RuleOutput{}, nil
} else { } else {
klog.V(4).InfoS("matchFeatures matched", "ruleName", r.Name, "matchedFeatures", utils.DelayedDumper(matches)) 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 return RuleOutput{}, err
} }
if err := r.executeVarsTemplate(matches, vars); err != nil { if err := executeVarsTemplate(r, matches, vars); err != nil {
return RuleOutput{}, err return RuleOutput{}, err
} }
} }
@ -107,7 +108,7 @@ func (r *Rule) Execute(features *Features) (RuleOutput, error) {
return ret, nil 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 == "" { if r.LabelsTemplate == "" {
return nil return nil
} }
@ -127,7 +128,7 @@ func (r *Rule) executeLabelsTemplate(in matchedFeatures, out map[string]string)
return nil 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 == "" { if r.VarsTemplate == "" {
return nil return nil
} }
@ -151,11 +152,11 @@ type matchedFeatures map[string]domainMatchedFeatures
type domainMatchedFeatures map[string][]MatchedElement type domainMatchedFeatures map[string][]MatchedElement
func (e *MatchAnyElem) match(features *Features) (bool, matchedFeatures, error) { func evaluateMatchAnyElem(e *nfdv1alpha1.MatchAnyElem, features *nfdv1alpha1.Features) (bool, matchedFeatures, error) {
return e.MatchFeatures.match(features) 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)) matches := make(matchedFeatures, len(*m))
// Logical AND over the terms // Logical AND over the terms
@ -180,30 +181,30 @@ func (m *FeatureMatcher) match(features *Features) (bool, matchedFeatures, error
var err error var err error
if f, ok := features.Flags[featureName]; ok { if f, ok := features.Flags[featureName]; ok {
if term.MatchExpressions != nil { if term.MatchExpressions != nil {
isMatch, matchedElems, err = term.MatchExpressions.MatchGetKeys(f.Elements) isMatch, matchedElems, err = MatchGetKeys(term.MatchExpressions, f.Elements)
} }
var meTmp []MatchedElement var meTmp []MatchedElement
if err == nil && isMatch && term.MatchName != nil { 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...) matchedElems = append(matchedElems, meTmp...)
} }
} else if f, ok := features.Attributes[featureName]; ok { } else if f, ok := features.Attributes[featureName]; ok {
if term.MatchExpressions != nil { if term.MatchExpressions != nil {
isMatch, matchedElems, err = term.MatchExpressions.MatchGetValues(f.Elements) isMatch, matchedElems, err = MatchGetValues(term.MatchExpressions, f.Elements)
} }
var meTmp []MatchedElement var meTmp []MatchedElement
if err == nil && isMatch && term.MatchName != nil { 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...) matchedElems = append(matchedElems, meTmp...)
} }
} else if f, ok := features.Instances[featureName]; ok { } else if f, ok := features.Instances[featureName]; ok {
if term.MatchExpressions != nil { if term.MatchExpressions != nil {
matchedElems, err = term.MatchExpressions.MatchGetInstances(f.Elements) matchedElems, err = MatchGetInstances(term.MatchExpressions, f.Elements)
isMatch = len(matchedElems) > 0 isMatch = len(matchedElems) > 0
} }
var meTmp []MatchedElement var meTmp []MatchedElement
if err == nil && isMatch && term.MatchName != nil { 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 isMatch = len(meTmp) > 0
matchedElems = append(matchedElems, meTmp...) matchedElems = append(matchedElems, meTmp...)

View file

@ -14,217 +14,219 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package v1alpha1 package nodefeaturerule
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
) )
// newMatchExpression returns a new MatchExpression instance. // newMatchExpression returns a new MatchExpression instance.
func newMatchExpression(op MatchOp, values ...string) *MatchExpression { func newMatchExpression(op nfdv1alpha1.MatchOp, values ...string) *nfdv1alpha1.MatchExpression {
return &MatchExpression{ return &nfdv1alpha1.MatchExpression{
Op: op, Op: op,
Value: values, Value: values,
} }
} }
func TestRule(t *testing.T) { func TestRule(t *testing.T) {
f := &Features{} f := &nfdv1alpha1.Features{}
r1 := Rule{Labels: map[string]string{"label-1": "", "label-2": "true"}} r1 := &nfdv1alpha1.Rule{Labels: map[string]string{"label-1": "", "label-2": "true"}}
r2 := Rule{ r2 := &nfdv1alpha1.Rule{
Labels: map[string]string{"label-1": "label-val-1"}, Labels: map[string]string{"label-1": "label-val-1"},
Vars: map[string]string{"var-1": "var-val-1"}, Vars: map[string]string{"var-1": "var-val-1"},
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain-1.kf-1", Feature: "domain-1.kf-1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-1": newMatchExpression(MatchExists), "key-1": newMatchExpression(nfdv1alpha1.MatchExists),
}, },
}, },
}, },
} }
// Test totally empty features // Test totally empty features
m, err := r1.Execute(f) m, err := Execute(r1, f)
assert.Nilf(t, err, "unexpected error: %v", err) assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features") 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") assert.Error(t, err, "matching against a missing feature should have returned an error")
// Test properly initialized empty features // 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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features") assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
assert.Empty(t, r1.Vars, "vars should be empty") 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") assert.Error(t, err, "matching against a missing feature type should have returned an error")
// Test empty feature sets // Test empty feature sets
f.Flags["domain-1.kf-1"] = NewFlagFeatures() f.Flags["domain-1.kf-1"] = nfdv1alpha1.NewFlagFeatures()
f.Attributes["domain-1.vf-1"] = NewAttributeFeatures(nil) f.Attributes["domain-1.vf-1"] = nfdv1alpha1.NewAttributeFeatures(nil)
f.Instances["domain-1.if-1"] = NewInstanceFeatures(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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features") 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.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m.Labels, "unexpected match") assert.Nil(t, m.Labels, "unexpected match")
// Test non-empty feature sets // 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.Attributes["domain-1.vf-1"].Elements["key-1"] = "val-x"
f.Instances["domain-1.if-1"] = NewInstanceFeatures([]InstanceFeature{ f.Instances["domain-1.if-1"] = nfdv1alpha1.NewInstanceFeatures([]nfdv1alpha1.InstanceFeature{
*NewInstanceFeature(map[string]string{"attr-1": "val-x"})}) *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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features") assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
// Test empty MatchExpressions // Test empty MatchExpressions
r1.MatchFeatures = FeatureMatcher{ r1.MatchFeatures = nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain-1.kf-1", 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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r1.Labels, m.Labels, "empty match expression set mathces anything") assert.Equal(t, r1.Labels, m.Labels, "empty match expression set mathces anything")
// Match "key" features // Match "key" features
m, err = r2.Execute(f) m, err = Execute(r2, f)
assert.Nilf(t, err, "unexpected error: %v", err) assert.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m.Labels, "keys should not have matched") assert.Nil(t, m.Labels, "keys should not have matched")
f.Flags["domain-1.kf-1"].Elements["key-1"] = Nil{} f.Flags["domain-1.kf-1"].Elements["key-1"] = nfdv1alpha1.Nil{}
m, err = r2.Execute(f) m, err = Execute(r2, f)
assert.Nilf(t, err, "unexpected error: %v", err) assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r2.Labels, m.Labels, "keys should have matched") assert.Equal(t, r2.Labels, m.Labels, "keys should have matched")
assert.Equal(t, r2.Vars, m.Vars, "vars should be present") assert.Equal(t, r2.Vars, m.Vars, "vars should be present")
// Match "value" features // Match "value" features
r3 := Rule{ r3 := &nfdv1alpha1.Rule{
Labels: map[string]string{"label-3": "label-val-3", "empty": ""}, Labels: map[string]string{"label-3": "label-val-3", "empty": ""},
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain-1.vf-1", Feature: "domain-1.vf-1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-1": newMatchExpression(MatchIn, "val-1"), "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.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m.Labels, "values should not have matched") assert.Nil(t, m.Labels, "values should not have matched")
f.Attributes["domain-1.vf-1"].Elements["key-1"] = "val-1" 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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r3.Labels, m.Labels, "values should have matched") assert.Equal(t, r3.Labels, m.Labels, "values should have matched")
// Match "instance" features // Match "instance" features
r4 := Rule{ r4 := &nfdv1alpha1.Rule{
Labels: map[string]string{"label-4": "label-val-4"}, Labels: map[string]string{"label-4": "label-val-4"},
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain-1.if-1", Feature: "domain-1.if-1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"attr-1": newMatchExpression(MatchIn, "val-1"), "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.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m.Labels, "instances should not have matched") assert.Nil(t, m.Labels, "instances should not have matched")
f.Instances["domain-1.if-1"].Elements[0].Attributes["attr-1"] = "val-1" 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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r4.Labels, m.Labels, "instances should have matched") assert.Equal(t, r4.Labels, m.Labels, "instances should have matched")
// Test multiple feature matchers // Test multiple feature matchers
r5 := Rule{ r5 := &nfdv1alpha1.Rule{
Labels: map[string]string{"label-5": "label-val-5"}, Labels: map[string]string{"label-5": "label-val-5"},
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain-1.vf-1", Feature: "domain-1.vf-1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-1": newMatchExpression(MatchIn, "val-x"), "key-1": newMatchExpression(nfdv1alpha1.MatchIn, "val-x"),
}, },
}, },
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain-1.if-1", Feature: "domain-1.if-1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"attr-1": newMatchExpression(MatchIn, "val-1"), "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.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m.Labels, "instances should not have matched") assert.Nil(t, m.Labels, "instances should not have matched")
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(MatchIn, "val-1") (*r5.MatchFeatures[0].MatchExpressions)["key-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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m.Labels, "instances should have matched") assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
// Test MatchAny // Test MatchAny
r5.MatchAny = []MatchAnyElem{ r5.MatchAny = []nfdv1alpha1.MatchAnyElem{
{ {
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain-1.kf-1", Feature: "domain-1.kf-1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-na": newMatchExpression(MatchExists), "key-na": newMatchExpression(nfdv1alpha1.MatchExists),
}, },
}, },
}, },
}, },
} }
m, err = r5.Execute(f) m, err = Execute(r5, f)
assert.Nilf(t, err, "unexpected error: %v", err) assert.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m.Labels, "instances should not have matched") assert.Nil(t, m.Labels, "instances should not have matched")
r5.MatchAny = append(r5.MatchAny, r5.MatchAny = append(r5.MatchAny,
MatchAnyElem{ nfdv1alpha1.MatchAnyElem{
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain-1.kf-1", Feature: "domain-1.kf-1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-1": newMatchExpression(MatchExists), "key-1": newMatchExpression(nfdv1alpha1.MatchExists),
}, },
}, },
}, },
}) })
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(MatchIn, "val-1") (*r5.MatchFeatures[0].MatchExpressions)["key-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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m.Labels, "instances should have matched") assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
} }
func TestTemplating(t *testing.T) { func TestTemplating(t *testing.T) {
f := &Features{ f := &nfdv1alpha1.Features{
Flags: map[string]FlagFeatureSet{ Flags: map[string]nfdv1alpha1.FlagFeatureSet{
"domain_1.kf_1": { "domain_1.kf_1": {
Elements: map[string]Nil{ Elements: map[string]nfdv1alpha1.Nil{
"key-a": {}, "key-a": {},
"key-b": {}, "key-b": {},
"key-c": {}, "key-c": {},
}, },
}, },
}, },
Attributes: map[string]AttributeFeatureSet{ Attributes: map[string]nfdv1alpha1.AttributeFeatureSet{
"domain_1.vf_1": { "domain_1.vf_1": {
Elements: map[string]string{ Elements: map[string]string{
"key-1": "val-1", "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": { "domain_1.if_1": {
Elements: []InstanceFeature{ Elements: []nfdv1alpha1.InstanceFeature{
{ {
Attributes: map[string]string{ Attributes: map[string]string{
"attr-1": "1", "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"}, Labels: map[string]string{"label-1": "label-val-1"},
LabelsTemplate: ` LabelsTemplate: `
label-1=will-be-overridden label-1=will-be-overridden
@ -284,34 +286,34 @@ var-1=value-will-be-overridden-by-vars
var-2= var-2=
{{range .domain_1.kf_1}}kf-{{.Name}}=true {{range .domain_1.kf_1}}kf-{{.Name}}=true
{{end}}`, {{end}}`,
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain_1.kf_1", Feature: "domain_1.kf_1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-a": newMatchExpression(MatchExists), "key-a": newMatchExpression(nfdv1alpha1.MatchExists),
"key-c": newMatchExpression(MatchExists), "key-c": newMatchExpression(nfdv1alpha1.MatchExists),
"foo": newMatchExpression(MatchDoesNotExist), "foo": newMatchExpression(nfdv1alpha1.MatchDoesNotExist),
}, },
}, },
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain_1.vf_1", Feature: "domain_1.vf_1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-1": newMatchExpression(MatchIn, "val-1", "val-2"), "key-1": newMatchExpression(nfdv1alpha1.MatchIn, "val-1", "val-2"),
"bar": newMatchExpression(MatchDoesNotExist), "bar": newMatchExpression(nfdv1alpha1.MatchDoesNotExist),
}, },
}, },
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain_1.if_1", Feature: "domain_1.if_1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"attr-1": newMatchExpression(MatchLt, "100"), "attr-1": newMatchExpression(nfdv1alpha1.MatchLt, "100"),
}, },
}, },
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain_1.if_1", Feature: "domain_1.if_1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"attr-1": newMatchExpression(MatchExists), "attr-1": newMatchExpression(nfdv1alpha1.MatchExists),
"attr-2": newMatchExpression(MatchExists), "attr-2": newMatchExpression(nfdv1alpha1.MatchExists),
"attr-3": newMatchExpression(MatchExists), "attr-3": newMatchExpression(nfdv1alpha1.MatchExists),
}, },
}, },
}, },
@ -319,7 +321,7 @@ var-2=
// test with empty MatchFeatures, but with MatchAny // test with empty MatchFeatures, but with MatchAny
r3 := r1.DeepCopy() r3 := r1.DeepCopy()
r3.MatchAny = []MatchAnyElem{{MatchFeatures: r3.MatchFeatures}} r3.MatchAny = []nfdv1alpha1.MatchAnyElem{{MatchFeatures: r3.MatchFeatures}}
r3.MatchFeatures = nil r3.MatchFeatures = nil
expectedLabels := map[string]string{ expectedLabels := map[string]string{
@ -346,12 +348,12 @@ var-2=
"kf-foo": "true", "kf-foo": "true",
} }
m, err := r1.Execute(f) m, err := Execute(r1, f)
assert.Nilf(t, err, "unexpected error: %v", err) assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched") assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
assert.Equal(t, expectedVars, m.Vars, "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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched") assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
assert.Equal(t, expectedVars, m.Vars, "instances should have matched") assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
@ -359,60 +361,60 @@ var-2=
// //
// Test error cases // Test error cases
// //
r2 := Rule{ r2 := &nfdv1alpha1.Rule{
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
// We need at least one matcher to match to execute the template. // We need at least one matcher to match to execute the template.
// Use a simple empty matchexpression set to match anything. // Use a simple empty matchexpression set to match anything.
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain_1.kf_1", Feature: "domain_1.kf_1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-a": newMatchExpression(MatchExists), "key-a": newMatchExpression(nfdv1alpha1.MatchExists),
}, },
}, },
}, },
} }
r2.LabelsTemplate = "foo=bar" r2.LabelsTemplate = "foo=bar"
m, err = r2.Execute(f) m, err = Execute(r2, f)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, map[string]string{"foo": "bar"}, m.Labels, "instances should have matched") assert.Equal(t, map[string]string{"foo": "bar"}, m.Labels, "instances should have matched")
assert.Empty(t, m.Vars) assert.Empty(t, m.Vars)
r2.LabelsTemplate = "foo" r2.LabelsTemplate = "foo"
_, err = r2.Execute(f) _, err = Execute(r2, f)
assert.Error(t, err) assert.Error(t, err)
r2.LabelsTemplate = "{{" r2.LabelsTemplate = "{{"
_, err = r2.Execute(f) _, err = Execute(r2, f)
assert.Error(t, err) assert.Error(t, err)
r2.LabelsTemplate = "" r2.LabelsTemplate = ""
r2.VarsTemplate = "bar=baz" r2.VarsTemplate = "bar=baz"
m, err = r2.Execute(f) m, err = Execute(r2, f)
assert.Nil(t, err) assert.Nil(t, err)
assert.Empty(t, m.Labels) assert.Empty(t, m.Labels)
assert.Equal(t, map[string]string{"bar": "baz"}, m.Vars, "instances should have matched") assert.Equal(t, map[string]string{"bar": "baz"}, m.Vars, "instances should have matched")
r2.VarsTemplate = "bar" r2.VarsTemplate = "bar"
_, err = r2.Execute(f) _, err = Execute(r2, f)
assert.Error(t, err) assert.Error(t, err)
r2.VarsTemplate = "{{" r2.VarsTemplate = "{{"
_, err = r2.Execute(f) _, err = Execute(r2, f)
assert.Error(t, err) assert.Error(t, err)
// //
// Test matchName // Test matchName
// //
r4 := Rule{ r4 := &nfdv1alpha1.Rule{
LabelsTemplate: "{{range .domain_1.vf_1}}{{.Name}}={{.Value}}\n{{end}}", LabelsTemplate: "{{range .domain_1.vf_1}}{{.Name}}={{.Value}}\n{{end}}",
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain_1.vf_1", Feature: "domain_1.vf_1",
MatchExpressions: &MatchExpressionSet{ MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
"key-5": newMatchExpression(MatchDoesNotExist), "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": "", "key-5": "",
} }
m, err = r4.Execute(f) m, err = Execute(r4, f)
assert.Nilf(t, err, "unexpected error: %v", err) assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched") assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
r4 = Rule{ r4 = &nfdv1alpha1.Rule{
Labels: map[string]string{"should-not-match": "true"}, Labels: map[string]string{"should-not-match": "true"},
MatchFeatures: FeatureMatcher{ MatchFeatures: nfdv1alpha1.FeatureMatcher{
FeatureMatcherTerm{ nfdv1alpha1.FeatureMatcherTerm{
Feature: "domain_1.vf_1", 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.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, map[string]string(nil), m.Labels, "instances should have matched") assert.Equal(t, map[string]string(nil), m.Labels, "instances should have matched")
} }

View file

@ -54,6 +54,7 @@ import (
"sigs.k8s.io/node-feature-discovery/pkg/apihelper" "sigs.k8s.io/node-feature-discovery/pkg/apihelper"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1" 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" "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
pb "sigs.k8s.io/node-feature-discovery/pkg/labeler" pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/pkg/utils" "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) klog.InfoS("executing NodeFeatureRule", "nodefeaturerule", klog.KObj(spec), "nodeName", nodeName)
} }
for _, rule := range spec.Spec.Rules { for _, rule := range spec.Spec.Rules {
ruleOut, err := rule.Execute(features) ruleOut, err := nodefeaturerule.Execute(&rule, features)
if err != nil { if err != nil {
klog.ErrorS(err, "failed to process rule", "ruleName", rule.Name, "nodefeaturerule", klog.KObj(spec), "nodeName", nodeName) klog.ErrorS(err, "failed to process rule", "ruleName", rule.Name, "nodefeaturerule", klog.KObj(spec), "nodeName", nodeName)
nfrProcessingErrors.Inc() nfrProcessingErrors.Inc()

View file

@ -23,6 +23,7 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1" 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/pkg/utils"
"sigs.k8s.io/node-feature-discovery/source" "sigs.k8s.io/node-feature-discovery/source"
api "sigs.k8s.io/node-feature-discovery/source/custom/api" 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)) klog.V(2).InfoS("resolving custom features", "configuration", utils.DelayedDumper(allFeatureConfig))
// Iterate over features // Iterate over features
for _, rule := range allFeatureConfig { for _, rule := range allFeatureConfig {
ruleOut, err := rule.Execute(features) ruleOut, err := nodefeaturerule.Execute(&rule, features)
if err != nil { if err != nil {
klog.ErrorS(err, "failed to execute rule") klog.ErrorS(err, "failed to execute rule")
continue continue