diff --git a/deployment/components/worker-config/nfd-worker.conf.example b/deployment/components/worker-config/nfd-worker.conf.example index c07c4147d7..c378607369 100644 --- a/deployment/components/worker-config/nfd-worker.conf.example +++ b/deployment/components/worker-config/nfd-worker.conf.example @@ -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"]} diff --git a/deployment/helm/node-feature-discovery/values.yaml b/deployment/helm/node-feature-discovery/values.yaml index 2c61b80a2a..5142c16a77 100644 --- a/deployment/helm/node-feature-discovery/values.yaml +++ b/deployment/helm/node-feature-discovery/values.yaml @@ -247,6 +247,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"]} ### podSecurityContext: {} diff --git a/source/custom/custom.go b/source/custom/custom.go index af8b11832f..a9aaf0ed8a 100644 --- a/source/custom/custom.go +++ b/source/custom/custom.go @@ -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 @@ -187,6 +192,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 @@ -203,6 +224,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 { diff --git a/source/custom/custom_test.go b/source/custom/custom_test.go index f2cc3500fb..5a01f017da 100644 --- a/source/custom/custom_test.go +++ b/source/custom/custom_test.go @@ -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") }