mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2025-03-28 10:47:23 +00:00
Added tests.
- Updated mock tests. - Refactored to get more coverage. - Minor changes to normalize error reporting.
This commit is contained in:
parent
e79ffd2c2a
commit
d5a9c621f9
7 changed files with 367 additions and 36 deletions
36
glide.lock
generated
36
glide.lock
generated
|
@ -1,5 +1,5 @@
|
|||
hash: b3a6656f552e85def420fa859d14f27dc2897209ce315ed25ce9cadeff6fae21
|
||||
updated: 2016-07-20T00:33:41.257155246-07:00
|
||||
hash: 9943a35009296355c6a66485228855db99eefab65890c8fa89a4776cba9c9b71
|
||||
updated: 2016-07-28T17:22:20.295749043-07:00
|
||||
imports:
|
||||
- name: github.com/beorn7/perks
|
||||
version: 3ac7bf7a47d159a033b107610db8a1b6575507a4
|
||||
|
@ -173,6 +173,10 @@ imports:
|
|||
- libcontainer/utils
|
||||
- name: github.com/pborman/uuid
|
||||
version: ca53cad383cad2479bbba7f7a1a05797ec1386e4
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
- difflib
|
||||
- name: github.com/prometheus/client_golang
|
||||
version: 3b78d7a77f51ccbc364d4bc170920153022cfd08
|
||||
subpackages:
|
||||
|
@ -189,13 +193,28 @@ imports:
|
|||
- model
|
||||
- name: github.com/prometheus/procfs
|
||||
version: 490cc6eb5fa45bf8a8b7b73c8bc82a8160e8531d
|
||||
- name: github.com/smartystreets/goconvey
|
||||
version: d4c757aa9afd1e2fc1832aaab209b5794eb336e1
|
||||
subpackages:
|
||||
- convey
|
||||
- convey/reporting
|
||||
- convey/gotest
|
||||
- name: github.com/spf13/pflag
|
||||
version: 08b1a584251b5b62f458943640fc8ebd4d50aaa5
|
||||
- name: github.com/stretchr/objx
|
||||
version: 1a9d0bb9f541897e62256577b352fdbc1fb4fd94
|
||||
- name: github.com/stretchr/testify
|
||||
version: f390dcf405f7b83c997eac1b06768bb9f44dec18
|
||||
subpackages:
|
||||
- mock
|
||||
- assert
|
||||
- name: github.com/ugorji/go
|
||||
version: f4485b318aadd133842532f841dc205a8e339d74
|
||||
subpackages:
|
||||
- codec
|
||||
- codec/codecgen
|
||||
- name: github.com/vektra/errors
|
||||
version: c64d83aba85aa4392895aadeefabbd24e89f3580
|
||||
- name: golang.org/x/net
|
||||
version: 62685c2d7ca23c807425dca88b11a3e2323dab41
|
||||
subpackages:
|
||||
|
@ -333,4 +352,15 @@ imports:
|
|||
- pkg/kubelet/qos/util
|
||||
- pkg/util/hash
|
||||
- pkg/util/net/sets
|
||||
testImports: []
|
||||
testImports:
|
||||
- name: github.com/gopherjs/gopherjs
|
||||
version: 72e303cb5f235b23471872477b57e573dc1c71d0
|
||||
subpackages:
|
||||
- js
|
||||
- name: github.com/jtolds/gls
|
||||
version: 8ddce2a84170772b95dd5d576c48d517b22cac63
|
||||
- name: github.com/smartystreets/assertions
|
||||
version: 2063fd1cc7c975db70502811a34b06ad034ccdf2
|
||||
subpackages:
|
||||
- internal/go-render/render
|
||||
- internal/oglematchers
|
||||
|
|
|
@ -9,3 +9,12 @@ import:
|
|||
subpackages:
|
||||
- pkg/api
|
||||
- pkg/client/unversioned
|
||||
- package: github.com/smartystreets/goconvey
|
||||
version: 1.6.2
|
||||
subpackages:
|
||||
- convey
|
||||
- package: github.com/stretchr/testify
|
||||
version: v1.1.3
|
||||
subpackages:
|
||||
- mock
|
||||
- package: github.com/vektra/errors
|
||||
|
|
122
main.go
122
main.go
|
@ -47,6 +47,23 @@ var (
|
|||
// Labels are a Kubernetes representation of discovered features.
|
||||
type Labels map[string]string
|
||||
|
||||
// APIHelpers represents a set of API helpers for Kubernetes
|
||||
type APIHelpers interface {
|
||||
// GetClient returns a client
|
||||
GetClient() (*client.Client, error)
|
||||
|
||||
// GetNode returns the Kubernetes node on which this container is running.
|
||||
GetNode(*client.Client) (*api.Node, error)
|
||||
|
||||
// addLabels modifies the supplied node's labels collection.
|
||||
// In order to publish the labels, the node must be subsequently updated via the
|
||||
// API server using the client library.
|
||||
AddLabels(*api.Node, Labels)
|
||||
|
||||
// UpdateNode updates the node via the API server using a client.
|
||||
UpdateNode(*client.Client, *api.Node) error
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Assert that the version is known
|
||||
if version == "" {
|
||||
|
@ -109,7 +126,12 @@ func main() {
|
|||
|
||||
// Do feature discovery from all configured sources.
|
||||
for _, source := range sources {
|
||||
for name, value := range featureLabels(source) {
|
||||
labelsFromSource, err := getFeatureLabels(source)
|
||||
if err != nil {
|
||||
log.Fatalf("discovery failed for source [%s]: %s", source.Name(), err.Error())
|
||||
}
|
||||
|
||||
for name, value := range labelsFromSource {
|
||||
labels[name] = value
|
||||
// Log discovered feature.
|
||||
log.Printf("%s = %s", name, value)
|
||||
|
@ -118,50 +140,72 @@ func main() {
|
|||
|
||||
// Update the node with the node labels, unless disabled via flags.
|
||||
if !noPublish {
|
||||
// Set up K8S client.
|
||||
cli, err := client.NewInCluster()
|
||||
helper := APIHelpers(k8sHelpers{})
|
||||
err := advertiseFeatureLabels(helper, labels)
|
||||
if err != nil {
|
||||
log.Fatalf("can't get kubernetes client: %s", err.Error())
|
||||
}
|
||||
|
||||
// Get the current node.
|
||||
node := getNode(cli)
|
||||
// Add labels to the node object.
|
||||
addLabels(node, labels)
|
||||
// Send the updated node to the apiserver.
|
||||
_, err = cli.Nodes().Update(node)
|
||||
if err != nil {
|
||||
log.Fatalf("can't update node: %s", err.Error())
|
||||
log.Fatalf("failed to advertise labels: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// featureLabels returns node labels for features discovered by the
|
||||
// getFeatureLabels returns node labels for features discovered by the
|
||||
// supplied source.
|
||||
func featureLabels(source FeatureSource) Labels {
|
||||
func getFeatureLabels(source FeatureSource) (Labels, error) {
|
||||
labels := Labels{}
|
||||
features, err := source.Discover()
|
||||
if err != nil {
|
||||
log.Fatalf("discovery failed for source [%s]: %s", source.Name(), err.Error())
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range features {
|
||||
labels[fmt.Sprintf("%s-%s-%s", prefix, source.Name(), f)] = "true"
|
||||
}
|
||||
return labels
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
// addLabels modifies the supplied node's labels collection.
|
||||
//
|
||||
// In order to publish the labels, the node must be subsequently updated via the
|
||||
// API server using the client library.
|
||||
func addLabels(n *api.Node, labels Labels) {
|
||||
for k, v := range labels {
|
||||
n.Labels[k] = v
|
||||
// advertiseFeatureLabels advertises the feature labels to a Kubernetes node
|
||||
// via the API server.
|
||||
func advertiseFeatureLabels(helper APIHelpers, labels Labels) error {
|
||||
// Set up K8S client.
|
||||
cli, err := helper.GetClient()
|
||||
if err != nil {
|
||||
log.Printf("can't get kubernetes client: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the current node.
|
||||
node, err := helper.GetNode(cli)
|
||||
if err != nil {
|
||||
log.Printf("failed to get node: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
// Add labels to the node object.
|
||||
helper.AddLabels(node, labels)
|
||||
|
||||
// Send the updated node to the apiserver.
|
||||
err = helper.UpdateNode(cli, node)
|
||||
if err != nil {
|
||||
log.Printf("can't update node: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getNode returns the Kubernetes node on which this container is running.
|
||||
func getNode(cli *client.Client) *api.Node {
|
||||
// Implements main.APIHelpers
|
||||
type k8sHelpers struct{}
|
||||
|
||||
func (h k8sHelpers) GetClient() (*client.Client, error) {
|
||||
// Set up K8S client.
|
||||
cli, err := client.NewInCluster()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cli, nil
|
||||
}
|
||||
|
||||
func (h k8sHelpers) GetNode(cli *client.Client) (*api.Node, error) {
|
||||
// Get the pod name and pod namespace from the env variables
|
||||
podName := os.Getenv(PodNameEnv)
|
||||
podns := os.Getenv(PodNamespaceEnv)
|
||||
|
@ -171,14 +215,32 @@ func getNode(cli *client.Client) *api.Node {
|
|||
// Get the pod object using the pod name and pod namespace
|
||||
pod, err := cli.Pods(podns).Get(podName)
|
||||
if err != nil {
|
||||
log.Fatalf("can't get pod: %s", err.Error())
|
||||
log.Printf("can't get pods: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the node object using the pod name and pod namespace
|
||||
node, err := cli.Nodes().Get(pod.Spec.NodeName)
|
||||
if err != nil {
|
||||
log.Fatalf("can't get node: %s", err.Error())
|
||||
log.Printf("can't get node: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return node
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (h k8sHelpers) AddLabels(n *api.Node, labels Labels) {
|
||||
for k, v := range labels {
|
||||
n.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (h k8sHelpers) UpdateNode(c *client.Client, n *api.Node) error {
|
||||
// Send the updated node to the apiserver.
|
||||
_, err := c.Nodes().Update(n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
103
main_test.go
Normal file
103
main_test.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
// "github.com/intelsdi-x/dbi-iafeature-discovery/mocks"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/vektra/errors"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
)
|
||||
|
||||
func TestDiscoveryWithMockSources(t *testing.T) {
|
||||
Convey("When I discover features from fake source and update the node using fake client", t, func() {
|
||||
mockFeatureSource := new(MockFeatureSource)
|
||||
fakeFeatureSourceName := string("testSource")
|
||||
fakeFeatures := []string{"testfeature1", "testfeature2", "testfeature3"}
|
||||
fakeFeatureLabels := Labels{}
|
||||
for _, f := range fakeFeatures {
|
||||
fakeFeatureLabels[fmt.Sprintf("%s-testSource-%s", prefix, f)] = "true"
|
||||
}
|
||||
fakeFeatureSource := FeatureSource(mockFeatureSource)
|
||||
|
||||
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)
|
||||
Convey("Proper label is returned", func() {
|
||||
So(returnedLabels, ShouldResemble, fakeFeatureLabels)
|
||||
})
|
||||
Convey("Error is nil", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When I fail to get the labels from the mock source", func() {
|
||||
expectedError := errors.New("fake error")
|
||||
mockFeatureSource.On("Discover").Return(nil, expectedError)
|
||||
|
||||
returnedLabels, err := getFeatureLabels(fakeFeatureSource)
|
||||
Convey("No label is returned", func() {
|
||||
So(returnedLabels, ShouldBeNil)
|
||||
})
|
||||
Convey("Error is produced", func() {
|
||||
So(err, ShouldEqual, expectedError)
|
||||
})
|
||||
})
|
||||
|
||||
mockAPIHelper := new(MockAPIHelpers)
|
||||
testHelper := APIHelpers(mockAPIHelper)
|
||||
var mockClient *client.Client
|
||||
var mockNode *api.Node
|
||||
|
||||
Convey("When I successfully advertise feature labels to a node", func() {
|
||||
mockAPIHelper.On("GetClient").Return(mockClient, nil)
|
||||
mockAPIHelper.On("GetNode", mockClient).Return(mockNode, nil).Once()
|
||||
mockAPIHelper.On("AddLabels", mockNode, fakeFeatureLabels).Return().Once()
|
||||
mockAPIHelper.On("UpdateNode", mockClient, mockNode).Return(nil).Once()
|
||||
err := advertiseFeatureLabels(testHelper, fakeFeatureLabels)
|
||||
|
||||
Convey("Error is nil", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When I fail to get a mock client while advertising feature labels", func() {
|
||||
expectedError := errors.New("fake error")
|
||||
mockAPIHelper.On("GetClient").Return(nil, expectedError)
|
||||
err := advertiseFeatureLabels(testHelper, fakeFeatureLabels)
|
||||
|
||||
Convey("Error is produced", func() {
|
||||
So(err, ShouldEqual, expectedError)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When I fail to get a mock node while advertising feature labels", func() {
|
||||
expectedError := errors.New("fake error")
|
||||
mockAPIHelper.On("GetClient").Return(mockClient, nil)
|
||||
mockAPIHelper.On("GetNode", mockClient).Return(nil, expectedError).Once()
|
||||
err := advertiseFeatureLabels(testHelper, fakeFeatureLabels)
|
||||
|
||||
Convey("Error is produced", func() {
|
||||
So(err, ShouldEqual, expectedError)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When I fail to update a mock node while advertising feature labels", func() {
|
||||
expectedError := errors.New("fake error")
|
||||
mockAPIHelper.On("GetClient").Return(mockClient, nil)
|
||||
mockAPIHelper.On("GetNode", mockClient).Return(mockNode, nil).Once()
|
||||
mockAPIHelper.On("AddLabels", mockNode, fakeFeatureLabels).Return().Once()
|
||||
mockAPIHelper.On("UpdateNode", mockClient, mockNode).Return(expectedError).Once()
|
||||
err := advertiseFeatureLabels(testHelper, fakeFeatureLabels)
|
||||
|
||||
Convey("Error is produced", func() {
|
||||
So(err, ShouldEqual, expectedError)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
}
|
80
mockapihelpers.go
Normal file
80
mockapihelpers.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/mock"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
)
|
||||
|
||||
type MockAPIHelpers struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetClient provides a mock function with no input arguments and
|
||||
// *client.Client and error as return value
|
||||
func (_m *MockAPIHelpers) GetClient() (*client.Client, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 *client.Client
|
||||
if rf, ok := ret.Get(0).(func() *client.Client); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*client.Client)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetNode provides a mock function with *client.Client as input argument and
|
||||
// *api.Node and error as return values
|
||||
func (_m *MockAPIHelpers) GetNode(_a0 *client.Client) (*api.Node, error) {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
var r0 *api.Node
|
||||
if rf, ok := ret.Get(0).(func(*client.Client) *api.Node); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*api.Node)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*client.Client) error); ok {
|
||||
r1 = rf(_a0)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// AddLabels provides a mock function with *api.Node and main.Labels as the input arguments and
|
||||
// no return value
|
||||
func (_m *MockAPIHelpers) AddLabels(_a0 *api.Node, _a1 Labels) {
|
||||
_m.Called(_a0, _a1)
|
||||
}
|
||||
|
||||
// UpdateNode provides a mock function with *client.Client and *api.Node as the input arguments and
|
||||
// error as the return value
|
||||
func (_m *MockAPIHelpers) UpdateNode(_a0 *client.Client, _a1 *api.Node) error {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*client.Client, *api.Node) error); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
46
mockfeaturesource.go
Normal file
46
mockfeaturesource.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package main
|
||||
|
||||
import "github.com/stretchr/testify/mock"
|
||||
|
||||
type MockFeatureSource struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Name provides a mock function with no input arguments
|
||||
// and string as return value
|
||||
func (_m *MockFeatureSource) Name() string {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Discover provides a mock function with no input arguments
|
||||
// and []string and error as the return values
|
||||
func (_m *MockFeatureSource) Discover() ([]string, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 []string
|
||||
if rf, ok := ret.Get(0).(func() []string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]string)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
|
@ -41,6 +41,7 @@ func (s cpuidSource) Discover() ([]string, error) {
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// RDT (Intel Resource Director Technology) Source
|
||||
|
||||
// Implements main.FeatureSource.
|
||||
type rdtSource struct{}
|
||||
|
||||
func (s rdtSource) Name() string { return "rdt" }
|
||||
|
@ -51,7 +52,7 @@ func (s rdtSource) Discover() ([]string, error) {
|
|||
|
||||
out, err := exec.Command("bash", "-c", path.Join(RDTBin, "mon-discovery")).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't detect support for RDT monitoring: %v", err)
|
||||
return nil, fmt.Errorf("can't detect support for RDT monitoring: %s", err.Error())
|
||||
}
|
||||
if string(out[:]) == DETECTED {
|
||||
// RDT monitoring detected.
|
||||
|
@ -60,7 +61,7 @@ func (s rdtSource) Discover() ([]string, error) {
|
|||
|
||||
out, err = exec.Command("bash", "-c", path.Join(RDTBin, "l3-alloc-discovery")).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't detect support for RDT L3 allocation: %v", err)
|
||||
return nil, fmt.Errorf("can't detect support for RDT L3 allocation: %s", err.Error())
|
||||
}
|
||||
if string(out[:]) == DETECTED {
|
||||
// RDT L3 cache allocation detected.
|
||||
|
@ -69,7 +70,7 @@ func (s rdtSource) Discover() ([]string, error) {
|
|||
|
||||
out, err = exec.Command("bash", "-c", path.Join(RDTBin, "l2-alloc-discovery")).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't detect support for RDT L2 allocation: %v", err)
|
||||
return nil, fmt.Errorf("can't detect support for RDT L2 allocation: %s", err.Error())
|
||||
}
|
||||
if string(out[:]) == DETECTED {
|
||||
// RDT L2 cache allocation detected.
|
||||
|
|
Loading…
Add table
Reference in a new issue