mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-14 11:57:51 +00:00
apis/nfd: add matchName field in feature matcher terms
Extend the format of feature matcher terms (the elements of the arrayspecified under under matchFeatures field) with new matchName field. The value of this field is an expression that is evaluated against the names of feature elements instead of their values (values are matched with the matchExpressions field, instead). The matchName field is useful e.g. in template rules for creating per-feature-element labels based on feature names (instead of values) and in non-template rules for checking if (at least) one of certain feature element names are present. If both matchExpressions and matchName for certain feature matcher term is specified, they both must match in order to get an overall match. Also, in this case the list of matched features (used in templating) is the union of the results from matchExpressions and matchName. An example of creating an "avx512" label if any AVX512* CPUID feature is present: - name: "avx wildcard rule" labels: avx512: "true" matchFeatures: - feature: cpu.cpuid matchName: {op: InRegexp, value: ["^AVX512"]} An example of a template rule creating a dynamic set of labels based on the existence of certain kconfig options. - name: "kconfig template rule" labelsTemplate: | {{ range .kernel.config }}kconfig-{{ .Name }}={{ .Value }} {{ end }} matchFeatures: - feature: kernel.config matchName: {op: In, value: ["SWAP", "X86", "ARM"]} NOTE: this patch changes the corner case of nil/null match expressions with instance features (i.e. "matchExpressions: null"). Previously, we returned all instances for templating but now a nil match expression is not evaluated and no instances for templating are returned.
This commit is contained in:
parent
443ff80748
commit
fe412a54b9
15 changed files with 551 additions and 66 deletions
|
@ -94,6 +94,13 @@ spec:
|
|||
vendor: {op: In, value: ["8086"]}
|
||||
class: {op: In, value: ["02"]}
|
||||
|
||||
- name: "avx wildcard rule"
|
||||
labels:
|
||||
"my-avx-feature": "true"
|
||||
matchFeatures:
|
||||
- feature: cpu.cpuid
|
||||
matchName: {op: InRegexp, value: ["^AVX512"]}
|
||||
|
||||
# The following features demonstreate label templating capabilities
|
||||
- name: "my system template feature"
|
||||
labelsTemplate: |
|
||||
|
@ -143,3 +150,11 @@ spec:
|
|||
matchExpressions:
|
||||
my.kernel.feature: {op: IsTrue}
|
||||
my.dummy.var: {op: Gt, value: ["0"]}
|
||||
|
||||
- name: "kconfig template rule"
|
||||
labelsTemplate: |
|
||||
{{ range .kernel.config }}kconfig-{{ .Name }}={{ .Value }}
|
||||
{{ end }}
|
||||
matchFeatures:
|
||||
- feature: kernel.config
|
||||
matchName: {op: In, value: ["SWAP", "X86", "ARM"]}
|
||||
|
|
|
@ -190,6 +190,8 @@ spec:
|
|||
in the feature set.
|
||||
properties:
|
||||
feature:
|
||||
description: Feature is the name of the feature
|
||||
set to match against.
|
||||
type: string
|
||||
matchExpressions:
|
||||
additionalProperties:
|
||||
|
@ -229,13 +231,46 @@ spec:
|
|||
required:
|
||||
- op
|
||||
type: object
|
||||
description: MatchExpressionSet contains a set of
|
||||
MatchExpressions, each of which is evaluated against
|
||||
a set of input values.
|
||||
description: MatchExpressions is the set of per-element
|
||||
expressions evaluated. These match against the
|
||||
value of the specified elements.
|
||||
type: object
|
||||
matchName:
|
||||
description: MatchName in an expression that is
|
||||
matched against the name of each element in the
|
||||
feature set.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: Value is the list of values that
|
||||
the operand evaluates the input against. Value
|
||||
should be empty if the operator is Exists,
|
||||
DoesNotExist, IsTrue or IsFalse. Value should
|
||||
contain exactly one element if the operator
|
||||
is Gt or Lt and exactly two elements if the
|
||||
operator is GtLt. In other cases Value should
|
||||
contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
required:
|
||||
- feature
|
||||
- matchExpressions
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
|
@ -251,6 +286,8 @@ spec:
|
|||
are evaluated against each element in the feature set.
|
||||
properties:
|
||||
feature:
|
||||
description: Feature is the name of the feature set to
|
||||
match against.
|
||||
type: string
|
||||
matchExpressions:
|
||||
additionalProperties:
|
||||
|
@ -288,12 +325,44 @@ spec:
|
|||
required:
|
||||
- op
|
||||
type: object
|
||||
description: MatchExpressionSet contains a set of MatchExpressions,
|
||||
each of which is evaluated against a set of input values.
|
||||
description: MatchExpressions is the set of per-element
|
||||
expressions evaluated. These match against the value
|
||||
of the specified elements.
|
||||
type: object
|
||||
matchName:
|
||||
description: MatchName in an expression that is matched
|
||||
against the name of each element in the feature set.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: Value is the list of values that the
|
||||
operand evaluates the input against. Value should
|
||||
be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly
|
||||
one element if the operator is Gt or Lt and exactly
|
||||
two elements if the operator is GtLt. In other cases
|
||||
Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
required:
|
||||
- feature
|
||||
- matchExpressions
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
|
|
|
@ -174,6 +174,13 @@
|
|||
# vendor: {op: In, value: ["8086"]}
|
||||
# class: {op: In, value: ["02"]}
|
||||
#
|
||||
# - name: "avx wildcard rule"
|
||||
# labels:
|
||||
# "my-avx-feature": "true"
|
||||
# matchFeatures:
|
||||
# - feature: cpu.cpuid
|
||||
# matchName: {op: InRegexp, value: ["^AVX512"]}
|
||||
#
|
||||
# # The following features demonstreate label templating capabilities
|
||||
# - name: "my template rule"
|
||||
# labelsTemplate: |
|
||||
|
@ -224,3 +231,10 @@
|
|||
# vendor.io/my.kernel.feature: {op: IsTrue}
|
||||
# my.dummy.var: {op: Gt, value: ["0"]}
|
||||
#
|
||||
# - name: "kconfig template rule"
|
||||
# labelsTemplate: |
|
||||
# {{ range .kernel.config }}kconfig-{{ .Name }}={{ .Value }}
|
||||
# {{ end }}
|
||||
# matchFeatures:
|
||||
# - feature: kernel.config
|
||||
# matchName: {op: In, value: ["SWAP", "X86", "ARM"]}
|
||||
|
|
|
@ -190,6 +190,8 @@ spec:
|
|||
in the feature set.
|
||||
properties:
|
||||
feature:
|
||||
description: Feature is the name of the feature
|
||||
set to match against.
|
||||
type: string
|
||||
matchExpressions:
|
||||
additionalProperties:
|
||||
|
@ -229,13 +231,46 @@ spec:
|
|||
required:
|
||||
- op
|
||||
type: object
|
||||
description: MatchExpressionSet contains a set of
|
||||
MatchExpressions, each of which is evaluated against
|
||||
a set of input values.
|
||||
description: MatchExpressions is the set of per-element
|
||||
expressions evaluated. These match against the
|
||||
value of the specified elements.
|
||||
type: object
|
||||
matchName:
|
||||
description: MatchName in an expression that is
|
||||
matched against the name of each element in the
|
||||
feature set.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: Value is the list of values that
|
||||
the operand evaluates the input against. Value
|
||||
should be empty if the operator is Exists,
|
||||
DoesNotExist, IsTrue or IsFalse. Value should
|
||||
contain exactly one element if the operator
|
||||
is Gt or Lt and exactly two elements if the
|
||||
operator is GtLt. In other cases Value should
|
||||
contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
required:
|
||||
- feature
|
||||
- matchExpressions
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
|
@ -251,6 +286,8 @@ spec:
|
|||
are evaluated against each element in the feature set.
|
||||
properties:
|
||||
feature:
|
||||
description: Feature is the name of the feature set to
|
||||
match against.
|
||||
type: string
|
||||
matchExpressions:
|
||||
additionalProperties:
|
||||
|
@ -288,12 +325,44 @@ spec:
|
|||
required:
|
||||
- op
|
||||
type: object
|
||||
description: MatchExpressionSet contains a set of MatchExpressions,
|
||||
each of which is evaluated against a set of input values.
|
||||
description: MatchExpressions is the set of per-element
|
||||
expressions evaluated. These match against the value
|
||||
of the specified elements.
|
||||
type: object
|
||||
matchName:
|
||||
description: MatchName in an expression that is matched
|
||||
against the name of each element in the feature set.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: Value is the list of values that the
|
||||
operand evaluates the input against. Value should
|
||||
be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly
|
||||
one element if the operator is Gt or Lt and exactly
|
||||
two elements if the operator is GtLt. In other cases
|
||||
Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
required:
|
||||
- feature
|
||||
- matchExpressions
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
|
|
|
@ -312,6 +312,13 @@ worker:
|
|||
# vendor: {op: In, value: ["8086"]}
|
||||
# class: {op: In, value: ["02"]}
|
||||
#
|
||||
# - name: "avx wildcard rule"
|
||||
# labels:
|
||||
# "my-avx-feature": "true"
|
||||
# matchFeatures:
|
||||
# - feature: cpu.cpuid
|
||||
# matchName: {op: InRegexp, value: ["^AVX512"]}
|
||||
#
|
||||
# # The following features demonstreate label templating capabilities
|
||||
# - name: "my template rule"
|
||||
# labelsTemplate: |
|
||||
|
@ -362,6 +369,13 @@ worker:
|
|||
# vendor.io/my.kernel.feature: {op: IsTrue}
|
||||
# my.dummy.var: {op: Gt, value: ["0"]}
|
||||
#
|
||||
# - name: "kconfig template rule"
|
||||
# labelsTemplate: |
|
||||
# {{ range .kernel.config }}kconfig-{{ .Name }}={{ .Value }}
|
||||
# {{ end }}
|
||||
# matchFeatures:
|
||||
# - feature: kernel.config
|
||||
# matchName: {op: In, value: ["SWAP", "X86", "ARM"]}
|
||||
### <NFD-WORKER-CONF-END-DO-NOT-REMOVE>
|
||||
|
||||
metricsPort: 8081
|
||||
|
|
|
@ -761,16 +761,46 @@ of them must match for the rule to trigger.
|
|||
value:
|
||||
- <value-1>
|
||||
- ...
|
||||
matchName:
|
||||
op: <op>
|
||||
value:
|
||||
- <value-1>
|
||||
- ...
|
||||
```
|
||||
|
||||
The `.matchFeatures[].feature` field specifies the feature against which to
|
||||
match.
|
||||
The `.matchFeatures[].feature` field specifies the feature which to evaluate.
|
||||
|
||||
The `.matchFeatures[].matchExpressions` field specifies a map of expressions
|
||||
which to evaluate against the elements of the feature.
|
||||
> **NOTE:**If both [`matchExpressions`](#matchexpressions) and
|
||||
> [`matchName`](#matchname) are specified, they both must match.
|
||||
|
||||
In each MatchExpression `op` specifies the operator to apply. Valid values are
|
||||
described below.
|
||||
##### matchExpressions
|
||||
|
||||
The `.matchFeatures[].matchExpressions` field is used to match against the
|
||||
value(s) of a feature. The `matchExpressions` field consists of a set of
|
||||
expressions, each of which is evaluated against all elements of the specified
|
||||
feature.
|
||||
|
||||
```yaml
|
||||
matchExpressions:
|
||||
<key>:
|
||||
op: <op>
|
||||
value:
|
||||
- <value-1>
|
||||
- ...
|
||||
```
|
||||
|
||||
In each MatchExpression the `key` specifies the name of of the feature element
|
||||
(*flag* and *attribute* features) or name of the attribute (*instance*
|
||||
features) which to look for. The behavior of MatchExpression depends on the
|
||||
[feature type](#feature-types):
|
||||
|
||||
- for *flag* and *attribute* features the MatchExpression operates on the
|
||||
feature element whose name matches the `<key>`
|
||||
- for *instance* features all MatchExpressions are evaluated against the
|
||||
attributes of each instance separately
|
||||
|
||||
The `op` field specifies the operator to apply. Valid values are described
|
||||
below.
|
||||
|
||||
| Operator | Number of values | Matches when |
|
||||
| --------------- | ---------------- | ----------- |
|
||||
|
@ -788,11 +818,57 @@ described below.
|
|||
The `value` field of MatchExpression is a list of string arguments to the
|
||||
operator.
|
||||
|
||||
The behavior of MatchExpression depends on the [feature type](#feature-types):
|
||||
for *flag* and *attribute* features the MatchExpression operates on the feature
|
||||
element whose name matches the `<key>`. However, for *instance* features all
|
||||
MatchExpressions are evaluated against the attributes of each instance
|
||||
separately.
|
||||
##### matchName
|
||||
|
||||
The `.matchFeatures[].matchName` field is used to match against the
|
||||
name(s) of a feature (whereas the [`matchExpressions`](#matchexpressions) field
|
||||
matches against the value(s). The `matchName` field consists of a single
|
||||
expression which is evaulated against the name of each element of the specified
|
||||
feature.
|
||||
|
||||
```yaml
|
||||
matchName:
|
||||
op: <op>
|
||||
value:
|
||||
- <value-1>
|
||||
- ...
|
||||
```
|
||||
|
||||
The behavior of `matchName` depends on the [feature type](#feature-types):
|
||||
|
||||
- for *flag* and *attribute* features the expression is evaluated against the
|
||||
name of each element
|
||||
- for *instance* features the expression is evaluated against the name of
|
||||
each attribute, for each element (instance) separately (matches if the
|
||||
attributes of any of the elements satisfy the expression)
|
||||
|
||||
The `op` field specifies the operator to apply. Same operators as for
|
||||
[`matchExpressions`](#matchexpressions) above are available.
|
||||
|
||||
| Operator | Number of values | Matches |
|
||||
| --------------- | ---------------- | ----------- |
|
||||
| `In` | 1 or greater | All name is equal to one of the values |
|
||||
| `NotIn` | 1 or greater | All name that is not equal to any of the values |
|
||||
| `InRegexp` | 1 or greater | All name that matches any of the values (treated as regexps) |
|
||||
| `Exists` | 0 | All elements |
|
||||
|
||||
Other operators are not practical with `matchName` (`DoesNotExist` never
|
||||
matches; `Gt`,`Lt` and `GtLt` are only usable if feature names are integers;
|
||||
`IsTrue` and `IsFalse` are only usable if the feature name is `true` or
|
||||
`false`).
|
||||
|
||||
The `value` field is a list of string arguments to the operator.
|
||||
|
||||
An example:
|
||||
|
||||
```yaml
|
||||
matchFeatures:
|
||||
- feature: cpu.cpuid
|
||||
matchName: {op: InRegexp, value: ["^AVX"]}
|
||||
```
|
||||
|
||||
The snippet above would match if any CPUID feature starting with AVX is present
|
||||
(e.g. AVX1 or AVX2 or AVX512F etc).
|
||||
|
||||
#### matchAny
|
||||
|
||||
|
@ -992,6 +1068,10 @@ feature:
|
|||
```
|
||||
|
||||
<!-- {% endraw %} -->
|
||||
> **NOTE:**If both `matchExpressions` and `matchName` for a feature matcher
|
||||
> term (see [`matchFeatures`](#matchfeatures)) is specified, the list of
|
||||
> matched features (for the template engine) is the union from both of these.
|
||||
<!-- note #2 -->
|
||||
> **NOTE:** In case of matchAny is specified, the template is executed
|
||||
> separately against each individual `matchFeatures` field and the final set of
|
||||
> labels will be superset of all these separate template expansions. E.g.
|
||||
|
|
|
@ -258,6 +258,88 @@ func (m *MatchExpression) MatchValues(name string, values map[string]string) (bo
|
|||
return matched, nil
|
||||
}
|
||||
|
||||
// MatchKeyNames evaluates the MatchExpression against names of a set of key features.
|
||||
func (m *MatchExpression) MatchKeyNames(keys map[string]Nil) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for k := range keys {
|
||||
if match, err := m.Match(true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k})
|
||||
}
|
||||
}
|
||||
// Sort for reproducible output
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
||||
|
||||
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 (key) names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value, "inputKeys", k)
|
||||
} else {
|
||||
klogV3.InfoS("matched (key) names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return len(ret) > 0, ret, nil
|
||||
}
|
||||
|
||||
// MatchValueNames evaluates the MatchExpression against names of a set of value features.
|
||||
func (m *MatchExpression) MatchValueNames(values map[string]string) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for k, v := range values {
|
||||
if match, err := m.Match(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"] })
|
||||
|
||||
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() {
|
||||
klogV3.InfoS("matched (value) names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value, "inputValues", values)
|
||||
} else {
|
||||
klogV3.InfoS("matched (value) names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return len(ret) > 0, ret, nil
|
||||
}
|
||||
|
||||
// MatchInstanceAttributeNames evaluates the MatchExpression against a set of
|
||||
// instance features, matching against the names of their attributes.
|
||||
func (m *MatchExpression) MatchInstanceAttributeNames(instances []InstanceFeature) ([]MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for _, i := range instances {
|
||||
if match, _, err := m.MatchValueNames(i.Attributes); err != nil {
|
||||
return nil, err
|
||||
} else if match {
|
||||
ret = append(ret, i.Attributes)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// matchExpression is a helper type for unmarshalling MatchExpression
|
||||
type matchExpression MatchExpression
|
||||
|
||||
|
|
|
@ -180,16 +180,39 @@ func (m *FeatureMatcher) match(features *Features) (bool, matchedFeatures, error
|
|||
matches[dom] = make(domainMatchedFeatures)
|
||||
}
|
||||
|
||||
var isMatch bool
|
||||
var isMatch = true
|
||||
var matchedElems []MatchedElement
|
||||
var err error
|
||||
if f, ok := features.Flags[featureName]; ok {
|
||||
isMatch, matchedElems, err = term.MatchExpressions.MatchGetKeys(f.Elements)
|
||||
if term.MatchExpressions != nil {
|
||||
isMatch, matchedElems, err = term.MatchExpressions.MatchGetKeys(f.Elements)
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
isMatch, meTmp, err = term.MatchName.MatchKeyNames(f.Elements)
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
}
|
||||
} else if f, ok := features.Attributes[featureName]; ok {
|
||||
isMatch, matchedElems, err = term.MatchExpressions.MatchGetValues(f.Elements)
|
||||
if term.MatchExpressions != nil {
|
||||
isMatch, matchedElems, err = term.MatchExpressions.MatchGetValues(f.Elements)
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
isMatch, meTmp, err = term.MatchName.MatchValueNames(f.Elements)
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
}
|
||||
} else if f, ok := features.Instances[featureName]; ok {
|
||||
matchedElems, err = term.MatchExpressions.MatchGetInstances(f.Elements)
|
||||
isMatch = len(matchedElems) > 0
|
||||
if term.MatchExpressions != nil {
|
||||
matchedElems, err = term.MatchExpressions.MatchGetInstances(f.Elements)
|
||||
isMatch = len(matchedElems) > 0
|
||||
}
|
||||
var meTmp []MatchedElement
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
meTmp, err = term.MatchName.MatchInstanceAttributeNames(f.Elements)
|
||||
isMatch = len(meTmp) > 0
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
|
||||
}
|
||||
} else {
|
||||
return false, nil, fmt.Errorf("feature %q not available", featureName)
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ func TestRule(t *testing.T) {
|
|||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain-1.kf-1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchExists),
|
||||
},
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ func TestRule(t *testing.T) {
|
|||
r1.MatchFeatures = FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain-1.kf-1",
|
||||
MatchExpressions: MatchExpressionSet{},
|
||||
MatchExpressions: &MatchExpressionSet{},
|
||||
},
|
||||
}
|
||||
m, err = r1.Execute(f)
|
||||
|
@ -108,7 +108,7 @@ func TestRule(t *testing.T) {
|
|||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain-1.vf-1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchIn, "val-1"),
|
||||
},
|
||||
},
|
||||
|
@ -129,7 +129,7 @@ func TestRule(t *testing.T) {
|
|||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain-1.if-1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(MatchIn, "val-1"),
|
||||
},
|
||||
},
|
||||
|
@ -150,13 +150,13 @@ func TestRule(t *testing.T) {
|
|||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain-1.vf-1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchIn, "val-x"),
|
||||
},
|
||||
},
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain-1.if-1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(MatchIn, "val-1"),
|
||||
},
|
||||
},
|
||||
|
@ -166,7 +166,7 @@ func TestRule(t *testing.T) {
|
|||
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(MatchIn, "val-1")
|
||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(MatchIn, "val-1")
|
||||
m, err = r5.Execute(f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
|
||||
|
@ -177,7 +177,7 @@ func TestRule(t *testing.T) {
|
|||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain-1.kf-1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-na": newMatchExpression(MatchExists),
|
||||
},
|
||||
},
|
||||
|
@ -193,13 +193,13 @@ func TestRule(t *testing.T) {
|
|||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain-1.kf-1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchExists),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
r5.MatchFeatures[0].MatchExpressions["key-1"] = newMatchExpression(MatchIn, "val-1")
|
||||
(*r5.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(MatchIn, "val-1")
|
||||
m, err = r5.Execute(f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
|
||||
|
@ -222,6 +222,7 @@ func TestTemplating(t *testing.T) {
|
|||
"key-1": "val-1",
|
||||
"keu-2": "val-2",
|
||||
"key-3": "val-3",
|
||||
"key-4": "val-4",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -278,7 +279,7 @@ var-2=
|
|||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain_1.kf_1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-a": newMatchExpression(MatchExists),
|
||||
"key-c": newMatchExpression(MatchExists),
|
||||
"foo": newMatchExpression(MatchDoesNotExist),
|
||||
|
@ -286,20 +287,20 @@ var-2=
|
|||
},
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain_1.vf_1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-1": newMatchExpression(MatchIn, "val-1", "val-2"),
|
||||
"bar": newMatchExpression(MatchDoesNotExist),
|
||||
},
|
||||
},
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain_1.if_1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(MatchLt, "100"),
|
||||
},
|
||||
},
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain_1.if_1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"attr-1": newMatchExpression(MatchExists),
|
||||
"attr-2": newMatchExpression(MatchExists),
|
||||
"attr-3": newMatchExpression(MatchExists),
|
||||
|
@ -356,7 +357,7 @@ var-2=
|
|||
// Use a simple empty matchexpression set to match anything.
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain_1.kf_1",
|
||||
MatchExpressions: MatchExpressionSet{
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-a": newMatchExpression(MatchExists),
|
||||
},
|
||||
},
|
||||
|
@ -397,4 +398,42 @@ var-2=
|
|||
_, err = r2.Execute(f)
|
||||
assert.Error(t, err)
|
||||
|
||||
//
|
||||
// Test matchName
|
||||
//
|
||||
r4 := Rule{
|
||||
LabelsTemplate: "{{range .domain_1.vf_1}}{{.Name}}={{.Value}}\n{{end}}",
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain_1.vf_1",
|
||||
MatchExpressions: &MatchExpressionSet{
|
||||
"key-5": newMatchExpression(MatchDoesNotExist),
|
||||
},
|
||||
MatchName: newMatchExpression(MatchIn, "key-1", "key-4"),
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedLabels = map[string]string{
|
||||
"key-1": "val-1",
|
||||
"key-4": "val-4",
|
||||
"key-5": "",
|
||||
}
|
||||
|
||||
m, err = r4.Execute(f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
|
||||
r4 = Rule{
|
||||
Labels: map[string]string{"should-not-match": "true"},
|
||||
MatchFeatures: FeatureMatcher{
|
||||
FeatureMatcherTerm{
|
||||
Feature: "domain_1.vf_1",
|
||||
MatchName: newMatchExpression(MatchIn, "key-not-exists"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
m, err = r4.Execute(f)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, map[string]string(nil), m.Labels, "instances should have matched")
|
||||
}
|
||||
|
|
|
@ -198,8 +198,16 @@ type FeatureMatcher []FeatureMatcherTerm
|
|||
// requirements (specified as MatchExpressions) are evaluated against each
|
||||
// element in the feature set.
|
||||
type FeatureMatcherTerm struct {
|
||||
Feature string `json:"feature"`
|
||||
MatchExpressions MatchExpressionSet `json:"matchExpressions"`
|
||||
// Feature is the name of the feature set to match against.
|
||||
Feature string `json:"feature"`
|
||||
// MatchExpressions is the set of per-element expressions evaluated. These
|
||||
// match against the value of the specified elements.
|
||||
// +optional
|
||||
MatchExpressions *MatchExpressionSet `json:"matchExpressions"`
|
||||
// MatchName in an expression that is matched against the name of each
|
||||
// element in the feature set.
|
||||
// +optional
|
||||
MatchName *MatchExpression `json:"matchName"`
|
||||
}
|
||||
|
||||
// MatchExpressionSet contains a set of MatchExpressions, each of which is
|
||||
|
@ -279,3 +287,7 @@ const (
|
|||
// output of preceding rules.
|
||||
RuleBackrefFeature = "matched"
|
||||
)
|
||||
|
||||
// MatchAllNames is a special key in MatchExpressionSet to use field names
|
||||
// (keys from the input) instead of values when matching.
|
||||
const MatchAllNames = "*"
|
||||
|
|
|
@ -58,19 +58,28 @@ func (in *FeatureMatcherTerm) DeepCopyInto(out *FeatureMatcherTerm) {
|
|||
*out = *in
|
||||
if in.MatchExpressions != nil {
|
||||
in, out := &in.MatchExpressions, &out.MatchExpressions
|
||||
*out = make(MatchExpressionSet, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal *MatchExpression
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = new(MatchExpression)
|
||||
(*in).DeepCopyInto(*out)
|
||||
*out = new(map[string]*MatchExpression)
|
||||
if **in != nil {
|
||||
in, out := *in, *out
|
||||
*out = make(map[string]*MatchExpression, len(*in))
|
||||
for key, val := range *in {
|
||||
var outVal *MatchExpression
|
||||
if val == nil {
|
||||
(*out)[key] = nil
|
||||
} else {
|
||||
in, out := &val, &outVal
|
||||
*out = new(MatchExpression)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
(*out)[key] = outVal
|
||||
}
|
||||
}
|
||||
if in.MatchName != nil {
|
||||
in, out := &in.MatchName, &out.MatchName
|
||||
*out = new(MatchExpression)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureMatcherTerm.
|
||||
|
|
|
@ -31,7 +31,7 @@ func getStaticFeatureConfig() []CustomRule {
|
|||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "pci.device",
|
||||
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"vendor": &nfdv1alpha1.MatchExpression{
|
||||
Op: nfdv1alpha1.MatchIn,
|
||||
Value: nfdv1alpha1.MatchValue{"15b3"}},
|
||||
|
@ -47,7 +47,7 @@ func getStaticFeatureConfig() []CustomRule {
|
|||
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||
nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: "kernel.loadedmodule",
|
||||
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
|
||||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
|
||||
"ib_uverbs": &nfdv1alpha1.MatchExpression{
|
||||
Op: nfdv1alpha1.MatchExists,
|
||||
},
|
||||
|
|
|
@ -17,6 +17,13 @@ spec:
|
|||
matchExpressions:
|
||||
"flag_1": {op: Exists}
|
||||
|
||||
- name: "e2e-flag-test-2"
|
||||
labels:
|
||||
e2e-flag-test-2: "true"
|
||||
matchFeatures:
|
||||
- feature: "fake.flag"
|
||||
matchName: {op: In, value: ["flag_2"]}
|
||||
|
||||
# Negative test not supposed to create a label
|
||||
- name: "e2e-flag-test-neg-1"
|
||||
labels:
|
||||
|
@ -26,6 +33,13 @@ spec:
|
|||
matchExpressions:
|
||||
"flag_1": {op: DoesNotExist}
|
||||
|
||||
- name: "e2e-flag-test-neg-2"
|
||||
labels:
|
||||
e2e-flag-test-neg-2: "true"
|
||||
matchFeatures:
|
||||
- feature: "fake.flag"
|
||||
matchName: {op: In, value: ["flag_x"]}
|
||||
|
||||
#
|
||||
# Simple test rules for attribute features
|
||||
#
|
||||
|
@ -40,6 +54,13 @@ spec:
|
|||
"attr_1": {op: IsTrue}
|
||||
"attr_2": {op: IsFalse}
|
||||
|
||||
- name: "e2e-attribute-test-2"
|
||||
labels:
|
||||
e2e-attribute-test-2: "true"
|
||||
matchFeatures:
|
||||
- feature: "fake.attribute"
|
||||
matchName: {op: In, value: ["attr_2", "attr_x"]}
|
||||
|
||||
# Negative test not supposed to create a label
|
||||
- name: "e2e-attribute-test-neg-1"
|
||||
labels:
|
||||
|
@ -50,8 +71,15 @@ spec:
|
|||
"attr_1": {op: IsTrue}
|
||||
"attr_2": {op: IsTrue}
|
||||
|
||||
- name: "e2e-attribute-test-neg-2"
|
||||
labels:
|
||||
e2e-attribute-test-neg-2: "true"
|
||||
matchFeatures:
|
||||
- feature: "fake.attribute"
|
||||
matchName: {op: In, value: ["attr_x"]}
|
||||
|
||||
#
|
||||
# Simple test rules for instnace features
|
||||
# Simple test rules for instance features
|
||||
#
|
||||
- name: "e2e-instance-test-1"
|
||||
labels:
|
||||
|
@ -65,6 +93,13 @@ spec:
|
|||
"attr_1": {op: In, value: ["true"]}
|
||||
"attr_3": {op: Gt, value: ["10"]}
|
||||
|
||||
- name: "e2e-instance-test-2"
|
||||
labels:
|
||||
e2e-instance-test-2: "true"
|
||||
matchFeatures:
|
||||
- feature: "fake.instance"
|
||||
matchName: {op: In, value: ["attr_1", "attr_x"]}
|
||||
|
||||
# Negative test not supposed to create a label
|
||||
- name: "e2e-instance-test-neg-1"
|
||||
labels:
|
||||
|
@ -74,3 +109,10 @@ spec:
|
|||
matchExpressions:
|
||||
"attr_1": {op: In, value: ["true"]}
|
||||
"attr_3": {op: Lt, value: ["10"]}
|
||||
|
||||
- name: "e2e-instance-test-neg-2"
|
||||
labels:
|
||||
e2e-instance-test-neg-2: "true"
|
||||
matchFeatures:
|
||||
- feature: "fake.instance"
|
||||
matchName: {op: In, value: ["attr_x"]}
|
||||
|
|
|
@ -40,3 +40,12 @@ spec:
|
|||
matchExpressions:
|
||||
"attr_1": {op: In, value: ["true"]}
|
||||
|
||||
- name: "e2e-template-test-2"
|
||||
labelsTemplate: |
|
||||
{{ range .fake.attribute }}e2e-template-test-2-{{ .Name }}={{ .Value }}
|
||||
{{ end }}
|
||||
matchFeatures:
|
||||
- feature: "fake.attribute"
|
||||
matchExpressions:
|
||||
"attr_2": {op: IsFalse}
|
||||
matchName: {op: In, value: ["attr_3"]}
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -653,7 +654,7 @@ var _ = NFDDescribe(Label("nfd-master"), func() {
|
|||
|
||||
// Test NodeFeatureRule
|
||||
//
|
||||
Context("and nfd-worker and NodeFeatureRules objects deployed", func() {
|
||||
Context("and nfd-worker and NodeFeatureRules objects deployed", Label("nodefeaturerule"), func() {
|
||||
testTolerations := []corev1.Toleration{
|
||||
{
|
||||
Key: "feature.node.kubernetes.io/fake-special-node",
|
||||
|
@ -714,8 +715,11 @@ core:
|
|||
expectedLabels := map[string]k8sLabels{
|
||||
"*": {
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-flag-test-1": "true",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-flag-test-2": "true",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-attribute-test-1": "true",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-attribute-test-2": "true",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-instance-test-1": "true",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-instance-test-2": "true",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -729,13 +733,17 @@ core:
|
|||
Expect(testutils.CreateNodeFeatureRulesFromFile(ctx, nfdClient, "nodefeaturerule-2.yaml")).NotTo(HaveOccurred())
|
||||
|
||||
// Add features from NodeFeatureRule #2
|
||||
expectedLabels["*"][nfdv1alpha1.FeatureLabelNs+"/e2e-matchany-test-1"] = "true"
|
||||
expectedLabels["*"][nfdv1alpha1.FeatureLabelNs+"/e2e-template-test-1-instance_1"] = "found"
|
||||
expectedLabels["*"][nfdv1alpha1.FeatureLabelNs+"/e2e-template-test-1-instance_2"] = "found"
|
||||
expectedLabels["*"][nfdv1alpha1.FeatureLabelNs+"/dynamic-label"] = "true"
|
||||
maps.Copy(expectedLabels["*"], k8sLabels{
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-matchany-test-1": "true",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-template-test-1-instance_1": "found",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-template-test-1-instance_2": "found",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-template-test-2-attr_2": "false",
|
||||
nfdv1alpha1.FeatureLabelNs + "/e2e-template-test-2-attr_3": "10",
|
||||
nfdv1alpha1.FeatureLabelNs + "/dynamic-label": "true",
|
||||
})
|
||||
expectedAnnotations := map[string]k8sAnnotations{
|
||||
"*": {
|
||||
"nfd.node.kubernetes.io/feature-labels": "dynamic-label,e2e-attribute-test-1,e2e-flag-test-1,e2e-instance-test-1,e2e-matchany-test-1,e2e-template-test-1-instance_1,e2e-template-test-1-instance_2"},
|
||||
"nfd.node.kubernetes.io/feature-labels": "dynamic-label,e2e-attribute-test-1,e2e-attribute-test-2,e2e-flag-test-1,e2e-flag-test-2,e2e-instance-test-1,e2e-instance-test-2,e2e-matchany-test-1,e2e-template-test-1-instance_1,e2e-template-test-1-instance_2,e2e-template-test-2-attr_2,e2e-template-test-2-attr_3"},
|
||||
}
|
||||
|
||||
By("Verifying node labels from NodeFeatureRules #1 and #2")
|
||||
|
|
Loading…
Reference in a new issue