mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-14 11:57:51 +00:00
Merge pull request #290 from adrianchiris/custom_features
Support custom features
This commit is contained in:
commit
7c4ff52a3c
12 changed files with 562 additions and 62 deletions
123
README.md
123
README.md
|
@ -153,6 +153,7 @@ for up-to-date information about the required volume mounts.
|
||||||
The current set of feature sources are the following:
|
The current set of feature sources are the following:
|
||||||
|
|
||||||
- CPU
|
- CPU
|
||||||
|
- Custom
|
||||||
- IOMMU
|
- IOMMU
|
||||||
- Kernel
|
- Kernel
|
||||||
- Memory
|
- Memory
|
||||||
|
@ -184,6 +185,7 @@ feature logically has sub-hierarchy, e.g. `sriov.capable` and
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"feature.node.kubernetes.io/cpu-<feature-name>": "true",
|
"feature.node.kubernetes.io/cpu-<feature-name>": "true",
|
||||||
|
"feature.node.kubernetes.io/custom-<feature-name>": "true",
|
||||||
"feature.node.kubernetes.io/iommu-<feature-name>": "true",
|
"feature.node.kubernetes.io/iommu-<feature-name>": "true",
|
||||||
"feature.node.kubernetes.io/kernel-<feature name>": "<feature value>",
|
"feature.node.kubernetes.io/kernel-<feature name>": "<feature value>",
|
||||||
"feature.node.kubernetes.io/memory-<feature-name>": "true",
|
"feature.node.kubernetes.io/memory-<feature-name>": "true",
|
||||||
|
@ -254,6 +256,127 @@ capability might be supported but not enabled.
|
||||||
| JSCVT | Perform Conversion to Match Javascript
|
| JSCVT | Perform Conversion to Match Javascript
|
||||||
| DCPOP | Persistent Memory Support
|
| DCPOP | Persistent Memory Support
|
||||||
|
|
||||||
|
### Custom Features
|
||||||
|
The Custom feature source allows the user to define features based on a mix of predefined rules.
|
||||||
|
A rule is provided input witch affects its process of matching for a defined feature.
|
||||||
|
|
||||||
|
To aid in making Custom Features clearer, we define a general and a per rule nomenclature, keeping things as
|
||||||
|
consistent as possible.
|
||||||
|
|
||||||
|
#### General Nomenclature & Definitions
|
||||||
|
```
|
||||||
|
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)
|
||||||
|
```yaml
|
||||||
|
- name: <feature name>
|
||||||
|
matchOn:
|
||||||
|
- <Rule-1>: <Rule-1 Input>
|
||||||
|
[<Rule-2>: <Rule-2 Input>]
|
||||||
|
- <Matcher-2>
|
||||||
|
- ...
|
||||||
|
- ...
|
||||||
|
- <Matcher-N>
|
||||||
|
- <custom feature 2>
|
||||||
|
- ...
|
||||||
|
- ...
|
||||||
|
- <custom feature M>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
```
|
||||||
|
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.
|
||||||
|
|
||||||
|
##### LoadedKMod Rule
|
||||||
|
###### Nomenclature
|
||||||
|
```
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
```yaml
|
||||||
|
custom:
|
||||||
|
- name: "my.kernel.feature"
|
||||||
|
matchOn:
|
||||||
|
- loadedKMod: ["kmod1", "kmod2"]
|
||||||
|
- name: "my.pci.feature"
|
||||||
|
matchOn:
|
||||||
|
- pciId:
|
||||||
|
vendor: ["15b3"]
|
||||||
|
device: ["1014", "1017"]
|
||||||
|
- name: "my.combined.feature"
|
||||||
|
matchOn:
|
||||||
|
- loadedKMod : ["vendor_kmod1", "vendor_kmod2"]
|
||||||
|
pciId:
|
||||||
|
vendor: ["15b3"]
|
||||||
|
device: ["1014", "1017"]
|
||||||
|
- name: "my.accumulated.feature"
|
||||||
|
matchOn:
|
||||||
|
- loadedKMod : ["some_kmod1", "some_kmod2"]
|
||||||
|
- pciId:
|
||||||
|
vendor: ["15b3"]
|
||||||
|
device: ["1014", "1017"]
|
||||||
|
```
|
||||||
|
|
||||||
|
__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.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: `feature.node.kubernetes.io/custom-my.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`.
|
||||||
|
|
||||||
|
#### Statically defined features
|
||||||
|
Some feature labels which are common and generic are defined statically in the `custom` feature source.
|
||||||
|
A user may add additional Matchers to these feature labels by defining them in the `nfd-worker` configuration file.
|
||||||
|
|
||||||
|
| Feature | Attribute | Description |
|
||||||
|
| ------- | --------- | -----------|
|
||||||
|
| rdma | capable | The node has an RDMA capable Network adapter |
|
||||||
|
| rdma | enabled | The node has the needed RDMA modules loaded to run RDMA traffic |
|
||||||
|
|
||||||
### IOMMU Features
|
### IOMMU Features
|
||||||
|
|
||||||
| Feature name | Description |
|
| Feature name | Description |
|
||||||
|
|
|
@ -91,7 +91,7 @@ func argsParse(argv []string) (worker.Args, error) {
|
||||||
in testing
|
in testing
|
||||||
[Default: ]
|
[Default: ]
|
||||||
--sources=<sources> Comma separated list of feature sources.
|
--sources=<sources> Comma separated list of feature sources.
|
||||||
[Default: cpu,iommu,kernel,local,memory,network,pci,storage,system]
|
[Default: cpu,custom,iommu,kernel,local,memory,network,pci,storage,system]
|
||||||
--no-publish Do not publish discovered features to the
|
--no-publish Do not publish discovered features to the
|
||||||
cluster-local Kubernetes API server.
|
cluster-local Kubernetes API server.
|
||||||
--label-whitelist=<pattern> Regular expression to filter label names to
|
--label-whitelist=<pattern> Regular expression to filter label names to
|
||||||
|
|
|
@ -23,6 +23,8 @@ import (
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var allSources = []string{"cpu", "custom", "iommu", "kernel", "local", "memory", "network", "pci", "storage", "system"}
|
||||||
|
|
||||||
func TestArgsParse(t *testing.T) {
|
func TestArgsParse(t *testing.T) {
|
||||||
Convey("When parsing command line arguments", t, func() {
|
Convey("When parsing command line arguments", t, func() {
|
||||||
Convey("When --no-publish and --oneshot flags are passed", func() {
|
Convey("When --no-publish and --oneshot flags are passed", func() {
|
||||||
|
@ -32,7 +34,7 @@ func TestArgsParse(t *testing.T) {
|
||||||
So(args.SleepInterval, ShouldEqual, 60*time.Second)
|
So(args.SleepInterval, ShouldEqual, 60*time.Second)
|
||||||
So(args.NoPublish, ShouldBeTrue)
|
So(args.NoPublish, ShouldBeTrue)
|
||||||
So(args.Oneshot, ShouldBeTrue)
|
So(args.Oneshot, ShouldBeTrue)
|
||||||
So(args.Sources, ShouldResemble, []string{"cpu", "iommu", "kernel", "local", "memory", "network", "pci", "storage", "system"})
|
So(args.Sources, ShouldResemble, allSources)
|
||||||
So(len(args.LabelWhiteList), ShouldEqual, 0)
|
So(len(args.LabelWhiteList), ShouldEqual, 0)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
|
@ -56,7 +58,7 @@ func TestArgsParse(t *testing.T) {
|
||||||
|
|
||||||
Convey("args.labelWhiteList is set to appropriate value and args.sources is set to default value", func() {
|
Convey("args.labelWhiteList is set to appropriate value and args.sources is set to default value", func() {
|
||||||
So(args.NoPublish, ShouldBeFalse)
|
So(args.NoPublish, ShouldBeFalse)
|
||||||
So(args.Sources, ShouldResemble, []string{"cpu", "iommu", "kernel", "local", "memory", "network", "pci", "storage", "system"})
|
So(args.Sources, ShouldResemble, allSources)
|
||||||
So(args.LabelWhiteList, ShouldResemble, ".*rdt.*")
|
So(args.LabelWhiteList, ShouldResemble, ".*rdt.*")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
|
|
|
@ -44,3 +44,22 @@
|
||||||
# - "device"
|
# - "device"
|
||||||
# - "subsystem_vendor"
|
# - "subsystem_vendor"
|
||||||
# - "subsystem_device"
|
# - "subsystem_device"
|
||||||
|
# custom:
|
||||||
|
# - name: "my.kernel.feature"
|
||||||
|
# matchOn:
|
||||||
|
# - loadedKMod: ["example_kmod1", "example_kmod2"]
|
||||||
|
# - name: "my.pci.feature"
|
||||||
|
# matchOn:
|
||||||
|
# - pciId:
|
||||||
|
# class: ["0200"]
|
||||||
|
# vendor: ["15b3"]
|
||||||
|
# device: ["1014", "1017"]
|
||||||
|
# - pciId :
|
||||||
|
# vendor: ["8086"]
|
||||||
|
# device: ["1000", "1100"]
|
||||||
|
# - name: "my.combined.feature"
|
||||||
|
# matchOn:
|
||||||
|
# - pciId:
|
||||||
|
# vendor: ["15b3"]
|
||||||
|
# device: ["1014", "1017"]
|
||||||
|
# loadedKMod : ["vendor_kmod1", "vendor_kmod2"]
|
||||||
|
|
|
@ -36,6 +36,7 @@ import (
|
||||||
"sigs.k8s.io/node-feature-discovery/pkg/version"
|
"sigs.k8s.io/node-feature-discovery/pkg/version"
|
||||||
"sigs.k8s.io/node-feature-discovery/source"
|
"sigs.k8s.io/node-feature-discovery/source"
|
||||||
"sigs.k8s.io/node-feature-discovery/source/cpu"
|
"sigs.k8s.io/node-feature-discovery/source/cpu"
|
||||||
|
"sigs.k8s.io/node-feature-discovery/source/custom"
|
||||||
"sigs.k8s.io/node-feature-discovery/source/fake"
|
"sigs.k8s.io/node-feature-discovery/source/fake"
|
||||||
"sigs.k8s.io/node-feature-discovery/source/iommu"
|
"sigs.k8s.io/node-feature-discovery/source/iommu"
|
||||||
"sigs.k8s.io/node-feature-discovery/source/kernel"
|
"sigs.k8s.io/node-feature-discovery/source/kernel"
|
||||||
|
@ -61,6 +62,7 @@ type NFDConfig struct {
|
||||||
Cpu *cpu.NFDConfig `json:"cpu,omitempty"`
|
Cpu *cpu.NFDConfig `json:"cpu,omitempty"`
|
||||||
Kernel *kernel.NFDConfig `json:"kernel,omitempty"`
|
Kernel *kernel.NFDConfig `json:"kernel,omitempty"`
|
||||||
Pci *pci.NFDConfig `json:"pci,omitempty"`
|
Pci *pci.NFDConfig `json:"pci,omitempty"`
|
||||||
|
Custom *custom.NFDConfig `json:"custom,omitempty"`
|
||||||
} `json:"sources,omitempty"`
|
} `json:"sources,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,6 +236,7 @@ func configParse(filepath string, overrides string) error {
|
||||||
config.Sources.Cpu = &cpu.Config
|
config.Sources.Cpu = &cpu.Config
|
||||||
config.Sources.Kernel = &kernel.Config
|
config.Sources.Kernel = &kernel.Config
|
||||||
config.Sources.Pci = &pci.Config
|
config.Sources.Pci = &pci.Config
|
||||||
|
config.Sources.Custom = &custom.Config
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(filepath)
|
data, err := ioutil.ReadFile(filepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -276,6 +279,7 @@ func configureParameters(sourcesWhiteList []string, labelWhiteListStr string) (e
|
||||||
pci.Source{},
|
pci.Source{},
|
||||||
storage.Source{},
|
storage.Source{},
|
||||||
system.Source{},
|
system.Source{},
|
||||||
|
custom.Source{},
|
||||||
// local needs to be the last source so that it is able to override
|
// local needs to be the last source so that it is able to override
|
||||||
// labels from other sources
|
// labels from other sources
|
||||||
local.Source{},
|
local.Source{},
|
||||||
|
|
93
source/custom/custom.go
Normal file
93
source/custom/custom.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
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 custom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"sigs.k8s.io/node-feature-discovery/source"
|
||||||
|
"sigs.k8s.io/node-feature-discovery/source/custom/rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Custom Features Configurations
|
||||||
|
type MatchRule struct {
|
||||||
|
PciId *rules.PciIdRule `json:"pciId,omitempty""`
|
||||||
|
LoadedKMod *rules.LoadedKModRule `json:"loadedKMod,omitempty""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomFeature struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
MatchOn []MatchRule `json:"matchOn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NFDConfig []CustomFeature
|
||||||
|
|
||||||
|
var Config = NFDConfig{}
|
||||||
|
|
||||||
|
// Implements FeatureSource Interface
|
||||||
|
type Source struct{}
|
||||||
|
|
||||||
|
// Return name of the feature source
|
||||||
|
func (s Source) Name() string { return "custom" }
|
||||||
|
|
||||||
|
// Discover features
|
||||||
|
func (s Source) Discover() (source.Features, error) {
|
||||||
|
features := source.Features{}
|
||||||
|
allFeatureConfig := append(getStaticFeatureConfig(), Config...)
|
||||||
|
log.Printf("INFO: Custom features: %+v", allFeatureConfig)
|
||||||
|
// Iterate over features
|
||||||
|
for _, customFeature := range allFeatureConfig {
|
||||||
|
featureExist, err := s.discoverFeature(customFeature)
|
||||||
|
if err != nil {
|
||||||
|
return features, fmt.Errorf("failed to discover feature: %s. %s", customFeature.Name, err.Error())
|
||||||
|
}
|
||||||
|
if featureExist {
|
||||||
|
features[customFeature.Name] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return features, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a single feature by Matching on the defined rules.
|
||||||
|
// A feature is present if all defined Rules in a MatchRule return a match.
|
||||||
|
func (s Source) discoverFeature(feature CustomFeature) (bool, error) {
|
||||||
|
for _, rule := range feature.MatchOn {
|
||||||
|
// PCI ID rule
|
||||||
|
if rule.PciId != nil {
|
||||||
|
match, err := rule.PciId.Match()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Loaded kernel module rule
|
||||||
|
if rule.LoadedKMod != nil {
|
||||||
|
match, err := rule.LoadedKMod.Match()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
61
source/custom/rules/loaded_kmod_rule.go
Normal file
61
source/custom/rules/loaded_kmod_rule.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rule that matches on loaded kernel modules in the system
|
||||||
|
type LoadedKModRule []string
|
||||||
|
|
||||||
|
const kmodProcfsPath = "/proc/modules"
|
||||||
|
|
||||||
|
// Match loaded kernel modules on provided list of kernel modules
|
||||||
|
func (kmods *LoadedKModRule) Match() (bool, error) {
|
||||||
|
loadedModules, err := kmods.getLoadedModules()
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to get loaded kernel modules. %s", err.Error())
|
||||||
|
}
|
||||||
|
for _, kmod := range *kmods {
|
||||||
|
if _, ok := loadedModules[kmod]; !ok {
|
||||||
|
// kernel module not loaded
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (kmods *LoadedKModRule) getLoadedModules() (map[string]struct{}, error) {
|
||||||
|
out, err := ioutil.ReadFile(kmodProcfsPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read file %s: %s", kmodProcfsPath, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedMods := make(map[string]struct{})
|
||||||
|
for _, line := range strings.Split(string(out), "\n") {
|
||||||
|
// skip empty lines
|
||||||
|
if len(line) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// append loaded module
|
||||||
|
loadedMods[strings.Fields(line)[0]] = struct{}{}
|
||||||
|
}
|
||||||
|
return loadedMods, nil
|
||||||
|
}
|
87
source/custom/rules/pci_id_rule.go
Normal file
87
source/custom/rules/pci_id_rule.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
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"
|
||||||
|
pciutils "sigs.k8s.io/node-feature-discovery/source/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rule that matches on the following PCI device attributes: <class, vendor, device>
|
||||||
|
// each device attribute will be a list elements(strings).
|
||||||
|
// Match operation: OR will be performed per element and AND will be performed per attribute.
|
||||||
|
// An empty attribute will not be included in the matching process.
|
||||||
|
type PciIdRuleInput struct {
|
||||||
|
Class []string `json:"class,omitempty"`
|
||||||
|
Vendor []string `json:"vendor,omitempty"`
|
||||||
|
Device []string `json:"device,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PciIdRule struct {
|
||||||
|
PciIdRuleInput
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match PCI devices on provided PCI device attributes
|
||||||
|
func (r *PciIdRule) Match() (bool, error) {
|
||||||
|
devAttr := map[string]bool{}
|
||||||
|
for _, attr := range []string{"class", "vendor", "device"} {
|
||||||
|
devAttr[attr] = true
|
||||||
|
}
|
||||||
|
allDevs, err := pciutils.DetectPci(devAttr)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to detect PCI devices: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, classDevs := range allDevs {
|
||||||
|
for _, dev := range classDevs {
|
||||||
|
// match rule on a single device
|
||||||
|
if r.matchDevOnRule(dev) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PciIdRule) matchDevOnRule(dev pciutils.PciDeviceInfo) bool {
|
||||||
|
if len(r.Class) == 0 && len(r.Vendor) == 0 && len(r.Device) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Class) > 0 && !in(dev["class"], r.Class) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Vendor) > 0 && !in(dev["vendor"], r.Vendor) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.Device) > 0 && !in(dev["device"], r.Device) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func in(item string, arr []string) bool {
|
||||||
|
for _, val := range arr {
|
||||||
|
if val == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
22
source/custom/rules/rule.go
Normal file
22
source/custom/rules/rule.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
type Rule interface {
|
||||||
|
// Match on rule
|
||||||
|
Match() (bool, error)
|
||||||
|
}
|
46
source/custom/static_features.go
Normal file
46
source/custom/static_features.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
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 custom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sigs.k8s.io/node-feature-discovery/source/custom/rules"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getStaticFeatures returns statically configured custom features to discover
|
||||||
|
// e.g RMDA related features. NFD configuration file may extend these custom features by adding rules.
|
||||||
|
func getStaticFeatureConfig() []CustomFeature {
|
||||||
|
return []CustomFeature{
|
||||||
|
CustomFeature{
|
||||||
|
Name: "rdma.capable",
|
||||||
|
MatchOn: []MatchRule{
|
||||||
|
MatchRule{
|
||||||
|
PciId: &rules.PciIdRule{
|
||||||
|
rules.PciIdRuleInput{Vendor: []string{"15b3"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CustomFeature{
|
||||||
|
Name: "rdma.available",
|
||||||
|
MatchOn: []MatchRule{
|
||||||
|
MatchRule{
|
||||||
|
LoadedKMod: &rules.LoadedKModRule{"ib_uverbs", "rdma_ucm"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
98
source/internal/pci_utils.go
Normal file
98
source/internal/pci_utils.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
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 pciutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PciDeviceInfo map[string]string
|
||||||
|
|
||||||
|
var DefaultPciDevAttrs = []string{"class", "vendor", "device", "subsystem_vendor", "subsystem_device"}
|
||||||
|
var ExtraPciDevAttrs = []string{"sriov_totalvfs"}
|
||||||
|
|
||||||
|
// Read a single PCI device attribute
|
||||||
|
// A PCI attribute in this context, maps to the corresponding sysfs file
|
||||||
|
func readSingleAttribute(devPath string, attrName string) (string, error) {
|
||||||
|
data, err := ioutil.ReadFile(path.Join(devPath, attrName))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read device attribute %s: %v", attrName, err)
|
||||||
|
}
|
||||||
|
// Strip whitespace and '0x' prefix
|
||||||
|
attrVal := strings.TrimSpace(strings.TrimPrefix(string(data), "0x"))
|
||||||
|
|
||||||
|
if attrName == "class" && len(attrVal) > 4 {
|
||||||
|
// Take four first characters, so that the programming
|
||||||
|
// interface identifier gets stripped from the raw class code
|
||||||
|
attrVal = attrVal[0:4]
|
||||||
|
}
|
||||||
|
return attrVal, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read information of one PCI device
|
||||||
|
func readDevInfo(devPath string, deviceAttrSpec map[string]bool) (PciDeviceInfo, error) {
|
||||||
|
info := PciDeviceInfo{}
|
||||||
|
|
||||||
|
for attr, must := range deviceAttrSpec {
|
||||||
|
attrVal, err := readSingleAttribute(devPath, attr)
|
||||||
|
if err != nil {
|
||||||
|
if must {
|
||||||
|
return info, fmt.Errorf("Failed to read device %s: %s", attr, err)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
info[attr] = attrVal
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List available PCI devices and retrieve device attributes.
|
||||||
|
// deviceAttrSpec is a map which specifies which attributes to retrieve.
|
||||||
|
// a false value for a specific attribute marks the attribute as optional.
|
||||||
|
// a true value for a specific attribute marks the attribute as mandatory.
|
||||||
|
// "class" attribute is considered mandatory.
|
||||||
|
// DetectPci() will fail if the retrieval of a mandatory attribute fails.
|
||||||
|
func DetectPci(deviceAttrSpec map[string]bool) (map[string][]PciDeviceInfo, error) {
|
||||||
|
const basePath = "/sys/bus/pci/devices/"
|
||||||
|
devInfo := make(map[string][]PciDeviceInfo)
|
||||||
|
|
||||||
|
devices, err := ioutil.ReadDir(basePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// "class" is a mandatory attribute, inject it to spec if needed.
|
||||||
|
deviceAttrSpec["class"] = true
|
||||||
|
|
||||||
|
// Iterate over devices
|
||||||
|
for _, device := range devices {
|
||||||
|
info, err := readDevInfo(path.Join(basePath, device.Name()), deviceAttrSpec)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
class := info["class"]
|
||||||
|
devInfo[class] = append(devInfo[class], info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return devInfo, nil
|
||||||
|
}
|
|
@ -18,16 +18,13 @@ package pci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/node-feature-discovery/source"
|
"sigs.k8s.io/node-feature-discovery/source"
|
||||||
|
"sigs.k8s.io/node-feature-discovery/source/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pciDeviceInfo map[string]string
|
|
||||||
|
|
||||||
type NFDConfig struct {
|
type NFDConfig struct {
|
||||||
DeviceClassWhitelist []string `json:"deviceClassWhitelist,omitempty"`
|
DeviceClassWhitelist []string `json:"deviceClassWhitelist,omitempty"`
|
||||||
DeviceLabelFields []string `json:"deviceLabelFields,omitempty"`
|
DeviceLabelFields []string `json:"deviceLabelFields,omitempty"`
|
||||||
|
@ -38,9 +35,6 @@ var Config = NFDConfig{
|
||||||
DeviceLabelFields: []string{"class", "vendor"},
|
DeviceLabelFields: []string{"class", "vendor"},
|
||||||
}
|
}
|
||||||
|
|
||||||
var devLabelAttrs = []string{"class", "vendor", "device", "subsystem_vendor", "subsystem_device"}
|
|
||||||
var extraDevAttrs = []string{"sriov_totalvfs"}
|
|
||||||
|
|
||||||
// Implement FeatureSource interface
|
// Implement FeatureSource interface
|
||||||
type Source struct{}
|
type Source struct{}
|
||||||
|
|
||||||
|
@ -58,7 +52,7 @@ func (s Source) Discover() (source.Features, error) {
|
||||||
configLabelFields[field] = true
|
configLabelFields[field] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, attr := range devLabelAttrs {
|
for _, attr := range pciutils.DefaultPciDevAttrs {
|
||||||
if _, ok := configLabelFields[attr]; ok {
|
if _, ok := configLabelFields[attr]; ok {
|
||||||
deviceLabelFields = append(deviceLabelFields, attr)
|
deviceLabelFields = append(deviceLabelFields, attr)
|
||||||
delete(configLabelFields, attr)
|
delete(configLabelFields, attr)
|
||||||
|
@ -79,14 +73,14 @@ func (s Source) Discover() (source.Features, error) {
|
||||||
// Read extraDevAttrs + configured or default labels. Attributes
|
// Read extraDevAttrs + configured or default labels. Attributes
|
||||||
// set to 'true' are considered must-have.
|
// set to 'true' are considered must-have.
|
||||||
deviceAttrs := map[string]bool{}
|
deviceAttrs := map[string]bool{}
|
||||||
for _, label := range extraDevAttrs {
|
for _, label := range pciutils.ExtraPciDevAttrs {
|
||||||
deviceAttrs[label] = false
|
deviceAttrs[label] = false
|
||||||
}
|
}
|
||||||
for _, label := range deviceLabelFields {
|
for _, label := range deviceLabelFields {
|
||||||
deviceAttrs[label] = true
|
deviceAttrs[label] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
devs, err := detectPci(deviceAttrs)
|
devs, err := pciutils.DetectPci(deviceAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to detect PCI devices: %s", err.Error())
|
return nil, fmt.Errorf("Failed to detect PCI devices: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
@ -112,54 +106,5 @@ func (s Source) Discover() (source.Features, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return features, nil
|
return features, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read information of one PCI device
|
|
||||||
func readDevInfo(devPath string, devAttrs map[string]bool) (pciDeviceInfo, error) {
|
|
||||||
info := pciDeviceInfo{}
|
|
||||||
for attr, must := range devAttrs {
|
|
||||||
data, err := ioutil.ReadFile(path.Join(devPath, attr))
|
|
||||||
if err != nil {
|
|
||||||
if must {
|
|
||||||
return info, fmt.Errorf("Failed to read device %s: %s", attr, err)
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Strip whitespace and '0x' prefix
|
|
||||||
info[attr] = strings.TrimSpace(strings.TrimPrefix(string(data), "0x"))
|
|
||||||
|
|
||||||
if attr == "class" && len(info[attr]) > 4 {
|
|
||||||
// Take four first characters, so that the programming
|
|
||||||
// interface identifier gets stripped from the raw class code
|
|
||||||
info[attr] = info[attr][0:4]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List available PCI devices
|
|
||||||
func detectPci(devAttrs map[string]bool) (map[string][]pciDeviceInfo, error) {
|
|
||||||
const basePath = "/sys/bus/pci/devices/"
|
|
||||||
devInfo := make(map[string][]pciDeviceInfo)
|
|
||||||
|
|
||||||
devices, err := ioutil.ReadDir(basePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over devices
|
|
||||||
for _, device := range devices {
|
|
||||||
info, err := readDevInfo(path.Join(basePath, device.Name()), devAttrs)
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
class := info["class"]
|
|
||||||
devInfo[class] = append(devInfo[class], info)
|
|
||||||
}
|
|
||||||
|
|
||||||
return devInfo, nil
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue