1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-05 08:17:04 +00:00

source/custom: move rule expressions to pkg/apis/nfd/v1alpha1

Create a new package pkg/apis/nfd/v1alpha1 and migrate the custom rule
expressions over there. This is the first step in creating a new CRD API
for custom rules.
This commit is contained in:
Markus Lehtonen 2021-08-31 11:07:44 +03:00
parent 389ff52ede
commit 0757248055
13 changed files with 567 additions and 546 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package expression
package v1alpha1
import (
"encoding/json"
@ -29,80 +29,6 @@ import (
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
)
// MatchExpressionSet contains a set of MatchExpressions, each of which is
// evaluated against a set of input values.
type MatchExpressionSet map[string]*MatchExpression
// MatchExpression specifies an expression to evaluate against a set of input
// values. It contains an operator that is applied when matching the input and
// an array of values that the operator evaluates the input against.
// NB: CreateMatchExpression or MustCreateMatchExpression() should be used for
// creating new instances.
// NB: Validate() must be called if Op or Value fields are modified or if a new
// instance is created from scratch without using the helper functions.
type MatchExpression struct {
// Op is the operator to be applied.
Op MatchOp
// Value is the list of values that the operand evaluates the input
// against. Value should be empty if the operator is Exists, DoesNotExist,
// IsTrue or IsFalse. Value should contain exactly one element if the
// operator is Gt or Lt and exactly two elements if the operator is GtLt.
// In other cases Value should contain at least one element.
Value MatchValue `json:",omitempty"`
// valueRe caches compiled regexps for "InRegexp" operator
valueRe []*regexp.Regexp
}
// MatchOp is the match operator that is applied on values when evaluating a
// MatchExpression.
type MatchOp string
// MatchValue is the list of values associated with a MatchExpression.
type MatchValue []string
const (
// MatchAny returns always true.
MatchAny MatchOp = ""
// MatchIn returns true if any of the values stored in the expression is
// equal to the input.
MatchIn MatchOp = "In"
// MatchIn returns true if none of the values in the expression are equal
// to the input.
MatchNotIn MatchOp = "NotIn"
// MatchInRegexp treats values of the expression as regular expressions and
// returns true if any of them matches the input.
MatchInRegexp MatchOp = "InRegexp"
// MatchExists returns true if the input is valid. The expression must not
// have any values.
MatchExists MatchOp = "Exists"
// MatchDoesNotExist returns true if the input is not valid. The expression
// must not have any values.
MatchDoesNotExist MatchOp = "DoesNotExist"
// MatchGt returns true if the input is greater than the value of the
// expression (number of values in the expression must be exactly one).
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchGt MatchOp = "Gt"
// MatchLt returns true if the input is less than the value of the
// expression (number of values in the expression must be exactly one).
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchLt MatchOp = "Lt"
// MatchGtLt returns true if the input is between two values, i.e. greater
// than the first value and less than the second value of the expression
// (number of values in the expression must be exactly two). Both the input
// and values must be integer numbers, otherwise an error is returned.
MatchGtLt MatchOp = "GtLt"
// MatchIsTrue returns true if the input holds the value "true". The
// expression must not have any values.
MatchIsTrue MatchOp = "IsTrue"
// MatchIsTrue returns true if the input holds the value "false". The
// expression must not have any values.
MatchIsFalse MatchOp = "IsFalse"
)
var matchOps = map[MatchOp]struct{}{
MatchAny: struct{}{},
MatchIn: struct{}{},

View file

@ -0,0 +1,439 @@
/*
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"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
api "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
)
type BoolAssertionFuncf func(assert.TestingT, bool, string, ...interface{}) bool
type ValueAssertionFuncf func(assert.TestingT, interface{}, string, ...interface{}) bool
func TestCreateMatchExpression(t *testing.T) {
type V = api.MatchValue
type TC struct {
op api.MatchOp
values V
err ValueAssertionFuncf
}
tcs := []TC{
{op: api.MatchAny, err: assert.Nilf}, // #0
{op: api.MatchAny, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchIn, err: assert.NotNilf},
{op: api.MatchIn, values: V{"1"}, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2", "3", "4"}, err: assert.Nilf},
{op: api.MatchNotIn, err: assert.NotNilf},
{op: api.MatchNotIn, values: V{"1"}, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2"}, err: assert.Nilf},
{op: api.MatchInRegexp, err: assert.NotNilf},
{op: api.MatchInRegexp, values: V{"1"}, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"()", "2", "3"}, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"("}, err: assert.NotNilf},
{op: api.MatchExists, err: assert.Nilf},
{op: api.MatchExists, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchDoesNotExist, err: assert.Nilf},
{op: api.MatchDoesNotExist, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchGt, err: assert.NotNilf},
{op: api.MatchGt, values: V{"1"}, err: assert.Nilf},
{op: api.MatchGt, values: V{"-10"}, err: assert.Nilf},
{op: api.MatchGt, values: V{"1", "2"}, err: assert.NotNilf},
{op: api.MatchGt, values: V{""}, err: assert.NotNilf},
{op: api.MatchLt, err: assert.NotNilf},
{op: api.MatchLt, values: V{"1"}, err: assert.Nilf},
{op: api.MatchLt, values: V{"-1"}, err: assert.Nilf},
{op: api.MatchLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: api.MatchLt, values: V{"a"}, err: assert.NotNilf},
{op: api.MatchGtLt, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1", "2"}, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"2", "1"}, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"a", "2"}, err: assert.NotNilf},
{op: api.MatchIsTrue, err: assert.Nilf},
{op: api.MatchIsTrue, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchIsFalse, err: assert.Nilf},
{op: api.MatchIsFalse, values: V{"1", "2"}, err: assert.NotNilf},
}
for i, tc := range tcs {
_, err := api.CreateMatchExpression(tc.op, tc.values...)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMatch(t *testing.T) {
type V = api.MatchValue
type TC struct {
op api.MatchOp
values V
input interface{}
valid bool
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: api.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: api.MatchAny, input: "2", valid: false, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"2"}, input: 2, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1"}, input: 2, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-12", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"val-[0-9]$", "al-[1-9]"}, input: "val-12", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchExists, input: nil, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchExists, input: nil, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, input: false, valid: false, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, input: 3, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, input: 2, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, input: 3, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGt, values: V{"-10"}, input: -3, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, input: "3a", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchLt, values: V{"2"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"-10"}, input: -3, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, input: "1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "1", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "10", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchIsTrue, input: true, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsTrue, input: true, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIsTrue, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsFalse, input: "false", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsFalse, input: "false", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIsFalse, input: "true", valid: true, result: assert.Falsef, err: assert.Nilf},
}
for i, tc := range tcs {
me := api.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.Match(tc.valid, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
// Check some special error cases separately because MustCreateMatch panics
tcs = []TC{
{op: api.MatchGt, values: V{"3.0"}, input: 1, valid: true},
{op: api.MatchLt, values: V{"0x2"}, input: 1, valid: true},
{op: api.MatchGtLt, values: V{"1", "str"}, input: 1, valid: true},
{op: "non-existent-op", values: V{"1"}, input: 1, valid: true},
}
for i, tc := range tcs {
me := api.MatchExpression{Op: tc.op, Value: tc.values}
res, err := me.Match(tc.valid, tc.input)
assert.Falsef(t, res, "err test case #%d (%v) failed", i, tc)
assert.NotNilf(t, err, "err test case #%d (%v) failed", i, tc)
}
}
func TestMatchKeys(t *testing.T) {
type V = api.MatchValue
type I = map[string]feature.Nil
type TC struct {
op api.MatchOp
values V
name string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: api.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: nil, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: I{"bar": {}}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: I{"bar": {}, "foo": {}}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: nil, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: I{}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: I{"bar": {}}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: I{"bar": {}, "foo": {}}, result: assert.Falsef, err: assert.Nilf},
// All other ops should return an error
{op: api.MatchIn, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchNotIn, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchInRegexp, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchGt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchLt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1", "10"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchIsFalse, name: "foo", result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
me := api.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.MatchKeys(tc.name, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMatchValues(t *testing.T) {
type V = []string
type I = map[string]string
type TC struct {
op api.MatchOp
values V
name string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: api.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "3"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "2"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "3"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"1", "[0-8]"}, name: "foo", input: I{"foo": "9"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"1", "[0-8]"}, name: "foo", input: I{"foo": "2"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: I{"foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: nil, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3", "foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3", "foo": "3"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "11"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "-11"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsTrue, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsTrue, name: "foo", input: I{"foo": "true"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIsFalse, name: "foo", input: I{"foo": "true"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsFalse, name: "foo", input: I{"foo": "false"}, result: assert.Truef, err: assert.Nilf},
}
for i, tc := range tcs {
me := api.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.MatchValues(tc.name, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchKeys(t *testing.T) {
type I = map[string]feature.Nil
type TC struct {
mes string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Truef, err: assert.Nilf},
{input: I{"foo": {}}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"foo": {}, "bar": {}, "baz": {}},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: In, value: ["bar"] }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &api.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchKeys(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchValues(t *testing.T) {
type I = map[string]string
type TC struct {
mes string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Truef, err: assert.Nilf},
{input: I{"foo": "bar"}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"bar": "val"},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"foo": "1", "bar": "val", "baz": "123"},
result: assert.Truef, err: assert.Nilf},
{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.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &api.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchValues(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchInstances(t *testing.T) {
type I = feature.InstanceFeature
type A = map[string]string
type TC struct {
mes string
input []I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Falsef, err: assert.Nilf}, // nil instances -> false
{input: []I{}, result: assert.Falsef, err: assert.Nilf}, // zero instances -> false
{input: []I{I{Attributes: A{}}}, result: assert.Truef, err: assert.Nilf}, // one "empty" instance
{mes: `
foo: { op: Exists }
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "1"}}},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"foo": "2", "bar": "1"}}},
result: assert.Truef, err: assert.Nilf},
{mes: `
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "0x1"}}},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &api.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchInstances(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}

View file

@ -0,0 +1,95 @@
/*
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
import (
"regexp"
)
// MatchExpressionSet contains a set of MatchExpressions, each of which is
// evaluated against a set of input values.
type MatchExpressionSet map[string]*MatchExpression
// MatchExpression specifies an expression to evaluate against a set of input
// values. It contains an operator that is applied when matching the input and
// an array of values that the operator evaluates the input against.
// NB: CreateMatchExpression or MustCreateMatchExpression() should be used for
// creating new instances.
// NB: Validate() must be called if Op or Value fields are modified or if a new
// instance is created from scratch without using the helper functions.
type MatchExpression struct {
// Op is the operator to be applied.
Op MatchOp
// Value is the list of values that the operand evaluates the input
// against. Value should be empty if the operator is Exists, DoesNotExist,
// IsTrue or IsFalse. Value should contain exactly one element if the
// operator is Gt or Lt and exactly two elements if the operator is GtLt.
// In other cases Value should contain at least one element.
Value MatchValue `json:",omitempty"`
// valueRe caches compiled regexps for "InRegexp" operator
valueRe []*regexp.Regexp
}
// MatchOp is the match operator that is applied on values when evaluating a
// MatchExpression.
type MatchOp string
// MatchValue is the list of values associated with a MatchExpression.
type MatchValue []string
const (
// MatchAny returns always true.
MatchAny MatchOp = ""
// MatchIn returns true if any of the values stored in the expression is
// equal to the input.
MatchIn MatchOp = "In"
// MatchIn returns true if none of the values in the expression are equal
// to the input.
MatchNotIn MatchOp = "NotIn"
// MatchInRegexp treats values of the expression as regular expressions and
// returns true if any of them matches the input.
MatchInRegexp MatchOp = "InRegexp"
// MatchExists returns true if the input is valid. The expression must not
// have any values.
MatchExists MatchOp = "Exists"
// MatchDoesNotExist returns true if the input is not valid. The expression
// must not have any values.
MatchDoesNotExist MatchOp = "DoesNotExist"
// MatchGt returns true if the input is greater than the value of the
// expression (number of values in the expression must be exactly one).
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchGt MatchOp = "Gt"
// MatchLt returns true if the input is less than the value of the
// expression (number of values in the expression must be exactly one).
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchLt MatchOp = "Lt"
// MatchGtLt returns true if the input is between two values, i.e. greater
// than the first value and less than the second value of the expression
// (number of values in the expression must be exactly two). Both the input
// and values must be integer numbers, otherwise an error is returned.
MatchGtLt MatchOp = "GtLt"
// MatchIsTrue returns true if the input holds the value "true". The
// expression must not have any values.
MatchIsTrue MatchOp = "IsTrue"
// MatchIsTrue returns true if the input holds the value "false". The
// expression must not have any values.
MatchIsFalse MatchOp = "IsFalse"
)

View file

@ -26,9 +26,9 @@ import (
"sigs.k8s.io/yaml"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
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/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/custom/rules"
)
@ -65,7 +65,7 @@ type FeatureMatcher []FeatureMatcherTerm
type FeatureMatcherTerm struct {
Feature string
MatchExpressions expression.MatchExpressionSet
MatchExpressions nfdv1alpha1.MatchExpressionSet
}
type config []CustomRule

View file

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
)
func TestRule(t *testing.T) {
@ -32,7 +32,7 @@ func TestRule(t *testing.T) {
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.kf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchExists)},
MatchExpressions: nfdv1alpha1.MatchExpressionSet{"key-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists)},
},
},
}
@ -95,7 +95,7 @@ func TestRule(t *testing.T) {
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.vf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchIn, "val-1")},
MatchExpressions: nfdv1alpha1.MatchExpressionSet{"key-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1")},
},
},
}
@ -114,7 +114,7 @@ func TestRule(t *testing.T) {
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.if-1",
MatchExpressions: expression.MatchExpressionSet{"attr-1": expression.MustCreateMatchExpression(expression.MatchIn, "val-1")},
MatchExpressions: nfdv1alpha1.MatchExpressionSet{"attr-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1")},
},
},
}
@ -133,11 +133,11 @@ func TestRule(t *testing.T) {
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.vf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchIn, "val-x")},
MatchExpressions: nfdv1alpha1.MatchExpressionSet{"key-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-x")},
},
FeatureMatcherTerm{
Feature: "domain-1.if-1",
MatchExpressions: expression.MatchExpressionSet{"attr-1": expression.MustCreateMatchExpression(expression.MatchIn, "val-1")},
MatchExpressions: nfdv1alpha1.MatchExpressionSet{"attr-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1")},
},
},
}
@ -145,7 +145,7 @@ func TestRule(t *testing.T) {
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m, "instances should not have matched")
r5.MatchFeatures[0].MatchExpressions["key-1"] = expression.MustCreateMatchExpression(expression.MatchIn, "val-1")
r5.MatchFeatures[0].MatchExpressions["key-1"] = nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1")
m, err = r5.execute(f)
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m, "instances should have matched")
@ -156,7 +156,7 @@ func TestRule(t *testing.T) {
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.kf-1",
MatchExpressions: expression.MatchExpressionSet{"key-na": expression.MustCreateMatchExpression(expression.MatchExists)},
MatchExpressions: nfdv1alpha1.MatchExpressionSet{"key-na": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists)},
},
},
},
@ -170,11 +170,11 @@ func TestRule(t *testing.T) {
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.kf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchExists)},
MatchExpressions: nfdv1alpha1.MatchExpressionSet{"key-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists)},
},
},
})
r5.MatchFeatures[0].MatchExpressions["key-1"] = expression.MustCreateMatchExpression(expression.MatchIn, "val-1")
r5.MatchFeatures[0].MatchExpressions["key-1"] = nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1")
m, err = r5.execute(f)
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m, "instances should have matched")

View file

@ -1,439 +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 expression_test
import (
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/yaml"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
e "sigs.k8s.io/node-feature-discovery/source/custom/expression"
)
type BoolAssertionFuncf func(assert.TestingT, bool, string, ...interface{}) bool
type ValueAssertionFuncf func(assert.TestingT, interface{}, string, ...interface{}) bool
func TestCreateMatchExpression(t *testing.T) {
type V = e.MatchValue
type TC struct {
op e.MatchOp
values V
err ValueAssertionFuncf
}
tcs := []TC{
{op: e.MatchAny, err: assert.Nilf}, // #0
{op: e.MatchAny, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchIn, err: assert.NotNilf},
{op: e.MatchIn, values: V{"1"}, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2", "3", "4"}, err: assert.Nilf},
{op: e.MatchNotIn, err: assert.NotNilf},
{op: e.MatchNotIn, values: V{"1"}, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2"}, err: assert.Nilf},
{op: e.MatchInRegexp, err: assert.NotNilf},
{op: e.MatchInRegexp, values: V{"1"}, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"()", "2", "3"}, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"("}, err: assert.NotNilf},
{op: e.MatchExists, err: assert.Nilf},
{op: e.MatchExists, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchDoesNotExist, err: assert.Nilf},
{op: e.MatchDoesNotExist, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchGt, err: assert.NotNilf},
{op: e.MatchGt, values: V{"1"}, err: assert.Nilf},
{op: e.MatchGt, values: V{"-10"}, err: assert.Nilf},
{op: e.MatchGt, values: V{"1", "2"}, err: assert.NotNilf},
{op: e.MatchGt, values: V{""}, err: assert.NotNilf},
{op: e.MatchLt, err: assert.NotNilf},
{op: e.MatchLt, values: V{"1"}, err: assert.Nilf},
{op: e.MatchLt, values: V{"-1"}, err: assert.Nilf},
{op: e.MatchLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: e.MatchLt, values: V{"a"}, err: assert.NotNilf},
{op: e.MatchGtLt, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "2"}, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"2", "1"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"a", "2"}, err: assert.NotNilf},
{op: e.MatchIsTrue, err: assert.Nilf},
{op: e.MatchIsTrue, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchIsFalse, err: assert.Nilf},
{op: e.MatchIsFalse, values: V{"1", "2"}, err: assert.NotNilf},
}
for i, tc := range tcs {
_, err := e.CreateMatchExpression(tc.op, tc.values...)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMatch(t *testing.T) {
type V = e.MatchValue
type TC struct {
op e.MatchOp
values V
input interface{}
valid bool
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: e.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: e.MatchAny, input: "2", valid: false, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"2"}, input: 2, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1"}, input: 2, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-12", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"val-[0-9]$", "al-[1-9]"}, input: "val-12", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchExists, input: nil, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchExists, input: nil, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, input: false, valid: false, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, input: 3, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, input: 2, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, input: 3, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGt, values: V{"-10"}, input: -3, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, input: "3a", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchLt, values: V{"2"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"-10"}, input: -3, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, input: "1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "1", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "10", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsTrue, input: true, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, input: true, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIsTrue, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsFalse, input: "false", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsFalse, input: "false", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIsFalse, input: "true", valid: true, result: assert.Falsef, err: assert.Nilf},
}
for i, tc := range tcs {
me := e.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.Match(tc.valid, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
// Check some special error cases separately because MustCreateMatch panics
tcs = []TC{
{op: e.MatchGt, values: V{"3.0"}, input: 1, valid: true},
{op: e.MatchLt, values: V{"0x2"}, input: 1, valid: true},
{op: e.MatchGtLt, values: V{"1", "str"}, input: 1, valid: true},
{op: "non-existent-op", values: V{"1"}, input: 1, valid: true},
}
for i, tc := range tcs {
me := e.MatchExpression{Op: tc.op, Value: tc.values}
res, err := me.Match(tc.valid, tc.input)
assert.Falsef(t, res, "err test case #%d (%v) failed", i, tc)
assert.NotNilf(t, err, "err test case #%d (%v) failed", i, tc)
}
}
func TestMatchKeys(t *testing.T) {
type V = e.MatchValue
type I = map[string]feature.Nil
type TC struct {
op e.MatchOp
values V
name string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: e.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: nil, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: I{"bar": {}}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: I{"bar": {}, "foo": {}}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: nil, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: I{}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: I{"bar": {}}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: I{"bar": {}, "foo": {}}, result: assert.Falsef, err: assert.Nilf},
// All other ops should return an error
{op: e.MatchIn, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchNotIn, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchInRegexp, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchLt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "10"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsFalse, name: "foo", result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
me := e.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.MatchKeys(tc.name, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMatchValues(t *testing.T) {
type V = []string
type I = map[string]string
type TC struct {
op e.MatchOp
values V
name string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: e.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "3"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "2"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "3"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"1", "[0-8]"}, name: "foo", input: I{"foo": "9"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"1", "[0-8]"}, name: "foo", input: I{"foo": "2"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: I{"foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: nil, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3", "foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3", "foo": "3"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "11"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "-11"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, name: "foo", input: I{"foo": "true"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIsFalse, name: "foo", input: I{"foo": "true"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsFalse, name: "foo", input: I{"foo": "false"}, result: assert.Truef, err: assert.Nilf},
}
for i, tc := range tcs {
me := e.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.MatchValues(tc.name, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchKeys(t *testing.T) {
type I = map[string]feature.Nil
type TC struct {
mes string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Truef, err: assert.Nilf},
{input: I{"foo": {}}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"foo": {}, "bar": {}, "baz": {}},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: In, value: ["bar"] }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &e.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchKeys(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchValues(t *testing.T) {
type I = map[string]string
type TC struct {
mes string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Truef, err: assert.Nilf},
{input: I{"foo": "bar"}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"bar": "val"},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"foo": "1", "bar": "val", "baz": "123"},
result: assert.Truef, err: assert.Nilf},
{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.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &e.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchValues(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchInstances(t *testing.T) {
type I = feature.InstanceFeature
type A = map[string]string
type TC struct {
mes string
input []I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Falsef, err: assert.Nilf}, // nil instances -> false
{input: []I{}, result: assert.Falsef, err: assert.Nilf}, // zero instances -> false
{input: []I{I{Attributes: A{}}}, result: assert.Truef, err: assert.Nilf}, // one "empty" instance
{mes: `
foo: { op: Exists }
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "1"}}},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"foo": "2", "bar": "1"}}},
result: assert.Truef, err: assert.Nilf},
{mes: `
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "0x1"}}},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &e.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchInstances(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}

View file

@ -19,14 +19,14 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/cpu"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
)
// CpuIDRule implements Rule for the custom source
type CpuIDRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
func (r *CpuIDRule) Match() (bool, error) {

View file

@ -19,14 +19,14 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/kernel"
)
// KconfigRule implements Rule for the custom source
type KconfigRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
func (r *KconfigRule) Match() (bool, error) {

View file

@ -19,14 +19,14 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/kernel"
)
// LoadedKModRule matches loaded kernel modules in the system
type LoadedKModRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
// Match loaded kernel modules on provided list of kernel modules

View file

@ -20,14 +20,14 @@ import (
"encoding/json"
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/system"
)
// NodenameRule matches on nodenames configured in a ConfigMap
type NodenameRule struct {
expression.MatchExpression
nfdv1alpha1.MatchExpression
}
func (r *NodenameRule) Match() (bool, error) {
@ -43,8 +43,8 @@ func (r *NodenameRule) UnmarshalJSON(data []byte) error {
return err
}
// Force regexp matching
if r.Op == expression.MatchIn {
r.Op = expression.MatchInRegexp
if r.Op == nfdv1alpha1.MatchIn {
r.Op = nfdv1alpha1.MatchInRegexp
}
// We need to run Validate() because operator forcing above
return r.Validate()

View file

@ -19,13 +19,13 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/pci"
)
type PciIDRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
// Match PCI devices on provided PCI device attributes

View file

@ -19,13 +19,13 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/usb"
)
type UsbIDRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
// Match USB devices on provided USB device attributes

View file

@ -17,7 +17,7 @@ limitations under the License.
package custom
import (
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source/custom/rules"
)
@ -31,8 +31,8 @@ func getStaticFeatureConfig() []CustomRule {
MatchOn: []LegacyMatcher{
{
PciID: &rules.PciIDRule{
MatchExpressionSet: expression.MatchExpressionSet{
"vendor": expression.MustCreateMatchExpression(expression.MatchIn, "15b3"),
MatchExpressionSet: nfdv1alpha1.MatchExpressionSet{
"vendor": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "15b3"),
},
},
},
@ -45,9 +45,9 @@ func getStaticFeatureConfig() []CustomRule {
MatchOn: []LegacyMatcher{
{
LoadedKMod: &rules.LoadedKModRule{
MatchExpressionSet: expression.MatchExpressionSet{
"ib_uverbs": expression.MustCreateMatchExpression(expression.MatchExists),
"rdma_ucm": expression.MustCreateMatchExpression(expression.MatchExists),
MatchExpressionSet: nfdv1alpha1.MatchExpressionSet{
"ib_uverbs": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
"rdma_ucm": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
},
},
},