From 0ade2f1c233446c8a84f0ce4fa035931591b7649 Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Thu, 3 Jul 2014 23:39:08 +0000 Subject: [PATCH 1/7] mock container handler --- container/test/mock.go | 88 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 container/test/mock.go diff --git a/container/test/mock.go b/container/test/mock.go new file mode 100644 index 00000000..f81144d9 --- /dev/null +++ b/container/test/mock.go @@ -0,0 +1,88 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// 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 test + +import ( + "github.com/google/cadvisor/container" + "github.com/google/cadvisor/info" + "github.com/stretchr/testify/mock" +) + +// This struct mocks a container handler. +type MockContainerHandler struct { + mock.Mock + Name string + Aliases []string +} + +// If self.Name is not empty, then ContainerReference() will return self.Name and self.Aliases. +// Otherwise, it will use the value provided by .On().Return(). +func (self *MockContainerHandler) ContainerReference() (info.ContainerReference, error) { + if len(self.Name) > 0 { + var aliases []string + if len(self.Aliases) > 0 { + aliases = make([]string, len(self.Aliases)) + copy(aliases, self.Aliases) + } + return info.ContainerReference{ + Name: self.Name, + Aliases: aliases, + }, nil + } + args := self.Called() + return args.Get(0).(info.ContainerReference), args.Error(1) +} + +func (self *MockContainerHandler) GetSpec() (*info.ContainerSpec, error) { + args := self.Called() + return args.Get(0).(*info.ContainerSpec), args.Error(1) +} + +func (self *MockContainerHandler) GetStats() (*info.ContainerStats, error) { + args := self.Called() + return args.Get(0).(*info.ContainerStats), args.Error(1) +} + +func (self *MockContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { + args := self.Called() + return args.Get(0).([]info.ContainerReference), args.Error(1) +} + +func (self *MockContainerHandler) ListThreads(listType container.ListType) ([]int, error) { + args := self.Called() + return args.Get(0).([]int), args.Error(1) +} + +func (self *MockContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { + args := self.Called() + return args.Get(0).([]int), args.Error(1) +} + +type FactoryForMockContainerHandler struct { + Name string + PrepareContainerHandlerFunc func(name string, handler *MockContainerHandler) +} + +func (self *FactoryForMockContainerHandler) String() string { + return self.Name +} + +func (self *FactoryForMockContainerHandler) NewContainerHandler(name string) (container.ContainerHandler, error) { + handler := &MockContainerHandler{} + if self.PrepareContainerHandlerFunc != nil { + self.PrepareContainerHandlerFunc(name, handler) + } + return handler, nil +} From 6f6c72f6818c5682dba929713428361089f4b727 Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Thu, 3 Jul 2014 23:54:39 +0000 Subject: [PATCH 2/7] randomly generate stats for test --- info/test/datagen.go | 50 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 info/test/datagen.go diff --git a/info/test/datagen.go b/info/test/datagen.go new file mode 100644 index 00000000..63d7ed31 --- /dev/null +++ b/info/test/datagen.go @@ -0,0 +1,50 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// 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 test + +import ( + "math/rand" + "time" + + "github.com/google/cadvisor/info" +) + +func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info.ContainerStats { + ret := make([]*info.ContainerStats, numStats) + perCoreUsages := make([]uint64, numCores) + currentTime := time.Now() + for i := range perCoreUsages { + perCoreUsages[i] = uint64(rand.Int63n(1000)) + } + for i := 0; i < numStats; i++ { + stats := new(info.ContainerStats) + stats.Cpu = new(info.CpuStats) + stats.Memory = new(info.MemoryStats) + stats.Timestamp = currentTime + currentTime = currentTime.Add(duration) + + percore := make([]uint64, numCores) + for i := range perCoreUsages { + perCoreUsages[i] += uint64(rand.Int63n(1000)) + percore[i] = perCoreUsages[i] + stats.Cpu.Usage.Total += percore[i] + } + stats.Cpu.Usage.PerCpu = percore + stats.Cpu.Usage.User = stats.Cpu.Usage.Total + stats.Cpu.Usage.System = 0 + stats.Memory.Usage = uint64(rand.Int63n(4096)) + } + return ret +} From 018d514981c4995b71b0373758256020d2458267 Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Thu, 3 Jul 2014 21:03:04 -0700 Subject: [PATCH 3/7] mock container --- container/test/mock.go | 6 +++--- storage/storage.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/container/test/mock.go b/container/test/mock.go index f81144d9..fbbe8fe0 100644 --- a/container/test/mock.go +++ b/container/test/mock.go @@ -56,17 +56,17 @@ func (self *MockContainerHandler) GetStats() (*info.ContainerStats, error) { } func (self *MockContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) { - args := self.Called() + args := self.Called(listType) return args.Get(0).([]info.ContainerReference), args.Error(1) } func (self *MockContainerHandler) ListThreads(listType container.ListType) ([]int, error) { - args := self.Called() + args := self.Called(listType) return args.Get(0).([]int), args.Error(1) } func (self *MockContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { - args := self.Called() + args := self.Called(listType) return args.Get(0).([]int), args.Error(1) } diff --git a/storage/storage.go b/storage/storage.go index 51f0b9c0..8b264a93 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -31,7 +31,7 @@ type StorageDriver interface { // Returns samples of the container stats. If numSamples < 0, then // the number of returned samples is implementation defined. Otherwise, the driver // should return at most numSamples samples. - Samples(containername string, numSamples int) ([]*info.ContainerStatsSample, error) + Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) // Close will clear the state of the storage driver. The elements // stored in the underlying storage may or may not be deleted depending From 842d18c6e6294fa168bde15b37483797c83e2693 Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Thu, 3 Jul 2014 21:03:20 -0700 Subject: [PATCH 4/7] mock storage driver --- storage/test/mock.go | 57 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 storage/test/mock.go diff --git a/storage/test/mock.go b/storage/test/mock.go new file mode 100644 index 00000000..24fe2500 --- /dev/null +++ b/storage/test/mock.go @@ -0,0 +1,57 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// 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 test + +import ( + "github.com/google/cadvisor/info" + "github.com/stretchr/testify/mock" +) + +type MockStorageDriver struct { + mock.Mock + MockCloseMethod bool +} + +func (self *MockStorageDriver) AddStats(ref info.ContainerReference, stats *info.ContainerStats) error { + args := self.Called(ref, stats) + return args.Error(0) +} + +func (self *MockStorageDriver) RecentStats(containerName string, numStats int) ([]*info.ContainerStats, error) { + args := self.Called(containerName, numStats) + return args.Get(0).([]*info.ContainerStats), args.Error(1) +} + +func (self *MockStorageDriver) Percentiles( + containerName string, + cpuUsagePercentiles []int, + memUsagePercentiles []int, +) (*info.ContainerStatsPercentiles, error) { + args := self.Called(containerName, cpuUsagePercentiles, memUsagePercentiles) + return args.Get(0).(*info.ContainerStatsPercentiles), args.Error(1) +} + +func (self *MockStorageDriver) Samples(containerName string, numSamples int) ([]*info.ContainerStatsSample, error) { + args := self.Called(containerName, numSamples) + return args.Get(0).([]*info.ContainerStatsSample), args.Error(1) +} + +func (self *MockStorageDriver) Close() error { + if self.MockCloseMethod { + args := self.Called() + return args.Error(0) + } + return nil +} From 4df4b0ee2e5c1988c587778a06e0aff767b69d1c Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Thu, 3 Jul 2014 22:08:24 -0700 Subject: [PATCH 5/7] unit tests for containerData --- manager/container_test.go | 136 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 manager/container_test.go diff --git a/manager/container_test.go b/manager/container_test.go new file mode 100644 index 00000000..a67cb34b --- /dev/null +++ b/manager/container_test.go @@ -0,0 +1,136 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// 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. + +// Per-container manager. + +package manager + +import ( + "fmt" + "testing" + "time" + + "github.com/google/cadvisor/container" + ctest "github.com/google/cadvisor/container/test" + "github.com/google/cadvisor/info" + itest "github.com/google/cadvisor/info/test" + "github.com/google/cadvisor/storage" + stest "github.com/google/cadvisor/storage/test" +) + +func createContainerDataAndSetHandler( + driver storage.StorageDriver, + f func(*ctest.MockContainerHandler), + t *testing.T, +) *containerData { + factory := &ctest.FactoryForMockContainerHandler{ + Name: "factoryForMockContainer", + PrepareContainerHandlerFunc: func(name string, handler *ctest.MockContainerHandler) { + handler.Name = name + f(handler) + }, + } + container.RegisterContainerHandlerFactory("/", factory) + + if driver == nil { + driver = &stest.MockStorageDriver{} + } + + ret, err := NewContainerData("/container", driver) + if err != nil { + t.Fatal(err) + } + return ret +} + +func TestContainerUpdateSubcontainers(t *testing.T) { + var handler *ctest.MockContainerHandler + subcontainers := []info.ContainerReference{ + {Name: "/container/ee0103"}, + {Name: "/container/abcd"}, + {Name: "/container/something"}, + } + cd := createContainerDataAndSetHandler( + nil, + func(h *ctest.MockContainerHandler) { + h.On("ListContainers", container.LIST_SELF).Return( + subcontainers, + nil, + ) + handler = h + }, + t, + ) + + err := cd.updateSubcontainers() + if err != nil { + t.Fatal(err) + } + + handler.AssertExpectations(t) +} + +func TestContainerUpdateSubcontainersWithError(t *testing.T) { + var handler *ctest.MockContainerHandler + cd := createContainerDataAndSetHandler( + nil, + func(h *ctest.MockContainerHandler) { + h.On("ListContainers", container.LIST_SELF).Return( + []info.ContainerReference{}, + fmt.Errorf("some error"), + ) + handler = h + }, + t, + ) + + err := cd.updateSubcontainers() + if err == nil { + t.Fatal("updateSubcontainers should return error") + } + + handler.AssertExpectations(t) +} + +func TestContainerUpdateStats(t *testing.T) { + var handler *ctest.MockContainerHandler + var ref info.ContainerReference + + driver := &stest.MockStorageDriver{} + + statsList := itest.GenerateRandomStats(1, 4, 1*time.Second) + stats := statsList[0] + + cd := createContainerDataAndSetHandler( + driver, + func(h *ctest.MockContainerHandler) { + h.On("GetStats").Return( + stats, + nil, + ) + handler = h + ref.Name = h.Name + }, + t, + ) + + driver.On("AddStats", ref, stats).Return(nil) + + err := cd.updateStats() + if err != nil { + t.Fatal(err) + } + + handler.AssertExpectations(t) +} From 7df7989849de1d9888066b2d817d626cc5d59fa2 Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Thu, 3 Jul 2014 22:25:03 -0700 Subject: [PATCH 6/7] test get info --- info/test/datagen.go | 18 +++++++++ manager/container_test.go | 82 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/info/test/datagen.go b/info/test/datagen.go index 63d7ed31..36841774 100644 --- a/info/test/datagen.go +++ b/info/test/datagen.go @@ -15,6 +15,7 @@ package test import ( + "math" "math/rand" "time" @@ -48,3 +49,20 @@ func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info } return ret } + +func GenerateRandomContainerSpec(numCores int) *info.ContainerSpec { + ret := &info.ContainerSpec{ + Cpu: &info.CpuSpec{}, + Memory: &info.MemorySpec{}, + } + ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000)) + ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000)) + n := (numCores + 63) / 64 + ret.Cpu.Mask.Data = make([]uint64, n) + for i := 0; i < n; i++ { + ret.Cpu.Mask.Data[i] = math.MaxUint64 + } + + ret.Memory.Limit = uint64(4096 + rand.Int63n(4096)) + return ret +} diff --git a/manager/container_test.go b/manager/container_test.go index a67cb34b..ad8b8c75 100644 --- a/manager/container_test.go +++ b/manager/container_test.go @@ -18,6 +18,7 @@ package manager import ( "fmt" + "reflect" "testing" "time" @@ -134,3 +135,84 @@ func TestContainerUpdateStats(t *testing.T) { handler.AssertExpectations(t) } + +func TestContainerUpdateSpec(t *testing.T) { + var handler *ctest.MockContainerHandler + spec := itest.GenerateRandomContainerSpec(4) + cd := createContainerDataAndSetHandler( + nil, + func(h *ctest.MockContainerHandler) { + h.On("GetSpec").Return( + spec, + nil, + ) + handler = h + }, + t, + ) + + err := cd.updateSpec() + if err != nil { + t.Fatal(err) + } + + handler.AssertExpectations(t) +} + +func TestContainerGetInfo(t *testing.T) { + var handler *ctest.MockContainerHandler + spec := itest.GenerateRandomContainerSpec(4) + subcontainers := []info.ContainerReference{ + {Name: "/container/ee0103"}, + {Name: "/container/abcd"}, + {Name: "/container/something"}, + } + aliases := []string{"a1", "a2"} + cd := createContainerDataAndSetHandler( + nil, + func(h *ctest.MockContainerHandler) { + h.On("GetSpec").Return( + spec, + nil, + ) + h.On("ListContainers", container.LIST_SELF).Return( + subcontainers, + nil, + ) + h.Aliases = aliases + handler = h + }, + t, + ) + + info, err := cd.GetInfo() + if err != nil { + t.Fatal(err) + } + + handler.AssertExpectations(t) + + if len(info.Subcontainers) != len(subcontainers) { + t.Errorf("Received %v subcontainers, should be %v", len(info.Subcontainers), len(subcontainers)) + } + + for _, sub := range info.Subcontainers { + found := false + for _, sub2 := range subcontainers { + if sub.Name == sub2.Name { + found = true + } + } + if !found { + t.Errorf("Received unknown sub container %v", sub) + } + } + + if !reflect.DeepEqual(spec, info.Spec) { + t.Errorf("received wrong container spec") + } + + if info.Name != handler.Name { + t.Errorf("received wrong container name: received %v; should be %v", info.Name, handler.Name) + } +} From f8542e2a3319259b2c19b348ac24dcdd36023533 Mon Sep 17 00:00:00 2001 From: Nan Deng Date: Mon, 7 Jul 2014 16:51:49 -0700 Subject: [PATCH 7/7] check subcontainers --- manager/container_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/manager/container_test.go b/manager/container_test.go index ad8b8c75..c942eb81 100644 --- a/manager/container_test.go +++ b/manager/container_test.go @@ -79,6 +79,22 @@ func TestContainerUpdateSubcontainers(t *testing.T) { t.Fatal(err) } + if len(cd.info.Subcontainers) != len(subcontainers) { + t.Errorf("Received %v subcontainers, should be %v", len(cd.info.Subcontainers), len(subcontainers)) + } + + for _, sub := range cd.info.Subcontainers { + found := false + for _, sub2 := range subcontainers { + if sub.Name == sub2.Name { + found = true + } + } + if !found { + t.Errorf("Received unknown sub container %v", sub) + } + } + handler.AssertExpectations(t) } @@ -100,6 +116,9 @@ func TestContainerUpdateSubcontainersWithError(t *testing.T) { if err == nil { t.Fatal("updateSubcontainers should return error") } + if len(cd.info.Subcontainers) != 0 { + t.Errorf("Received %v subcontainers, should be 0", len(cd.info.Subcontainers)) + } handler.AssertExpectations(t) }