mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-14 11:57:51 +00:00
Merge pull request #1671 from marquiz/devel/nfd-api-multi-type-feature
apis/nfd: allow different types of features of the same name
This commit is contained in:
commit
814255b7f1
8 changed files with 700 additions and 88 deletions
|
@ -80,21 +80,6 @@ func (f *Features) InsertAttributeFeatures(domain, feature string, values map[st
|
||||||
maps.Copy(f.Attributes[key].Elements, values)
|
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
|
// MergeInto merges two FeatureSpecs into one. Data in the input object takes
|
||||||
// precedence (overwrite) over data of the existing object we're merging into.
|
// precedence (overwrite) over data of the existing object we're merging into.
|
||||||
func (in *NodeFeatureSpec) MergeInto(out *NodeFeatureSpec) {
|
func (in *NodeFeatureSpec) MergeInto(out *NodeFeatureSpec) {
|
||||||
|
|
|
@ -113,12 +113,9 @@ func TestInstanceFeatureSet(t *testing.T) {
|
||||||
func TestFeature(t *testing.T) {
|
func TestFeature(t *testing.T) {
|
||||||
f := Features{}
|
f := Features{}
|
||||||
|
|
||||||
// Test Exists() and InsertAttributeFeatures()
|
// Test InsertAttributeFeatures()
|
||||||
assert.Empty(t, f.Exists("dom.attr"), "empty features shouldn't contain anything")
|
|
||||||
|
|
||||||
f.InsertAttributeFeatures("dom", "attr", map[string]string{"k1": "v1", "k2": "v2"})
|
f.InsertAttributeFeatures("dom", "attr", map[string]string{"k1": "v1", "k2": "v2"})
|
||||||
expectedAttributes := 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)
|
assert.Equal(t, expectedAttributes, f.Attributes["dom.attr"].Elements)
|
||||||
|
|
||||||
f.InsertAttributeFeatures("dom", "attr", map[string]string{"k2": "v2.override", "k3": "v3"})
|
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
|
### 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
|
- **flag** features: a set of names without any associated values, e.g. CPUID
|
||||||
flags or loaded kernel modules
|
flags or loaded kernel modules
|
||||||
|
@ -955,7 +955,7 @@ true).
|
||||||
|
|
||||||
The following features are available for matching:
|
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 |
|
| **`cpu.cpuid`** | flag | | | Supported CPU capabilities |
|
||||||
| | | **`<cpuid-flag>`** | | CPUID flag is present |
|
| | | **`<cpuid-flag>`** | | CPUID flag is present |
|
||||||
|
|
|
@ -248,11 +248,12 @@ bar: { op: Lt, value: ["10"] }
|
||||||
t.Fatal("failed to parse data of test case")
|
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)
|
assert.Equal(t, tc.output, out)
|
||||||
|
tc.result(t, res)
|
||||||
tc.err(t, err)
|
tc.err(t, err)
|
||||||
|
|
||||||
res, err := api.MatchInstances(mes, tc.input)
|
res, err = api.MatchInstances(mes, tc.input)
|
||||||
tc.result(t, res)
|
tc.result(t, res)
|
||||||
tc.err(t, err)
|
tc.err(t, err)
|
||||||
})
|
})
|
||||||
|
@ -440,6 +441,7 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
me *nfdv1alpha1.MatchExpression
|
me *nfdv1alpha1.MatchExpression
|
||||||
input I
|
input I
|
||||||
|
result bool
|
||||||
output O
|
output O
|
||||||
err ValueAssertionFunc
|
err ValueAssertionFunc
|
||||||
}
|
}
|
||||||
|
@ -449,6 +451,7 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
||||||
name: "empty input",
|
name: "empty input",
|
||||||
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchAny},
|
me: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchAny},
|
||||||
input: I{},
|
input: I{},
|
||||||
|
result: false,
|
||||||
output: O{},
|
output: O{},
|
||||||
err: assert.Nil,
|
err: assert.Nil,
|
||||||
},
|
},
|
||||||
|
@ -462,6 +465,7 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
||||||
{Attributes: A{"bar": "1"}},
|
{Attributes: A{"bar": "1"}},
|
||||||
{Attributes: A{"baz": "2"}},
|
{Attributes: A{"baz": "2"}},
|
||||||
},
|
},
|
||||||
|
result: false,
|
||||||
output: O{},
|
output: O{},
|
||||||
err: assert.Nil,
|
err: assert.Nil,
|
||||||
},
|
},
|
||||||
|
@ -476,6 +480,7 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
||||||
{Attributes: A{"bar": "2"}},
|
{Attributes: A{"bar": "2"}},
|
||||||
{Attributes: A{"foo": "3", "baz": "4"}},
|
{Attributes: A{"foo": "3", "baz": "4"}},
|
||||||
},
|
},
|
||||||
|
result: true,
|
||||||
output: O{
|
output: O{
|
||||||
{"foo": "1"},
|
{"foo": "1"},
|
||||||
{"foo": "3", "baz": "4"},
|
{"foo": "3", "baz": "4"},
|
||||||
|
@ -497,9 +502,416 @@ func TestMatchInstanceAttributeNames(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range tcs {
|
for _, tc := range tcs {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
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)
|
assert.Equal(t, tc.output, matched)
|
||||||
tc.err(t, err)
|
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
|
// 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 MatchInstanceAttributeNames(m *nfdv1alpha1.MatchExpression, instances []nfdv1alpha1.InstanceFeature) ([]MatchedElement, error) {
|
func MatchInstanceAttributeNames(m *nfdv1alpha1.MatchExpression, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||||
ret := []MatchedElement{}
|
ret := []MatchedElement{}
|
||||||
|
|
||||||
for _, i := range instances {
|
for _, i := range instances {
|
||||||
if match, _, err := MatchValueNames(m, i.Attributes); err != nil {
|
if match, _, err := MatchValueNames(m, i.Attributes); err != nil {
|
||||||
return nil, err
|
return false, nil, err
|
||||||
} else if match {
|
} else if match {
|
||||||
ret = append(ret, i.Attributes)
|
ret = append(ret, i.Attributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret, nil
|
return len(ret) > 0, ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchKeys evaluates the MatchExpressionSet against a set of keys.
|
// 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
|
// features, each of which is an individual set of key-value pairs
|
||||||
// (attributes).
|
// (attributes).
|
||||||
func MatchInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, error) {
|
func MatchInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, error) {
|
||||||
v, err := MatchGetInstances(m, instances)
|
isMatch, _, err := MatchGetInstances(m, instances)
|
||||||
return len(v) > 0, err
|
return isMatch, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchGetInstances evaluates the MatchExpressionSet against a set of instance
|
// MatchGetInstances 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). A slice containing all matching instances is returned. An
|
// (attributes). Returns a boolean that reports whether the expression matched.
|
||||||
// empty (non-nil) slice is returned if no matching instances were found.
|
// Also, returns a slice containing all matching instances. An empty (non-nil)
|
||||||
func MatchGetInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) ([]MatchedElement, error) {
|
// slice is returned if no matching instances were found.
|
||||||
|
func MatchGetInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||||
ret := []MatchedElement{}
|
ret := []MatchedElement{}
|
||||||
|
|
||||||
for _, i := range instances {
|
for _, i := range instances {
|
||||||
if match, err := MatchValues(m, i.Attributes); err != nil {
|
if match, err := MatchValues(m, i.Attributes); err != nil {
|
||||||
return nil, err
|
return false, nil, err
|
||||||
} else if match {
|
} else if match {
|
||||||
ret = append(ret, i.Attributes)
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,39 +214,24 @@ func evaluateFeatureMatcher(m *nfdv1alpha1.FeatureMatcher, features *nfdv1alpha1
|
||||||
var isMatch = true
|
var isMatch = true
|
||||||
var matchedElems []MatchedElement
|
var matchedElems []MatchedElement
|
||||||
var err error
|
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...)
|
|
||||||
|
|
||||||
}
|
fF, okF := features.Flags[featureName]
|
||||||
} else {
|
fA, okA := features.Attributes[featureName]
|
||||||
|
fI, okI := features.Instances[featureName]
|
||||||
|
if !okF && !okA && !okI {
|
||||||
return false, nil, fmt.Errorf("feature %q not available", featureName)
|
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...)
|
matches[dom][nam] = append(matches[dom][nam], matchedElems...)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -133,7 +133,7 @@ func TestRule(t *testing.T) {
|
||||||
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 := &nfdv1alpha1.Rule{
|
r3 = &nfdv1alpha1.Rule{
|
||||||
Labels: map[string]string{"label-4": "label-val-4"},
|
Labels: map[string]string{"label-4": "label-val-4"},
|
||||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||||
nfdv1alpha1.FeatureMatcherTerm{
|
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.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 = Execute(r4, 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, 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
|
// Test multiple feature matchers
|
||||||
r5 := &nfdv1alpha1.Rule{
|
r3 = &nfdv1alpha1.Rule{
|
||||||
Labels: map[string]string{"label-5": "label-val-5"},
|
Labels: map[string]string{"label-5": "label-val-5"},
|
||||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||||
nfdv1alpha1.FeatureMatcherTerm{
|
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.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(nfdv1alpha1.MatchIn, "val-1")
|
(*r3.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||||
m, err = Execute(r5, 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, r5.Labels, m.Labels, "instances should have matched")
|
assert.Equal(t, r3.Labels, m.Labels, "instances should have matched")
|
||||||
|
|
||||||
// Test MatchAny
|
// Test MatchAny
|
||||||
r5.MatchAny = []nfdv1alpha1.MatchAnyElem{
|
r3.MatchAny = []nfdv1alpha1.MatchAnyElem{
|
||||||
{
|
{
|
||||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||||
nfdv1alpha1.FeatureMatcherTerm{
|
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.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,
|
r3.MatchAny = append(r3.MatchAny,
|
||||||
nfdv1alpha1.MatchAnyElem{
|
nfdv1alpha1.MatchAnyElem{
|
||||||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||||
nfdv1alpha1.FeatureMatcherTerm{
|
nfdv1alpha1.FeatureMatcherTerm{
|
||||||
|
@ -208,10 +281,10 @@ func TestRule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
(*r3.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||||
m, err = Execute(r5, 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, r5.Labels, m.Labels, "instances should have matched")
|
assert.Equal(t, r3.Labels, m.Labels, "instances should have matched")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplating(t *testing.T) {
|
func TestTemplating(t *testing.T) {
|
||||||
|
@ -224,6 +297,13 @@ func TestTemplating(t *testing.T) {
|
||||||
"key-c": {},
|
"key-c": {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"domain.mf": {
|
||||||
|
Elements: map[string]nfdv1alpha1.Nil{
|
||||||
|
"key-a": {},
|
||||||
|
"key-b": {},
|
||||||
|
"key-c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Attributes: map[string]nfdv1alpha1.AttributeFeatureSet{
|
Attributes: map[string]nfdv1alpha1.AttributeFeatureSet{
|
||||||
"domain_1.vf_1": {
|
"domain_1.vf_1": {
|
||||||
|
@ -234,6 +314,12 @@ func TestTemplating(t *testing.T) {
|
||||||
"key-4": "val-4",
|
"key-4": "val-4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"domain.mf": {
|
||||||
|
Elements: map[string]string{
|
||||||
|
"key-d": "val-d",
|
||||||
|
"key-e": "val-e",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Instances: map[string]nfdv1alpha1.InstanceFeatureSet{
|
Instances: map[string]nfdv1alpha1.InstanceFeatureSet{
|
||||||
"domain_1.if_1": {
|
"domain_1.if_1": {
|
||||||
|
@ -357,6 +443,32 @@ var-2=
|
||||||
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")
|
||||||
|
|
||||||
|
// 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
|
// Test error cases
|
||||||
//
|
//
|
||||||
|
|
|
@ -164,25 +164,16 @@ func GetAllFeatures() *nfdv1alpha1.Features {
|
||||||
for k, v := range f.Flags {
|
for k, v := range f.Flags {
|
||||||
// Prefix feature with the name of the source
|
// Prefix feature with the name of the source
|
||||||
k = n + "." + k
|
k = n + "." + k
|
||||||
if typ := features.Exists(k); typ != "" {
|
|
||||||
panic(fmt.Sprintf("feature source %q returned flag feature %q which already exists (type %q)", n, k, typ))
|
|
||||||
}
|
|
||||||
features.Flags[k] = v
|
features.Flags[k] = v
|
||||||
}
|
}
|
||||||
for k, v := range f.Attributes {
|
for k, v := range f.Attributes {
|
||||||
// Prefix feature with the name of the source
|
// Prefix feature with the name of the source
|
||||||
k = n + "." + k
|
k = n + "." + k
|
||||||
if typ := features.Exists(k); typ != "" {
|
|
||||||
panic(fmt.Sprintf("feature source %q returned attribute feature %q which already exists (type %q)", n, k, typ))
|
|
||||||
}
|
|
||||||
features.Attributes[k] = v
|
features.Attributes[k] = v
|
||||||
}
|
}
|
||||||
for k, v := range f.Instances {
|
for k, v := range f.Instances {
|
||||||
// Prefix feature with the name of the source
|
// Prefix feature with the name of the source
|
||||||
k = n + "." + k
|
k = n + "." + k
|
||||||
if typ := features.Exists(k); typ != "" {
|
|
||||||
panic(fmt.Sprintf("feature source %q returned instance feature %q which already exists (type %q)", n, k, typ))
|
|
||||||
}
|
|
||||||
features.Instances[k] = v
|
features.Instances[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue