1
0
Fork 0
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:
Balaji Subramaniam 2016-07-28 18:00:42 -07:00 committed by Connor Doyle
parent e79ffd2c2a
commit d5a9c621f9
7 changed files with 367 additions and 36 deletions

36
glide.lock generated
View file

@ -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

View file

@ -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
View file

@ -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
View 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
View 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
View 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
}

View file

@ -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.