From f7f4515e43f182b81d8452492f382a9b11d1f50e Mon Sep 17 00:00:00 2001 From: Renaud Gaubert Date: Tue, 15 Aug 2017 14:46:30 -0700 Subject: [PATCH] Testing --- pkg/kubelet/deviceplugin/BUILD | 16 +++ pkg/kubelet/deviceplugin/endpoint_test.go | 125 ++++++++++++++++++ pkg/kubelet/deviceplugin/manager_test.go | 58 ++++++++ .../deviceplugin/mock_device_plugin.go | 123 +++++++++++++++++ pkg/kubelet/deviceplugin/utils_test.go | 54 ++++++++ 5 files changed, 376 insertions(+) create mode 100644 pkg/kubelet/deviceplugin/endpoint_test.go create mode 100644 pkg/kubelet/deviceplugin/manager_test.go create mode 100644 pkg/kubelet/deviceplugin/mock_device_plugin.go create mode 100644 pkg/kubelet/deviceplugin/utils_test.go diff --git a/pkg/kubelet/deviceplugin/BUILD b/pkg/kubelet/deviceplugin/BUILD index 30e5891b2b3..ccf6f0ef96b 100644 --- a/pkg/kubelet/deviceplugin/BUILD +++ b/pkg/kubelet/deviceplugin/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -12,6 +13,7 @@ go_library( srcs = [ "endpoint.go", "manager.go", + "mock_device_plugin.go", "types.go", "utils.go", ], @@ -36,3 +38,17 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = [ + "endpoint_test.go", + "manager_test.go", + "utils_test.go", + ], + library = ":go_default_library", + deps = [ + "//pkg/kubelet/apis/deviceplugin/v1alpha1:go_default_library", + "//vendor/github.com/stretchr/testify/require:go_default_library", + ], +) diff --git a/pkg/kubelet/deviceplugin/endpoint_test.go b/pkg/kubelet/deviceplugin/endpoint_test.go new file mode 100644 index 00000000000..8898b3dd133 --- /dev/null +++ b/pkg/kubelet/deviceplugin/endpoint_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2017 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 deviceplugin + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" +) + +const ( + socket = "/tmp/mock.sock" +) + +func TestNewEndpoint(t *testing.T) { + devs := []*pluginapi.Device{ + {ID: "ADeviceId", Health: pluginapi.Healthy}, + } + + p, e := esetup(t, devs, socket, "mock", func(n string, a, u, r []*pluginapi.Device) {}) + defer ecleanup(t, p, e) +} + +func TestList(t *testing.T) { + devs := []*pluginapi.Device{ + {ID: "ADeviceId", Health: pluginapi.Healthy}, + } + + p, e := esetup(t, devs, socket, "mock", func(n string, a, u, r []*pluginapi.Device) {}) + defer ecleanup(t, p, e) + + _, err := e.list() + require.NoError(t, err) + + e.mutex.Lock() + defer e.mutex.Unlock() + + require.Len(t, e.devices, 1) + + d, ok := e.devices[devs[0].ID] + require.True(t, ok) + + require.Equal(t, d.ID, devs[0].ID) + require.Equal(t, d.Health, devs[0].Health) +} + +func TestListAndWatch(t *testing.T) { + devs := []*pluginapi.Device{ + {ID: "ADeviceId", Health: pluginapi.Healthy}, + {ID: "AnotherDeviceId", Health: pluginapi.Healthy}, + } + + updated := []*pluginapi.Device{ + {ID: "ADeviceId", Health: pluginapi.Unhealthy}, + {ID: "AThirdDeviceId", Health: pluginapi.Healthy}, + } + + p, e := esetup(t, devs, socket, "mock", func(n string, a, u, r []*pluginapi.Device) { + require.Len(t, a, 1) + require.Len(t, u, 1) + require.Len(t, r, 1) + + require.Equal(t, a[0].ID, updated[1].ID) + + require.Equal(t, u[0].ID, updated[0].ID) + require.Equal(t, u[0].Health, updated[0].Health) + + require.Equal(t, r[0].ID, devs[1].ID) + }) + defer ecleanup(t, p, e) + + s, err := e.list() + require.NoError(t, err) + + go e.listAndWatch(s) + p.Update(updated) + time.Sleep(time.Second) + + e.mutex.Lock() + defer e.mutex.Unlock() + + require.Len(t, e.devices, 2) + for _, dref := range updated { + d, ok := e.devices[dref.ID] + + require.True(t, ok) + require.Equal(t, d.ID, dref.ID) + require.Equal(t, d.Health, dref.Health) + } + +} + +func esetup(t *testing.T, devs []*pluginapi.Device, socket, resourceName string, callback MonitorCallback) (*MockDevicePlugin, *endpoint) { + p := NewMockDevicePlugin(devs, socket) + + err := p.Start() + require.NoError(t, err) + + e, err := newEndpoint(socket, "mock", func(n string, a, u, r []*pluginapi.Device) {}) + require.NoError(t, err) + + return p, e +} + +func ecleanup(t *testing.T, p *MockDevicePlugin, e *endpoint) { + p.Stop() + e.stop() +} diff --git a/pkg/kubelet/deviceplugin/manager_test.go b/pkg/kubelet/deviceplugin/manager_test.go new file mode 100644 index 00000000000..8aed51007d1 --- /dev/null +++ b/pkg/kubelet/deviceplugin/manager_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2017 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 deviceplugin + +import ( + "testing" + + "github.com/stretchr/testify/require" + + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" +) + +const ( + msocket = "/tmp/server.sock" +) + +func TestNewManagerImpl(t *testing.T) { + _, err := NewManagerImpl("", func(n string, a, u, r []*pluginapi.Device) {}) + require.Error(t, err) + + _, err = NewManagerImpl(msocket, func(n string, a, u, r []*pluginapi.Device) {}) + require.NoError(t, err) +} + +func TestNewManagerImplStart(t *testing.T) { + _, err := NewManagerImpl(msocket, func(n string, a, u, r []*pluginapi.Device) {}) + require.NoError(t, err) +} + +func setup(t *testing.T, devs []*pluginapi.Device, pluginSocket, serverSocket string, callback MonitorCallback) (Manager, *MockDevicePlugin) { + m, err := NewManagerImpl(serverSocket, callback) + require.NoError(t, err) + + p := NewMockDevicePlugin(devs, pluginSocket) + err = p.Start() + require.NoError(t, err) + + return m, p +} + +func cleanup(t *testing.T, m Manager, p *MockDevicePlugin) { + p.Stop() + m.Stop() +} diff --git a/pkg/kubelet/deviceplugin/mock_device_plugin.go b/pkg/kubelet/deviceplugin/mock_device_plugin.go new file mode 100644 index 00000000000..676536cfe02 --- /dev/null +++ b/pkg/kubelet/deviceplugin/mock_device_plugin.go @@ -0,0 +1,123 @@ +/* +Copyright 2017 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 deviceplugin + +import ( + "log" + "net" + "os" + + "golang.org/x/net/context" + "google.golang.org/grpc" + + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" +) + +// MockDevicePlugin is a mock device plugin +type MockDevicePlugin struct { + devs []*pluginapi.Device + socket string + + stop chan interface{} + update chan []*pluginapi.Device + + server *grpc.Server +} + +// NewMockDevicePlugin returns an initialized MockDevicePlugin +func NewMockDevicePlugin(devs []*pluginapi.Device, socket string) *MockDevicePlugin { + return &MockDevicePlugin{ + devs: devs, + socket: socket, + + stop: make(chan interface{}), + update: make(chan []*pluginapi.Device), + } +} + +// Start starts the gRPC server of the device plugin +func (m *MockDevicePlugin) Start() error { + err := m.cleanup() + if err != nil { + return err + } + + sock, err := net.Listen("unix", m.socket) + if err != nil { + return err + } + + m.server = grpc.NewServer([]grpc.ServerOption{}...) + pluginapi.RegisterDevicePluginServer(m.server, m) + + go m.server.Serve(sock) + log.Println("Starting to serve on", m.socket) + + return nil +} + +// Stop stops the gRPC server +func (m *MockDevicePlugin) Stop() error { + m.server.Stop() + + return m.cleanup() +} + +// ListAndWatch lists devices and update that list according to the Update call +func (m *MockDevicePlugin) ListAndWatch(e *pluginapi.Empty, s pluginapi.DevicePlugin_ListAndWatchServer) error { + log.Println("ListAndWatch") + var devs []*pluginapi.Device + + for _, d := range m.devs { + devs = append(devs, &pluginapi.Device{ + ID: d.ID, + Health: pluginapi.Healthy, + }) + } + + s.Send(&pluginapi.ListAndWatchResponse{Devices: devs}) + + for { + select { + case <-m.stop: + return nil + case updated := <-m.update: + s.Send(&pluginapi.ListAndWatchResponse{Devices: updated}) + } + } +} + +// Update allows the device plugin to send new devices through ListAndWatch +func (m *MockDevicePlugin) Update(devs []*pluginapi.Device) { + m.update <- devs +} + +// Allocate does a mock allocation +func (m *MockDevicePlugin) Allocate(ctx context.Context, r *pluginapi.AllocateRequest) (*pluginapi.AllocateResponse, error) { + log.Printf("Allocate, %+v", r) + + var response pluginapi.AllocateResponse + return &response, nil +} + +func (m *MockDevicePlugin) cleanup() error { + if err := os.Remove(m.socket); err != nil && !os.IsNotExist(err) { + return err + } + + return nil +} diff --git a/pkg/kubelet/deviceplugin/utils_test.go b/pkg/kubelet/deviceplugin/utils_test.go new file mode 100644 index 00000000000..cf4a1186647 --- /dev/null +++ b/pkg/kubelet/deviceplugin/utils_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2017 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 deviceplugin + +import ( + "testing" + + "github.com/stretchr/testify/require" + + pluginapi "k8s.io/kubernetes/pkg/kubelet/apis/deviceplugin/v1alpha1" +) + +func TestCloneDevice(t *testing.T) { + d := CloneDevice(&pluginapi.Device{ID: "ADeviceId", Health: pluginapi.Healthy}) + + require.Equal(t, d.ID, "ADeviceId") + require.Equal(t, d.Health, pluginapi.Healthy) +} + +func TestCopyDevices(t *testing.T) { + d := map[string]*pluginapi.Device{ + "ADeviceId": {ID: "ADeviceId", Health: pluginapi.Healthy}, + } + + devs := copyDevices(d) + require.Len(t, devs, 1) +} + +func TestGetDevice(t *testing.T) { + devs := []*pluginapi.Device{ + {ID: "ADeviceId", Health: pluginapi.Healthy}, + } + + _, ok := GetDevice(&pluginapi.Device{ID: "AnotherDeviceId"}, devs) + require.False(t, ok) + + d, ok := GetDevice(&pluginapi.Device{ID: "ADeviceId"}, devs) + require.True(t, ok) + require.Equal(t, d, devs[0]) +}