1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-14 11:57:51 +00:00

source/custom: implement matchAny directive

Implement a new 'matchAny' directive in the new rule format, building on
top of the previously implemented 'matchFeatures' matcher. MatchAny
applies a logical OR over multiple matchFeatures directives. That is, it
allows specifying multiple alternative matchers (at least one of which
must match) in a single label rule.

The configuration format for the new matchers is

  matchAny:
    - matchFeatures:
        - feature: <domain>.<feature>
          matchExpressions:
            <attribute>:
              op: <operator>
              value:
                - <list-of-values>
    - matchFeatures:
      ...

A configuration example. In order to require a cpu feature, kernel
module and one of two specific PCI devices (taking use of the shortform
notation):

  - name: multi-device-test
    labels:
      multi-device-feature: "true"
    matchFeatures:
      - feature: kernel.loadedmodule
        matchExpressions: [driver-module]
      - feature: cpu.cpuid
        matchExpressions: [AVX512F]
    matchAny:
      - matchFeatures:
          - feature; pci.device
            matchExpressions:
              vendor: "8086"
              device: "1234"
      - matchFeatures:
          - feature: pci.device
            matchExpressions:
              vendor: "8086"
              device: "abcd"
This commit is contained in:
Markus Lehtonen 2021-03-04 12:39:42 +02:00
parent e206f0b86b
commit 6cbed379df
4 changed files with 101 additions and 0 deletions

View file

@ -160,3 +160,27 @@
# - feature: local.label
# matchExpressions:
# custom-feature-knob: {op: Gt, value: ["100"]}
#
# # The following feature demonstrates the capabilities of the matchAny
# - name: "my.ng.feature.2"
# labels:
# my-ng-feature-2: "my-value"
# # matchAny implements a logical IF over all elements (sub-matchers) in
# # the list (i.e. at least one feature matcher must match)
# matchAny:
# - matchFeatures:
# - feature: kernel.loadedmodule
# matchExpressions:
# driver-module-X: {op: Exists}
# - feature: pci.device
# matchExpressions:
# vendor: {op: In, value: ["8086"]}
# class: {op: In, value: ["0200"]}
# - matchFeatures:
# - feature: kernel.loadedmodule
# matchExpressions:
# driver-module-Y: {op: Exists}
# - feature: usb.device
# matchExpressions:
# vendor: {op: In, value: ["8086"]}
# class: {op: In, value: ["02"]}

View file

@ -246,6 +246,30 @@ worker:
# - feature: local.label
# matchExpressions:
# custom-feature-knob: {op: Gt, value: ["100"]}
#
# # The following feature demonstrates the capabilities of the matchAny
# - name: "my.ng.feature.2"
# labels:
# my-ng-feature-2: "my-value"
# # matchAny implements a logical IF over all elements (sub-matchers) in
# # the list (i.e. at least one feature matcher must match)
# matchAny:
# - matchFeatures:
# - feature: kernel.loadedmodule
# matchExpressions:
# driver-module-X: {op: Exists}
# - feature: pci.device
# matchExpressions:
# vendor: {op: In, value: ["8086"]}
# class: {op: In, value: ["0200"]}
# - matchFeatures:
# - feature: kernel.loadedmodule
# matchExpressions:
# driver-module-Y: {op: Exists}
# - feature: usb.device
# matchExpressions:
# vendor: {op: In, value: ["8086"]}
# class: {op: In, value: ["02"]}
### <NFD-WORKER-CONF-END-DO-NOT-REMOVE>
podSecurityContext: {}

View file

@ -54,6 +54,11 @@ type Rule struct {
Name string `json:"name"`
Labels map[string]string `json:"labels"`
MatchFeatures FeatureMatcher `json:"matchFeatures"`
MatchAny []MatchAnyElem `json:"matchAny"`
}
type MatchAnyElem struct {
MatchFeatures FeatureMatcher
}
type FeatureMatcher []FeatureMatcherTerm
@ -185,6 +190,22 @@ func (r *LegacyRule) execute(features map[string]*feature.DomainFeatures) (map[s
}
func (r *Rule) execute(features map[string]*feature.DomainFeatures) (map[string]string, error) {
if len(r.MatchAny) > 0 {
// Logical OR over the matchAny matchers
matched := false
for _, matcher := range r.MatchAny {
if m, err := matcher.match(features); err != nil {
return nil, err
} else if m {
matched = true
break
}
}
if !matched {
return nil, nil
}
}
if len(r.MatchFeatures) > 0 {
if m, err := r.MatchFeatures.match(features); err != nil {
return nil, err
@ -201,6 +222,10 @@ func (r *Rule) execute(features map[string]*feature.DomainFeatures) (map[string]
return labels, nil
}
func (e *MatchAnyElem) match(features map[string]*feature.DomainFeatures) (bool, error) {
return e.MatchFeatures.match(features)
}
func (m *FeatureMatcher) match(features map[string]*feature.DomainFeatures) (bool, error) {
// Logical AND over the terms
for _, term := range *m {

View file

@ -150,4 +150,32 @@ func TestRule(t *testing.T) {
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m, "instances should have matched")
// Test MatchAny
r5.MatchAny = []MatchAnyElem{
MatchAnyElem{
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.kf-1",
MatchExpressions: expression.MatchExpressionSet{"key-na": expression.MustCreateMatchExpression(expression.MatchExists)},
},
},
},
}
m, err = r5.execute(f)
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m, "instances should not have matched")
r5.MatchAny = append(r5.MatchAny,
MatchAnyElem{
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.kf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchExists)},
},
},
})
r5.MatchFeatures[0].MatchExpressions["key-1"] = expression.MustCreateMatchExpression(expression.MatchIn, "val-1")
m, err = r5.execute(f)
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m, "instances should have matched")
}