dockershim: add unit tests for sandbox/container status
Also add a fake clock in the FakeDockerClient to allow testing container CreatedAt, StartedAt, FinishedAt timestamps.
This commit is contained in:
		
							
								
								
									
										153
									
								
								pkg/kubelet/dockershim/docker_container_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								pkg/kubelet/dockershim/docker_container_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,153 @@
 | 
			
		||||
/*
 | 
			
		||||
Copyright 2016 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 dockershim
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// A helper to create a basic config.
 | 
			
		||||
func makeContainerConfig(sConfig *runtimeApi.PodSandboxConfig, name, image string, attempt uint32) *runtimeApi.ContainerConfig {
 | 
			
		||||
	return &runtimeApi.ContainerConfig{
 | 
			
		||||
		Metadata: &runtimeApi.ContainerMetadata{
 | 
			
		||||
			Name:    &name,
 | 
			
		||||
			Attempt: &attempt,
 | 
			
		||||
		},
 | 
			
		||||
		Image: &runtimeApi.ImageSpec{Image: &image},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestListContainers creates several containers and then list them to check
 | 
			
		||||
// whether the correct metadatas, states, and labels are returned.
 | 
			
		||||
func TestListContainers(t *testing.T) {
 | 
			
		||||
	ds, _, _ := newTestDockerSevice()
 | 
			
		||||
	podName, namespace := "foo", "bar"
 | 
			
		||||
	containerName, image := "sidecar", "logger"
 | 
			
		||||
 | 
			
		||||
	configs := []*runtimeApi.ContainerConfig{}
 | 
			
		||||
	sConfigs := []*runtimeApi.PodSandboxConfig{}
 | 
			
		||||
	for i := 0; i < 3; i++ {
 | 
			
		||||
		s := makeSandboxConfig(fmt.Sprintf("%s%d", podName, i),
 | 
			
		||||
			fmt.Sprintf("%s%d", namespace, i), fmt.Sprintf("%d", i), 0)
 | 
			
		||||
		c := makeContainerConfig(s, fmt.Sprintf("%s%d", containerName, i),
 | 
			
		||||
			fmt.Sprintf("%s:v%d", image, i), uint32(i))
 | 
			
		||||
		sConfigs = append(sConfigs, s)
 | 
			
		||||
		configs = append(configs, c)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expected := []*runtimeApi.Container{}
 | 
			
		||||
	state := runtimeApi.ContainerState_RUNNING
 | 
			
		||||
	for i := range configs {
 | 
			
		||||
		// We don't care about the sandbox id; pass a bogus one.
 | 
			
		||||
		sandboxID := fmt.Sprintf("sandboxid%d", i)
 | 
			
		||||
		id, err := ds.CreateContainer(sandboxID, configs[i], sConfigs[i])
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		err = ds.StartContainer(id)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		imageRef := "" // FakeDockerClient doesn't populate ImageRef yet.
 | 
			
		||||
		// Prepend to the expected list because ListContainers returns
 | 
			
		||||
		// the most recent containers first.
 | 
			
		||||
		expected = append([]*runtimeApi.Container{{
 | 
			
		||||
			Metadata: configs[i].Metadata,
 | 
			
		||||
			Id:       &id,
 | 
			
		||||
			State:    &state,
 | 
			
		||||
			Image:    configs[i].Image,
 | 
			
		||||
			ImageRef: &imageRef,
 | 
			
		||||
			Labels:   map[string]string{containerTypeLabelKey: containerTypeLabelContainer},
 | 
			
		||||
		}}, expected...)
 | 
			
		||||
	}
 | 
			
		||||
	containers, err := ds.ListContainers(nil)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, containers, len(expected))
 | 
			
		||||
	assert.Equal(t, expected, containers)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestContainerStatus tests the basic lifecycle operations and verify that
 | 
			
		||||
// the status returned reflects the operations performed.
 | 
			
		||||
func TestContainerStatus(t *testing.T) {
 | 
			
		||||
	ds, _, fClock := newTestDockerSevice()
 | 
			
		||||
	sConfig := makeSandboxConfig("foo", "bar", "1", 0)
 | 
			
		||||
	config := makeContainerConfig(sConfig, "pause", "iamimage", 0)
 | 
			
		||||
 | 
			
		||||
	var defaultTime time.Time
 | 
			
		||||
	dt := defaultTime.Unix()
 | 
			
		||||
	ct, st, ft := dt, dt, dt
 | 
			
		||||
	state := runtimeApi.ContainerState_CREATED
 | 
			
		||||
	// The following variables are not set in FakeDockerClient.
 | 
			
		||||
	imageRef := ""
 | 
			
		||||
	exitCode := int32(0)
 | 
			
		||||
	reason := ""
 | 
			
		||||
 | 
			
		||||
	expected := &runtimeApi.ContainerStatus{
 | 
			
		||||
		State:      &state,
 | 
			
		||||
		CreatedAt:  &ct,
 | 
			
		||||
		StartedAt:  &st,
 | 
			
		||||
		FinishedAt: &ft,
 | 
			
		||||
		Metadata:   config.Metadata,
 | 
			
		||||
		Image:      config.Image,
 | 
			
		||||
		ImageRef:   &imageRef,
 | 
			
		||||
		ExitCode:   &exitCode,
 | 
			
		||||
		Reason:     &reason,
 | 
			
		||||
		Mounts:     []*runtimeApi.Mount{},
 | 
			
		||||
		Labels:     map[string]string{containerTypeLabelKey: containerTypeLabelContainer},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create the container.
 | 
			
		||||
	fClock.SetTime(time.Now())
 | 
			
		||||
	*expected.CreatedAt = fClock.Now().Unix()
 | 
			
		||||
	id, err := ds.CreateContainer("sandboxid", config, sConfig)
 | 
			
		||||
	// Set the id manually since we don't know the id until it's created.
 | 
			
		||||
	expected.Id = &id
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	status, err := ds.ContainerStatus(id)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, expected, status)
 | 
			
		||||
 | 
			
		||||
	// Advance the clock and start the container.
 | 
			
		||||
	fClock.SetTime(time.Now())
 | 
			
		||||
	*expected.StartedAt = fClock.Now().Unix()
 | 
			
		||||
	*expected.State = runtimeApi.ContainerState_RUNNING
 | 
			
		||||
 | 
			
		||||
	err = ds.StartContainer(id)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	status, err = ds.ContainerStatus(id)
 | 
			
		||||
	assert.Equal(t, expected, status)
 | 
			
		||||
 | 
			
		||||
	// Advance the clock and stop the container.
 | 
			
		||||
	fClock.SetTime(time.Now())
 | 
			
		||||
	*expected.FinishedAt = fClock.Now().Unix()
 | 
			
		||||
	*expected.State = runtimeApi.ContainerState_EXITED
 | 
			
		||||
	*expected.Reason = "Completed"
 | 
			
		||||
 | 
			
		||||
	err = ds.StopContainer(id, 0)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	status, err = ds.ContainerStatus(id)
 | 
			
		||||
	assert.Equal(t, expected, status)
 | 
			
		||||
 | 
			
		||||
	// Remove the container.
 | 
			
		||||
	err = ds.RemoveContainer(id)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	status, err = ds.ContainerStatus(id)
 | 
			
		||||
	assert.Error(t, err, fmt.Sprintf("status of container: %+v", status))
 | 
			
		||||
}
 | 
			
		||||
@@ -18,9 +18,10 @@ package dockershim
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	dockertypes "github.com/docker/engine-api/types"
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
	runtimeApi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
 | 
			
		||||
@@ -38,27 +39,10 @@ func makeSandboxConfig(name, namespace, uid string, attempt uint32) *runtimeApi.
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestRunSandbox tests that RunSandbox creates and starts a container
 | 
			
		||||
// acting a the sandbox for the pod.
 | 
			
		||||
func TestRunSandbox(t *testing.T) {
 | 
			
		||||
	ds, fakeDocker := newTestDockerSevice()
 | 
			
		||||
	config := makeSandboxConfig("foo", "bar", "1", 0)
 | 
			
		||||
	id, err := ds.RunPodSandbox(config)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.NoError(t, fakeDocker.AssertStarted([]string{id}))
 | 
			
		||||
 | 
			
		||||
	// List running containers and verify that there is one (and only one)
 | 
			
		||||
	// running container that we just created.
 | 
			
		||||
	containers, err := fakeDocker.ListContainers(dockertypes.ContainerListOptions{All: false})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Len(t, containers, 1)
 | 
			
		||||
	assert.Equal(t, id, containers[0].ID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestListSandboxes creates several sandboxes and then list them to check
 | 
			
		||||
// whether the correct metadatas, states, and labels are returned.
 | 
			
		||||
func TestListSandboxes(t *testing.T) {
 | 
			
		||||
	ds, _ := newTestDockerSevice()
 | 
			
		||||
	ds, _, _ := newTestDockerSevice()
 | 
			
		||||
	name, namespace := "foo", "bar"
 | 
			
		||||
	configs := []*runtimeApi.PodSandboxConfig{}
 | 
			
		||||
	for i := 0; i < 3; i++ {
 | 
			
		||||
@@ -88,3 +72,48 @@ func TestListSandboxes(t *testing.T) {
 | 
			
		||||
	assert.Len(t, sandboxes, len(expected))
 | 
			
		||||
	assert.Equal(t, expected, sandboxes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestSandboxStatus tests the basic lifecycle operations and verify that
 | 
			
		||||
// the status returned reflects the operations performed.
 | 
			
		||||
func TestSandboxStatus(t *testing.T) {
 | 
			
		||||
	ds, _, fClock := newTestDockerSevice()
 | 
			
		||||
	config := makeSandboxConfig("foo", "bar", "1", 0)
 | 
			
		||||
 | 
			
		||||
	// TODO: The following variables depend on the internal
 | 
			
		||||
	// implementation of FakeDockerClient, and should be fixed.
 | 
			
		||||
	fakeIP := "2.3.4.5"
 | 
			
		||||
	fakeNS := fmt.Sprintf("/proc/%d/ns/net", os.Getpid())
 | 
			
		||||
 | 
			
		||||
	state := runtimeApi.PodSandBoxState_READY
 | 
			
		||||
	ct := int64(0)
 | 
			
		||||
	expected := &runtimeApi.PodSandboxStatus{
 | 
			
		||||
		State:     &state,
 | 
			
		||||
		CreatedAt: &ct,
 | 
			
		||||
		Metadata:  config.Metadata,
 | 
			
		||||
		Labels:    map[string]string{containerTypeLabelKey: containerTypeLabelSandbox},
 | 
			
		||||
		Network:   &runtimeApi.PodSandboxNetworkStatus{Ip: &fakeIP},
 | 
			
		||||
		Linux:     &runtimeApi.LinuxPodSandboxStatus{Namespaces: &runtimeApi.Namespace{Network: &fakeNS}},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create the sandbox.
 | 
			
		||||
	fClock.SetTime(time.Now())
 | 
			
		||||
	*expected.CreatedAt = fClock.Now().Unix()
 | 
			
		||||
	id, err := ds.RunPodSandbox(config)
 | 
			
		||||
	expected.Id = &id // ID is only known after the creation.
 | 
			
		||||
	status, err := ds.PodSandboxStatus(id)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, expected, status)
 | 
			
		||||
 | 
			
		||||
	// Stop the sandbox.
 | 
			
		||||
	*expected.State = runtimeApi.PodSandBoxState_NOTREADY
 | 
			
		||||
	err = ds.StopPodSandbox(id)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	status, err = ds.PodSandboxStatus(id)
 | 
			
		||||
	assert.Equal(t, expected, status)
 | 
			
		||||
 | 
			
		||||
	// Remove the container.
 | 
			
		||||
	err = ds.RemovePodSandbox(id)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	status, err = ds.PodSandboxStatus(id)
 | 
			
		||||
	assert.Error(t, err, fmt.Sprintf("status of sandbox: %+v", status))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,10 +17,14 @@ limitations under the License.
 | 
			
		||||
package dockershim
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/kubelet/dockertools"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/clock"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func newTestDockerSevice() (*dockerService, *dockertools.FakeDockerClient) {
 | 
			
		||||
func newTestDockerSevice() (*dockerService, *dockertools.FakeDockerClient, *clock.FakeClock) {
 | 
			
		||||
	c := dockertools.NewFakeDockerClient()
 | 
			
		||||
	return &dockerService{client: c}, c
 | 
			
		||||
	fakeClock := clock.NewFakeClock(time.Time{})
 | 
			
		||||
	return &dockerService{client: c}, c, fakeClock
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	dockertypes "github.com/docker/engine-api/types"
 | 
			
		||||
	dockercontainer "github.com/docker/engine-api/types/container"
 | 
			
		||||
	"k8s.io/kubernetes/pkg/util/clock"
 | 
			
		||||
 | 
			
		||||
	"k8s.io/kubernetes/pkg/api"
 | 
			
		||||
)
 | 
			
		||||
@@ -40,6 +41,7 @@ type calledDetail struct {
 | 
			
		||||
// FakeDockerClient is a simple fake docker client, so that kubelet can be run for testing without requiring a real docker setup.
 | 
			
		||||
type FakeDockerClient struct {
 | 
			
		||||
	sync.Mutex
 | 
			
		||||
	Clock                clock.Clock
 | 
			
		||||
	RunningContainerList []dockertypes.Container
 | 
			
		||||
	ExitedContainerList  []dockertypes.Container
 | 
			
		||||
	ContainerMap         map[string]*dockertypes.ContainerJSON
 | 
			
		||||
@@ -70,11 +72,20 @@ func NewFakeDockerClient() *FakeDockerClient {
 | 
			
		||||
	return NewFakeDockerClientWithVersion(fakeDockerVersion, minimumDockerAPIVersion)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFakeDockerClientWithClock(c clock.Clock) *FakeDockerClient {
 | 
			
		||||
	return newClientWithVersionAndClock(fakeDockerVersion, minimumDockerAPIVersion, c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFakeDockerClientWithVersion(version, apiVersion string) *FakeDockerClient {
 | 
			
		||||
	return newClientWithVersionAndClock(version, apiVersion, clock.RealClock{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newClientWithVersionAndClock(version, apiVersion string, c clock.Clock) *FakeDockerClient {
 | 
			
		||||
	return &FakeDockerClient{
 | 
			
		||||
		VersionInfo:  dockertypes.Version{Version: version, APIVersion: apiVersion},
 | 
			
		||||
		Errors:       make(map[string]error),
 | 
			
		||||
		ContainerMap: make(map[string]*dockertypes.ContainerJSON),
 | 
			
		||||
		Clock:        c,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -292,7 +303,11 @@ func (f *FakeDockerClient) InspectContainer(id string) (*dockertypes.ContainerJS
 | 
			
		||||
	if container, ok := f.ContainerMap[id]; ok {
 | 
			
		||||
		return container, err
 | 
			
		||||
	}
 | 
			
		||||
	return nil, err
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// Use the custom error if it exists.
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return nil, fmt.Errorf("container %q not found", id)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InspectImage is a test-spy implementation of DockerInterface.InspectImage.
 | 
			
		||||
@@ -337,7 +352,8 @@ func (f *FakeDockerClient) CreateContainer(c dockertypes.ContainerCreateConfig)
 | 
			
		||||
	f.RunningContainerList = append([]dockertypes.Container{
 | 
			
		||||
		{ID: name, Names: []string{name}, Image: c.Config.Image, Labels: c.Config.Labels},
 | 
			
		||||
	}, f.RunningContainerList...)
 | 
			
		||||
	f.ContainerMap[name] = convertFakeContainer(&FakeContainer{ID: id, Name: name, Config: c.Config, HostConfig: c.HostConfig})
 | 
			
		||||
	f.ContainerMap[name] = convertFakeContainer(&FakeContainer{
 | 
			
		||||
		ID: id, Name: name, Config: c.Config, HostConfig: c.HostConfig, CreatedAt: f.Clock.Now()})
 | 
			
		||||
	f.normalSleep(100, 25, 25)
 | 
			
		||||
	return &dockertypes.ContainerCreateResponse{ID: id}, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -358,7 +374,7 @@ func (f *FakeDockerClient) StartContainer(id string) error {
 | 
			
		||||
	}
 | 
			
		||||
	container.State.Running = true
 | 
			
		||||
	container.State.Pid = os.Getpid()
 | 
			
		||||
	container.State.StartedAt = dockerTimestampToString(time.Now())
 | 
			
		||||
	container.State.StartedAt = dockerTimestampToString(f.Clock.Now())
 | 
			
		||||
	container.NetworkSettings.IPAddress = "2.3.4.5"
 | 
			
		||||
	f.ContainerMap[id] = container
 | 
			
		||||
	f.updateContainerStatus(id, statusRunningPrefix)
 | 
			
		||||
@@ -398,7 +414,7 @@ func (f *FakeDockerClient) StopContainer(id string, timeout int) error {
 | 
			
		||||
			FinishedAt: time.Now(),
 | 
			
		||||
		})
 | 
			
		||||
	} else {
 | 
			
		||||
		container.State.FinishedAt = dockerTimestampToString(time.Now())
 | 
			
		||||
		container.State.FinishedAt = dockerTimestampToString(f.Clock.Now())
 | 
			
		||||
		container.State.Running = false
 | 
			
		||||
	}
 | 
			
		||||
	f.ContainerMap[id] = container
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user