1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-16 21:38:23 +00:00

Advertise NFD version as an Annotation instead of a Label

Add new 'nfd.node.kubernetes.io/version' annotation for advertising the
version of NFD that created the feature labels on the node. Introduces
new 'nfd.node.kubernetes.io' namespace that is supposed to be used by
all future NFD annotations. The old
'node.alpha.kubernetes-incubator.io/node-feature-discovery.version' is
dropped in favor of the new annotation.

Annotations are better suited for this kind of metadata. NFD version
should not be used for pod scheduling, especially because all the nodes
in the cluster should normally run the same version of NFD.
This commit is contained in:
Markus Lehtonen 2018-11-20 16:31:54 +02:00
parent 2cefd312a8
commit 035efad4ad
3 changed files with 54 additions and 28 deletions

43
main.go
View file

@ -35,15 +35,18 @@ const (
ProgramName = "node-feature-discovery" ProgramName = "node-feature-discovery"
// Namespace is the prefix for all published labels. // Namespace is the prefix for all published labels.
Namespace = "feature.node.kubernetes.io" labelNs = "feature.node.kubernetes.io/"
// Namespace is the prefix for all published labels.
annotationNs = "nfd.node.kubernetes.io/"
// NodeNameEnv is the environment variable that contains this node's name. // NodeNameEnv is the environment variable that contains this node's name.
NodeNameEnv = "NODE_NAME" NodeNameEnv = "NODE_NAME"
) )
var ( var (
version = "" // Must not be const, set using ldflags at build time version = "" // Must not be const, set using ldflags at build time
prefix = fmt.Sprintf("%s/nfd", Namespace) labelPrefix = labelNs + "nfd"
) )
// package loggers // package loggers
@ -64,6 +67,9 @@ var config = NFDConfig{}
// Labels are a Kubernetes representation of discovered features. // Labels are a Kubernetes representation of discovered features.
type Labels map[string]string type Labels map[string]string
// Annotations are used for NFD-related node metadata
type Annotations map[string]string
// APIHelpers represents a set of API helpers for Kubernetes // APIHelpers represents a set of API helpers for Kubernetes
type APIHelpers interface { type APIHelpers interface {
// GetClient returns a client // GetClient returns a client
@ -82,6 +88,9 @@ type APIHelpers interface {
// API server using the client library. // API server using the client library.
AddLabels(*api.Node, Labels) AddLabels(*api.Node, Labels)
// Add annotations
AddAnnotations(*api.Node, Annotations)
// UpdateNode updates the node via the API server using a client. // UpdateNode updates the node via the API server using a client.
UpdateNode(*k8sclient.Clientset, *api.Node) error UpdateNode(*k8sclient.Clientset, *api.Node) error
} }
@ -102,6 +111,7 @@ func main() {
if version == "" { if version == "" {
stderrLogger.Fatalf("main.version not set! Set -ldflags \"-X main.version `git describe --tags --dirty --always`\" during build or run.") stderrLogger.Fatalf("main.version not set! Set -ldflags \"-X main.version `git describe --tags --dirty --always`\" during build or run.")
} }
stdoutLogger.Printf("Node Feature Discovery %s", version)
// Parse command-line arguments. // Parse command-line arguments.
args := argsParse(nil) args := argsParse(nil)
@ -276,12 +286,6 @@ func configureParameters(sourcesWhiteList []string, labelWhiteListStr string) (e
// sources and the whitelist argument. // sources and the whitelist argument.
func createFeatureLabels(sources []source.FeatureSource, labelWhiteList *regexp.Regexp) (labels Labels) { func createFeatureLabels(sources []source.FeatureSource, labelWhiteList *regexp.Regexp) (labels Labels) {
labels = Labels{} labels = Labels{}
// Add the version of this discovery code as a node label
versionLabel := fmt.Sprintf("%s/%s.version", Namespace, ProgramName)
labels[versionLabel] = version
// Log version label.
stdoutLogger.Printf("%s = %s", versionLabel, version)
// Do feature discovery from all configured sources. // Do feature discovery from all configured sources.
for _, source := range sources { for _, source := range sources {
@ -310,7 +314,10 @@ func createFeatureLabels(sources []source.FeatureSource, labelWhiteList *regexp.
// disabled via --no-publish flag. // disabled via --no-publish flag.
func updateNodeWithFeatureLabels(helper APIHelpers, noPublish bool, labels Labels) error { func updateNodeWithFeatureLabels(helper APIHelpers, noPublish bool, labels Labels) error {
if !noPublish { if !noPublish {
err := advertiseFeatureLabels(helper, labels) // Advertise NFD version as an annotation
annotations := Annotations{"version": version}
err := advertiseFeatureLabels(helper, labels, annotations)
if err != nil { if err != nil {
stderrLogger.Printf("failed to advertise labels: %s", err.Error()) stderrLogger.Printf("failed to advertise labels: %s", err.Error())
return err return err
@ -335,14 +342,14 @@ func getFeatureLabels(source source.FeatureSource) (labels Labels, err error) {
return nil, err return nil, err
} }
for k := range features { for k := range features {
labels[fmt.Sprintf("%s-%s-%s", prefix, source.Name(), k)] = fmt.Sprintf("%v", features[k]) labels[fmt.Sprintf("%s-%s-%s", labelPrefix, source.Name(), k)] = fmt.Sprintf("%v", features[k])
} }
return labels, nil return labels, nil
} }
// advertiseFeatureLabels advertises the feature labels to a Kubernetes node // advertiseFeatureLabels advertises the feature labels to a Kubernetes node
// via the API server. // via the API server.
func advertiseFeatureLabels(helper APIHelpers, labels Labels) error { func advertiseFeatureLabels(helper APIHelpers, labels Labels, annotations Annotations) error {
cli, err := helper.GetClient() cli, err := helper.GetClient()
if err != nil { if err != nil {
stderrLogger.Printf("can't get kubernetes client: %s", err.Error()) stderrLogger.Printf("can't get kubernetes client: %s", err.Error())
@ -357,10 +364,13 @@ func advertiseFeatureLabels(helper APIHelpers, labels Labels) error {
} }
// Remove labels with our prefix // Remove labels with our prefix
helper.RemoveLabels(node, prefix) helper.RemoveLabels(node, labelPrefix)
// Add labels to the node object. // Add labels to the node object.
helper.AddLabels(node, labels) helper.AddLabels(node, labels)
// Add annotations
helper.AddAnnotations(node, annotations)
// Send the updated node to the apiserver. // Send the updated node to the apiserver.
err = helper.UpdateNode(cli, node) err = helper.UpdateNode(cli, node)
if err != nil { if err != nil {
@ -418,6 +428,13 @@ func (h k8sHelpers) AddLabels(n *api.Node, labels Labels) {
} }
} }
// Add Annotations to the Node object
func (h k8sHelpers) AddAnnotations(n *api.Node, annotations Annotations) {
for k, v := range annotations {
n.Annotations[annotationNs+k] = v
}
}
func (h k8sHelpers) UpdateNode(c *k8sclient.Clientset, n *api.Node) error { func (h k8sHelpers) UpdateNode(c *k8sclient.Clientset, n *api.Node) error {
// Send the updated node to the apiserver. // Send the updated node to the apiserver.
_, err := c.Core().Nodes().Update(n) _, err := c.Core().Nodes().Update(n)

View file

@ -25,9 +25,10 @@ func TestDiscoveryWithMockSources(t *testing.T) {
fakeFeatureNames := []string{"testfeature1", "testfeature2", "testfeature3"} fakeFeatureNames := []string{"testfeature1", "testfeature2", "testfeature3"}
fakeFeatures := source.Features{} fakeFeatures := source.Features{}
fakeFeatureLabels := Labels{} fakeFeatureLabels := Labels{}
fakeAnnotations := Annotations{"version": version}
for _, f := range fakeFeatureNames { for _, f := range fakeFeatureNames {
fakeFeatures[f] = true fakeFeatures[f] = true
fakeFeatureLabels[fmt.Sprintf("%s-testSource-%s", prefix, f)] = "true" fakeFeatureLabels[fmt.Sprintf("%s-testSource-%s", labelPrefix, f)] = "true"
} }
fakeFeatureSource := source.FeatureSource(mockFeatureSource) fakeFeatureSource := source.FeatureSource(mockFeatureSource)
@ -66,7 +67,8 @@ func TestDiscoveryWithMockSources(t *testing.T) {
mockAPIHelper.On("GetClient").Return(mockClient, nil) mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetNode", mockClient).Return(mockNode, nil).Once() mockAPIHelper.On("GetNode", mockClient).Return(mockNode, nil).Once()
mockAPIHelper.On("AddLabels", mockNode, fakeFeatureLabels).Return().Once() mockAPIHelper.On("AddLabels", mockNode, fakeFeatureLabels).Return().Once()
mockAPIHelper.On("RemoveLabels", mockNode, prefix).Return().Once() mockAPIHelper.On("RemoveLabels", mockNode, labelPrefix).Return().Once()
mockAPIHelper.On("AddAnnotations", mockNode, fakeAnnotations).Return().Once()
mockAPIHelper.On("UpdateNode", mockClient, mockNode).Return(nil).Once() mockAPIHelper.On("UpdateNode", mockClient, mockNode).Return(nil).Once()
noPublish := false noPublish := false
err := updateNodeWithFeatureLabels(testHelper, noPublish, fakeFeatureLabels) err := updateNodeWithFeatureLabels(testHelper, noPublish, fakeFeatureLabels)
@ -90,7 +92,7 @@ func TestDiscoveryWithMockSources(t *testing.T) {
Convey("When I fail to get a mock client while advertising feature labels", func() { Convey("When I fail to get a mock client while advertising feature labels", func() {
expectedError := errors.New("fake error") expectedError := errors.New("fake error")
mockAPIHelper.On("GetClient").Return(nil, expectedError) mockAPIHelper.On("GetClient").Return(nil, expectedError)
err := advertiseFeatureLabels(testHelper, fakeFeatureLabels) err := advertiseFeatureLabels(testHelper, fakeFeatureLabels, fakeAnnotations)
Convey("Error is produced", func() { Convey("Error is produced", func() {
So(err, ShouldEqual, expectedError) So(err, ShouldEqual, expectedError)
@ -101,7 +103,7 @@ func TestDiscoveryWithMockSources(t *testing.T) {
expectedError := errors.New("fake error") expectedError := errors.New("fake error")
mockAPIHelper.On("GetClient").Return(mockClient, nil) mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetNode", mockClient).Return(nil, expectedError).Once() mockAPIHelper.On("GetNode", mockClient).Return(nil, expectedError).Once()
err := advertiseFeatureLabels(testHelper, fakeFeatureLabels) err := advertiseFeatureLabels(testHelper, fakeFeatureLabels, fakeAnnotations)
Convey("Error is produced", func() { Convey("Error is produced", func() {
So(err, ShouldEqual, expectedError) So(err, ShouldEqual, expectedError)
@ -112,10 +114,11 @@ func TestDiscoveryWithMockSources(t *testing.T) {
expectedError := errors.New("fake error") expectedError := errors.New("fake error")
mockAPIHelper.On("GetClient").Return(mockClient, nil) mockAPIHelper.On("GetClient").Return(mockClient, nil)
mockAPIHelper.On("GetNode", mockClient).Return(mockNode, nil).Once() mockAPIHelper.On("GetNode", mockClient).Return(mockNode, nil).Once()
mockAPIHelper.On("RemoveLabels", mockNode, prefix).Return().Once() mockAPIHelper.On("RemoveLabels", mockNode, labelPrefix).Return().Once()
mockAPIHelper.On("AddLabels", mockNode, fakeFeatureLabels).Return().Once() mockAPIHelper.On("AddLabels", mockNode, fakeFeatureLabels).Return().Once()
mockAPIHelper.On("AddAnnotations", mockNode, fakeAnnotations).Return().Once()
mockAPIHelper.On("UpdateNode", mockClient, mockNode).Return(expectedError).Once() mockAPIHelper.On("UpdateNode", mockClient, mockNode).Return(expectedError).Once()
err := advertiseFeatureLabels(testHelper, fakeFeatureLabels) err := advertiseFeatureLabels(testHelper, fakeFeatureLabels, fakeAnnotations)
Convey("Error is produced", func() { Convey("Error is produced", func() {
So(err, ShouldEqual, expectedError) So(err, ShouldEqual, expectedError)
@ -281,10 +284,10 @@ func TestCreateFeatureLabels(t *testing.T) {
labels := createFeatureLabels(sources, emptyLabelWL) labels := createFeatureLabels(sources, emptyLabelWL)
Convey("Proper fake labels are returned", func() { Convey("Proper fake labels are returned", func() {
So(len(labels), ShouldEqual, 4) So(len(labels), ShouldEqual, 3)
So(labels, ShouldContainKey, prefix+"-fake-fakefeature1") So(labels, ShouldContainKey, labelPrefix+"-fake-fakefeature1")
So(labels, ShouldContainKey, prefix+"-fake-fakefeature2") So(labels, ShouldContainKey, labelPrefix+"-fake-fakefeature2")
So(labels, ShouldContainKey, prefix+"-fake-fakefeature3") So(labels, ShouldContainKey, labelPrefix+"-fake-fakefeature3")
}) })
}) })
Convey("When fake feature source is configured with a whitelist that doesn't match", func() { Convey("When fake feature source is configured with a whitelist that doesn't match", func() {
@ -295,10 +298,10 @@ func TestCreateFeatureLabels(t *testing.T) {
labels := createFeatureLabels(sources, emptyLabelWL) labels := createFeatureLabels(sources, emptyLabelWL)
Convey("fake labels are not returned", func() { Convey("fake labels are not returned", func() {
So(len(labels), ShouldEqual, 1) So(len(labels), ShouldEqual, 0)
So(labels, ShouldNotContainKey, prefix+"-fake-fakefeature1") So(labels, ShouldNotContainKey, labelPrefix+"-fake-fakefeature1")
So(labels, ShouldNotContainKey, prefix+"-fake-fakefeature2") So(labels, ShouldNotContainKey, labelPrefix+"-fake-fakefeature2")
So(labels, ShouldNotContainKey, prefix+"-fake-fakefeature3") So(labels, ShouldNotContainKey, labelPrefix+"-fake-fakefeature3")
}) })
}) })
}) })
@ -323,7 +326,7 @@ func TestAddLabels(t *testing.T) {
}) })
Convey("They should be added to the node.Labels", func() { Convey("They should be added to the node.Labels", func() {
test1 := prefix + ".test1" test1 := labelPrefix + ".test1"
labels[test1] = "true" labels[test1] = "true"
helper.AddLabels(n, labels) helper.AddLabels(n, labels)
So(n.Labels, ShouldContainKey, test1) So(n.Labels, ShouldContainKey, test1)

View file

@ -70,6 +70,12 @@ func (_m *MockAPIHelpers) AddLabels(_a0 *api.Node, _a1 Labels) {
_m.Called(_a0, _a1) _m.Called(_a0, _a1)
} }
// AddAnnotations provides a mock function with *api.Node and main.Annotations as the input arguments and
// no return value
func (_m *MockAPIHelpers) AddAnnotations(_a0 *api.Node, _a1 Annotations) {
_m.Called(_a0, _a1)
}
// UpdateNode provides a mock function with *k8sclient.Clientset and *api.Node as the input arguments and // UpdateNode provides a mock function with *k8sclient.Clientset and *api.Node as the input arguments and
// error as the return value // error as the return value
func (_m *MockAPIHelpers) UpdateNode(_a0 *k8sclient.Clientset, _a1 *api.Node) error { func (_m *MockAPIHelpers) UpdateNode(_a0 *k8sclient.Clientset, _a1 *api.Node) error {