1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-05 08:17:04 +00:00

nfd-worker: switch to flag in command line parsing

This commit is contained in:
Markus Lehtonen 2021-02-19 15:43:31 +02:00
parent 47033db9c1
commit 3fd61eacdb
8 changed files with 251 additions and 283 deletions

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Kubernetes Authors.
Copyright 2019-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -17,14 +17,13 @@ limitations under the License.
package main
import (
"flag"
"fmt"
"log"
"regexp"
"strings"
"time"
"os"
"github.com/docopt/docopt-go"
worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
"sigs.k8s.io/node-feature-discovery/pkg/version"
)
@ -34,17 +33,22 @@ const (
)
func main() {
flags := flag.NewFlagSet(ProgramName, flag.ExitOnError)
printVersion := flags.Bool("version", false, "Print version and exit.")
args := parseArgs(flags, os.Args[1:]...)
if *printVersion {
fmt.Println(ProgramName, version.Get())
os.Exit(0)
}
// Assert that the version is known
if version.Undefined() {
log.Printf("WARNING: version not set! Set -ldflags \"-X sigs.k8s.io/node-feature-discovery/pkg/version.version=`git describe --tags --dirty --always`\" during build or run.")
}
// Parse command-line arguments.
args, err := argsParse(nil)
if err != nil {
log.Fatalf("failed to parse command line: %v", err)
}
// Get new NfdWorker instance
instance, err := worker.NewNfdWorker(args)
if err != nil {
@ -56,104 +60,74 @@ func main() {
}
}
// argsParse parses the command line arguments passed to the program.
// The argument argv is passed only for testing purposes.
func argsParse(argv []string) (worker.Args, error) {
args := worker.Args{}
usage := fmt.Sprintf(`%s.
func parseArgs(flags *flag.FlagSet, osArgs ...string) *worker.Args {
args, overrides := initFlags(flags)
Usage:
%s [--no-publish] [--sources=<sources>] [--label-whitelist=<pattern>]
[--oneshot | --sleep-interval=<seconds>] [--config=<path>]
[--options=<config>] [--server=<server>] [--server-name-override=<name>]
[--ca-file=<path>] [--cert-file=<path>] [--key-file=<path>]
%s -h | --help
%s --version
_ = flags.Parse(osArgs)
if len(flags.Args()) > 0 {
fmt.Printf("unknown command line argument: %s\n", flags.Args()[0])
flags.Usage()
os.Exit(2)
}
Options:
-h --help Show this screen.
--version Output version and exit.
--config=<path> Config file to use.
[Default: /etc/kubernetes/node-feature-discovery/nfd-worker.conf]
--options=<config> Specify config options from command line. Config
options are specified in the same format as in the
config file (i.e. json or yaml). These options
will override settings read from the config file.
[Default: ]
--ca-file=<path> Root certificate for verifying connections
[Default: ]
--cert-file=<path> Certificate used for authenticating connections
[Default: ]
--key-file=<path> Private key matching --cert-file
[Default: ]
--server=<server> NFD server address to connecto to.
[Default: localhost:8080]
--server-name-override=<name> Name (CN) expect from server certificate, useful
in testing
[Default: ]
--sources=<sources> Comma separated list of feature sources. Special
value 'all' enables all feature sources.
(DEPRECATED: This parameter should be set via the
config file)
--no-publish Do not publish discovered features to the
cluster-local Kubernetes API server.
--label-whitelist=<pattern> Regular expression to filter label names to
publish to the Kubernetes API server.
NB: the label namespace is omitted i.e. the filter
is only applied to the name part after '/'.
(DEPRECATED: This parameter should be set via the
config file)
--oneshot Label once and exit.
--sleep-interval=<seconds> Time to sleep between re-labeling. Non-positive
value implies no re-labeling (i.e. infinite
sleep).
(DEPRECATED: This parameter should be set via the
config file)`,
ProgramName,
ProgramName,
ProgramName,
ProgramName,
)
arguments, _ := docopt.ParseArgs(usage, argv,
fmt.Sprintf("%s %s", ProgramName, version.Get()))
// Parse argument values as usable types.
args.CaFile = arguments["--ca-file"].(string)
args.CertFile = arguments["--cert-file"].(string)
args.ConfigFile = arguments["--config"].(string)
args.KeyFile = arguments["--key-file"].(string)
args.Options = arguments["--options"].(string)
args.Server = arguments["--server"].(string)
args.ServerNameOverride = arguments["--server-name-override"].(string)
args.Oneshot = arguments["--oneshot"].(bool)
// Parse deprecated/override args
if v := arguments["--label-whitelist"]; v != nil {
s := v.(string)
// Compile labelWhiteList regex
if r, err := regexp.Compile(s); err != nil {
return args, fmt.Errorf("error parsing --label-whitelist regex (%s): %v", s, err)
} else {
args.LabelWhiteList = r
// Handle overrides
flags.Visit(func(f *flag.Flag) {
switch f.Name {
case "no-publish":
args.Overrides.NoPublish = overrides.NoPublish
case "label-whitelist":
log.Printf("WARNING: --label-whitelist is deprecated, use 'core.labelWhiteList' option in the config file, instead")
args.Overrides.LabelWhiteList = overrides.LabelWhiteList
case "sleep-interval":
log.Printf("WARNING: --sleep-interval is deprecated, use 'core.sleepInterval' option in the config file, instead")
args.Overrides.SleepInterval = overrides.SleepInterval
case "sources":
log.Printf("WARNING: --sources is deprecated, use 'core.sources' option in the config file, instead")
args.Overrides.Sources = overrides.Sources
}
}
if arguments["--no-publish"].(bool) {
b := true
args.NoPublish = &b
}
if v := arguments["--sleep-interval"]; v != nil {
log.Printf("WARNING: --sleep-interval is deprecated, use 'core.sleepInterval' option in the config file, instead")
if s, err := time.ParseDuration(v.(string)); err != nil {
return args, fmt.Errorf("invalid --sleep-interval specified: %s", err.Error())
} else {
args.SleepInterval = &s
}
}
if v := arguments["--sources"]; v != nil {
fmt.Println(v)
s := strings.Split(v.(string), ",")
args.Sources = &s
}
return args, nil
})
return args
}
func initFlags(flagset *flag.FlagSet) (*worker.Args, *worker.ConfigOverrideArgs) {
args := &worker.Args{}
flagset.StringVar(&args.CaFile, "ca-file", "",
"Root certificate for verifying connections")
flagset.StringVar(&args.CertFile, "cert-file", "",
"Certificate used for authenticating connections")
flagset.StringVar(&args.ConfigFile, "config", "/etc/kubernetes/node-feature-discovery/nfd-worker.conf",
"Config file to use.")
flagset.StringVar(&args.KeyFile, "key-file", "",
"Private key matching -cert-file")
flagset.BoolVar(&args.Oneshot, "oneshot", false,
"Do not publish feature labels")
flagset.StringVar(&args.Options, "options", "",
"Specify config options from command line. Config options are specified "+
"in the same format as in the config file (i.e. json or yaml). These options")
flagset.StringVar(&args.Server, "server", "localhost:8080",
"NFD server address to connecto to.")
flagset.StringVar(&args.ServerNameOverride, "server-name-override", "",
"Hostname expected from server certificate, useful in testing")
// Flags overlapping with config file options
overrides := &worker.ConfigOverrideArgs{
LabelWhiteList: &utils.RegexpVal{},
Sources: &utils.StringSliceVal{},
}
overrides.NoPublish = flagset.Bool("no-publish", false,
"Do not publish discovered features, disable connection to nfd-master.")
flagset.Var(overrides.LabelWhiteList, "label-whitelist",
"Regular expression to filter label names to publish to the Kubernetes API server. "+
"NB: the label namespace is omitted i.e. the filter is only applied to the name part after '/'. "+
"DEPRECATED: This parameter should be set via the config file.")
overrides.SleepInterval = flagset.Duration("sleep-interval", 0,
"Time to sleep between re-labeling. Non-positive value implies no re-labeling (i.e. infinite sleep). "+
"DEPRECATED: This parameter should be set via the config file")
flagset.Var(overrides.Sources, "sources",
"Comma separated list of feature sources. Special value 'all' enables all feature sources. "+
"DEPRECATED: This parameter should be set via the config file")
return args, overrides
}

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Kubernetes Authors.
Copyright 2019-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -17,62 +17,44 @@ limitations under the License.
package main
import (
"flag"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
)
func TestArgsParse(t *testing.T) {
func TestParseArgs(t *testing.T) {
Convey("When parsing command line arguments", t, func() {
Convey("When --no-publish and --oneshot flags are passed", func() {
args, err := argsParse([]string{"--no-publish", "--oneshot"})
flags := flag.NewFlagSet(ProgramName, flag.ExitOnError)
Convey("noPublish is set and args.sources is set to the default value", func() {
So(args.SleepInterval, ShouldEqual, nil)
So(*args.NoPublish, ShouldBeTrue)
Convey("When no override args are specified", func() {
args := parseArgs(flags, "--oneshot")
Convey("overrides should be nil", func() {
So(args.Oneshot, ShouldBeTrue)
So(args.Sources, ShouldBeNil)
So(args.LabelWhiteList, ShouldBeNil)
So(err, ShouldBeNil)
So(args.Overrides.NoPublish, ShouldBeNil)
So(args.Overrides.LabelWhiteList, ShouldBeNil)
So(args.Overrides.SleepInterval, ShouldBeNil)
So(args.Overrides.Sources, ShouldBeNil)
})
})
Convey("When --sources flag is passed and set to some values, --sleep-inteval is specified", func() {
args, err := argsParse([]string{"--sources=fake1,fake2,fake3", "--sleep-interval=30s"})
Convey("When all override args are specified", func() {
args := parseArgs(flags,
"--no-publish",
"-label-whitelist=.*rdt.*",
"-sources=fake1,fake2,fake3",
"-sleep-interval=30s")
Convey("args.sources is set to appropriate values", func() {
So(*args.SleepInterval, ShouldEqual, 30*time.Second)
So(args.NoPublish, ShouldBeNil)
So(args.Oneshot, ShouldBeFalse)
So(*args.Sources, ShouldResemble, []string{"fake1", "fake2", "fake3"})
So(args.LabelWhiteList, ShouldBeNil)
So(err, ShouldBeNil)
})
})
Convey("When --label-whitelist flag is passed and set to some value", func() {
args, err := argsParse([]string{"--label-whitelist=.*rdt.*"})
Convey("args.labelWhiteList is set to appropriate value and args.sources is set to default value", func() {
So(args.NoPublish, ShouldBeNil)
So(args.Sources, ShouldBeNil)
So(args.LabelWhiteList.String(), ShouldResemble, ".*rdt.*")
So(err, ShouldBeNil)
})
})
Convey("When valid args are specified", func() {
args, err := argsParse([]string{"--no-publish", "--sources=fake1,fake2,fake3", "--ca-file=ca", "--cert-file=crt", "--key-file=key"})
Convey("--no-publish is set and args.sources is set to appropriate values", func() {
So(*args.NoPublish, ShouldBeTrue)
So(args.CaFile, ShouldEqual, "ca")
So(args.CertFile, ShouldEqual, "crt")
So(args.KeyFile, ShouldEqual, "key")
So(*args.Sources, ShouldResemble, []string{"fake1", "fake2", "fake3"})
So(args.LabelWhiteList, ShouldBeNil)
So(err, ShouldBeNil)
So(*args.Overrides.NoPublish, ShouldBeTrue)
So(*args.Overrides.SleepInterval, ShouldEqual, 30*time.Second)
So(*args.Overrides.Sources, ShouldResemble, utils.StringSliceVal{"fake1", "fake2", "fake3"})
So(args.Overrides.LabelWhiteList.Regexp.String(), ShouldResemble, ".*rdt.*")
})
})
})

View file

@ -211,52 +211,34 @@ pass the `--no-publish` flag to nfd-worker.
Command line flags of nfd-worker:
```bash
$ docker run --rm ${NFD_CONTAINER_IMAGE} nfd-worker --help
...
Usage:
nfd-worker [--no-publish] [--sources=<sources>] [--label-whitelist=<pattern>]
[--oneshot | --sleep-interval=<seconds>] [--config=<path>]
[--options=<config>] [--server=<server>] [--server-name-override=<name>]
[--ca-file=<path>] [--cert-file=<path>] [--key-file=<path>]
nfd-worker -h | --help
nfd-worker --version
Options:
-h --help Show this screen.
--version Output version and exit.
--config=<path> Config file to use.
[Default: /etc/kubernetes/node-feature-discovery/nfd-worker.conf]
--options=<config> Specify config options from command line. Config
options are specified in the same format as in the
config file (i.e. json or yaml). These options
will override settings read from the config file.
[Default: ]
--ca-file=<path> Root certificate for verifying connections
[Default: ]
--cert-file=<path> Certificate used for authenticating connections
[Default: ]
--key-file=<path> Private key matching --cert-file
[Default: ]
--server=<server> NFD server address to connecto to.
[Default: localhost:8080]
--server-name-override=<name> Name (CN) expect from server certificate, useful
in testing
[Default: ]
--sources=<sources> Comma separated list of feature sources. Special
value 'all' enables all feature sources.
[Default: all]
--no-publish Do not publish discovered features to the
cluster-local Kubernetes API server.
--label-whitelist=<pattern> Regular expression to filter label names to
publish to the Kubernetes API server.
NB: the label namespace is omitted i.e. the filter
is only applied to the name part after '/'.
[Default: ]
--oneshot Label once and exit.
--sleep-interval=<seconds> Time to sleep between re-labeling. Non-positive
value implies no re-labeling (i.e. infinite
sleep). [Default: 60s]
$ docker run --rm ${NFD_CONTAINER_IMAGE} nfd-worker -help
Usage of nfd-worker:
-ca-file string
Root certificate for verifying connections
-cert-file string
Certificate used for authenticating connections
-config string
Config file to use. (default "/etc/kubernetes/node-feature-discovery/nfd-worker.conf")
-key-file string
Private key matching -cert-file
-label-whitelist value
Regular expression to filter label names to publish to the Kubernetes API server. NB: the label namespace is omitted i.e. the filter is only applied to the name part after '/'. DEPRECATED: This parameter should be set via the config file.
-no-publish
Do not publish discovered features, disable connection to nfd-master.
-oneshot
Do not publish feature labels
-options string
Specify config options from command line. Config options are specified in the same format as in the config file (i.e. json or yaml). These options
-server string
NFD server address to connecto to. (default "localhost:8080")
-server-name-override string
Hostname expected from server certificate, useful in testing
-sleep-interval duration
Time to sleep between re-labeling. Non-positive value implies no re-labeling (i.e. infinite sleep). DEPRECATED: This parameter should be set via the config file
-sources value
Comma separated list of feature sources. Special value 'all' enables all feature sources. DEPRECATED: This parameter should be set via the config file
-version
Print version and exit.
```
**NOTE** Some feature sources need certain directories and/or files from the

1
go.mod
View file

@ -3,7 +3,6 @@ module sigs.k8s.io/node-feature-discovery
go 1.15
require (
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/fsnotify/fsnotify v1.4.9
github.com/golang/protobuf v1.4.3
github.com/klauspost/cpuid/v2 v2.0.2

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Kubernetes Authors.
Copyright 2019-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -26,12 +26,12 @@ import (
"testing"
"time"
//"github.com/onsi/gomega"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/mock"
"github.com/vektra/errors"
"sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/cpu"
"sigs.k8s.io/node-feature-discovery/source/fake"
@ -53,13 +53,13 @@ func TestDiscoveryWithMockSources(t *testing.T) {
fakeFeatureSource := source.FeatureSource(mockFeatureSource)
labelWhiteList := regex{*regexp.MustCompile("^test")}
labelWhiteList := utils.RegexpVal{Regexp: *regexp.MustCompile("^test")}
Convey("When I successfully get the labels from the mock source", func() {
mockFeatureSource.On("Name").Return(fakeFeatureSourceName)
mockFeatureSource.On("Discover").Return(fakeFeatures, nil)
returnedLabels, err := getFeatureLabels(fakeFeatureSource, labelWhiteList)
returnedLabels, err := getFeatureLabels(fakeFeatureSource, labelWhiteList.Regexp)
Convey("Proper label is returned", func() {
So(returnedLabels, ShouldResemble, fakeFeatureLabels)
})
@ -72,7 +72,7 @@ func TestDiscoveryWithMockSources(t *testing.T) {
expectedError := errors.New("fake error")
mockFeatureSource.On("Discover").Return(nil, expectedError)
returnedLabels, err := getFeatureLabels(fakeFeatureSource, labelWhiteList)
returnedLabels, err := getFeatureLabels(fakeFeatureSource, labelWhiteList.Regexp)
Convey("No label is returned", func() {
So(returnedLabels, ShouldBeNil)
})
@ -109,7 +109,7 @@ func (w *nfdWorker) getSource(name string) source.FeatureSource {
func TestConfigParse(t *testing.T) {
Convey("When parsing configuration", t, func() {
w, err := NewNfdWorker(Args{})
w, err := NewNfdWorker(&Args{})
So(err, ShouldBeNil)
worker := w.(*nfdWorker)
overrides := `{"core": {"sources": ["fake"],"noPublish": true},"sources": {"cpu": {"cpuid": {"attributeBlacklist": ["foo","bar"]}}}}`
@ -123,7 +123,7 @@ func TestConfigParse(t *testing.T) {
})
})
Convey("and a non-accessible file, but core cmdline flags and some overrides are specified", func() {
worker.args = Args{Sources: &[]string{"cpu", "kernel", "pci"}}
worker.args = Args{Overrides: ConfigOverrideArgs{Sources: &utils.StringSliceVal{"cpu", "kernel", "pci"}}}
So(worker.configure("non-existing-file", overrides), ShouldBeNil)
Convey("core cmdline flags should be in effect instead overrides", func() {
@ -157,7 +157,7 @@ sources:
So(err, ShouldBeNil)
Convey("and a proper config file is specified", func() {
worker.args = Args{Sources: &[]string{"cpu", "kernel", "pci"}}
worker.args = Args{Overrides: ConfigOverrideArgs{Sources: &utils.StringSliceVal{"cpu", "kernel", "pci"}}}
So(worker.configure(f.Name(), ""), ShouldBeNil)
Convey("specified configuration should take effect", func() {
@ -178,7 +178,7 @@ sources:
Convey("and a proper config file and overrides are given", func() {
sleepIntervalArg := 15 * time.Second
worker.args = Args{SleepInterval: &sleepIntervalArg}
worker.args = Args{Overrides: ConfigOverrideArgs{SleepInterval: &sleepIntervalArg}}
overrides := `{"core": {"sources": ["fake"],"noPublish": true},"sources": {"pci": {"deviceClassWhitelist": ["03"]}}}`
So(worker.configure(f.Name(), overrides), ShouldBeNil)
@ -229,11 +229,11 @@ core:
`)
noPublish := true
w, err := NewNfdWorker(Args{
ConfigFile: configFile,
Sources: &[]string{"fake"},
NoPublish: &noPublish,
SleepInterval: new(time.Duration),
w, err := NewNfdWorker(&Args{
ConfigFile: configFile,
Overrides: ConfigOverrideArgs{
Sources: &utils.StringSliceVal{"fake"},
NoPublish: &noPublish},
})
So(err, ShouldBeNil)
worker := w.(*nfdWorker)
@ -309,10 +309,10 @@ func withTimeout(actual interface{}, expected ...interface{}) string {
func TestNewNfdWorker(t *testing.T) {
Convey("When creating new NfdWorker instance", t, func() {
emptyRegexp := regex{*regexp.MustCompile("")}
emptyRegexp := utils.RegexpVal{Regexp: *regexp.MustCompile("")}
Convey("without any args specified", func() {
args := Args{}
args := &Args{}
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
@ -326,7 +326,7 @@ func TestNewNfdWorker(t *testing.T) {
})
Convey("with non-empty Sources arg specified", func() {
args := Args{Sources: &[]string{"fake"}}
args := &Args{Overrides: ConfigOverrideArgs{Sources: &utils.StringSliceVal{"fake"}}}
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
@ -341,14 +341,14 @@ func TestNewNfdWorker(t *testing.T) {
})
Convey("with valid LabelWhiteListStr arg specified", func() {
args := Args{LabelWhiteList: regexp.MustCompile(".*rdt.*")}
args := &Args{Overrides: ConfigOverrideArgs{LabelWhiteList: &utils.RegexpVal{Regexp: *regexp.MustCompile(".*rdt.*")}}}
w, err := NewNfdWorker(args)
Convey("no error should be returned", func() {
So(err, ShouldBeNil)
})
worker := w.(*nfdWorker)
So(worker.configure("", ""), ShouldBeNil)
expectRegexp := regex{*regexp.MustCompile(".*rdt.*")}
expectRegexp := utils.RegexpVal{Regexp: *regexp.MustCompile(".*rdt.*")}
Convey("proper labelWhiteList regexp should be produced", func() {
So(worker.config.Core.LabelWhiteList, ShouldResemble, expectRegexp)
})
@ -359,12 +359,12 @@ func TestNewNfdWorker(t *testing.T) {
func TestCreateFeatureLabels(t *testing.T) {
Convey("When creating feature labels from the configured sources", t, func() {
Convey("When fake feature source is configured", func() {
emptyLabelWL := regex{*regexp.MustCompile("")}
emptyLabelWL := regexp.MustCompile("")
fakeFeatureSource := source.FeatureSource(new(fake.Source))
fakeFeatureSource.SetConfig(fakeFeatureSource.NewConfig())
sources := []source.FeatureSource{}
sources = append(sources, fakeFeatureSource)
labels := createFeatureLabels(sources, emptyLabelWL)
labels := createFeatureLabels(sources, *emptyLabelWL)
Convey("Proper fake labels are returned", func() {
So(len(labels), ShouldEqual, 3)
@ -374,11 +374,10 @@ func TestCreateFeatureLabels(t *testing.T) {
})
})
Convey("When fake feature source is configured with a whitelist that doesn't match", func() {
labelWL := regex{*regexp.MustCompile(".*rdt.*")}
fakeFeatureSource := source.FeatureSource(new(fake.Source))
sources := []source.FeatureSource{}
sources = append(sources, fakeFeatureSource)
labels := createFeatureLabels(sources, labelWL)
labels := createFeatureLabels(sources, *regexp.MustCompile(".*rdt.*"))
Convey("fake labels are not returned", func() {
So(len(labels), ShouldEqual, 0)
@ -394,7 +393,7 @@ func TestGetFeatureLabels(t *testing.T) {
Convey("When I get feature labels and panic occurs during discovery of a feature source", t, func() {
fakePanicFeatureSource := source.FeatureSource(new(panicfake.Source))
returnedLabels, err := getFeatureLabels(fakePanicFeatureSource, regex{*regexp.MustCompile("")})
returnedLabels, err := getFeatureLabels(fakePanicFeatureSource, *regexp.MustCompile(""))
Convey("No label is returned", func() {
So(len(returnedLabels), ShouldEqual, 0)
})

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Kubernetes Authors.
Copyright 2019-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -34,7 +34,10 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/yaml"
pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
"sigs.k8s.io/node-feature-discovery/pkg/version"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/cpu"
@ -50,7 +53,6 @@ import (
"sigs.k8s.io/node-feature-discovery/source/storage"
"sigs.k8s.io/node-feature-discovery/source/system"
"sigs.k8s.io/node-feature-discovery/source/usb"
"sigs.k8s.io/yaml"
)
var (
@ -66,7 +68,7 @@ type NFDConfig struct {
}
type coreConfig struct {
LabelWhiteList regex
LabelWhiteList utils.RegexpVal
NoPublish bool
Sources []string
SleepInterval duration
@ -87,11 +89,18 @@ type Args struct {
Oneshot bool
Server string
ServerNameOverride string
// Deprecated options that should be set via the config file
LabelWhiteList *regexp.Regexp
NoPublish *bool
Overrides ConfigOverrideArgs
}
// ConfigOverrideArgs are args that override config file options
type ConfigOverrideArgs struct {
NoPublish *bool
// Deprecated
LabelWhiteList *utils.RegexpVal
SleepInterval *time.Duration
Sources *[]string
Sources *utils.StringSliceVal
}
type NfdWorker interface {
@ -111,18 +120,14 @@ type nfdWorker struct {
enabledSources []source.FeatureSource
}
type regex struct {
regexp.Regexp
}
type duration struct {
time.Duration
}
// Create new NfdWorker instance.
func NewNfdWorker(args Args) (NfdWorker, error) {
func NewNfdWorker(args *Args) (NfdWorker, error) {
nfd := &nfdWorker{
args: args,
args: *args,
config: &NFDConfig{},
realSources: []source.FeatureSource{
&cpu.Source{},
@ -203,7 +208,7 @@ func addConfigWatch(path string) (*fsnotify.Watcher, map[string]struct{}, error)
func newDefaultConfig() *NFDConfig {
return &NFDConfig{
Core: coreConfig{
LabelWhiteList: regex{*regexp.MustCompile("")},
LabelWhiteList: utils.RegexpVal{Regexp: *regexp.MustCompile("")},
SleepInterval: duration{60 * time.Second},
Sources: []string{"all"},
},
@ -238,7 +243,7 @@ func (w *nfdWorker) Run() error {
select {
case <-labelTrigger:
// Get the set of feature labels.
labels := createFeatureLabels(w.enabledSources, w.config.Core.LabelWhiteList)
labels := createFeatureLabels(w.enabledSources, w.config.Core.LabelWhiteList.Regexp)
// Update the node with the feature labels.
if w.client != nil {
@ -448,17 +453,17 @@ func (w *nfdWorker) configure(filepath string, overrides string) error {
return fmt.Errorf("Failed to parse --options: %s", err)
}
if w.args.LabelWhiteList != nil {
c.Core.LabelWhiteList = regex{*w.args.LabelWhiteList}
if w.args.Overrides.LabelWhiteList != nil {
c.Core.LabelWhiteList = *w.args.Overrides.LabelWhiteList
}
if w.args.NoPublish != nil {
c.Core.NoPublish = *w.args.NoPublish
if w.args.Overrides.NoPublish != nil {
c.Core.NoPublish = *w.args.Overrides.NoPublish
}
if w.args.SleepInterval != nil {
c.Core.SleepInterval = duration{*w.args.SleepInterval}
if w.args.Overrides.SleepInterval != nil {
c.Core.SleepInterval = duration{*w.args.Overrides.SleepInterval}
}
if w.args.Sources != nil {
c.Core.Sources = *w.args.Sources
if w.args.Overrides.Sources != nil {
c.Core.Sources = *w.args.Overrides.Sources
}
c.Core.sanitize()
@ -477,7 +482,7 @@ func (w *nfdWorker) configure(filepath string, overrides string) error {
// createFeatureLabels returns the set of feature labels from the enabled
// sources and the whitelist argument.
func createFeatureLabels(sources []source.FeatureSource, labelWhiteList regex) (labels Labels) {
func createFeatureLabels(sources []source.FeatureSource, labelWhiteList regexp.Regexp) (labels Labels) {
labels = Labels{}
// Do feature discovery from all configured sources.
@ -500,7 +505,7 @@ func createFeatureLabels(sources []source.FeatureSource, labelWhiteList regex) (
// getFeatureLabels returns node labels for features discovered by the
// supplied source.
func getFeatureLabels(source source.FeatureSource, labelWhiteList regex) (labels Labels, err error) {
func getFeatureLabels(source source.FeatureSource, labelWhiteList regexp.Regexp) (labels Labels, err error) {
defer func() {
if r := recover(); r != nil {
stderrLogger.Printf("panic occurred during discovery of source [%s]: %v", source.Name(), r)
@ -584,25 +589,6 @@ func advertiseFeatureLabels(client pb.LabelerClient, labels Labels) error {
return nil
}
// UnmarshalJSON implements the Unmarshaler interface from "encoding/json"
func (r *regex) UnmarshalJSON(data []byte) error {
var v interface{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
switch val := v.(type) {
case string:
if rr, err := regexp.Compile(string(val)); err != nil {
return err
} else {
*r = regex{*rr}
}
default:
return fmt.Errorf("invalid regexp %s", data)
}
return nil
}
// UnmarshalJSON implements the Unmarshaler interface from "encoding/json"
func (d *duration) UnmarshalJSON(data []byte) error {
var v interface{}

View file

@ -1,5 +1,5 @@
/*
Copyright 2019 The Kubernetes Authors.
Copyright 2019-2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -24,22 +24,24 @@ import (
"time"
. "github.com/smartystreets/goconvey/convey"
nfdmaster "sigs.k8s.io/node-feature-discovery/pkg/nfd-master"
w "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker"
master "sigs.k8s.io/node-feature-discovery/pkg/nfd-master"
worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
"sigs.k8s.io/node-feature-discovery/test/data"
)
type testContext struct {
master nfdmaster.NfdMaster
master master.NfdMaster
errs chan error
}
func setupTest(args *nfdmaster.Args) testContext {
func setupTest(args *master.Args) testContext {
// Fixed port and no-publish, for convenience
args.NoPublish = true
args.Port = 8192
args.LabelWhiteList.Regexp = *regexp.MustCompile("")
m, err := nfdmaster.NewNfdMaster(args)
m, err := master.NewNfdMaster(args)
if err != nil {
fmt.Printf("Test setup failed: %v\n", err)
os.Exit(1)
@ -73,9 +75,9 @@ func teardownTest(ctx testContext) {
func TestNewNfdWorker(t *testing.T) {
Convey("When initializing new NfdWorker instance", t, func() {
Convey("When one of --cert-file, --key-file or --ca-file is missing", func() {
_, err := w.NewNfdWorker(w.Args{CertFile: "crt", KeyFile: "key"})
_, err2 := w.NewNfdWorker(w.Args{KeyFile: "key", CaFile: "ca"})
_, err3 := w.NewNfdWorker(w.Args{CertFile: "crt", CaFile: "ca"})
_, err := worker.NewNfdWorker(&worker.Args{CertFile: "crt", KeyFile: "key"})
_, err2 := worker.NewNfdWorker(&worker.Args{KeyFile: "key", CaFile: "ca"})
_, err3 := worker.NewNfdWorker(&worker.Args{CertFile: "crt", CaFile: "ca"})
Convey("An error should be returned", func() {
So(err, ShouldNotBeNil)
So(err2, ShouldNotBeNil)
@ -86,12 +88,17 @@ func TestNewNfdWorker(t *testing.T) {
}
func TestRun(t *testing.T) {
ctx := setupTest(&nfdmaster.Args{})
ctx := setupTest(&master.Args{})
defer teardownTest(ctx)
Convey("When running nfd-worker against nfd-master", t, func() {
Convey("When publishing features from fake source", func() {
worker, _ := w.NewNfdWorker(w.Args{Oneshot: true, Sources: &[]string{"fake"}, Server: "localhost:8192"})
err := worker.Run()
args := &worker.Args{
Oneshot: true,
Server: "localhost:8192",
Overrides: worker.ConfigOverrideArgs{Sources: &utils.StringSliceVal{"fake"}},
}
fooasdf, _ := worker.NewNfdWorker(args)
err := fooasdf.Run()
Convey("No error should be returned", func() {
So(err, ShouldBeNil)
})
@ -100,7 +107,7 @@ func TestRun(t *testing.T) {
}
func TestRunTls(t *testing.T) {
masterArgs := &nfdmaster.Args{
masterArgs := &master.Args{
CaFile: data.FilePath("ca.crt"),
CertFile: data.FilePath("nfd-test-master.crt"),
KeyFile: data.FilePath("nfd-test-master.key"),
@ -110,16 +117,17 @@ func TestRunTls(t *testing.T) {
defer teardownTest(ctx)
Convey("When running nfd-worker against nfd-master with mutual TLS auth enabled", t, func() {
Convey("When publishing features from fake source", func() {
workerArgs := w.Args{
workerArgs := worker.Args{
CaFile: data.FilePath("ca.crt"),
CertFile: data.FilePath("nfd-test-worker.crt"),
KeyFile: data.FilePath("nfd-test-worker.key"),
Oneshot: true,
Sources: &[]string{"fake"},
Server: "localhost:8192",
ServerNameOverride: "nfd-test-master"}
worker, _ := w.NewNfdWorker(workerArgs)
err := worker.Run()
ServerNameOverride: "nfd-test-master",
Overrides: worker.ConfigOverrideArgs{Sources: &utils.StringSliceVal{"fake"}},
}
w, _ := worker.NewNfdWorker(&workerArgs)
err := w.Run()
Convey("No error should be returned", func() {
So(err, ShouldBeNil)
})

View file

@ -17,6 +17,8 @@ limitations under the License.
package utils
import (
"encoding/json"
"fmt"
"regexp"
"sort"
"strings"
@ -34,6 +36,25 @@ func (a *RegexpVal) Set(val string) error {
return err
}
// UnmarshalJSON implements the Unmarshaler interface from "encoding/json"
func (a *RegexpVal) UnmarshalJSON(data []byte) error {
var v interface{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
switch val := v.(type) {
case string:
if r, err := regexp.Compile(string(val)); err != nil {
return err
} else {
*a = RegexpVal{*r}
}
default:
return fmt.Errorf("invalid regexp %s", data)
}
return nil
}
// StringSetVal is a Value encapsulating a set of comma-separated strings
type StringSetVal map[string]struct{}
@ -60,3 +81,20 @@ func (a *StringSetVal) String() string {
sort.Strings(vals)
return strings.Join(vals, ",")
}
// StringSliceVal is a Value encapsulating a slice of comma-separated strings
type StringSliceVal []string
// Set implements the regexp.Value interface
func (a *StringSliceVal) Set(val string) error {
*a = strings.Split(val, ",")
return nil
}
// String implements the regexp.Value interface
func (a *StringSliceVal) String() string {
if *a == nil {
return ""
}
return strings.Join(*a, ",")
}