Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add variables to feature rule spec and support backrefs #663

Merged
merged 2 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions deployment/base/nfd-crds/cr-sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,29 @@ spec:
- feature: cpu.cpuid
matchExpressions:
AVX: {op: Exists}

# The following examples demonstrate vars field and back-referencing
# previous labels and vars
- name: "my dummy kernel rule"
labels:
"my.kernel.feature": "true"
matchFeatures:
- feature: kernel.version
matchExpressions:
major: {op: Gt, value: ["2"]}

- name: "my dummy rule with no labels"
vars:
"my.dummy.var": "1"
matchFeatures:
- feature: cpu.cpuid
matchExpressions: {}

- name: "my rule using backrefs"
labels:
"my.backref.feature": "true"
matchFeatures:
- feature: rule.matched
matchExpressions:
my.kernel.feature: {op: IsTrue}
my.dummy.var: {op: Gt, value: ["0"]}
15 changes: 15 additions & 0 deletions deployment/base/nfd-crds/nodefeaturerule-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ spec:
name:
description: Name of the rule.
type: string
vars:
additionalProperties:
type: string
description: Vars is the variables to store if the rule matches.
Variables do not directly inflict any changes in the node
object. However, they can be referenced from other rules enabling
more complex rule hierarchies, without exposing intermediary
output values as labels.
type: object
varsTemplate:
description: VarsTemplate specifies a template to expand for
dynamically generating multiple variables. Data (after template
expansion) must be keys with an optional value (<key>[=<value>])
separated by newlines.
type: string
required:
- name
type: object
Expand Down
26 changes: 26 additions & 0 deletions deployment/components/worker-config/nfd-worker.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,29 @@
# - feature: cpu.cpuid
# matchExpressions:
# AVX: {op: Exists}
#
# # The following examples demonstrate vars field and back-referencing
# # previous labels and vars
# - name: "my dummy kernel rule"
# labels:
# "my.kernel.feature": "true"
# matchFeatures:
# - feature: kernel.version
# matchExpressions:
# major: {op: Gt, value: ["2"]}
#
# - name: "my dummy rule with no labels"
# vars:
# "my.dummy.var": "1"
# matchFeatures:
# - feature: cpu.cpuid
# matchExpressions: {}
#
# - name: "my rule using backrefs"
# labels:
# "my.backref.feature": "true"
# matchFeatures:
# - feature: rule.matched
# matchExpressions:
# my.kernel.feature: {op: IsTrue}
# my.dummy.var: {op: Gt, value: ["0"]}
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ spec:
name:
description: Name of the rule.
type: string
vars:
additionalProperties:
type: string
description: Vars is the variables to store if the rule matches.
Variables do not directly inflict any changes in the node
object. However, they can be referenced from other rules enabling
more complex rule hierarchies, without exposing intermediary
output values as labels.
type: object
varsTemplate:
description: VarsTemplate specifies a template to expand for
dynamically generating multiple variables. Data (after template
expansion) must be keys with an optional value (<key>[=<value>])
separated by newlines.
type: string
required:
- name
type: object
Expand Down
26 changes: 26 additions & 0 deletions deployment/helm/node-feature-discovery/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,32 @@ worker:
# - feature: cpu.cpuid
# matchExpressions:
# AVX: {op: Exists}
#
# # The following examples demonstrate vars field and back-referencing
# # previous labels and vars
# - name: "my dummy kernel rule"
# labels:
# "my.kernel.feature": "true"
# matchFeatures:
# - feature: kernel.version
# matchExpressions:
# major: {op: Gt, value: ["2"]}
#
# - name: "my dummy rule with no labels"
# vars:
# "my.dummy.var": "1"
# matchFeatures:
# - feature: cpu.cpuid
# matchExpressions: {}
#
# - name: "my rule using backrefs"
# labels:
# "my.backref.feature": "true"
# matchFeatures:
# - feature: rule.matched
# matchExpressions:
# my.kernel.feature: {op: IsTrue}
# my.dummy.var: {op: Gt, value: ["0"]}
### <NFD-WORKER-CONF-END-DO-NOT-REMOVE>

podSecurityContext: {}
Expand Down
15 changes: 15 additions & 0 deletions pkg/api/feature/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,18 @@ func NewInstanceFeature(attrs map[string]string) *InstanceFeature {
}
return &InstanceFeature{Attributes: attrs}
}

// InsertFeatureValues inserts new values into a specific feature.
func InsertFeatureValues(f Features, domain, feature string, values map[string]string) {
if _, ok := f[domain]; !ok {
f[domain] = NewDomainFeatures()
}
if _, ok := f[domain].Values[feature]; !ok {
f[domain].Values[feature] = NewValueFeatures(values)
return
}

for k, v := range values {
f[domain].Values[feature].Elements[k] = v
}
}
72 changes: 58 additions & 14 deletions pkg/apis/nfd/v1alpha1/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,35 @@ limitations under the License.
package v1alpha1

import (
"bytes"
"fmt"
"strings"
"text/template"

"bytes"
"fmt"
"k8s.io/klog/v2"

"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
)

// RuleOutput contains the output out rule execution.
// +k8s:deepcopy-gen=false
type RuleOutput struct {
Labels map[string]string
Vars map[string]string
}

// Execute the rule against a set of input features.
func (r *Rule) Execute(features map[string]*feature.DomainFeatures) (map[string]string, error) {
ret := make(map[string]string)
func (r *Rule) Execute(features feature.Features) (RuleOutput, error) {
labels := make(map[string]string)
vars := make(map[string]string)

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
return RuleOutput{}, err
} else if m != nil {
matched = true
utils.KlogDump(4, "matches for matchAny "+r.Name, " ", m)
Expand All @@ -46,34 +55,47 @@ func (r *Rule) Execute(features map[string]*feature.DomainFeatures) (map[string]
// produce the same labels)
break
}
if err := r.executeLabelsTemplate(m, ret); err != nil {
return nil, err
if err := r.executeLabelsTemplate(m, labels); err != nil {
return RuleOutput{}, err
}
if err := r.executeVarsTemplate(m, vars); err != nil {
return RuleOutput{}, err
}

}
}
if !matched {
return nil, nil
klog.V(2).Infof("rule %q did not match", r.Name)
return RuleOutput{}, nil
}
}

if len(r.MatchFeatures) > 0 {
if m, err := r.MatchFeatures.match(features); err != nil {
return nil, err
return RuleOutput{}, err
} else if m == nil {
return nil, nil
klog.V(2).Infof("rule %q did not match", r.Name)
return RuleOutput{}, nil
} else {
utils.KlogDump(4, "matches for matchFeatures "+r.Name, " ", m)
if err := r.executeLabelsTemplate(m, ret); err != nil {
return nil, err
if err := r.executeLabelsTemplate(m, labels); err != nil {
return RuleOutput{}, err
}
if err := r.executeVarsTemplate(m, vars); err != nil {
return RuleOutput{}, err
}
}
}

for k, v := range r.Labels {
ret[k] = v
labels[k] = v
}
for k, v := range r.Vars {
vars[k] = v
}

ret := RuleOutput{Labels: labels, Vars: vars}
utils.KlogDump(2, fmt.Sprintf("rule %q matched with: ", r.Name), " ", ret)

return ret, nil
}

Expand All @@ -100,6 +122,28 @@ func (r *Rule) executeLabelsTemplate(in matchedFeatures, out map[string]string)
return nil
}

func (r *Rule) executeVarsTemplate(in matchedFeatures, out map[string]string) error {
if r.VarsTemplate == "" {
return nil
}
if r.varsTemplate == nil {
t, err := newTemplateHelper(r.VarsTemplate)
if err != nil {
return err
}
r.varsTemplate = t
}

vars, err := r.varsTemplate.expandMap(in)
if err != nil {
return err
}
for k, v := range vars {
out[k] = v
}
return nil
}

type matchedFeatures map[string]domainMatchedFeatures

type domainMatchedFeatures map[string]interface{}
Expand Down
Loading