1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-14 11:57:51 +00:00

nfd-worker: rework configuration handling

Extend the FeatureSource interface with new methods for configuration
handling. This enables easier on-the fly reconfiguration of the
feature sources. Further, it simplifies adding config support to feature
sources in the future. Stub methods are added to sources that do not
currently have any configurability.

The patch fixes some (corner) cases with the overrides (--options)
handling, too:
- Overrides were not applied if config file was missing or its parsing
  failed
- Overrides for a certain source did not have effect if an empty config
  for the source was specified in the config file. This was caused by
  the first pass of parsing (config file) setting a nil pointer to the
  source-specific config, effectively detaching it from the main config.
  The second pass would then create a new instance of the source
  specific config, but, this was not visible in the feature source, of
  course.
This commit is contained in:
Markus Lehtonen 2020-04-21 22:03:37 +03:00
parent c95ad3198c
commit a2b9df5cd3
19 changed files with 441 additions and 156 deletions

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,13 +131,28 @@ 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"})
})
})
})
@ -132,11 +164,11 @@ func TestNewNfdWorker(t *testing.T) {
Convey("without any args specified", func() {
args := Args{}
emptyRegexp, _ := regexp.Compile("")
worker, err := NewNfdWorker(args)
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
})
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)
@ -146,22 +178,22 @@ func TestNewNfdWorker(t *testing.T) {
Convey("with non-empty Sources arg specified", func() {
args := Args{Sources: []string{"fake"}}
emptyRegexp, _ := regexp.Compile("")
worker, err := NewNfdWorker(args)
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
})
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.sources[0], ShouldHaveSameTypeAs, &fake.Source{})
So(worker.labelWhiteList, ShouldResemble, emptyRegexp)
})
})
Convey("with invalid LabelWhiteList arg specified", func() {
args := Args{LabelWhiteList: "*"}
worker, err := NewNfdWorker(args)
w, err := NewNfdWorker(args)
worker := w.(*nfdWorker)
Convey("an error should be returned", func() {
So(len(worker.sources), ShouldEqual, 0)
So(worker.labelWhiteList, ShouldBeNil)
@ -171,12 +203,12 @@ func TestNewNfdWorker(t *testing.T) {
Convey("with valid LabelWhiteListStr arg specified", func() {
args := Args{LabelWhiteList: ".*rdt.*"}
worker, err := NewNfdWorker(args)
expectRegexp, err := regexp.Compile(".*rdt.*")
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
})
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
@ -97,6 +91,7 @@ type nfdWorker struct {
args Args
clientConn *grpc.ClientConn
client pb.LabelerClient
config NFDConfig
sources []source.FeatureSource
labelWhiteList *regexp.Regexp
}
@ -128,21 +123,21 @@ 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{},
&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{},
&local.Source{},
}
sourceWhiteList := map[string]struct{}{}
@ -173,14 +168,11 @@ 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)
}
// Parse and apply configuration
w.configure(w.args.ConfigFile, w.args.Options)
// Connect to NFD master
err = w.connect()
err := w.connect()
if err != nil {
return fmt.Errorf("failed to connect: %v", err)
}
@ -274,31 +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
data, err := ioutil.ReadFile(filepath)
if err != nil {
return fmt.Errorf("Failed to read config file: %s", err)
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()
}
// Read config file
err = yaml.Unmarshal(data, &config)
// Try to read and parse config file
data, err := ioutil.ReadFile(filepath)
if err != nil {
return fmt.Errorf("Failed to parse config file: %s", err)
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)
}
}
// Parse config overrides
err = yaml.Unmarshal([]byte(overrides), &config)
err = yaml.Unmarshal([]byte(overrides), &c)
if err != nil {
return fmt.Errorf("Failed to parse --options: %s", err)
stderrLogger.Printf("Failed to parse --options: %s", err)
}
return 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
@ -343,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 = ""
}
@ -409,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 := ""