mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-14 11:57:51 +00:00
apis/nfd: allow different types of features of the same name
This patch changes the handling of NodeFeatureRules so that one feature name (say "cpu.cpuid") can hold different types of features (flags, attributes and/or instances). Requiring features to choose one single type has not been a limitation of the API itself (and there has been no validation on this) but an implementation decision. The new evalutation logic of match expressions is such that "flags" and "attributes" are basically evaluated as an union - they are both maps but "flags" just don't have any value associated with the key. However, "instances" are handled separately as that is basically an array of maps and needs to be evaluated in a different way (loop over the array of instances and evaluate expressions against the attributes of each). Because of this difference care must be taken if mixing "instance" features with "flag" and/or "attribute" features. Note that the API types or their validation is not changed - just the implementation of how the NodeFeatureRules are evaluated.
This commit is contained in:
parent
c2e3de68f6
commit
3b448ae623
7 changed files with 700 additions and 79 deletions
|
@ -80,21 +80,6 @@ func (f *Features) InsertAttributeFeatures(domain, feature string, values map[st
|
|||
maps.Copy(f.Attributes[key].Elements, values)
|
||||
}
|
||||
|
||||
// Exists returns a non-empty string if a feature exists. The return value is
|
||||
// the type of the feautre, i.e. "flag", "attribute" or "instance".
|
||||
func (f *Features) Exists(name string) string {
|
||||
if _, ok := f.Flags[name]; ok {
|
||||
return "flag"
|
||||
}
|
||||
if _, ok := f.Attributes[name]; ok {
|
||||
return "attribute"
|
||||
}
|
||||
if _, ok := f.Instances[name]; ok {
|
||||
return "instance"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MergeInto merges two FeatureSpecs into one. Data in the input object takes
|
||||
// precedence (overwrite) over data of the existing object we're merging into.
|
||||
func (in *NodeFeatureSpec) MergeInto(out *NodeFeatureSpec) {
|
||||
|
|
|
@ -113,12 +113,9 @@ func TestInstanceFeatureSet(t *testing.T) {
|
|||
func TestFeature(t *testing.T) {
|
||||
f := Features{}
|
||||
|
||||
// Test Exists() and InsertAttributeFeatures()
|
||||
assert.Empty(t, f.Exists("dom.attr"), "empty features shouldn't contain anything")
|
||||
|
||||
// Test InsertAttributeFeatures()
|
||||
f.InsertAttributeFeatures("dom", "attr", map[string]string{"k1": "v1", "k2": "v2"})
|
||||
expectedAttributes := map[string]string{"k1": "v1", "k2": "v2"}
|
||||
assert.Equal(t, "attribute", f.Exists("dom.attr"), "attribute feature should exist")
|
||||
assert.Equal(t, expectedAttributes, f.Attributes["dom.attr"].Elements)
|
||||
|
||||
f.InsertAttributeFeatures("dom", "attr", map[string]string{"k2": "v2.override", "k3": "v3"})
|
||||
|
|
|
@ -101,7 +101,7 @@ NodeFeature object as NFD uses it to determine the node which it is targeting.
|
|||
|
||||
### Feature types
|
||||
|
||||
Features are divided into three different types:
|
||||
Features have three different types:
|
||||
|
||||
- **flag** features: a set of names without any associated values, e.g. CPUID
|
||||
flags or loaded kernel modules
|
||||
|
@ -912,7 +912,7 @@ true).
|
|||
|
||||
The following features are available for matching:
|
||||
|
||||
| Feature | [Feature type](#feature-types) | Elements | Value type | Description |
|
||||
| Feature | [Feature types](#feature-types) | Elements | Value type | Description |
|
||||
| ---------------- | ------------ | -------- | ---------- | ----------- |
|
||||
| **`cpu.cpuid`** | flag | | | Supported CPU capabilities |
|
||||
| | | **`<cpuid-flag>`** | | CPUID flag is present |
|
||||
|
|
|
@ -248,11 +248,12 @@ bar: { op: Lt, value: ["10"] }
|
|||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
out, err := api.MatchGetInstances(mes, tc.input)
|
||||
res, out, err := api.MatchGetInstances(mes, tc.input)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err := api.MatchInstances(mes, tc.input)
|
||||
res, err = api.MatchInstances(mes, tc.input)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
|
@ -440,6 +441,7 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
|||
name string
|
||||
me *nfdv1alpha1.MatchExpression
|
||||
input I
|
||||
result bool
|
||||
output O
|
||||
err ValueAssertionFunc
|
||||
}
|
||||
|
@ -449,6 +451,7 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
|||
name: "empty input",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchAny},
|
||||
input: I{},
|
||||
result: false,
|
||||
output: O{},
|
||||
err: assert.Nil,
|
||||
},
|
||||
|
@ -462,6 +465,7 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
|||
{Attributes: A{"bar": "1"}},
|
||||
{Attributes: A{"baz": "2"}},
|
||||
},
|
||||
result: false,
|
||||
output: O{},
|
||||
err: assert.Nil,
|
||||
},
|
||||
|
@ -476,6 +480,7 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
|||
{Attributes: A{"bar": "2"}},
|
||||
{Attributes: A{"foo": "3", "baz": "4"}},
|
||||
},
|
||||
result: true,
|
||||
output: O{
|
||||
{"foo": "1"},
|
||||
{"foo": "3", "baz": "4"},
|
||||
|
@ -497,9 +502,416 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
|||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
matched, err := api.MatchInstanceAttributeNames(tc.me, tc.input)
|
||||
match, matched, err := api.MatchInstanceAttributeNames(tc.me, tc.input)
|
||||
assert.Equal(t, tc.result, match)
|
||||
assert.Equal(t, tc.output, matched)
|
||||
tc.err(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchMulti(t *testing.T) {
|
||||
type O = []api.MatchedElement
|
||||
type IK = map[string]nfdv1alpha1.Nil
|
||||
type IV = map[string]string
|
||||
type II = []nfdv1alpha1.InstanceFeature
|
||||
type A = map[string]string
|
||||
|
||||
type TC struct {
|
||||
name string
|
||||
mes *nfdv1alpha1.MatchExpressionSet
|
||||
inputKeys IK
|
||||
inputValues IV
|
||||
inputInstances II
|
||||
output O
|
||||
result BoolAssertionFunc
|
||||
expectErr bool
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{
|
||||
name: "empty expression and nil input",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{},
|
||||
output: O{},
|
||||
result: assert.False,
|
||||
},
|
||||
{
|
||||
name: "empty expression and empty input keys",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{},
|
||||
inputKeys: IK{},
|
||||
inputValues: IV{},
|
||||
inputInstances: II{},
|
||||
output: O{},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "empty expression and empty input values",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{},
|
||||
inputValues: IV{},
|
||||
output: O{},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "empty expression and empty input instances",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{},
|
||||
inputInstances: II{},
|
||||
output: O{},
|
||||
result: assert.False,
|
||||
},
|
||||
{
|
||||
name: "empty expression and one input instance with empty attributes",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{},
|
||||
inputInstances: II{{Attributes: A{}}},
|
||||
output: O{A{}},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "empty expression",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{},
|
||||
inputValues: IV{"foo": "bar"},
|
||||
output: O{},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "keys match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchDoesNotExist},
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "buzz": {}},
|
||||
output: O{{"Name": "bar"}, {"Name": "foo"}},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "keys do not match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchDoesNotExist},
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
},
|
||||
inputKeys: IK{"foo": {}, "bar": {}, "baz": {}},
|
||||
output: O{},
|
||||
result: assert.False,
|
||||
},
|
||||
{
|
||||
name: "keys error",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists, Value: nfdv1alpha1.MatchValue{"val"}},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "buzz": {}},
|
||||
output: nil,
|
||||
result: assert.False,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "values match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"val", "wal"}},
|
||||
"baz": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchGt, Value: nfdv1alpha1.MatchValue{"10"}},
|
||||
},
|
||||
inputValues: IV{"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,
|
||||
},
|
||||
{
|
||||
name: "values do not match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"val", "wal"}},
|
||||
"baz": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchGt, Value: nfdv1alpha1.MatchValue{"10"}},
|
||||
},
|
||||
inputValues: IV{"bar": "val"},
|
||||
output: O{},
|
||||
result: assert.False,
|
||||
},
|
||||
{
|
||||
name: "values error",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn},
|
||||
},
|
||||
inputValues: IV{"foo": "1", "bar": "val", "baz": "123", "buzz": "light"},
|
||||
output: nil,
|
||||
result: assert.False,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "instances match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchLt, Value: nfdv1alpha1.MatchValue{"10"}},
|
||||
},
|
||||
inputInstances: II{{Attributes: A{"foo": "1"}}, {Attributes: A{"foo": "2", "bar": "1"}}},
|
||||
output: O{A{"foo": "2", "bar": "1"}},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "instances do not match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
"baz": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchLt, Value: nfdv1alpha1.MatchValue{"10"}},
|
||||
},
|
||||
inputInstances: II{{Attributes: A{"foo": "1"}}, {Attributes: A{"bar": "1"}}},
|
||||
output: O{},
|
||||
result: assert.False,
|
||||
},
|
||||
{
|
||||
name: "instances error",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn},
|
||||
},
|
||||
inputInstances: II{{Attributes: A{"foo": "1"}}, {Attributes: A{"foo": "2", "bar": "1"}}},
|
||||
output: nil,
|
||||
result: assert.False,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "multi: keys and values either matches",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"1"}},
|
||||
"baz": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "qux": {}},
|
||||
inputValues: IV{"foo": "1", "bar": "2", "quux": "3"},
|
||||
output: O{{"Name": "baz"}, {"Name": "foo", "Value": "1"}},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "multi: keys and values duplicate match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"1"}},
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "qux": {}},
|
||||
inputValues: IV{"foo": "1", "bar": "2", "quux": "3"},
|
||||
output: O{{"Name": "bar"}, {"Name": "bar", "Value": "2"}, {"Name": "foo", "Value": "1"}},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "multi: keys and values NotIn match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchNotIn, Value: nfdv1alpha1.MatchValue{"1", "3"}},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "qux": {}},
|
||||
inputValues: IV{"foo": "1", "bar": "2", "quux": "3"},
|
||||
output: O{{"Name": "bar", "Value": "2"}},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "multi: keys and values NotIn does not match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchNotIn, Value: nfdv1alpha1.MatchValue{"2"}},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "qux": {}},
|
||||
inputValues: IV{"foo": "1", "bar": "2", "quux": "3"},
|
||||
output: O{},
|
||||
result: assert.False,
|
||||
},
|
||||
{
|
||||
name: "multi: keys and values DoesNotExist match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"xyzzy": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchDoesNotExist},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "qux": {}},
|
||||
inputValues: IV{"foo": "1", "bar": "2", "quux": "3"},
|
||||
output: O{{"Name": "xyzzy"}, {"Name": "xyzzy", "Value": ""}},
|
||||
result: assert.True,
|
||||
},
|
||||
{
|
||||
name: "multi: keys and values DoesNotExist does not match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"quux": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchDoesNotExist},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "qux": {}},
|
||||
inputValues: IV{"foo": "1", "bar": "2", "quux": "3"},
|
||||
output: O{},
|
||||
result: assert.False,
|
||||
},
|
||||
{
|
||||
name: "multi: keys, values and instances all match",
|
||||
mes: &nfdv1alpha1.MatchExpressionSet{
|
||||
"foo": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"1"}},
|
||||
"bar": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
},
|
||||
inputKeys: IK{"bar": {}, "baz": {}, "qux": {}},
|
||||
inputValues: IV{"foo": "1", "bar": "2", "quux": "3"},
|
||||
inputInstances: II{
|
||||
{Attributes: A{"foo": "1", "bar": "2"}},
|
||||
{Attributes: A{"foo": "10", "bar": "20"}},
|
||||
},
|
||||
output: O{{"Name": "bar"}, {"Name": "bar", "Value": "2"}, {"Name": "foo", "Value": "1"}, {"bar": "2", "foo": "1"}},
|
||||
result: assert.True,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, out, err := api.MatchMulti(tc.mes, tc.inputKeys, tc.inputValues, tc.inputInstances)
|
||||
if tc.expectErr {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
tc.result(t, res)
|
||||
assert.Equal(t, tc.output, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchNamesMulti(t *testing.T) {
|
||||
type O = []api.MatchedElement
|
||||
type IK = map[string]nfdv1alpha1.Nil
|
||||
type IV = map[string]string
|
||||
type II = []nfdv1alpha1.InstanceFeature
|
||||
type A = map[string]string
|
||||
|
||||
type TC struct {
|
||||
name string
|
||||
me *nfdv1alpha1.MatchExpression
|
||||
inputKeys IK
|
||||
inputValues IV
|
||||
inputInstances II
|
||||
output O
|
||||
result bool
|
||||
expectErr bool
|
||||
}
|
||||
|
||||
tcs := []TC{
|
||||
{
|
||||
name: "nil input",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchAny},
|
||||
result: false,
|
||||
output: O{},
|
||||
},
|
||||
{
|
||||
name: "empty input keys",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchAny},
|
||||
inputKeys: IK{},
|
||||
result: false,
|
||||
output: O{},
|
||||
},
|
||||
{
|
||||
name: "empty input values",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchAny},
|
||||
inputValues: IV{},
|
||||
result: false,
|
||||
output: O{},
|
||||
},
|
||||
{
|
||||
name: "empty input instances",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchAny},
|
||||
inputInstances: II{},
|
||||
result: false,
|
||||
output: O{},
|
||||
},
|
||||
{
|
||||
name: "input keys match",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchExists},
|
||||
inputKeys: IK{"key1": {}, "key2": {}},
|
||||
result: true,
|
||||
output: O{{"Name": "key1"}, {"Name": "key2"}},
|
||||
},
|
||||
{
|
||||
name: "input keys do not match",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchDoesNotExist},
|
||||
inputKeys: IK{"key1": {}, "key2": {}},
|
||||
result: false,
|
||||
output: O{},
|
||||
},
|
||||
{
|
||||
name: "input keys error",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn},
|
||||
inputKeys: IK{"key1": {}, "key2": {}},
|
||||
result: false,
|
||||
output: nil,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "input values match",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"key1"}},
|
||||
inputValues: IV{"key1": "val1", "key2": "val2"},
|
||||
result: true,
|
||||
output: O{{"Name": "key1", "Value": "val1"}},
|
||||
},
|
||||
{
|
||||
name: "input values do not match",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"key3"}},
|
||||
inputValues: IV{"key1": "val1", "key2": "val2"},
|
||||
result: false,
|
||||
output: O{},
|
||||
},
|
||||
{
|
||||
name: "input values error",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn},
|
||||
inputValues: IV{"key1": "val1", "key2": "val2"},
|
||||
result: false,
|
||||
output: nil,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "input instances match",
|
||||
me: &nfdv1alpha1.MatchExpression{
|
||||
Op: nfdv1alpha1.MatchIn,
|
||||
Value: nfdv1alpha1.MatchValue{"foo"},
|
||||
},
|
||||
inputInstances: II{
|
||||
{Attributes: A{"foo": "1"}},
|
||||
{Attributes: A{"bar": "2"}},
|
||||
{Attributes: A{"foo": "3", "baz": "4"}},
|
||||
},
|
||||
result: true,
|
||||
output: O{
|
||||
{"foo": "1"},
|
||||
{"foo": "3", "baz": "4"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "input instances do not match",
|
||||
me: &nfdv1alpha1.MatchExpression{
|
||||
Op: nfdv1alpha1.MatchIn,
|
||||
Value: nfdv1alpha1.MatchValue{"foo"},
|
||||
},
|
||||
inputInstances: II{
|
||||
{Attributes: A{"bar": "1"}},
|
||||
{Attributes: A{"baz": "2"}},
|
||||
},
|
||||
result: false,
|
||||
output: O{},
|
||||
},
|
||||
{
|
||||
name: "input instances error",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn},
|
||||
inputInstances: II{
|
||||
{Attributes: A{"bar": "1"}},
|
||||
},
|
||||
result: false,
|
||||
output: nil,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "input keys, values and instances match",
|
||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"key2"}},
|
||||
inputKeys: IK{"key1": {}, "key2": {}},
|
||||
inputValues: IV{"key1": "val1", "key2": "val2"},
|
||||
inputInstances: II{
|
||||
{Attributes: A{"key1": "1"}},
|
||||
{Attributes: A{"key1": "2"}},
|
||||
{Attributes: A{"key1": "3", "key2": "4"}},
|
||||
},
|
||||
result: true,
|
||||
output: O{{"Name": "key2"}, {"Name": "key2", "Value": "val2"}, {"key1": "3", "key2": "4"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, ret, err := api.MatchNamesMulti(tc.me, tc.inputKeys, tc.inputValues, tc.inputInstances)
|
||||
if tc.expectErr {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
assert.Equal(t, tc.result, res)
|
||||
assert.Equal(t, tc.output, ret)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,17 +263,17 @@ func MatchValueNames(m *nfdv1alpha1.MatchExpression, values map[string]string) (
|
|||
|
||||
// MatchInstanceAttributeNames evaluates the MatchExpression against a set of
|
||||
// instance features, matching against the names of their attributes.
|
||||
func MatchInstanceAttributeNames(m *nfdv1alpha1.MatchExpression, instances []nfdv1alpha1.InstanceFeature) ([]MatchedElement, error) {
|
||||
func MatchInstanceAttributeNames(m *nfdv1alpha1.MatchExpression, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for _, i := range instances {
|
||||
if match, _, err := MatchValueNames(m, i.Attributes); err != nil {
|
||||
return nil, err
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, i.Attributes)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
return len(ret) > 0, ret, nil
|
||||
}
|
||||
|
||||
// MatchKeys evaluates the MatchExpressionSet against a set of keys.
|
||||
|
@ -337,23 +337,153 @@ func MatchGetValues(m *nfdv1alpha1.MatchExpressionSet, values map[string]string)
|
|||
// features, each of which is an individual set of key-value pairs
|
||||
// (attributes).
|
||||
func MatchInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, error) {
|
||||
v, err := MatchGetInstances(m, instances)
|
||||
return len(v) > 0, err
|
||||
isMatch, _, err := MatchGetInstances(m, instances)
|
||||
return isMatch, err
|
||||
}
|
||||
|
||||
// MatchGetInstances evaluates the MatchExpressionSet against a set of instance
|
||||
// features, each of which is an individual set of key-value pairs
|
||||
// (attributes). A slice containing all matching instances is returned. An
|
||||
// empty (non-nil) slice is returned if no matching instances were found.
|
||||
func MatchGetInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) ([]MatchedElement, error) {
|
||||
// (attributes). Returns a boolean that reports whether the expression matched.
|
||||
// Also, returns a slice containing all matching instances. An empty (non-nil)
|
||||
// slice is returned if no matching instances were found.
|
||||
func MatchGetInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for _, i := range instances {
|
||||
if match, err := MatchValues(m, i.Attributes); err != nil {
|
||||
return nil, err
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, i.Attributes)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
return len(ret) > 0, ret, nil
|
||||
}
|
||||
|
||||
// MatchMulti evaluates a MatchExpressionSet against key, value and instance
|
||||
// features all at once. Key and values features are evaluated together so that
|
||||
// a match in either (or both) of them is accepted as success. Instances are
|
||||
// handled separately as the way of evaluating match expressions is different.
|
||||
// This function is written to handle "multi-type" features where one feature
|
||||
// (say "cpu.cpuid") contains multiple types (flag, attribute and/or instance).
|
||||
func MatchMulti(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil, values map[string]string, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||
matchedElems := []MatchedElement{}
|
||||
isMatch := false
|
||||
|
||||
// Keys and values are handled as a union, it is enough to find a match in
|
||||
// either of them
|
||||
if keys != nil || values != nil {
|
||||
// Handle the special case of empty match expression
|
||||
isMatch = true
|
||||
}
|
||||
for n, e := range *m {
|
||||
var (
|
||||
matchK bool
|
||||
matchV bool
|
||||
err error
|
||||
)
|
||||
if keys != nil {
|
||||
matchK, err = evaluateMatchExpressionKeys(e, n, keys)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if matchK {
|
||||
matchedElems = append(matchedElems, MatchedElement{"Name": n})
|
||||
} else if e.Op == nfdv1alpha1.MatchDoesNotExist {
|
||||
// DoesNotExist is special in that both "keys" and "values" should match (i.e. the name is not found in either of them).
|
||||
isMatch = false
|
||||
matchedElems = []MatchedElement{}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if values != nil {
|
||||
matchV, err = evaluateMatchExpressionValues(e, n, values)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if matchV {
|
||||
matchedElems = append(matchedElems, MatchedElement{"Name": n, "Value": values[n]})
|
||||
} else if e.Op == nfdv1alpha1.MatchDoesNotExist {
|
||||
// DoesNotExist is special in that both "keys" and "values" should match (i.e. the name is not found in either of them).
|
||||
isMatch = false
|
||||
matchedElems = []MatchedElement{}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matchK && !matchV {
|
||||
isMatch = false
|
||||
matchedElems = []MatchedElement{}
|
||||
break
|
||||
}
|
||||
}
|
||||
// Sort for reproducible output
|
||||
sort.Slice(matchedElems, func(i, j int) bool { return matchedElems[i]["Name"] < matchedElems[j]["Name"] })
|
||||
|
||||
// Instances are handled separately as the logic is fundamentally different
|
||||
// from keys and values and cannot be combined with them. We want to find
|
||||
// instance(s) that match all match expressions. I.e. the set of all match
|
||||
// expressions are evaluated against every instance separately.
|
||||
ma, me, err := MatchGetInstances(m, instances)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
isMatch = isMatch || ma
|
||||
matchedElems = append(matchedElems, me...)
|
||||
|
||||
return isMatch, matchedElems, nil
|
||||
}
|
||||
|
||||
// MatchNamesMulti evaluates the MatchExpression against the names of key,
|
||||
// value and attributes of instance features all at once. It is meant to handle
|
||||
// "multi-type" features where one feature (say "cpu.cpuid") contains multiple
|
||||
// types (flag, attribute and/or instance).
|
||||
func MatchNamesMulti(m *nfdv1alpha1.MatchExpression, keys map[string]nfdv1alpha1.Nil, values map[string]string, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for k := range keys {
|
||||
if match, err := evaluateMatchExpression(m, true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k})
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range values {
|
||||
if match, err := evaluateMatchExpression(m, true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k, "Value": v})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort for reproducible output
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
||||
|
||||
_, me, err := MatchInstanceAttributeNames(m, instances)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
ret = append(ret, me...)
|
||||
|
||||
if klogV3 := klog.V(3); klogV3.Enabled() {
|
||||
mk := make([]string, len(ret))
|
||||
for i, v := range ret {
|
||||
mk[i] = v["Name"]
|
||||
}
|
||||
mkMsg := strings.Join(mk, ", ")
|
||||
|
||||
if klogV4 := klog.V(4); klogV4.Enabled() {
|
||||
k := make([]string, 0, len(keys))
|
||||
for n := range keys {
|
||||
k = append(k, n)
|
||||
}
|
||||
sort.Strings(k)
|
||||
klogV3.InfoS("matched names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value, "inputKeys", k)
|
||||
} else {
|
||||
klogV3.InfoS("matched names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return len(ret) > 0, ret, nil
|
||||
}
|
||||
|
|
|
@ -179,39 +179,24 @@ func evaluateFeatureMatcher(m *nfdv1alpha1.FeatureMatcher, features *nfdv1alpha1
|
|||
var isMatch = true
|
||||
var matchedElems []MatchedElement
|
||||
var err error
|
||||
if f, ok := features.Flags[featureName]; ok {
|
||||
if term.MatchExpressions != nil {
|
||||
isMatch, matchedElems, err = MatchGetKeys(term.MatchExpressions, f.Elements)
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
isMatch, meTmp, err = MatchKeyNames(term.MatchName, f.Elements)
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
}
|
||||
} else if f, ok := features.Attributes[featureName]; ok {
|
||||
if term.MatchExpressions != nil {
|
||||
isMatch, matchedElems, err = MatchGetValues(term.MatchExpressions, f.Elements)
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
isMatch, meTmp, err = MatchValueNames(term.MatchName, f.Elements)
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
}
|
||||
} else if f, ok := features.Instances[featureName]; ok {
|
||||
if term.MatchExpressions != nil {
|
||||
matchedElems, err = MatchGetInstances(term.MatchExpressions, f.Elements)
|
||||
isMatch = len(matchedElems) > 0
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
meTmp, err = MatchInstanceAttributeNames(term.MatchName, f.Elements)
|
||||
isMatch = len(meTmp) > 0
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
|
||||
}
|
||||
} else {
|
||||
fF, okF := features.Flags[featureName]
|
||||
fA, okA := features.Attributes[featureName]
|
||||
fI, okI := features.Instances[featureName]
|
||||
if !okF && !okA && !okI {
|
||||
return false, nil, fmt.Errorf("feature %q not available", featureName)
|
||||
}
|
||||
|
||||
if term.MatchExpressions != nil {
|
||||
isMatch, matchedElems, err = MatchMulti(term.MatchExpressions, fF.Elements, fA.Elements, fI.Elements)
|
||||
}
|
||||
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
var meTmp []MatchedElement
|
||||
isMatch, meTmp, err = MatchNamesMulti(term.MatchName, fF.Elements, fA.Elements, fI.Elements)
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
}
|
||||
|
||||
matches[dom][nam] = append(matches[dom][nam], matchedElems...)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -133,7 +133,7 @@ func TestRule(t *testing.T) {
|
|||
assert.Equal(t, r3.Labels, m.Labels, "values should have matched")
|
||||
|
||||
// Match "instance" features
|
||||
r4 := &nfdv1alpha1.Rule{
|
||||
r3 = &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"label-4": "label-val-4"},
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
|
@ -144,17 +144,90 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r4, f)
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
f.Instances["domain-1.if-1"].Elements[0].Attributes["attr-1"] = "val-1"
|
||||
m, err = Execute(r4, f)
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r4.Labels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, r3.Labels, m.Labels, "instances should have matched")
|
||||
|
||||
// Match "multi-type" features
|
||||
f2 := nfdv1alpha1.NewFeatures()
|
||||
f2.Flags["dom.feat"] = nfdv1alpha1.NewFlagFeatures("k-1", "k-2")
|
||||
f2.Attributes["dom.feat"] = nfdv1alpha1.NewAttributeFeatures(map[string]string{"a-1": "v-1", "a-2": "v-2"})
|
||||
f2.Instances["dom.feat"] = nfdv1alpha1.NewInstanceFeatures(
|
||||
*nfdv1alpha1.NewInstanceFeature(map[string]string{"ia-1": "iv-1"}),
|
||||
*nfdv1alpha1.NewInstanceFeature(map[string]string{"ia-2": "iv-2"}),
|
||||
)
|
||||
|
||||
r3 = &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"feat": "val-1"},
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "dom.feat",
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"k-1": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "key in multi-type feature should have matched")
|
||||
|
||||
r3.MatchFeatures = nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "dom.feat",
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"a-1": newMatchExpression(nfdv1alpha1.MatchIn, "v-1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "attribute in multi-type feature should have matched")
|
||||
|
||||
r3.MatchFeatures = nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "dom.feat",
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"ia-1": newMatchExpression(nfdv1alpha1.MatchIn, "iv-1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "attribute in multi-type feature should have matched")
|
||||
|
||||
r3.MatchFeatures = nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "dom.feat",
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"k-2": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
"a-2": newMatchExpression(nfdv1alpha1.MatchIn, "v-2"),
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "features in multi-type feature should have matched flags and attributes")
|
||||
|
||||
r3.MatchFeatures = nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "dom.feat",
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"ia-2": newMatchExpression(nfdv1alpha1.MatchIn, "iv-2"),
|
||||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "features in multi-type feature should have matched instance")
|
||||
|
||||
// Test multiple feature matchers
|
||||
r5 := &nfdv1alpha1.Rule{
|
||||
r3 = &nfdv1alpha1.Rule{
|
||||
Labels: map[string]string{"label-5": "label-val-5"},
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
|
@ -171,17 +244,17 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r5, f)
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||
m, err = Execute(r5, f)
|
||||
(*r3.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, r3.Labels, m.Labels, "instances should have matched")
|
||||
|
||||
// Test MatchAny
|
||||
r5.MatchAny = []nfdv1alpha1.MatchAnyElem{
|
||||
r3.MatchAny = []nfdv1alpha1.MatchAnyElem{
|
||||
{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
|
@ -193,11 +266,11 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r5, f)
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
r5.MatchAny = append(r5.MatchAny,
|
||||
r3.MatchAny = append(r3.MatchAny,
|
||||
nfdv1alpha1.MatchAnyElem{
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
|
@ -208,10 +281,10 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
})
|
||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||
m, err = Execute(r5, f)
|
||||
(*r3.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, r3.Labels, m.Labels, "instances should have matched")
|
||||
}
|
||||
|
||||
func TestTemplating(t *testing.T) {
|
||||
|
@ -224,6 +297,13 @@ func TestTemplating(t *testing.T) {
|
|||
"key-c": {},
|
||||
},
|
||||
},
|
||||
"domain.mf": {
|
||||
Elements: map[string]nfdv1alpha1.Nil{
|
||||
"key-a": {},
|
||||
"key-b": {},
|
||||
"key-c": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
Attributes: map[string]nfdv1alpha1.AttributeFeatureSet{
|
||||
"domain_1.vf_1": {
|
||||
|
@ -234,6 +314,12 @@ func TestTemplating(t *testing.T) {
|
|||
"key-4": "val-4",
|
||||
},
|
||||
},
|
||||
"domain.mf": {
|
||||
Elements: map[string]string{
|
||||
"key-d": "val-d",
|
||||
"key-e": "val-e",
|
||||
},
|
||||
},
|
||||
},
|
||||
Instances: map[string]nfdv1alpha1.InstanceFeatureSet{
|
||||
"domain_1.if_1": {
|
||||
|
@ -357,6 +443,32 @@ var-2=
|
|||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
|
||||
|
||||
// Test "multi-type" feature
|
||||
r3 = &nfdv1alpha1.Rule{
|
||||
LabelsTemplate: `
|
||||
{{range .domain.mf}}mf-{{.Name}}=found
|
||||
{{end}}`,
|
||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "domain.mf",
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"key-a": newMatchExpression(nfdv1alpha1.MatchExists),
|
||||
"key-d": newMatchExpression(nfdv1alpha1.MatchIn, "val-d"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedLabels = map[string]string{
|
||||
"mf-key-a": "found",
|
||||
"mf-key-d": "found",
|
||||
}
|
||||
expectedVars = map[string]string{}
|
||||
m, err = Execute(r3, f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
|
||||
|
||||
//
|
||||
// Test error cases
|
||||
//
|
||||
|
|
Loading…
Reference in a new issue