1
0
Fork 0
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:
Kubernetes Prow Robot 2024-05-24 03:42:30 -07:00 committed by GitHub
commit 814255b7f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 700 additions and 88 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {

View file

@ -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
// //

View file

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