diff --git a/crd/nfd.kubernetes.test_labelrules.yaml b/crd/nfd.kubernetes.test_labelrules.yaml index 0fc97f3380..1d30abf635 100644 --- a/crd/nfd.kubernetes.test_labelrules.yaml +++ b/crd/nfd.kubernetes.test_labelrules.yaml @@ -134,6 +134,12 @@ spec: name: description: Name of the label to be generated. type: string + noLabel: + description: NoLabel disables the label output of the rule. + Output value(s) of the matching process are only added as + values under a special "MATCHES" key for subsequent rules + to use as input features for matching. + type: boolean value: description: Value of the label, optional. type: string diff --git a/custom-test.conf.yaml b/custom-test.conf.yaml index 7834fd8fb4..4ef810a040 100644 --- a/custom-test.conf.yaml +++ b/custom-test.conf.yaml @@ -118,3 +118,14 @@ sources: matchAny: - cpu.cpuid: "*": {op: InRegexp, value: ["^SSE"]} + + - name: nolabel-test-1 + matchAll: + - cpu.cpuid: + noLabel: true + + - name: BACKREF-test-1 + matchAll: + - rule.matched: + system-test-1: "true" + nolabel-test-1: "true" diff --git a/pkg/api/feature/feature.go b/pkg/api/feature/feature.go index 656a6b17de..cb62f78e4c 100644 --- a/pkg/api/feature/feature.go +++ b/pkg/api/feature/feature.go @@ -34,3 +34,17 @@ func NewInstanceFeatures() *InstanceFeatures { return &InstanceFeatures{} } func NewInstanceFeature() *InstanceFeature { return &InstanceFeature{Attributes: make(map[string]string)} } + +func InsertValues(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() + } + + features := f[domain].Values[feature].Features + for k, v := range values { + features[k] = v + } +} diff --git a/pkg/apis/nfd/v1alpha1/expression.go b/pkg/apis/nfd/v1alpha1/expression.go index 8d16a48ccb..a1752a597a 100644 --- a/pkg/apis/nfd/v1alpha1/expression.go +++ b/pkg/apis/nfd/v1alpha1/expression.go @@ -71,7 +71,7 @@ func (m *MatchExpression) Validate() error { return fmt.Errorf("Values should be empty for Op %q (got %v)", m.Op, m.Value) } case MatchGt, MatchLt: - if len(m.Value) != 0 { + if len(m.Value) != 1 { return fmt.Errorf("Values should contain exactly one element for Op %q (got %v)", m.Op, m.Value) } default: diff --git a/pkg/apis/nfd/v1alpha1/types.go b/pkg/apis/nfd/v1alpha1/types.go index cbdaebc926..0b6b901491 100644 --- a/pkg/apis/nfd/v1alpha1/types.go +++ b/pkg/apis/nfd/v1alpha1/types.go @@ -55,6 +55,12 @@ type Rule struct { // +optional Value *string `json:"value,omitempty"` + // NoLabel disables the label output of the rule. Output value(s) of the + // matching process are only added as values under a special "MATCHES" key + // for subsequent rules to use as input features for matching. + // +optional + NoLabel bool `json:"noLabel"` + // MatchAny specifies a list of expression sets one of which must match // +optional MatchAny []MatchRule `json:"matchAny"` @@ -124,3 +130,12 @@ const ( // MatchAllNames is a special key in MatchExpressionSet to use field names // (keys from the input) instead of values when matching. const MatchAllNames = "*" + +const ( + // RuleBackrefDomain is the special feature domain for backreferencing + // output of preceding rules. + RuleBackrefDomain = "rule" + // RuleBackrefFeature is the special feature name for backreferencing + // output of preceding rules. + RuleBackrefFeature = "matched" +) diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go index cacc7f0fff..d7a4f79d5d 100644 --- a/pkg/nfd-master/nfd-master.go +++ b/pkg/nfd-master/nfd-master.go @@ -39,7 +39,9 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/klog/v2" + "sigs.k8s.io/node-feature-discovery/pkg/api/feature" "sigs.k8s.io/node-feature-discovery/pkg/apihelper" + nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1" pb "sigs.k8s.io/node-feature-discovery/pkg/labeler" "sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/pkg/version" @@ -454,6 +456,10 @@ func (m *nfdMaster) crLabels(r *pb.SetLabelsRequest) map[string]string { l := make(map[string]string) ruleSpecs, err := m.nfdController.lister.List(labels.Everything()) + sort.Slice(ruleSpecs, func(i, j int) bool { + return ruleSpecs[i].Name < ruleSpecs[j].Name + }) + if err != nil { klog.Errorf("failed to list LabelRule resources: %w", err) return nil @@ -474,10 +480,16 @@ func (m *nfdMaster) crLabels(r *pb.SetLabelsRequest) map[string]string { klog.Errorf("failed to process Rule %q: %w", rule.Name, err) continue } - for k, v := range ruleOut { - l[k] = v + + if !rule.NoLabel { + for k, v := range ruleOut { + l[k] = v + } } utils.KlogDump(1, "", " ", ruleOut) + + // Feed back rule output to features map for subsequent rules to match + feature.InsertValues(r.Features, nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut) } } diff --git a/source/custom/custom.go b/source/custom/custom.go index 7f8389a81d..2ccd107d9c 100644 --- a/source/custom/custom.go +++ b/source/custom/custom.go @@ -114,9 +114,13 @@ func (s *customSource) GetLabels() (source.FeatureLabels, error) { klog.Errorf("failed to discover feature: %q: %s", spec.Name, err.Error()) continue } - for k, v := range ruleOut { - labels[k] = v + if !spec.NoLabel { + for k, v := range ruleOut { + labels[k] = v + } } + // Feed back rule output to features map for subsequent rules to match + feature.InsertValues(domainFeatures, nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut) } return labels, nil } @@ -154,6 +158,7 @@ func (s *FeatureSpec) Match(features map[string]*feature.DomainFeatures) (map[st return nil, nil } } + return ret, nil }