diff --git a/container/test/mock.go b/container/test/mock.go new file mode 100644 index 00000000..fbbe8fe0 --- /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(listType) + return args.Get(0).([]info.ContainerReference), args.Error(1) +} + +func (self *MockContainerHandler) ListThreads(listType container.ListType) ([]int, error) { + args := self.Called(listType) + return args.Get(0).([]int), args.Error(1) +} + +func (self *MockContainerHandler) ListProcesses(listType container.ListType) ([]int, error) { + args := self.Called(listType) + 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 +} diff --git a/info/test/datagen.go b/info/test/datagen.go new file mode 100644 index 00000000..36841774 --- /dev/null +++ b/info/test/datagen.go @@ -0,0 +1,68 @@ +// 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" + "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 +} + +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 new file mode 100644 index 00000000..c942eb81 --- /dev/null +++ b/manager/container_test.go @@ -0,0 +1,237 @@ +// 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" + "reflect" + "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) + } + + 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) +} + +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") + } + if len(cd.info.Subcontainers) != 0 { + t.Errorf("Received %v subcontainers, should be 0", len(cd.info.Subcontainers)) + } + + 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) +} + +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) + } +} 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 +}