docs/cluster/engine_test.go

379 lines
14 KiB
Go

package cluster
import (
"errors"
"fmt"
"math"
"testing"
"time"
"github.com/samalba/dockerclient"
"github.com/samalba/dockerclient/mockclient"
"github.com/samalba/dockerclient/nopclient"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var (
mockInfo = &dockerclient.Info{
ID: "id",
Name: "name",
NCPU: 10,
MemTotal: 20,
Driver: "driver-test",
ExecutionDriver: "execution-driver-test",
KernelVersion: "1.2.3",
OperatingSystem: "golang",
Labels: []string{"foo=bar"},
}
mockVersion = &dockerclient.Version{
Version: "1.6.2",
}
)
func TestEngineConnectionFailure(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0, opts)
assert.False(t, engine.isConnected())
// Always fail.
client := mockclient.NewMockClient()
client.On("Info").Return(&dockerclient.Info{}, errors.New("fail"))
// Connect() should fail
assert.Error(t, engine.ConnectWithClient(client))
// isConnected() should return false
nop := nopclient.NewNopClient()
assert.Error(t, engine.ConnectWithClient(nop))
assert.False(t, engine.isConnected())
client.Mock.AssertExpectations(t)
}
func TestOutdatedEngine(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0, opts)
client := mockclient.NewMockClient()
client.On("Info").Return(&dockerclient.Info{}, nil)
assert.Error(t, engine.ConnectWithClient(client))
nop := nopclient.NewNopClient()
assert.Error(t, engine.ConnectWithClient(nop))
assert.False(t, engine.isConnected())
client.Mock.AssertExpectations(t)
}
func TestEngineCpusMemory(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0, opts)
assert.False(t, engine.isConnected())
client := mockclient.NewMockClient()
client.On("Info").Return(mockInfo, nil)
client.On("Version").Return(mockVersion, nil)
client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil)
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil)
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return()
assert.NoError(t, engine.ConnectWithClient(client))
assert.True(t, engine.isConnected())
assert.True(t, engine.IsHealthy())
assert.Equal(t, engine.UsedCpus(), int64(0))
assert.Equal(t, engine.UsedMemory(), int64(0))
client.Mock.AssertExpectations(t)
}
func TestEngineSpecs(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0, opts)
assert.False(t, engine.isConnected())
client := mockclient.NewMockClient()
client.On("Info").Return(mockInfo, nil)
client.On("Version").Return(mockVersion, nil)
client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil)
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil)
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return()
assert.NoError(t, engine.ConnectWithClient(client))
assert.True(t, engine.isConnected())
assert.True(t, engine.IsHealthy())
assert.Equal(t, engine.Cpus, mockInfo.NCPU)
assert.Equal(t, engine.Memory, mockInfo.MemTotal)
assert.Equal(t, engine.Labels["storagedriver"], mockInfo.Driver)
assert.Equal(t, engine.Labels["executiondriver"], mockInfo.ExecutionDriver)
assert.Equal(t, engine.Labels["kernelversion"], mockInfo.KernelVersion)
assert.Equal(t, engine.Labels["operatingsystem"], mockInfo.OperatingSystem)
assert.Equal(t, engine.Labels["foo"], "bar")
client.Mock.AssertExpectations(t)
}
func TestEngineState(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0, opts)
assert.False(t, engine.isConnected())
client := mockclient.NewMockClient()
client.On("Info").Return(mockInfo, nil)
client.On("Version").Return(mockVersion, nil)
client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return()
// The client will return one container at first, then a second one will appear.
client.On("ListContainers", true, false, "").Return([]dockerclient.Container{{Id: "one"}}, nil).Once()
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once()
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("InspectContainer", "one").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: 100}}, nil).Once()
client.On("ListContainers", true, false, fmt.Sprintf("{%q:[%q]}", "id", "two")).Return([]dockerclient.Container{{Id: "two"}}, nil).Once()
client.On("InspectContainer", "two").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: 100}}, nil).Once()
assert.NoError(t, engine.ConnectWithClient(client))
assert.True(t, engine.isConnected())
// The engine should only have a single container at this point.
containers := engine.Containers()
assert.Len(t, containers, 1)
if containers[0].Id != "one" {
t.Fatalf("Missing container: one")
}
// Fake an event which will trigger a refresh. The second container will appear.
engine.handler(&dockerclient.Event{Id: "two", Status: "created"}, nil)
containers = engine.Containers()
assert.Len(t, containers, 2)
if containers[0].Id != "one" && containers[1].Id != "one" {
t.Fatalf("Missing container: one")
}
if containers[0].Id != "two" && containers[1].Id != "two" {
t.Fatalf("Missing container: two")
}
client.Mock.AssertExpectations(t)
}
func TestCreateContainer(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
var (
config = &ContainerConfig{dockerclient.ContainerConfig{
Image: "busybox",
CpuShares: 1,
Cmd: []string{"date"},
Tty: false,
}}
engine = NewEngine("test", 0, opts)
client = mockclient.NewMockClient()
)
client.On("Info").Return(mockInfo, nil)
client.On("Version").Return(mockVersion, nil)
client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return()
client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once()
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once()
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
assert.NoError(t, engine.ConnectWithClient(client))
assert.True(t, engine.isConnected())
mockConfig := config.ContainerConfig
mockConfig.CpuShares = int64(math.Ceil(float64(config.CpuShares*1024) / float64(mockInfo.NCPU)))
mockConfig.HostConfig.CpuShares = mockConfig.CpuShares
// Everything is ok
name := "test1"
id := "id1"
client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once()
client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once()
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once()
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once()
container, err := engine.Create(config, name, false)
assert.Nil(t, err)
assert.Equal(t, container.Id, id)
assert.Len(t, engine.Containers(), 1)
// Image not found, pullImage == false
name = "test2"
mockConfig.CpuShares = int64(math.Ceil(float64(config.CpuShares*1024) / float64(mockInfo.NCPU)))
client.On("CreateContainer", &mockConfig, name).Return("", dockerclient.ErrImageNotFound).Once()
container, err = engine.Create(config, name, false)
assert.Equal(t, err, dockerclient.ErrImageNotFound)
assert.Nil(t, container)
// Image not found, pullImage == true, and the image can be pulled successfully
name = "test3"
id = "id3"
mockConfig.CpuShares = int64(math.Ceil(float64(config.CpuShares*1024) / float64(mockInfo.NCPU)))
client.On("PullImage", config.Image+":latest", mock.Anything).Return(nil).Once()
client.On("CreateContainer", &mockConfig, name).Return("", dockerclient.ErrImageNotFound).Once()
client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once()
client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once()
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once()
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once()
container, err = engine.Create(config, name, true)
assert.Nil(t, err)
assert.Equal(t, container.Id, id)
assert.Len(t, engine.Containers(), 2)
}
func TestImages(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0, opts)
engine.images = []*Image{
{dockerclient.Image{Id: "a"}, engine},
{dockerclient.Image{Id: "b"}, engine},
{dockerclient.Image{Id: "c"}, engine},
}
result := engine.Images()
assert.Equal(t, len(result), 3)
}
func TestTotalMemory(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0.05, opts)
engine.Memory = 1024
assert.Equal(t, engine.TotalMemory(), int64(1024+1024*5/100))
engine = NewEngine("test", 0, opts)
engine.Memory = 1024
assert.Equal(t, engine.TotalMemory(), int64(1024))
}
func TestTotalCpus(t *testing.T) {
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0.05, opts)
engine.Cpus = 2
assert.Equal(t, engine.TotalCpus(), int64(2+2*5/100))
engine = NewEngine("test", 0, opts)
engine.Cpus = 2
assert.Equal(t, engine.TotalCpus(), int64(2))
}
func TestUsedCpus(t *testing.T) {
var (
containerNcpu = []int64{1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47}
hostNcpu = []int64{1, 2, 4, 8, 10, 12, 16, 20, 32, 36, 40, 48}
)
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0, opts)
client := mockclient.NewMockClient()
for _, hn := range hostNcpu {
for _, cn := range containerNcpu {
if cn <= hn {
mockInfo.NCPU = hn
cpuShares := int64(math.Ceil(float64(cn*1024) / float64(mockInfo.NCPU)))
client.On("Info").Return(mockInfo, nil)
client.On("Version").Return(mockVersion, nil)
client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return()
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil).Once()
client.On("ListContainers", true, false, "").Return([]dockerclient.Container{{Id: "test"}}, nil).Once()
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("InspectContainer", "test").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: cpuShares}}, nil).Once()
engine.ConnectWithClient(client)
assert.Equal(t, engine.UsedCpus(), cn)
}
}
}
}
func TestContainerRemovedDuringRefresh(t *testing.T) {
var (
container1 = dockerclient.Container{Id: "c1"}
container2 = dockerclient.Container{Id: "c2"}
info1 *dockerclient.ContainerInfo
info2 = &dockerclient.ContainerInfo{Id: "c2", Config: &dockerclient.ContainerConfig{}}
)
opts := &EngineOpts{
RefreshMinInterval: time.Duration(30),
RefreshMaxInterval: time.Duration(60),
RefreshRetry: 3,
}
engine := NewEngine("test", 0, opts)
assert.False(t, engine.isConnected())
// A container is removed before it can be inspected.
client := mockclient.NewMockClient()
client.On("Info").Return(mockInfo, nil)
client.On("Version").Return(mockVersion, nil)
client.On("ListImages", mock.Anything).Return([]*dockerclient.Image{}, nil)
client.On("StartMonitorEvents", mock.Anything, mock.Anything, mock.Anything).Return()
client.On("ListContainers", true, false, "").Return([]dockerclient.Container{container1, container2}, nil)
client.On("ListVolumes", mock.Anything).Return([]*dockerclient.Volume{}, nil)
client.On("ListNetworks", mock.Anything).Return([]*dockerclient.NetworkResource{}, nil)
client.On("InspectContainer", "c1").Return(info1, errors.New("Not found"))
client.On("InspectContainer", "c2").Return(info2, nil)
assert.NoError(t, engine.ConnectWithClient(client))
assert.Nil(t, engine.RefreshContainers(true))
// List of containers is still valid
containers := engine.Containers()
assert.Len(t, containers, 1)
assert.Equal(t, containers[0].Id, "c2")
client.Mock.AssertExpectations(t)
}