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: drop support for the legacy rule format

This commit is contained in:
Markus Lehtonen 2023-10-05 16:15:37 +03:00
parent 160c9107f5
commit 7d1df87305
9 changed files with 19 additions and 681 deletions

View file

@ -1062,278 +1062,3 @@ must be present):
vendor: "0fff"
device: "abcd"
```
## Legacy custom rule syntax
**DEPRECATED**: use the new rule syntax instead.
The `custom` source supports the legacy `matchOn` rule syntax for
backwards-compatibility.
To aid in making the legacy rule syntax clearer, we define a general and a per
rule nomenclature, keeping things as consistent as possible.
### General nomenclature and definitions
```plaintext
Rule :Represents a matching logic that is used to match on a feature.
Rule Input :The input a Rule is provided. This determines how a Rule performs the match operation.
Matcher :A composition of Rules, each Matcher may be composed of at most one instance of each Rule.
```
### Custom features format (using the nomenclature defined above)
Rules are specified under `sources.custom` in the nfd-worker configuration
file.
```yaml
sources:
custom:
- name: <feature name>
value: <optional feature value, defaults to "true">
matchOn:
- <Rule-1>: <Rule-1 Input>
[<Rule-2>: <Rule-2 Input>]
- <Matcher-2>
- ...
- ...
- <Matcher-N>
- <custom feature 2>
- ...
- ...
- <custom feature M>
```
The label is constructed by adding `custom-` prefix to the name field, label
value defaults to `true` if not specified in the rule spec:
```plaintext
feature.node.kubernetes.io/custom-<name> = <value>
```
### Matching process
Specifying Rules to match on a feature is done by providing a list of Matchers.
Each Matcher contains one or more Rules.
Logical _OR_ is performed between Matchers and logical _AND_ is performed
between Rules of a given Matcher.
### Rules
#### pciid rule
##### Nomenclature
```plaintext
Attribute :A PCI attribute.
Element :An identifier of the PCI attribute.
```
The PciId Rule allows matching the PCI devices in the system on the following
Attributes: `class`,`vendor` and `device`. A list of Elements is provided for
each Attribute.
##### Format
```yaml
pciId :
class: [<class id>, ...]
vendor: [<vendor id>, ...]
device: [<device id>, ...]
```
Matching is done by performing a logical _OR_ between Elements of an Attribute
and logical _AND_ between the specified Attributes for each PCI device in the
system. At least one Attribute must be specified. Missing attributes will not
partake in the matching process.
#### UsbId rule
##### Nomenclature
```plaintext
Attribute :A USB attribute.
Element :An identifier of the USB attribute.
```
The UsbId Rule allows matching the USB devices in the system on the following
Attributes: `class`,`vendor`, `device` and `serial`. A list of Elements is
provided for each Attribute.
##### Format
```yaml
usbId :
class: [<class id>, ...]
vendor: [<vendor id>, ...]
device: [<device id>, ...]
serial: [<serial>, ...]
```
Matching is done by performing a logical _OR_ between Elements of an Attribute
and logical _AND_ between the specified Attributes for each USB device in the
system. At least one Attribute must be specified. Missing attributes will not
partake in the matching process.
#### LoadedKMod rule
##### Nomenclature
```plaintext
Element :A kernel module
```
The LoadedKMod Rule allows matching the loaded kernel modules in the system
against a provided list of Elements.
##### Format
```yaml
loadedKMod : [<kernel module>, ...]
```
Matching is done by performing logical _AND_ for each provided Element, i.e
the Rule will match if all provided Elements (kernel modules) are loaded in the
system.
#### CpuId rule
##### Nomenclature
```plaintext
Element :A CPUID flag
```
The Rule allows matching the available CPUID flags in the system against a
provided list of Elements.
##### Format
```yaml
cpuId : [<CPUID flag string>, ...]
```
Matching is done by performing logical _AND_ for each provided Element, i.e the
Rule will match if all provided Elements (CPUID flag strings) are available in
the system.
#### Kconfig rule
##### Nomenclature
```plaintext
Element :A Kconfig option
```
The Rule allows matching the kconfig options in the system against a provided
list of Elements.
##### Format
```yaml
kConfig: [<kernel config option ('y' or 'm') or '=<value>'>, ...]
```
Matching is done by performing logical _AND_ for each provided Element, i.e the
Rule will match if all provided Elements (kernel config options) are enabled
(`y` or `m`) or matching `=<value>` in the kernel.
#### Nodename rule
##### Nomenclature
```plaintext
Element :A nodename regexp pattern
```
The Rule allows matching the node's name against a provided list of Elements.
##### Format
```yaml
nodename: [ <nodename regexp pattern>, ... ]
```
Matching is done by performing logical _OR_ for each provided Element, i.e the
Rule will match if one of the provided Elements (nodename regexp pattern)
matches the node's name.
### Legacy custom rule example
```yaml
custom:
- name: "my.kernel.feature"
matchOn:
- loadedKMod: ["kmod1", "kmod2"]
- name: "my.pci.feature"
matchOn:
- pciId:
vendor: ["15b3"]
device: ["1014", "1017"]
- name: "my.usb.feature"
matchOn:
- usbId:
vendor: ["1d6b"]
device: ["0003"]
serial: ["090129a"]
- name: "my.combined.feature"
matchOn:
- loadedKMod : ["vendor_kmod1", "vendor_kmod2"]
pciId:
vendor: ["15b3"]
device: ["1014", "1017"]
- name: "vendor.feature.node.kubernetes.io/accumulated.feature"
matchOn:
- loadedKMod : ["some_kmod1", "some_kmod2"]
- pciId:
vendor: ["15b3"]
device: ["1014", "1017"]
- name: "my.kernel.featureneedscpu"
matchOn:
- kConfig: ["KVM_INTEL"]
- cpuId: ["VMX"]
- name: "my.kernel.modulecompiler"
matchOn:
- kConfig: ["GCC_VERSION=100101"]
loadedKMod: ["kmod1"]
- name: "profile.node.kubernetes.io/my-datacenter"
value: "datacenter-1"
matchOn:
- nodename: [ "node-datacenter1-rack.*-server.*" ]
```
__In the example above:__
- A node would contain the label:
`feature.node.kubernetes.io/custom-my.kernel.feature=true` if the node has
`kmod1` _AND_ `kmod2` kernel modules loaded.
- A node would contain the label:
`feature.node.kubernetes.io/custom-my.pci.feature=true` if the node contains
a PCI device with a PCI vendor ID of `15b3` _AND_ PCI device ID of `1014` _OR_
`1017`.
- A node would contain the label:
`feature.node.kubernetes.io/custom-my.usb.feature=true` if the node contains
a USB device with a USB vendor ID of `1d6b` _AND_ USB device ID of `0003`.
- A node would contain the label:
`feature.node.kubernetes.io/custom-my.combined.feature=true` if
`vendor_kmod1` _AND_ `vendor_kmod2` kernel modules are loaded __AND__ the node
contains a PCI device
with a PCI vendor ID of `15b3` _AND_ PCI device ID of `1014` _or_ `1017`.
- A node would contain the label:
`vendor.feature.node.kubernetes.io/accumulated.feature=true` if
`some_kmod1` _AND_ `some_kmod2` kernel modules are loaded __OR__ the node
contains a PCI device
with a PCI vendor ID of `15b3` _AND_ PCI device ID of `1014` _OR_ `1017`.
- A node would contain the label:
`feature.node.kubernetes.io/custom-my.kernel.featureneedscpu=true` if
`KVM_INTEL` kernel config is enabled __AND__ the node CPU supports `VMX`
virtual machine extensions
- A node would contain the label:
`feature.node.kubernetes.io/custom-my.kernel.modulecompiler=true` if the
in-tree `kmod1` kernel module is loaded __AND__ it's built with
`GCC_VERSION=100101`.
- A node would contain the label:
`profile.node.kubernetes.io/my-datacenter=datacenter-1` if the node's name
matches the `node-datacenter1-rack.*-server.*` pattern, e.g.
`node-datacenter1-rack2-server42`

View file

@ -17,50 +17,24 @@ limitations under the License.
package custom
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"k8s.io/klog/v2"
"sigs.k8s.io/yaml"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/rules"
)
// Name of this feature source
const Name = "custom"
// LegacyMatcher contains the legacy custom rules.
type LegacyMatcher struct {
PciID *rules.PciIDRule `json:"pciId,omitempty"`
UsbID *rules.UsbIDRule `json:"usbId,omitempty"`
LoadedKMod *rules.LoadedKModRule `json:"loadedKMod,omitempty"`
CpuID *rules.CpuIDRule `json:"cpuId,omitempty"`
Kconfig *rules.KconfigRule `json:"kConfig,omitempty"`
Nodename *rules.NodenameRule `json:"nodename,omitempty"`
}
type LegacyRule struct {
Name string `json:"name"`
Value *string `json:"value,omitempty"`
MatchOn []LegacyMatcher `json:"matchOn"`
}
type Rule struct {
type CustomRule struct {
nfdv1alpha1.Rule
}
type config []CustomRule
type CustomRule struct {
*LegacyRule
*Rule
}
// newDefaultConfig returns a new config with pre-populated defaults
func newDefaultConfig() *config {
return &config{}
@ -71,10 +45,6 @@ type customSource struct {
config *config
}
type legacyRule interface {
Match() (bool, error)
}
// Singleton source instance
var (
src = customSource{config: newDefaultConfig()}
@ -115,7 +85,7 @@ func (s *customSource) GetLabels() (source.FeatureLabels, error) {
klog.V(2).InfoS("resolving custom features", "configuration", utils.DelayedDumper(allFeatureConfig))
// Iterate over features
for _, rule := range allFeatureConfig {
ruleOut, err := rule.execute(features)
ruleOut, err := rule.Execute(features)
if err != nil {
klog.ErrorS(err, "failed to execute rule")
continue
@ -132,112 +102,6 @@ func (s *customSource) GetLabels() (source.FeatureLabels, error) {
return labels, nil
}
func (r *CustomRule) execute(features *nfdv1alpha1.Features) (nfdv1alpha1.RuleOutput, error) {
if r.LegacyRule != nil {
klog.InfoS("legacy 'matchOn' rule format is deprecated, please convert to the new 'matchFeatures' rule format", "ruleName", r.LegacyRule.Name)
ruleOut, err := r.LegacyRule.execute()
if err != nil {
return nfdv1alpha1.RuleOutput{}, fmt.Errorf("failed to execute legacy rule %s: %w", r.LegacyRule.Name, err)
}
return nfdv1alpha1.RuleOutput{Labels: ruleOut}, nil
}
if r.Rule != nil {
ruleOut, err := r.Rule.Execute(features)
if err != nil {
return ruleOut, fmt.Errorf("failed to execute rule %s: %w", r.Rule.Name, err)
}
return ruleOut, nil
}
return nfdv1alpha1.RuleOutput{}, fmt.Errorf("BUG: an empty rule, this really should not happen")
}
func (r *LegacyRule) execute() (map[string]string, error) {
if len(r.MatchOn) > 0 {
// Logical OR over the legacy rules
matched := false
for _, matcher := range r.MatchOn {
if m, err := matcher.match(); err != nil {
return nil, err
} else if m {
matched = true
break
}
}
if !matched {
return nil, nil
}
}
// Prefix non-namespaced labels with "custom-"
name := r.Name
if !strings.Contains(name, "/") {
name = "custom-" + name
}
value := "true"
if r.Value != nil {
value = *r.Value
}
return map[string]string{name: value}, nil
}
func (m *LegacyMatcher) match() (bool, error) {
allRules := []legacyRule{
m.PciID,
m.UsbID,
m.LoadedKMod,
m.CpuID,
m.Kconfig,
m.Nodename,
}
// return true, nil if all rules match
matchRules := func(rules []legacyRule) (bool, error) {
for _, rule := range rules {
if reflect.ValueOf(rule).IsNil() {
continue
}
if match, err := rule.Match(); err != nil {
return false, err
} else if !match {
return false, nil
}
}
return true, nil
}
return matchRules(allRules)
}
// UnmarshalJSON implements the Unmarshaler interface from "encoding/json"
func (c *CustomRule) UnmarshalJSON(data []byte) error {
// Do a raw parse to determine if this is a legacy rule
raw := map[string]json.RawMessage{}
err := yaml.Unmarshal(data, &raw)
if err != nil {
return err
}
for k := range raw {
if strings.ToLower(k) == "matchon" {
return yaml.Unmarshal(data, &c.LegacyRule)
}
}
return yaml.Unmarshal(data, &c.Rule)
}
// MarshalJSON implements the Marshaler interface from "encoding/json"
func (c *CustomRule) MarshalJSON() ([]byte, error) {
if c.LegacyRule != nil {
return json.Marshal(c.LegacyRule)
}
return json.Marshal(c.Rule)
}
func init() {
source.Register(&src)
}

View file

@ -1,39 +0,0 @@
/*
Copyright 2020-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/cpu"
)
// CpuIDRule implements Rule for the custom source
type CpuIDRule struct {
nfdv1alpha1.MatchExpressionSet
}
// Match checks if CpuId rule matches.
func (r *CpuIDRule) Match() (bool, error) {
flags, ok := source.GetFeatureSource("cpu").GetFeatures().Flags[cpu.CpuidFeature]
if !ok {
return false, fmt.Errorf("cpuid information not available")
}
return r.MatchKeys(flags.Elements)
}

View file

@ -1,38 +0,0 @@
/*
Copyright 2020-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source/kernel"
)
// KconfigRule implements Rule for the custom source
type KconfigRule struct {
nfdv1alpha1.MatchExpressionSet
}
// Match compares the values of Kernel config provided and legacy config
func (r *KconfigRule) Match() (bool, error) {
options := kernel.GetLegacyKconfig()
if options == nil {
return false, fmt.Errorf("kernel config options not available")
}
return r.MatchValues(options)
}

View file

@ -1,40 +0,0 @@
/*
Copyright 2020-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/kernel"
)
// LoadedKModRule matches loaded kernel modules in the system
type LoadedKModRule struct {
nfdv1alpha1.MatchExpressionSet
}
// Match loaded kernel modules on provided list of kernel modules
func (r *LoadedKModRule) Match() (bool, error) {
modules, ok := source.GetFeatureSource("kernel").GetFeatures().Flags[kernel.LoadedModuleFeature]
if !ok {
return false, fmt.Errorf("info about loaded modules not available")
}
return r.MatchKeys(modules.Elements)
}

View file

@ -1,53 +0,0 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rules
import (
"encoding/json"
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/system"
)
// NodenameRule matches on nodenames configured in a ConfigMap
type NodenameRule struct {
nfdv1alpha1.MatchExpression
}
// Match checks if node name matches the rule.
func (r *NodenameRule) Match() (bool, error) {
nodeName, ok := source.GetFeatureSource("system").GetFeatures().Attributes[system.NameFeature].Elements["nodename"]
if !ok || nodeName == "" {
return false, fmt.Errorf("node name not available")
}
return r.MatchExpression.Match(true, nodeName)
}
// UnmarshalJSON unmarshals and validates the data provided
func (r *NodenameRule) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &r.MatchExpression); err != nil {
return err
}
// Force regexp matching
if r.Op == nfdv1alpha1.MatchIn {
r.Op = nfdv1alpha1.MatchInRegexp
}
// We need to run Validate() because operator forcing above
return r.Validate()
}

View file

@ -1,39 +0,0 @@
/*
Copyright 2020-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/pci"
)
type PciIDRule struct {
nfdv1alpha1.MatchExpressionSet
}
// Match PCI devices on provided PCI device attributes
func (r *PciIDRule) Match() (bool, error) {
devs, ok := source.GetFeatureSource("pci").GetFeatures().Instances[pci.DeviceFeature]
if !ok {
return false, fmt.Errorf("cpuid information not available")
}
return r.MatchInstances(devs.Elements)
}

View file

@ -1,38 +0,0 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/usb"
)
type UsbIDRule struct {
nfdv1alpha1.MatchExpressionSet
}
// Match USB devices on provided USB device attributes
func (r *UsbIDRule) Match() (bool, error) {
devs, ok := source.GetFeatureSource("usb").GetFeatures().Instances[usb.DeviceFeature]
if !ok {
return false, fmt.Errorf("usb device information not available")
}
return r.MatchInstances(devs.Elements)
}

View file

@ -25,33 +25,29 @@ import (
func getStaticFeatureConfig() []CustomRule {
return []CustomRule{
{
Rule: &Rule{
nfdv1alpha1.Rule{
Name: "RDMA capable static rule",
Labels: map[string]string{"rdma.capable": "true"},
MatchFeatures: nfdv1alpha1.FeatureMatcher{
nfdv1alpha1.FeatureMatcherTerm{
Feature: "pci.device",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
"vendor": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "15b3"),
},
nfdv1alpha1.Rule{
Name: "RDMA capable static rule",
Labels: map[string]string{"rdma.capable": "true"},
MatchFeatures: nfdv1alpha1.FeatureMatcher{
nfdv1alpha1.FeatureMatcherTerm{
Feature: "pci.device",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
"vendor": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "15b3"),
},
},
},
},
},
{
Rule: &Rule{
nfdv1alpha1.Rule{
Name: "RDMA available static rule",
Labels: map[string]string{"rdma.available": "true"},
MatchFeatures: nfdv1alpha1.FeatureMatcher{
nfdv1alpha1.FeatureMatcherTerm{
Feature: "kernel.loadedmodule",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
"ib_uverbs": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
"rdma_ucm": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
},
nfdv1alpha1.Rule{
Name: "RDMA available static rule",
Labels: map[string]string{"rdma.available": "true"},
MatchFeatures: nfdv1alpha1.FeatureMatcher{
nfdv1alpha1.FeatureMatcherTerm{
Feature: "kernel.loadedmodule",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
"ib_uverbs": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
"rdma_ucm": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
},
},
},