1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-28 02:37:11 +00:00

Merge pull request #304 from marquiz/devel/config-reload

Rework config handling
This commit is contained in:
Kubernetes Prow Robot 2020-05-21 03:10:36 -07:00 committed by GitHub
commit e9017bef06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 518 additions and 236 deletions

View file

@ -714,8 +714,11 @@ kubectl create -f nfd-worker-daemonset.yaml
Nfd-worker connects to the nfd-master service to advertise hardware features.
When run as a daemonset, nodes are re-labeled at an interval specified using
the `--sleep-interval` option. In the [template](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/nfd-worker-daemonset.yaml.template#L26) the default interval is set to 60s
which is also the default when no `--sleep-interval` is specified.
the `--sleep-interval` option. In the
[template](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/nfd-worker-daemonset.yaml.template#L26)
the default interval is set to 60s which is also the default when no
`--sleep-interval` is specified. Also, the configuration file is re-read on
each iteration providing a simple mechanism of run-time reconfiguration.
Feature discovery can alternatively be configured as a one-shot job. There is
an example script in this repo that demonstrates how to deploy the job in the cluster.
@ -771,11 +774,16 @@ each nfd-worker requires a individual node-specific TLS certificate.
Nfd-worker supports a configuration file. The default location is
`/etc/kubernetes/node-feature-discovery/nfd-worker.conf`, but,
this can be changed by specifying the`--config` command line flag. The file is
read inside the container, and thus, Volumes and VolumeMounts are needed to
make your configuration available for NFD. The preferred method is to use a
ConfigMap.
For example, create a config map using the example config as a template:
this can be changed by specifying the`--config` command line flag.
Configuration file is re-read on each labeling pass (determined by
`--sleep-interval`) which makes run-time re-configuration of nfd-worker
possible.
Worker configuration file is read inside the container, and thus, Volumes and
VolumeMounts are needed to make your configuration available for NFD. The
preferred method is to use a ConfigMap which provides easy deployment and
re-configurability. For example, create a config map using the example config
as a template:
```
cp nfd-worker.conf.example nfd-worker.conf
vim nfd-worker.conf # edit the configuration

View file

@ -4,9 +4,12 @@
package apihelper
import kubernetes "k8s.io/client-go/kubernetes"
import mock "github.com/stretchr/testify/mock"
import v1 "k8s.io/api/core/v1"
import (
mock "github.com/stretchr/testify/mock"
kubernetes "k8s.io/client-go/kubernetes"
v1 "k8s.io/api/core/v1"
)
// MockAPIHelpers is an autogenerated mock type for the APIHelpers type
type MockAPIHelpers struct {

View file

@ -4,9 +4,13 @@
package labeler
import context "context"
import grpc "google.golang.org/grpc"
import mock "github.com/stretchr/testify/mock"
import (
context "context"
grpc "google.golang.org/grpc"
mock "github.com/stretchr/testify/mock"
)
// MockLabelerClient is an autogenerated mock type for the LabelerClient type
type MockLabelerClient struct {

View file

@ -29,8 +29,11 @@ import (
"github.com/vektra/errors"
"sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/cpu"
"sigs.k8s.io/node-feature-discovery/source/fake"
"sigs.k8s.io/node-feature-discovery/source/kernel"
"sigs.k8s.io/node-feature-discovery/source/panic_fake"
"sigs.k8s.io/node-feature-discovery/source/pci"
)
const fakeFeatureSourceName string = "testSource"
@ -91,13 +94,27 @@ func makeFakeFeatures(names []string) (source.Features, Labels) {
return features, labels
}
func TestConfigParse(t *testing.T) {
Convey("When parsing configuration file", t, func() {
Convey("When non-accessible file is given", func() {
err := configParse("non-existing-file", "")
func (w *nfdWorker) getSource(name string) source.FeatureSource {
for _, s := range w.sources {
if s.Name() == name {
return s
}
}
return nil
}
Convey("Should return error", func() {
So(err, ShouldNotBeNil)
func TestConfigParse(t *testing.T) {
Convey("When parsing configuration", t, func() {
w, err := NewNfdWorker(Args{Sources: []string{"cpu", "kernel", "pci"}})
So(err, ShouldBeNil)
worker := w.(*nfdWorker)
Convey("and a non-accessible file and some overrides are specified", func() {
overrides := `{"sources": {"cpu": {"cpuid": {"attributeBlacklist": ["foo","bar"]}}}}`
worker.configure("non-existing-file", overrides)
Convey("overrides should take effect", func() {
c := worker.getSource("cpu").GetConfig().(*cpu.Config)
So(c.Cpuid.AttributeBlacklist, ShouldResemble, []string{"foo", "bar"})
})
})
// Create a temporary config file
@ -114,76 +131,87 @@ func TestConfigParse(t *testing.T) {
f.Close()
So(err, ShouldBeNil)
Convey("When proper config file is given", func() {
err := configParse(f.Name(), "")
Convey("and a proper config file is specified", func() {
worker.configure(f.Name(), "")
Convey("Should return error", func() {
Convey("specified configuration should take effect", func() {
So(err, ShouldBeNil)
So(config.Sources.Kernel.ConfigOpts, ShouldResemble, []string{"DMI"})
So(config.Sources.Pci.DeviceClassWhitelist, ShouldResemble, []string{"ff"})
c := worker.getSource("kernel").GetConfig()
So(c.(*kernel.Config).ConfigOpts, ShouldResemble, []string{"DMI"})
c = worker.getSource("pci").GetConfig()
So(c.(*pci.Config).DeviceClassWhitelist, ShouldResemble, []string{"ff"})
})
})
Convey("and a proper config file and overrides are given", func() {
overrides := `{"sources": {"pci": {"deviceClassWhitelist": ["03"]}}}`
worker.configure(f.Name(), overrides)
Convey("overrides should take precedence over the config file", func() {
So(err, ShouldBeNil)
c := worker.getSource("kernel").GetConfig()
So(c.(*kernel.Config).ConfigOpts, ShouldResemble, []string{"DMI"})
c = worker.getSource("pci").GetConfig()
So(c.(*pci.Config).DeviceClassWhitelist, ShouldResemble, []string{"03"})
})
})
})
}
func TestConfigureParameters(t *testing.T) {
Convey("When configuring parameters for node feature discovery", t, func() {
func TestNewNfdWorker(t *testing.T) {
Convey("When creating new NfdWorker instance", t, func() {
Convey("When no sourcesWhiteList and labelWhiteListStr are passed", func() {
sourcesWhiteList := []string{}
labelWhiteListStr := ""
Convey("without any args specified", func() {
args := Args{}
emptyRegexp, _ := regexp.Compile("")
enabledSources, labelWhiteList, err := configureParameters(sourcesWhiteList, labelWhiteListStr)
Convey("Error should not be produced", func() {
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
})
Convey("No sourcesWhiteList or labelWhiteList are returned", func() {
So(len(enabledSources), ShouldEqual, 0)
So(labelWhiteList, ShouldResemble, emptyRegexp)
worker := w.(*nfdWorker)
Convey("no sources should be enabled and the whitelist regexp should be empty", func() {
So(len(worker.sources), ShouldEqual, 0)
So(worker.labelWhiteList, ShouldResemble, emptyRegexp)
})
})
Convey("When sourcesWhiteList is passed", func() {
sourcesWhiteList := []string{"fake"}
labelWhiteListStr := ""
Convey("with non-empty Sources arg specified", func() {
args := Args{Sources: []string{"fake"}}
emptyRegexp, _ := regexp.Compile("")
enabledSources, labelWhiteList, err := configureParameters(sourcesWhiteList, labelWhiteListStr)
Convey("Error should not be produced", func() {
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
})
Convey("Proper sourcesWhiteList are returned", func() {
So(len(enabledSources), ShouldEqual, 1)
So(enabledSources[0], ShouldHaveSameTypeAs, fake.Source{})
So(labelWhiteList, ShouldResemble, emptyRegexp)
worker := w.(*nfdWorker)
Convey("proper sources should be enabled", func() {
So(len(worker.sources), ShouldEqual, 1)
So(worker.sources[0], ShouldHaveSameTypeAs, &fake.Source{})
So(worker.labelWhiteList, ShouldResemble, emptyRegexp)
})
})
Convey("When invalid labelWhiteListStr is passed", func() {
sourcesWhiteList := []string{""}
labelWhiteListStr := "*"
enabledSources, labelWhiteList, err := configureParameters(sourcesWhiteList, labelWhiteListStr)
Convey("Error is produced", func() {
So(enabledSources, ShouldBeNil)
So(labelWhiteList, ShouldBeNil)
Convey("with invalid LabelWhiteList arg specified", func() {
args := Args{LabelWhiteList: "*"}
w, err := NewNfdWorker(args)
worker := w.(*nfdWorker)
Convey("an error should be returned", func() {
So(len(worker.sources), ShouldEqual, 0)
So(worker.labelWhiteList, ShouldBeNil)
So(err, ShouldNotBeNil)
})
})
Convey("When valid labelWhiteListStr is passed", func() {
sourcesWhiteList := []string{""}
labelWhiteListStr := ".*rdt.*"
expectRegexp, err := regexp.Compile(".*rdt.*")
enabledSources, labelWhiteList, err := configureParameters(sourcesWhiteList, labelWhiteListStr)
Convey("Error should not be produced", func() {
Convey("with valid LabelWhiteListStr arg specified", func() {
args := Args{LabelWhiteList: ".*rdt.*"}
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
})
Convey("Proper labelWhiteList is returned", func() {
So(len(enabledSources), ShouldEqual, 0)
So(labelWhiteList, ShouldResemble, expectRegexp)
worker := w.(*nfdWorker)
expectRegexp := regexp.MustCompile(".*rdt.*")
Convey("proper labelWhiteList regexp should be produced", func() {
So(len(worker.sources), ShouldEqual, 0)
So(worker.labelWhiteList, ShouldResemble, expectRegexp)
})
})
})

View file

@ -19,6 +19,7 @@ package nfdworker
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"log"
@ -50,7 +51,6 @@ import (
"sigs.k8s.io/yaml"
)
// package loggers
var (
stdoutLogger = log.New(os.Stdout, "", log.LstdFlags)
stderrLogger = log.New(os.Stderr, "", log.LstdFlags)
@ -59,16 +59,10 @@ var (
// Global config
type NFDConfig struct {
Sources struct {
CPU *cpu.NFDConfig `json:"cpu,omitempty"`
Kernel *kernel.NFDConfig `json:"kernel,omitempty"`
Pci *pci.NFDConfig `json:"pci,omitempty"`
Usb *usb.NFDConfig `json:"usb,omitempty"`
Custom *custom.NFDConfig `json:"custom,omitempty"`
} `json:"sources,omitempty"`
Sources sourcesConfig
}
var config = NFDConfig{}
type sourcesConfig map[string]source.Config
// Labels are a Kubernetes representation of discovered features.
type Labels map[string]string
@ -94,14 +88,21 @@ type NfdWorker interface {
}
type nfdWorker struct {
args Args
clientConn *grpc.ClientConn
client pb.LabelerClient
args Args
clientConn *grpc.ClientConn
client pb.LabelerClient
config NFDConfig
sources []source.FeatureSource
labelWhiteList *regexp.Regexp
}
// Create new NfdWorker instance.
func NewNfdWorker(args Args) (NfdWorker, error) {
nfd := &nfdWorker{args: args}
nfd := &nfdWorker{
args: args,
sources: []source.FeatureSource{},
}
if args.SleepInterval > 0 && args.SleepInterval < time.Second {
stderrLogger.Printf("WARNING: too short sleep-intervall specified (%s), forcing to 1s", args.SleepInterval.String())
args.SleepInterval = time.Second
@ -120,6 +121,44 @@ func NewNfdWorker(args Args) (NfdWorker, error) {
}
}
// Figure out active sources
allSources := []source.FeatureSource{
&cpu.Source{},
&fake.Source{},
&iommu.Source{},
&kernel.Source{},
&memory.Source{},
&network.Source{},
&panicfake.Source{},
&pci.Source{},
&storage.Source{},
&system.Source{},
&usb.Source{},
&custom.Source{},
// local needs to be the last source so that it is able to override
// labels from other sources
&local.Source{},
}
sourceWhiteList := map[string]struct{}{}
for _, s := range args.Sources {
sourceWhiteList[strings.TrimSpace(s)] = struct{}{}
}
nfd.sources = []source.FeatureSource{}
for _, s := range allSources {
if _, enabled := sourceWhiteList[s.Name()]; enabled {
nfd.sources = append(nfd.sources, s)
}
}
// Compile labelWhiteList regex
var err error
nfd.labelWhiteList, err = regexp.Compile(args.LabelWhiteList)
if err != nil {
return nfd, fmt.Errorf("error parsing label whitelist regex (%s): %s", args.LabelWhiteList, err)
}
return nfd, nil
}
@ -129,28 +168,19 @@ func (w *nfdWorker) Run() error {
stdoutLogger.Printf("Node Feature Discovery Worker %s", version.Get())
stdoutLogger.Printf("NodeName: '%s'", nodeName)
// Parse config
err := configParse(w.args.ConfigFile, w.args.Options)
if err != nil {
stderrLogger.Print(err)
}
// Configure the parameters for feature discovery.
enabledSources, labelWhiteList, err := configureParameters(w.args.Sources, w.args.LabelWhiteList)
if err != nil {
return fmt.Errorf("error occurred while configuring parameters: %s", err.Error())
}
// Connect to NFD master
err = w.connect()
err := w.connect()
if err != nil {
return fmt.Errorf("failed to connect: %v", err)
}
defer w.disconnect()
for {
// Parse and apply configuration
w.configure(w.args.ConfigFile, w.args.Options)
// Get the set of feature labels.
labels := createFeatureLabels(enabledSources, labelWhiteList)
labels := createFeatureLabels(w.sources, w.labelWhiteList)
// Update the node with the feature labels.
if w.client != nil {
@ -236,76 +266,38 @@ func (w *nfdWorker) disconnect() {
}
// Parse configuration options
func configParse(filepath string, overrides string) error {
config.Sources.CPU = &cpu.Config
config.Sources.Kernel = &kernel.Config
config.Sources.Pci = &pci.Config
config.Sources.Usb = &usb.Config
config.Sources.Custom = &custom.Config
func (w *nfdWorker) configure(filepath string, overrides string) {
// Create a new default config
c := NFDConfig{Sources: make(map[string]source.Config, len(w.sources))}
for _, s := range w.sources {
c.Sources[s.Name()] = s.NewConfig()
}
// Try to read and parse config file
data, err := ioutil.ReadFile(filepath)
if err != nil {
return fmt.Errorf("Failed to read config file: %s", err)
}
// Read config file
err = yaml.Unmarshal(data, &config)
if err != nil {
return fmt.Errorf("Failed to parse config file: %s", err)
}
// Parse config overrides
err = yaml.Unmarshal([]byte(overrides), &config)
if err != nil {
return fmt.Errorf("Failed to parse --options: %s", err)
}
return nil
}
// configureParameters returns all the variables required to perform feature
// discovery based on command line arguments.
func configureParameters(sourcesWhiteList []string, labelWhiteListStr string) (enabledSources []source.FeatureSource, labelWhiteList *regexp.Regexp, err error) {
// A map for lookup
sourcesWhiteListMap := map[string]struct{}{}
for _, s := range sourcesWhiteList {
sourcesWhiteListMap[strings.TrimSpace(s)] = struct{}{}
}
// Configure feature sources.
allSources := []source.FeatureSource{
cpu.Source{},
fake.Source{},
iommu.Source{},
kernel.Source{},
memory.Source{},
network.Source{},
panicfake.Source{},
pci.Source{},
storage.Source{},
system.Source{},
usb.Source{},
custom.Source{},
// local needs to be the last source so that it is able to override
// labels from other sources
local.Source{},
}
enabledSources = []source.FeatureSource{}
for _, s := range allSources {
if _, enabled := sourcesWhiteListMap[s.Name()]; enabled {
enabledSources = append(enabledSources, s)
stderrLogger.Printf("Failed to read config file: %s", err)
} else {
err = yaml.Unmarshal(data, &c)
if err != nil {
stderrLogger.Printf("Failed to parse config file: %s", err)
} else {
stdoutLogger.Printf("Configuration successfully loaded from %q", filepath)
}
}
// Compile labelWhiteList regex
labelWhiteList, err = regexp.Compile(labelWhiteListStr)
// Parse config overrides
err = yaml.Unmarshal([]byte(overrides), &c)
if err != nil {
stderrLogger.Printf("error parsing whitelist regex (%s): %s", labelWhiteListStr, err)
return nil, nil, err
stderrLogger.Printf("Failed to parse --options: %s", err)
}
return enabledSources, labelWhiteList, nil
w.config = c
// (Re-)configure all sources
for _, s := range w.sources {
s.SetConfig(c.Sources[s.Name()])
}
}
// createFeatureLabels returns the set of feature labels from the enabled
@ -350,7 +342,7 @@ func getFeatureLabels(source source.FeatureSource, labelWhiteList *regexp.Regexp
// Prefix for labels in the default namespace
prefix := source.Name() + "-"
switch source.(type) {
case local.Source:
case *local.Source:
// Do not prefix labels from the hooks
prefix = ""
}
@ -416,3 +408,27 @@ func advertiseFeatureLabels(client pb.LabelerClient, labels Labels) error {
return nil
}
// UnmarshalJSON implements the Unmarshaler interface from "encoding/json"
func (c *sourcesConfig) UnmarshalJSON(data []byte) error {
// First do a raw parse to get the per-source data
raw := map[string]json.RawMessage{}
err := yaml.Unmarshal(data, &raw)
if err != nil {
return err
}
// Then parse each source-specific data structure
// NOTE: we expect 'c' to be pre-populated with correct per-source data
// types. Non-pre-populated keys are ignored.
for k, rawv := range raw {
if v, ok := (*c)[k]; ok {
err := yaml.Unmarshal(rawv, &v)
if err != nil {
return fmt.Errorf("failed to parse %q source config: %v", k, err)
}
}
}
return nil
}

View file

@ -29,42 +29,43 @@ type cpuidConfig struct {
AttributeWhitelist []string `json:"attributeWhitelist,omitempty"`
}
// NFDConfig is the type holding configuration of the cpuid feature source
type NFDConfig struct {
type Config struct {
Cpuid cpuidConfig `json:"cpuid,omitempty"`
}
// Config contains the configuration of the cpuid source
var Config = NFDConfig{
cpuidConfig{
AttributeBlacklist: []string{
"BMI1",
"BMI2",
"CLMUL",
"CMOV",
"CX16",
"ERMS",
"F16C",
"HTT",
"LZCNT",
"MMX",
"MMXEXT",
"NX",
"POPCNT",
"RDRAND",
"RDSEED",
"RDTSCP",
"SGX",
"SGXLC",
"SSE",
"SSE2",
"SSE3",
"SSE4.1",
"SSE4.2",
"SSSE3",
// newDefaultConfig returns a new config with pre-populated defaults
func newDefaultConfig() *Config {
return &Config{
cpuidConfig{
AttributeBlacklist: []string{
"BMI1",
"BMI2",
"CLMUL",
"CMOV",
"CX16",
"ERMS",
"F16C",
"HTT",
"LZCNT",
"MMX",
"MMXEXT",
"NX",
"POPCNT",
"RDRAND",
"RDSEED",
"RDTSCP",
"SGX",
"SGXLC",
"SSE",
"SSE2",
"SSE3",
"SSE4.1",
"SSE4.2",
"SSSE3",
},
AttributeWhitelist: []string{},
},
AttributeWhitelist: []string{},
},
}
}
// Filter for cpuid labels
@ -73,19 +74,33 @@ type keyFilter struct {
whitelist bool
}
var cpuidFilter *keyFilter
// Implement FeatureSource interface
type Source struct{}
type Source struct {
config *Config
cpuidFilter *keyFilter
}
func (s Source) Name() string { return "cpu" }
func (s Source) Discover() (source.Features, error) {
features := source.Features{}
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
if cpuidFilter == nil {
initCpuidFilter()
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return s.config }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(conf source.Config) {
switch v := conf.(type) {
case *Config:
s.config = v
s.initCpuidFilter()
default:
log.Printf("PANIC: invalid config type: %T", conf)
}
}
func (s *Source) Discover() (source.Features, error) {
features := source.Features{}
// Check if hyper-threading seems to be enabled
found, err := haveThreadSiblings()
@ -106,7 +121,7 @@ func (s Source) Discover() (source.Features, error) {
// Detect CPUID
cpuidFlags := getCpuidFlags()
for _, f := range cpuidFlags {
if cpuidFilter.unmask(f) {
if s.cpuidFilter.unmask(f) {
features["cpuid."+f] = true
}
}
@ -155,20 +170,20 @@ func haveThreadSiblings() (bool, error) {
return false, nil
}
func initCpuidFilter() {
func (s *Source) initCpuidFilter() {
newFilter := keyFilter{keys: map[string]struct{}{}}
if len(Config.Cpuid.AttributeWhitelist) > 0 {
for _, k := range Config.Cpuid.AttributeWhitelist {
if len(s.config.Cpuid.AttributeWhitelist) > 0 {
for _, k := range s.config.Cpuid.AttributeWhitelist {
newFilter.keys[k] = struct{}{}
}
newFilter.whitelist = true
} else {
for _, k := range Config.Cpuid.AttributeBlacklist {
for _, k := range s.config.Cpuid.AttributeBlacklist {
newFilter.keys[k] = struct{}{}
}
newFilter.whitelist = false
}
cpuidFilter = &newFilter
s.cpuidFilter = &newFilter
}
func (f keyFilter) unmask(k string) bool {

View file

@ -36,20 +36,41 @@ type FeatureSpec struct {
MatchOn []MatchRule `json:"matchOn"`
}
type NFDConfig []FeatureSpec
type config []FeatureSpec
var Config = NFDConfig{}
// newDefaultConfig returns a new config with pre-populated defaults
func newDefaultConfig() *config {
return &config{}
}
// Implements FeatureSource Interface
type Source struct{}
type Source struct {
config *config
}
// Return name of the feature source
func (s Source) Name() string { return "custom" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return s.config }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(conf source.Config) {
switch v := conf.(type) {
case *config:
s.config = v
default:
log.Printf("PANIC: invalid config type: %T", conf)
}
}
// Discover features
func (s Source) Discover() (source.Features, error) {
features := source.Features{}
allFeatureConfig := append(getStaticFeatureConfig(), Config...)
allFeatureConfig := append(getStaticFeatureConfig(), *s.config...)
log.Printf("INFO: Custom features: %+v", allFeatureConfig)
// Iterate over features
for _, customFeature := range allFeatureConfig {

View file

@ -24,6 +24,18 @@ type Source struct{}
// Name returns an identifier string for this feature source.
func (s Source) Name() string { return "fake" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return nil }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return nil }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(source.Config) {}
// Configure method of the FeatureSource interface
func (s Source) Configure([]byte) error { return nil }
// Discover returns feature names for some fake features.
func (s Source) Discover() (source.Features, error) {
// Adding three fake features.

View file

@ -28,6 +28,15 @@ type Source struct{}
func (s Source) Name() string { return "iommu" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return nil }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return nil }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(source.Config) {}
func (s Source) Discover() (source.Features, error) {
features := source.Features{}

View file

@ -30,27 +30,48 @@ import (
)
// Configuration file options
type NFDConfig struct {
type Config struct {
KconfigFile string
ConfigOpts []string `json:"configOpts,omitempty"`
}
var Config = NFDConfig{
KconfigFile: "",
ConfigOpts: []string{
"NO_HZ",
"NO_HZ_IDLE",
"NO_HZ_FULL",
"PREEMPT",
},
// newDefaultConfig returns a new config with pre-populated defaults
func newDefaultConfig() *Config {
return &Config{
KconfigFile: "",
ConfigOpts: []string{
"NO_HZ",
"NO_HZ_IDLE",
"NO_HZ_FULL",
"PREEMPT",
},
}
}
// Implement FeatureSource interface
type Source struct{}
type Source struct {
config *Config
}
func (s Source) Name() string { return "kernel" }
func (s *Source) Name() string { return "kernel" }
func (s Source) Discover() (source.Features, error) {
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return s.config }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(conf source.Config) {
switch v := conf.(type) {
case *Config:
s.config = v
default:
log.Printf("PANIC: invalid config type: %T", conf)
}
}
func (s *Source) Discover() (source.Features, error) {
features := source.Features{}
// Read kernel version
@ -64,13 +85,13 @@ func (s Source) Discover() (source.Features, error) {
}
// Read kconfig
kconfig, err := parseKconfig()
kconfig, err := s.parseKconfig()
if err != nil {
log.Printf("ERROR: Failed to read kconfig: %s", err)
}
// Check flags
for _, opt := range Config.ConfigOpts {
for _, opt := range s.config.ConfigOpts {
if val, ok := kconfig[opt]; ok {
features["config."+opt] = val
}
@ -137,16 +158,16 @@ func readKconfigGzip(filename string) ([]byte, error) {
}
// Read kconfig into a map
func parseKconfig() (map[string]string, error) {
func (s *Source) parseKconfig() (map[string]string, error) {
kconfig := map[string]string{}
raw := []byte(nil)
var err error
// First, try kconfig specified in the config file
if len(Config.KconfigFile) > 0 {
raw, err = ioutil.ReadFile(Config.KconfigFile)
if len(s.config.KconfigFile) > 0 {
raw, err = ioutil.ReadFile(s.config.KconfigFile)
if err != nil {
log.Printf("ERROR: Failed to read kernel config from %s: %s", Config.KconfigFile, err)
log.Printf("ERROR: Failed to read kernel config from %s: %s", s.config.KconfigFile, err)
}
}

View file

@ -40,6 +40,15 @@ type Source struct{}
func (s Source) Name() string { return "local" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return nil }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return nil }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(source.Config) {}
func (s Source) Discover() (source.Features, error) {
featuresFromHooks, err := getFeaturesFromHooks()
if err != nil {

View file

@ -31,6 +31,15 @@ type Source struct{}
// Name returns an identifier string for this feature source.
func (s Source) Name() string { return "memory" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return nil }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return nil }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(source.Config) {}
// Discover returns feature names for memory: numa if more than one memory node is present.
func (s Source) Discover() (source.Features, error) {
features := source.Features{}

View file

@ -34,6 +34,22 @@ func (_m *MockFeatureSource) Discover() (Features, error) {
return r0, r1
}
// GetConfig provides a mock function with given fields:
func (_m *MockFeatureSource) GetConfig() Config {
ret := _m.Called()
var r0 Config
if rf, ok := ret.Get(0).(func() Config); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Config)
}
}
return r0
}
// Name provides a mock function with given fields:
func (_m *MockFeatureSource) Name() string {
ret := _m.Called()
@ -47,3 +63,24 @@ func (_m *MockFeatureSource) Name() string {
return r0
}
// NewConfig provides a mock function with given fields:
func (_m *MockFeatureSource) NewConfig() Config {
ret := _m.Called()
var r0 Config
if rf, ok := ret.Get(0).(func() Config); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(Config)
}
}
return r0
}
// SetConfig provides a mock function with given fields: _a0
func (_m *MockFeatureSource) SetConfig(_a0 Config) {
_m.Called(_a0)
}

View file

@ -34,6 +34,15 @@ type Source struct{}
// Name returns an identifier string for this feature source.
func (s Source) Name() string { return "network" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return nil }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return nil }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(source.Config) {}
// Discover returns feature names sriov-configured and sriov if SR-IOV capable NICs are present and/or SR-IOV virtual functions are configured on the node
func (s Source) Discover() (source.Features, error) {
features := source.Features{}

View file

@ -24,6 +24,15 @@ type Source struct{}
// Name returns an identifier string for this feature source.
func (s Source) Name() string { return "panic_fake" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return nil }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return nil }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(source.Config) {}
// Discover calls panic().
func (s Source) Discover() (source.Features, error) {
panic("fake panic error")

View file

@ -25,22 +25,43 @@ import (
pciutils "sigs.k8s.io/node-feature-discovery/source/internal"
)
type NFDConfig struct {
type Config struct {
DeviceClassWhitelist []string `json:"deviceClassWhitelist,omitempty"`
DeviceLabelFields []string `json:"deviceLabelFields,omitempty"`
}
var Config = NFDConfig{
DeviceClassWhitelist: []string{"03", "0b40", "12"},
DeviceLabelFields: []string{"class", "vendor"},
// newDefaultConfig returns a new config with pre-populated defaults
func newDefaultConfig() *Config {
return &Config{
DeviceClassWhitelist: []string{"03", "0b40", "12"},
DeviceLabelFields: []string{"class", "vendor"},
}
}
// Implement FeatureSource interface
type Source struct{}
type Source struct {
config *Config
}
// Return name of the feature source
func (s Source) Name() string { return "pci" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return s.config }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(conf source.Config) {
switch v := conf.(type) {
case *Config:
s.config = v
default:
log.Printf("PANIC: invalid config type: %T", conf)
}
}
// Discover features
func (s Source) Discover() (source.Features, error) {
features := source.Features{}
@ -48,7 +69,7 @@ func (s Source) Discover() (source.Features, error) {
// Construct a device label format, a sorted list of valid attributes
deviceLabelFields := []string{}
configLabelFields := map[string]bool{}
for _, field := range Config.DeviceLabelFields {
for _, field := range s.config.DeviceLabelFields {
configLabelFields[field] = true
}
@ -87,7 +108,7 @@ func (s Source) Discover() (source.Features, error) {
// Iterate over all device classes
for class, classDevs := range devs {
for _, white := range Config.DeviceClassWhitelist {
for _, white := range s.config.DeviceClassWhitelist {
if strings.HasPrefix(class, strings.ToLower(white)) {
for _, dev := range classDevs {
devLabel := ""

View file

@ -39,4 +39,16 @@ type FeatureSource interface {
// Discover returns discovered features for this node.
Discover() (Features, error)
// NewConfig returns a new default config of the source
NewConfig() Config
// GetConfig returns the effective configuration of the source
GetConfig() Config
// SetConfig changes the effective configuration of the source
SetConfig(Config)
}
type Config interface {
}

View file

@ -29,6 +29,15 @@ type Source struct{}
// Name returns an identifier string for this feature source.
func (s Source) Name() string { return "storage" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return nil }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return nil }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(source.Config) {}
// Discover returns feature names for storage: nonrotationaldisk if any SSD drive present.
func (s Source) Discover() (source.Features, error) {
features := source.Features{}

View file

@ -36,6 +36,15 @@ type Source struct{}
func (s Source) Name() string { return "system" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return nil }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return nil }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(source.Config) {}
func (s Source) Discover() (source.Features, error) {
features := source.Features{}

View file

@ -25,25 +25,46 @@ import (
usbutils "sigs.k8s.io/node-feature-discovery/source/internal"
)
type NFDConfig struct {
type Config struct {
DeviceClassWhitelist []string `json:"deviceClassWhitelist,omitempty"`
DeviceLabelFields []string `json:"deviceLabelFields,omitempty"`
}
var Config = NFDConfig{
// Whitelist specific USB classes: https://www.usb.org/defined-class-codes
// By default these include classes where different accelerators are typically mapped:
// Video (0e), Miscellaneous (ef), Application Specific (fe), and Vendor Specific (ff).
DeviceClassWhitelist: []string{"0e", "ef", "fe", "ff"},
DeviceLabelFields: []string{"class", "vendor", "device"},
// newDefaultConfig returns a new config with pre-populated defaults
func newDefaultConfig() *Config {
return &Config{
// Whitelist specific USB classes: https://www.usb.org/defined-class-codes
// By default these include classes where different accelerators are typically mapped:
// Video (0e), Miscellaneous (ef), Application Specific (fe), and Vendor Specific (ff).
DeviceClassWhitelist: []string{"0e", "ef", "fe", "ff"},
DeviceLabelFields: []string{"class", "vendor", "device"},
}
}
// Implement FeatureSource interface
type Source struct{}
type Source struct {
config *Config
}
// Return name of the feature source
func (s Source) Name() string { return "usb" }
// NewConfig method of the FeatureSource interface
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
// GetConfig method of the FeatureSource interface
func (s *Source) GetConfig() source.Config { return s.config }
// SetConfig method of the FeatureSource interface
func (s *Source) SetConfig(conf source.Config) {
switch v := conf.(type) {
case *Config:
s.config = v
default:
log.Printf("PANIC: invalid config type: %T", conf)
}
}
// Discover features
func (s Source) Discover() (source.Features, error) {
features := source.Features{}
@ -51,7 +72,7 @@ func (s Source) Discover() (source.Features, error) {
// Construct a device label format, a sorted list of valid attributes
deviceLabelFields := []string{}
configLabelFields := map[string]bool{}
for _, field := range Config.DeviceLabelFields {
for _, field := range s.config.DeviceLabelFields {
configLabelFields[field] = true
}
@ -86,7 +107,7 @@ func (s Source) Discover() (source.Features, error) {
// Iterate over all device classes
for class, classDevs := range devs {
for _, white := range Config.DeviceClassWhitelist {
for _, white := range s.config.DeviceClassWhitelist {
if strings.HasPrefix(class, strings.ToLower(white)) {
for _, dev := range classDevs {
devLabel := ""