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.
|
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)
|
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.
|
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...)
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue