mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-14 11:57:51 +00:00
Merge pull request #597 from marquiz/devel/source-register
source: make sources register themselves
This commit is contained in:
commit
102003f8b3
18 changed files with 443 additions and 237 deletions
2
Makefile
2
Makefile
|
@ -129,7 +129,7 @@ helm-lint:
|
|||
helm lint --strict deployment/helm/node-feature-discovery/
|
||||
|
||||
test:
|
||||
$(GO_CMD) test ./cmd/... ./pkg/...
|
||||
$(GO_CMD) test ./cmd/... ./pkg/... ./source/...
|
||||
|
||||
e2e-test:
|
||||
@if [ -z ${KUBECONFIG} ]; then echo "[ERR] KUBECONFIG missing, must be defined"; exit 1; fi
|
||||
|
|
|
@ -33,7 +33,6 @@ import (
|
|||
"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"
|
||||
"sigs.k8s.io/node-feature-discovery/source/kernel"
|
||||
"sigs.k8s.io/node-feature-discovery/source/pci"
|
||||
)
|
||||
|
@ -96,15 +95,6 @@ func makeFakeFeatures(names []string) (source.FeatureLabels, Labels) {
|
|||
return features, labels
|
||||
}
|
||||
|
||||
func (w *nfdWorker) getSource(name string) source.LabelSource {
|
||||
for _, s := range w.realSources {
|
||||
if s.Name() == name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConfigParse(t *testing.T) {
|
||||
Convey("When parsing configuration", t, func() {
|
||||
w, err := NewNfdWorker(&Args{})
|
||||
|
@ -130,7 +120,7 @@ func TestConfigParse(t *testing.T) {
|
|||
Convey("overrides should take effect", func() {
|
||||
So(worker.config.Core.NoPublish, ShouldBeTrue)
|
||||
|
||||
c := worker.getSource("cpu").GetConfig().(*cpu.Config)
|
||||
c := source.GetConfigurableSource("cpu").GetConfig().(*cpu.Config)
|
||||
So(c.Cpuid.AttributeBlacklist, ShouldResemble, []string{"foo", "bar"})
|
||||
})
|
||||
})
|
||||
|
@ -167,9 +157,9 @@ sources:
|
|||
|
||||
// Verify feature source config
|
||||
So(err, ShouldBeNil)
|
||||
c := worker.getSource("kernel").GetConfig()
|
||||
c := source.GetConfigurableSource("kernel").GetConfig()
|
||||
So(c.(*kernel.Config).ConfigOpts, ShouldResemble, []string{"DMI"})
|
||||
c = worker.getSource("pci").GetConfig()
|
||||
c = source.GetConfigurableSource("pci").GetConfig()
|
||||
So(c.(*pci.Config).DeviceClassWhitelist, ShouldResemble, []string{"ff"})
|
||||
})
|
||||
})
|
||||
|
@ -189,9 +179,9 @@ sources:
|
|||
|
||||
// Verify feature source config
|
||||
So(err, ShouldBeNil)
|
||||
c := worker.getSource("kernel").GetConfig()
|
||||
c := source.GetConfigurableSource("kernel").GetConfig()
|
||||
So(c.(*kernel.Config).ConfigOpts, ShouldResemble, []string{"DMI"})
|
||||
c = worker.getSource("pci").GetConfig()
|
||||
c = source.GetConfigurableSource("pci").GetConfig()
|
||||
So(c.(*pci.Config).DeviceClassWhitelist, ShouldResemble, []string{"03"})
|
||||
})
|
||||
})
|
||||
|
@ -318,7 +308,7 @@ func TestNewNfdWorker(t *testing.T) {
|
|||
worker := w.(*nfdWorker)
|
||||
So(worker.configure("", ""), ShouldBeNil)
|
||||
Convey("all sources should be enabled and the whitelist regexp should be empty", func() {
|
||||
So(len(worker.enabledSources), ShouldEqual, len(worker.realSources))
|
||||
So(len(worker.enabledSources), ShouldEqual, len(source.GetAllLabelSources())-1)
|
||||
So(worker.config.Core.LabelWhiteList, ShouldResemble, emptyRegexp)
|
||||
})
|
||||
})
|
||||
|
@ -333,7 +323,7 @@ func TestNewNfdWorker(t *testing.T) {
|
|||
So(worker.configure("", ""), ShouldBeNil)
|
||||
Convey("proper sources should be enabled", func() {
|
||||
So(len(worker.enabledSources), ShouldEqual, 1)
|
||||
So(worker.enabledSources[0], ShouldHaveSameTypeAs, &fake.Source{})
|
||||
So(worker.enabledSources[0].Name(), ShouldEqual, "fake")
|
||||
So(worker.config.Core.LabelWhiteList, ShouldResemble, emptyRegexp)
|
||||
})
|
||||
})
|
||||
|
@ -356,9 +346,9 @@ func TestNewNfdWorker(t *testing.T) {
|
|||
|
||||
func TestCreateFeatureLabels(t *testing.T) {
|
||||
Convey("When creating feature labels from the configured sources", t, func() {
|
||||
fakeLabelSource := source.LabelSource(new(fake.Source))
|
||||
fakeLabelSource.SetConfig(fakeLabelSource.NewConfig())
|
||||
sources := []source.LabelSource{fakeLabelSource}
|
||||
cs := source.GetConfigurableSource("fake")
|
||||
cs.SetConfig(cs.NewConfig())
|
||||
sources := []source.LabelSource{source.GetLabelSource("fake")}
|
||||
|
||||
Convey("When fake feature source is configured", func() {
|
||||
emptyLabelWL := regexp.MustCompile("")
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -36,18 +37,20 @@ import (
|
|||
"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"
|
||||
"sigs.k8s.io/node-feature-discovery/source/custom"
|
||||
"sigs.k8s.io/node-feature-discovery/source/fake"
|
||||
"sigs.k8s.io/node-feature-discovery/source/iommu"
|
||||
"sigs.k8s.io/node-feature-discovery/source/kernel"
|
||||
"sigs.k8s.io/node-feature-discovery/source/local"
|
||||
"sigs.k8s.io/node-feature-discovery/source/memory"
|
||||
"sigs.k8s.io/node-feature-discovery/source/network"
|
||||
"sigs.k8s.io/node-feature-discovery/source/pci"
|
||||
"sigs.k8s.io/node-feature-discovery/source/storage"
|
||||
"sigs.k8s.io/node-feature-discovery/source/system"
|
||||
"sigs.k8s.io/node-feature-discovery/source/usb"
|
||||
|
||||
// Register all source packages
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/cpu"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/custom"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/fake"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/iommu"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/kernel"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/local"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/memory"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/network"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/pci"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/storage"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/system"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/usb"
|
||||
)
|
||||
|
||||
// Global config
|
||||
|
@ -99,9 +102,7 @@ type nfdWorker struct {
|
|||
client pb.LabelerClient
|
||||
configFilePath string
|
||||
config *NFDConfig
|
||||
realSources []source.LabelSource
|
||||
stop chan struct{} // channel for signaling stop
|
||||
testSources []source.LabelSource
|
||||
enabledSources []source.LabelSource
|
||||
}
|
||||
|
||||
|
@ -121,25 +122,7 @@ func NewNfdWorker(args *Args) (nfdclient.NfdClient, error) {
|
|||
|
||||
args: *args,
|
||||
config: &NFDConfig{},
|
||||
realSources: []source.LabelSource{
|
||||
&cpu.Source{},
|
||||
&iommu.Source{},
|
||||
&kernel.Source{},
|
||||
&memory.Source{},
|
||||
&network.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{},
|
||||
},
|
||||
testSources: []source.LabelSource{
|
||||
&fake.Source{},
|
||||
},
|
||||
stop: make(chan struct{}, 1),
|
||||
stop: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
if args.ConfigFile != "" {
|
||||
|
@ -301,36 +284,44 @@ func (w *nfdWorker) configureCore(c coreConfig) error {
|
|||
}
|
||||
|
||||
// Determine enabled feature sources
|
||||
sourceList := map[string]struct{}{}
|
||||
all := false
|
||||
for _, s := range c.Sources {
|
||||
if s == "all" {
|
||||
all = true
|
||||
continue
|
||||
enabled := make(map[string]source.LabelSource)
|
||||
for _, name := range c.Sources {
|
||||
if name == "all" {
|
||||
for n, s := range source.GetAllLabelSources() {
|
||||
if ts, ok := s.(source.TestSource); !ok || !ts.IsTestSource() {
|
||||
enabled[n] = s
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if s := source.GetLabelSource(name); s != nil {
|
||||
enabled[name] = s
|
||||
} else {
|
||||
klog.Warningf("skipping unknown source %q specified in core.sources (or --sources)", name)
|
||||
}
|
||||
}
|
||||
sourceList[strings.TrimSpace(s)] = struct{}{}
|
||||
}
|
||||
|
||||
w.enabledSources = []source.LabelSource{}
|
||||
for _, s := range w.realSources {
|
||||
if _, enabled := sourceList[s.Name()]; all || enabled {
|
||||
w.enabledSources = append(w.enabledSources, s)
|
||||
delete(sourceList, s.Name())
|
||||
}
|
||||
w.enabledSources = make([]source.LabelSource, 0, len(enabled))
|
||||
for _, s := range enabled {
|
||||
w.enabledSources = append(w.enabledSources, s)
|
||||
}
|
||||
for _, s := range w.testSources {
|
||||
if _, enabled := sourceList[s.Name()]; enabled {
|
||||
w.enabledSources = append(w.enabledSources, s)
|
||||
delete(sourceList, s.Name())
|
||||
|
||||
sort.Slice(w.enabledSources, func(i, j int) bool {
|
||||
iP, jP := w.enabledSources[i].Priority(), w.enabledSources[j].Priority()
|
||||
if iP != jP {
|
||||
return iP < jP
|
||||
}
|
||||
}
|
||||
if len(sourceList) > 0 {
|
||||
names := make([]string, 0, len(sourceList))
|
||||
for n := range sourceList {
|
||||
names = append(names, n)
|
||||
return w.enabledSources[i].Name() < w.enabledSources[j].Name()
|
||||
})
|
||||
|
||||
if klog.V(1).Enabled() {
|
||||
n := make([]string, len(w.enabledSources))
|
||||
for i, s := range w.enabledSources {
|
||||
n[i] = s.Name()
|
||||
}
|
||||
klog.Warningf("skipping unknown source(s) %q specified in core.sources (or --sources)", strings.Join(names, ", "))
|
||||
klog.Infof("enabled label sources: %s", strings.Join(n, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -338,9 +329,9 @@ func (w *nfdWorker) configureCore(c coreConfig) error {
|
|||
func (w *nfdWorker) configure(filepath string, overrides string) error {
|
||||
// Create a new default config
|
||||
c := newDefaultConfig()
|
||||
allSources := append(w.realSources, w.testSources...)
|
||||
c.Sources = make(map[string]source.Config, len(allSources))
|
||||
for _, s := range allSources {
|
||||
confSources := source.GetAllConfigurableSources()
|
||||
c.Sources = make(map[string]source.Config, len(confSources))
|
||||
for _, s := range confSources {
|
||||
c.Sources[s.Name()] = s.NewConfig()
|
||||
}
|
||||
|
||||
|
@ -388,8 +379,8 @@ func (w *nfdWorker) configure(filepath string, overrides string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// (Re-)configure all "real" sources, test sources are not configurable
|
||||
for _, s := range allSources {
|
||||
// (Re-)configure sources
|
||||
for _, s := range confSources {
|
||||
s.SetConfig(c.Sources[s.Name()])
|
||||
}
|
||||
|
||||
|
@ -432,8 +423,8 @@ func getFeatureLabels(source source.LabelSource, labelWhiteList regexp.Regexp) (
|
|||
|
||||
// Prefix for labels in the default namespace
|
||||
prefix := source.Name() + "-"
|
||||
switch source.(type) {
|
||||
case *local.Source:
|
||||
switch source.Name() {
|
||||
case "local":
|
||||
// Do not prefix labels from the hooks
|
||||
prefix = ""
|
||||
}
|
||||
|
|
|
@ -78,22 +78,29 @@ type keyFilter struct {
|
|||
whitelist bool
|
||||
}
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct {
|
||||
// cpuSource implements the LabelSource and ConfigurableSource interfaces.
|
||||
type cpuSource struct {
|
||||
config *Config
|
||||
cpuidFilter *keyFilter
|
||||
}
|
||||
|
||||
func (s Source) Name() string { return Name }
|
||||
// Singleton source instance
|
||||
var (
|
||||
src cpuSource
|
||||
_ source.LabelSource = &src
|
||||
_ source.ConfigurableSource = &src
|
||||
)
|
||||
|
||||
func (s *cpuSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
|
||||
func (s *cpuSource) NewConfig() source.Config { return newDefaultConfig() }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return s.config }
|
||||
func (s *cpuSource) GetConfig() source.Config { return s.config }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(conf source.Config) {
|
||||
func (s *cpuSource) SetConfig(conf source.Config) {
|
||||
switch v := conf.(type) {
|
||||
case *Config:
|
||||
s.config = v
|
||||
|
@ -103,7 +110,10 @@ func (s *Source) SetConfig(conf source.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Source) Discover() (source.FeatureLabels, error) {
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *cpuSource) Priority() int { return 0 }
|
||||
|
||||
func (s *cpuSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
// Check if hyper-threading seems to be enabled
|
||||
|
@ -182,7 +192,7 @@ func haveThreadSiblings() (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
func (s *Source) initCpuidFilter() {
|
||||
func (s *cpuSource) initCpuidFilter() {
|
||||
newFilter := keyFilter{keys: map[string]struct{}{}}
|
||||
if len(s.config.Cpuid.AttributeWhitelist) > 0 {
|
||||
for _, k := range s.config.Cpuid.AttributeWhitelist {
|
||||
|
@ -210,3 +220,7 @@ func (f keyFilter) unmask(k string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -51,22 +51,29 @@ func newDefaultConfig() *config {
|
|||
return &config{}
|
||||
}
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct {
|
||||
// customSource implements the LabelSource and ConfigurableSource interfaces.
|
||||
type customSource struct {
|
||||
config *config
|
||||
}
|
||||
|
||||
// Singleton source instance
|
||||
var (
|
||||
src customSource
|
||||
_ source.LabelSource = &src
|
||||
_ source.ConfigurableSource = &src
|
||||
)
|
||||
|
||||
// Name returns the name of the feature source
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *customSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
|
||||
func (s *customSource) NewConfig() source.Config { return newDefaultConfig() }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return s.config }
|
||||
func (s *customSource) GetConfig() source.Config { return s.config }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(conf source.Config) {
|
||||
func (s *customSource) SetConfig(conf source.Config) {
|
||||
switch v := conf.(type) {
|
||||
case *config:
|
||||
s.config = v
|
||||
|
@ -75,8 +82,11 @@ func (s *Source) SetConfig(conf source.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *customSource) Priority() int { return 10 }
|
||||
|
||||
// Discover features
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *customSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
allFeatureConfig := append(getStaticFeatureConfig(), *s.config...)
|
||||
allFeatureConfig = append(allFeatureConfig, getDirectoryFeatureConfig()...)
|
||||
|
@ -101,7 +111,7 @@ func (s Source) Discover() (source.FeatureLabels, error) {
|
|||
|
||||
// Process a single feature by Matching on the defined rules.
|
||||
// A feature is present if all defined Rules in a MatchRule return a match.
|
||||
func (s Source) discoverFeature(feature FeatureSpec) (bool, error) {
|
||||
func (s *customSource) discoverFeature(feature FeatureSpec) (bool, error) {
|
||||
for _, matchRules := range feature.MatchOn {
|
||||
|
||||
allRules := []rules.Rule{
|
||||
|
@ -136,3 +146,7 @@ func (s Source) discoverFeature(feature FeatureSpec) (bool, error) {
|
|||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
Copyright 2017-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.
|
||||
|
@ -40,22 +40,29 @@ func newDefaultConfig() *Config {
|
|||
}
|
||||
}
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct {
|
||||
// fakeSource implements the LabelSource and ConfigurableSource interfaces.
|
||||
type fakeSource struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Singleton source instance
|
||||
var (
|
||||
src fakeSource
|
||||
_ source.LabelSource = &src
|
||||
_ source.ConfigurableSource = &src
|
||||
)
|
||||
|
||||
// Name returns an identifier string for this feature source.
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *fakeSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
|
||||
func (s *fakeSource) NewConfig() source.Config { return newDefaultConfig() }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return s.config }
|
||||
func (s *fakeSource) GetConfig() source.Config { return s.config }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(conf source.Config) {
|
||||
func (s *fakeSource) SetConfig(conf source.Config) {
|
||||
switch v := conf.(type) {
|
||||
case *Config:
|
||||
s.config = v
|
||||
|
@ -65,10 +72,13 @@ func (s *Source) SetConfig(conf source.Config) {
|
|||
}
|
||||
|
||||
// Configure method of the LabelSource interface
|
||||
func (s Source) Configure([]byte) error { return nil }
|
||||
func (s *fakeSource) Configure([]byte) error { return nil }
|
||||
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *fakeSource) Priority() int { return 0 }
|
||||
|
||||
// Discover returns feature names for some fake features.
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *fakeSource) Discover() (source.FeatureLabels, error) {
|
||||
// Adding three fake features.
|
||||
features := make(source.FeatureLabels, len(s.config.Labels))
|
||||
for k, v := range s.config.Labels {
|
||||
|
@ -77,3 +87,10 @@ func (s Source) Discover() (source.FeatureLabels, error) {
|
|||
|
||||
return features, nil
|
||||
}
|
||||
|
||||
// IsTestSource method of the LabelSource interface
|
||||
func (s *fakeSource) IsTestSource() bool { return true }
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
Copyright 2018-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.
|
||||
|
@ -25,21 +25,21 @@ import (
|
|||
|
||||
const Name = "iommu"
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct{}
|
||||
// iommuSource implements the LabelSource interface.
|
||||
type iommuSource struct{}
|
||||
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *iommuSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return nil }
|
||||
// Singleton source instance
|
||||
var (
|
||||
src iommuSource
|
||||
_ source.LabelSource = &src
|
||||
)
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return nil }
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *iommuSource) Priority() int { return 0 }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(source.Config) {}
|
||||
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *iommuSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
// Check if any iommu devices are available
|
||||
|
@ -54,3 +54,7 @@ func (s Source) Discover() (source.FeatureLabels, error) {
|
|||
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -47,21 +47,28 @@ func newDefaultConfig() *Config {
|
|||
}
|
||||
}
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct {
|
||||
// kernelSource implements the LabelSource and ConfigurableSource interfaces.
|
||||
type kernelSource struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
func (s *Source) Name() string { return Name }
|
||||
// Singleton source instance
|
||||
var (
|
||||
src kernelSource
|
||||
_ source.LabelSource = &src
|
||||
_ source.ConfigurableSource = &src
|
||||
)
|
||||
|
||||
func (s *kernelSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
|
||||
func (s *kernelSource) NewConfig() source.Config { return newDefaultConfig() }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return s.config }
|
||||
func (s *kernelSource) GetConfig() source.Config { return s.config }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(conf source.Config) {
|
||||
func (s *kernelSource) SetConfig(conf source.Config) {
|
||||
switch v := conf.(type) {
|
||||
case *Config:
|
||||
s.config = v
|
||||
|
@ -70,7 +77,10 @@ func (s *Source) SetConfig(conf source.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Source) Discover() (source.FeatureLabels, error) {
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *kernelSource) Priority() int { return 0 }
|
||||
|
||||
func (s *kernelSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
// Read kernel version
|
||||
|
@ -135,3 +145,7 @@ func parseVersion() (map[string]string, error) {
|
|||
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -38,23 +38,23 @@ var (
|
|||
hookDir = "/etc/kubernetes/node-feature-discovery/source.d/"
|
||||
)
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct{}
|
||||
// localSource implements the LabelSource interface.
|
||||
type localSource struct{}
|
||||
|
||||
// Singleton source instance
|
||||
var (
|
||||
src localSource
|
||||
_ source.LabelSource = &src
|
||||
)
|
||||
|
||||
// Name method of the LabelSource interface
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *localSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return nil }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return nil }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(source.Config) {}
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *localSource) Priority() int { return 20 }
|
||||
|
||||
// Discover method of the LabelSource interface
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *localSource) Discover() (source.FeatureLabels, error) {
|
||||
featuresFromHooks, err := getFeaturesFromHooks()
|
||||
if err != nil {
|
||||
klog.Error(err)
|
||||
|
@ -240,3 +240,7 @@ func getFileContent(fileName string) ([][]byte, error) {
|
|||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -28,23 +28,23 @@ import (
|
|||
|
||||
const Name = "memory"
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct{}
|
||||
// memorySource implements the LabelSource interface.
|
||||
type memorySource struct{}
|
||||
|
||||
// Singleton source instance
|
||||
var (
|
||||
src memorySource
|
||||
_ source.LabelSource = &src
|
||||
)
|
||||
|
||||
// Name returns an identifier string for this feature source.
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *memorySource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return nil }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return nil }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(source.Config) {}
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *memorySource) Priority() int { return 0 }
|
||||
|
||||
// Discover returns feature names for memory: numa if more than one memory node is present.
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *memorySource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
// Detect NUMA
|
||||
|
@ -118,3 +118,7 @@ func detectNvdimm() (map[string]bool, error) {
|
|||
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -34,22 +34,6 @@ func (_m *MockLabelSource) Discover() (FeatureLabels, error) {
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// GetConfig provides a mock function with given fields:
|
||||
func (_m *MockLabelSource) 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 *MockLabelSource) Name() string {
|
||||
ret := _m.Called()
|
||||
|
@ -64,23 +48,16 @@ func (_m *MockLabelSource) Name() string {
|
|||
return r0
|
||||
}
|
||||
|
||||
// NewConfig provides a mock function with given fields:
|
||||
func (_m *MockLabelSource) NewConfig() Config {
|
||||
// Priority provides a mock function with given fields:
|
||||
func (_m *MockLabelSource) Priority() int {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 Config
|
||||
if rf, ok := ret.Get(0).(func() Config); ok {
|
||||
var r0 int
|
||||
if rf, ok := ret.Get(0).(func() int); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(Config)
|
||||
}
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetConfig provides a mock function with given fields: _a0
|
||||
func (_m *MockLabelSource) SetConfig(_a0 Config) {
|
||||
_m.Called(_a0)
|
||||
}
|
||||
|
|
|
@ -41,23 +41,23 @@ const (
|
|||
|
||||
const sysfsBaseDir = "class/net"
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct{}
|
||||
// networkSource implements the LabelSource interface.
|
||||
type networkSource struct{}
|
||||
|
||||
// Singleton source instance
|
||||
var (
|
||||
src networkSource
|
||||
_ source.LabelSource = &src
|
||||
)
|
||||
|
||||
// Name returns an identifier string for this feature source.
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *networkSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return nil }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return nil }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(source.Config) {}
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *networkSource) Priority() int { return 0 }
|
||||
|
||||
// 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.FeatureLabels, error) {
|
||||
func (s *networkSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
netInterfaces, err := ioutil.ReadDir(source.SysfsDir.Path(sysfsBaseDir))
|
||||
|
@ -128,3 +128,7 @@ func readIfFlags(name string) (uint64, error) {
|
|||
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -41,22 +41,29 @@ func newDefaultConfig() *Config {
|
|||
}
|
||||
}
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct {
|
||||
// pciSource implements the LabelSource and ConfigurableSource interfaces.
|
||||
type pciSource struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Singleton source instance
|
||||
var (
|
||||
src pciSource
|
||||
_ source.LabelSource = &src
|
||||
_ source.ConfigurableSource = &src
|
||||
)
|
||||
|
||||
// Name returns the name of the feature source
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *pciSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
|
||||
func (s *pciSource) NewConfig() source.Config { return newDefaultConfig() }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return s.config }
|
||||
func (s *pciSource) GetConfig() source.Config { return s.config }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(conf source.Config) {
|
||||
func (s *pciSource) SetConfig(conf source.Config) {
|
||||
switch v := conf.(type) {
|
||||
case *Config:
|
||||
s.config = v
|
||||
|
@ -65,8 +72,11 @@ func (s *Source) SetConfig(conf source.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *pciSource) Priority() int { return 0 }
|
||||
|
||||
// Discover features
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *pciSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
// Construct a device label format, a sorted list of valid attributes
|
||||
|
@ -132,3 +142,7 @@ func (s Source) Discover() (source.FeatureLabels, error) {
|
|||
}
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
Copyright 2017-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.
|
||||
|
@ -16,20 +16,31 @@ limitations under the License.
|
|||
|
||||
package source
|
||||
|
||||
// FeatureLabelValue represents the value of one feature label
|
||||
type FeatureLabelValue interface{}
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// FeatureLabels is a collection of feature labels
|
||||
type FeatureLabels map[string]FeatureLabelValue
|
||||
// Source is the base interface for all other source interfaces
|
||||
type Source interface {
|
||||
// Name returns a friendly name for this source
|
||||
Name() string
|
||||
}
|
||||
|
||||
// LabelSource represents a source of node feature labels
|
||||
type LabelSource interface {
|
||||
// Name returns a friendly name for this source
|
||||
Name() string
|
||||
Source
|
||||
|
||||
// Discover returns discovered feature labels
|
||||
Discover() (FeatureLabels, error)
|
||||
|
||||
// Priority returns the priority of the source
|
||||
Priority() int
|
||||
}
|
||||
|
||||
// ConfigurableSource is an interface for a source that can be configured
|
||||
type ConfigurableSource interface {
|
||||
Source
|
||||
|
||||
// NewConfig returns a new default config of the source
|
||||
NewConfig() Config
|
||||
|
||||
|
@ -40,6 +51,69 @@ type LabelSource interface {
|
|||
SetConfig(Config)
|
||||
}
|
||||
|
||||
// TestSource represents a source purposed for testing only
|
||||
type TestSource interface {
|
||||
Source
|
||||
|
||||
// IsTestSource returns true if the source is not for production
|
||||
IsTestSource() bool
|
||||
}
|
||||
|
||||
// FeatureLabelValue represents the value of one feature label
|
||||
type FeatureLabelValue interface{}
|
||||
|
||||
// FeatureLabels is a collection of feature labels
|
||||
type FeatureLabels map[string]FeatureLabelValue
|
||||
|
||||
// Config is the generic interface for source configuration data
|
||||
type Config interface {
|
||||
}
|
||||
|
||||
// sources contain all registered sources
|
||||
var sources = make(map[string]Source)
|
||||
|
||||
// RegisterSource registers a source
|
||||
func Register(s Source) {
|
||||
if name, ok := sources[s.Name()]; ok {
|
||||
panic(fmt.Sprintf("source %q already registered", name))
|
||||
}
|
||||
sources[s.Name()] = s
|
||||
}
|
||||
|
||||
// GetLabelSource a registered label source
|
||||
func GetLabelSource(name string) LabelSource {
|
||||
if s, ok := sources[name].(LabelSource); ok {
|
||||
return s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllLabelSources returns all registered label sources
|
||||
func GetAllLabelSources() map[string]LabelSource {
|
||||
all := make(map[string]LabelSource)
|
||||
for k, v := range sources {
|
||||
if s, ok := v.(LabelSource); ok {
|
||||
all[k] = s
|
||||
}
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
// GetConfigurableSource a registered configurable source
|
||||
func GetConfigurableSource(name string) ConfigurableSource {
|
||||
if s, ok := sources[name].(ConfigurableSource); ok {
|
||||
return s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllConfigurableSources returns all registered configurable sources
|
||||
func GetAllConfigurableSources() map[string]ConfigurableSource {
|
||||
all := make(map[string]ConfigurableSource)
|
||||
for k, v := range sources {
|
||||
if s, ok := v.(ConfigurableSource); ok {
|
||||
all[k] = s
|
||||
}
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
|
63
source/source_test.go
Normal file
63
source/source_test.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
source "sigs.k8s.io/node-feature-discovery/source"
|
||||
|
||||
// Register all source packages
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/cpu"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/custom"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/fake"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/iommu"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/kernel"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/local"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/memory"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/network"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/pci"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/storage"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/system"
|
||||
_ "sigs.k8s.io/node-feature-discovery/source/usb"
|
||||
)
|
||||
|
||||
func TestLabelSources(t *testing.T) {
|
||||
sources := source.GetAllLabelSources()
|
||||
assert.NotZero(t, len(sources))
|
||||
|
||||
for n, s := range sources {
|
||||
assert.Equalf(t, n, s.Name(), "testing labelsource %q failed", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigurableSources(t *testing.T) {
|
||||
sources := source.GetAllConfigurableSources()
|
||||
assert.NotZero(t, len(sources))
|
||||
|
||||
for n, s := range sources {
|
||||
assert.Equalf(t, n, s.Name(), "testing ConfigurableSource %q failed", n)
|
||||
|
||||
c := s.NewConfig()
|
||||
s.SetConfig(c)
|
||||
rc := s.GetConfig()
|
||||
|
||||
assert.Equalf(t, c, rc, "testing ConfigurableSource %q failed", n)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
Copyright 2018-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.
|
||||
|
@ -25,23 +25,23 @@ import (
|
|||
|
||||
const Name = "storage"
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct{}
|
||||
// storageSource implements the LabelSource interface.
|
||||
type storageSource struct{}
|
||||
|
||||
// Singleton source instance
|
||||
var (
|
||||
src storageSource
|
||||
_ source.LabelSource = &src
|
||||
)
|
||||
|
||||
// Name returns an identifier string for this feature source.
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *storageSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return nil }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return nil }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(source.Config) {}
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *storageSource) Priority() int { return 0 }
|
||||
|
||||
// Discover returns feature names for storage: nonrotationaldisk if any SSD drive present.
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *storageSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
// Check if there is any non-rotational block devices attached to the node
|
||||
|
@ -62,3 +62,7 @@ func (s Source) Discover() (source.FeatureLabels, error) {
|
|||
}
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -34,21 +34,21 @@ var osReleaseFields = [...]string{
|
|||
|
||||
const Name = "system"
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct{}
|
||||
// systemSource implements the LabelSource interface.
|
||||
type systemSource struct{}
|
||||
|
||||
func (s Source) Name() string { return Name }
|
||||
// Singleton source instance
|
||||
var (
|
||||
src systemSource
|
||||
_ source.LabelSource = &src
|
||||
)
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return nil }
|
||||
func (s *systemSource) Name() string { return Name }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return nil }
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *systemSource) Priority() int { return 0 }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(source.Config) {}
|
||||
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *systemSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
release, err := parseOSRelease()
|
||||
|
@ -112,3 +112,7 @@ func splitVersion(version string) map[string]string {
|
|||
}
|
||||
return components
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
|
@ -44,22 +44,29 @@ func newDefaultConfig() *Config {
|
|||
}
|
||||
}
|
||||
|
||||
// Source implements LabelSource.
|
||||
type Source struct {
|
||||
// usbSource implements the LabelSource and ConfigurableSource interfaces.
|
||||
type usbSource struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Singleton source instance
|
||||
var (
|
||||
src usbSource
|
||||
_ source.LabelSource = &src
|
||||
_ source.ConfigurableSource = &src
|
||||
)
|
||||
|
||||
// Name returns the name of the feature source
|
||||
func (s Source) Name() string { return Name }
|
||||
func (s *usbSource) Name() string { return Name }
|
||||
|
||||
// NewConfig method of the LabelSource interface
|
||||
func (s *Source) NewConfig() source.Config { return newDefaultConfig() }
|
||||
func (s *usbSource) NewConfig() source.Config { return newDefaultConfig() }
|
||||
|
||||
// GetConfig method of the LabelSource interface
|
||||
func (s *Source) GetConfig() source.Config { return s.config }
|
||||
func (s *usbSource) GetConfig() source.Config { return s.config }
|
||||
|
||||
// SetConfig method of the LabelSource interface
|
||||
func (s *Source) SetConfig(conf source.Config) {
|
||||
func (s *usbSource) SetConfig(conf source.Config) {
|
||||
switch v := conf.(type) {
|
||||
case *Config:
|
||||
s.config = v
|
||||
|
@ -68,8 +75,11 @@ func (s *Source) SetConfig(conf source.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// Priority method of the LabelSource interface
|
||||
func (s *usbSource) Priority() int { return 0 }
|
||||
|
||||
// Discover features
|
||||
func (s Source) Discover() (source.FeatureLabels, error) {
|
||||
func (s *usbSource) Discover() (source.FeatureLabels, error) {
|
||||
features := source.FeatureLabels{}
|
||||
|
||||
// Construct a device label format, a sorted list of valid attributes
|
||||
|
@ -127,3 +137,7 @@ func (s Source) Discover() (source.FeatureLabels, error) {
|
|||
}
|
||||
return features, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
source.Register(&src)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue