From 5d3102854bb63a1a2b56bcdc4ba880c2f280d54d Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Thu, 8 Feb 2018 12:57:38 -0500 Subject: [PATCH 1/6] Fix container cleanup on daemon restart When the daemon restores containers on daemon restart, it syncs up with containerd to determine the existing state. For stopped containers it then removes the container metadata from containerd. In some cases this is not handled properly and causes an error when someone attempts to start that container again. In particular, this case is just a bad error check. Signed-off-by: Brian Goff Upstream-commit: c0d56ab71701ba47ca6066c7952e724f4f5977c0 Component: engine --- .../container/daemon_linux_test.go | 84 +++++++++++++++++++ .../engine/libcontainerd/client_daemon.go | 2 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 components/engine/integration/container/daemon_linux_test.go diff --git a/components/engine/integration/container/daemon_linux_test.go b/components/engine/integration/container/daemon_linux_test.go new file mode 100644 index 0000000000..872c7ab4cc --- /dev/null +++ b/components/engine/integration/container/daemon_linux_test.go @@ -0,0 +1,84 @@ +package container + +import ( + "context" + "fmt" + "io/ioutil" + "strconv" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/integration-cli/daemon" + "github.com/stretchr/testify/assert" + "golang.org/x/sys/unix" +) + +// This is a regression test for #36145 +// It ensures that a container can be started when the daemon was improperly +// shutdown when the daemon is brought back up. +// +// The regression is due to improper error handling preventing a container from +// being restored and as such have the resources cleaned up. +// +// To test this, we need to kill dockerd, then kill both the containerd-shim and +// the container process, then start dockerd back up and attempt to start the +// container again. +func TestContainerStartOnDaemonRestart(t *testing.T) { + t.Parallel() + + d := daemon.New(t, "", "dockerd", daemon.Config{}) + d.StartWithBusybox(t, "--iptables=false") + defer d.Stop(t) + + client, err := d.NewClient() + assert.NoError(t, err, "error creating client") + + ctx := context.Background() + c, err := client.ContainerCreate(ctx, + &container.Config{ + Image: "busybox", + Cmd: []string{"top"}, + }, + nil, + nil, + "", + ) + assert.NoError(t, err, "error creating test container") + defer client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}) + + err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}) + assert.NoError(t, err, "error starting test container") + + inspect, err := client.ContainerInspect(ctx, c.ID) + assert.NoError(t, err, "error getting inspect data") + + ppid := getContainerdShimPid(t, inspect) + + err = d.Kill() + assert.NoError(t, err, "failed to kill test daemon") + + err = unix.Kill(inspect.State.Pid, unix.SIGKILL) + assert.NoError(t, err, "failed to kill container process") + + err = unix.Kill(ppid, unix.SIGKILL) + assert.NoError(t, err, "failed to kill containerd-shim") + + d.Start(t, "--iptables=false") + + err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}) + assert.NoError(t, err, "failed to start test container") +} + +func getContainerdShimPid(t *testing.T, c types.ContainerJSON) int { + statB, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", c.State.Pid)) + assert.NoError(t, err, "error looking up containerd-shim pid") + + // ppid is the 4th entry in `/proc/pid/stat` + ppid, err := strconv.Atoi(strings.Fields(string(statB))[3]) + assert.NoError(t, err, "error converting ppid field to int") + + assert.NotEqual(t, ppid, 1, "got unexpected ppid") + return ppid +} diff --git a/components/engine/libcontainerd/client_daemon.go b/components/engine/libcontainerd/client_daemon.go index eef3a4863d..2c914469a1 100644 --- a/components/engine/libcontainerd/client_daemon.go +++ b/components/engine/libcontainerd/client_daemon.go @@ -159,7 +159,7 @@ func (c *client) Restore(ctx context.Context, id string, attachStdio StdioCallba return attachStdio(dio) } t, err := ctr.Task(ctx, attachIO) - if err != nil && !errdefs.IsNotFound(errors.Cause(err)) { + if err != nil && !containerderrors.IsNotFound(err) { return false, -1, err } From 4adc380b902a9120e38d6108a17dad6359287efa Mon Sep 17 00:00:00 2001 From: bin liu Date: Sat, 10 Feb 2018 19:26:20 +0800 Subject: [PATCH 2/6] Fix typos in daemon Signed-off-by: bin liu Upstream-commit: b00a67be6e3d3f241879110bd342abaa8e23cbac Component: engine --- .../engine/daemon/cluster/controllers/plugin/controller.go | 2 +- components/engine/daemon/graphdriver/lcow/lcow.go | 2 +- components/engine/daemon/logger/logger.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/engine/daemon/cluster/controllers/plugin/controller.go b/components/engine/daemon/cluster/controllers/plugin/controller.go index dbeeb6daa3..f1d6dddb6a 100644 --- a/components/engine/daemon/cluster/controllers/plugin/controller.go +++ b/components/engine/daemon/cluster/controllers/plugin/controller.go @@ -26,7 +26,7 @@ import ( // We'll also end up with many tasks all pointing to the same plugin ID. // // TODO(@cpuguy83): registry auth is intentionally not supported until we work out -// the right way to pass registry crednetials via secrets. +// the right way to pass registry credentials via secrets. type Controller struct { backend Backend spec runtime.PluginSpec diff --git a/components/engine/daemon/graphdriver/lcow/lcow.go b/components/engine/daemon/graphdriver/lcow/lcow.go index 04701c9832..f5a2ee25fe 100644 --- a/components/engine/daemon/graphdriver/lcow/lcow.go +++ b/components/engine/daemon/graphdriver/lcow/lcow.go @@ -409,7 +409,7 @@ func (d *Driver) terminateServiceVM(id, context string, force bool) (err error) svm.signalStopFinished(err) }() - // Now it's possible that the serivce VM failed to start and now we are trying to termiante it. + // Now it's possible that the serivce VM failed to start and now we are trying to terminate it. // In this case, we will relay the error to the goroutines waiting for this vm to stop. if err := svm.getStartError(); err != nil { logrus.Debugf("lcowdriver: terminateservicevm: %s had failed to start up: %s", id, err) diff --git a/components/engine/daemon/logger/logger.go b/components/engine/daemon/logger/logger.go index a548768bf8..b64d77840e 100644 --- a/components/engine/daemon/logger/logger.go +++ b/components/engine/daemon/logger/logger.go @@ -42,7 +42,7 @@ func PutMessage(msg *Message) { messagePool.Put(msg) } -// Message is datastructure that represents piece of output produced by some +// Message is data structure that represents piece of output produced by some // container. The Line member is a slice of an array whose contents can be // changed after a log driver's Log() method returns. // @@ -133,7 +133,7 @@ func (w *LogWatcher) WatchClose() <-chan struct{} { return w.closeNotifier } -// Capability defines the list of capabilties that a driver can implement +// Capability defines the list of capabilities that a driver can implement // These capabilities are not required to be a logging driver, however do // determine how a logging driver can be used type Capability struct { From 1d4b48882894a3615598a1d02b4427971598245c Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 10 Feb 2018 00:13:09 +0000 Subject: [PATCH 3/6] Remove integration-cli/docker_cli_pause_test.go Signed-off-by: Yong Tang Upstream-commit: 6453d49d05a020aef1426c1676d9cc73217c8ff2 Component: engine --- .../integration-cli/docker_cli_pause_test.go | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 components/engine/integration-cli/docker_cli_pause_test.go diff --git a/components/engine/integration-cli/docker_cli_pause_test.go b/components/engine/integration-cli/docker_cli_pause_test.go deleted file mode 100644 index 682384fc1a..0000000000 --- a/components/engine/integration-cli/docker_cli_pause_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "strings" - "time" - - "github.com/docker/docker/integration-cli/checker" - "github.com/docker/docker/integration-cli/cli" - "github.com/go-check/check" -) - -func (s *DockerSuite) TestPause(c *check.C) { - testRequires(c, IsPausable) - - name := "testeventpause" - runSleepingContainer(c, "-d", "--name", name) - - cli.DockerCmd(c, "pause", name) - pausedContainers := strings.Fields( - cli.DockerCmd(c, "ps", "-f", "status=paused", "-q", "-a").Combined(), - ) - c.Assert(len(pausedContainers), checker.Equals, 1) - - cli.DockerCmd(c, "unpause", name) - - out := cli.DockerCmd(c, "events", "--since=0", "--until", daemonUnixTime(c)).Combined() - events := strings.Split(strings.TrimSpace(out), "\n") - actions := eventActionsByIDAndType(c, events, name, "container") - - c.Assert(actions[len(actions)-2], checker.Equals, "pause") - c.Assert(actions[len(actions)-1], checker.Equals, "unpause") -} - -func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) { - testRequires(c, IsPausable) - - containers := []string{ - "testpausewithmorecontainers1", - "testpausewithmorecontainers2", - } - for _, name := range containers { - runSleepingContainer(c, "-d", "--name", name) - } - cli.DockerCmd(c, append([]string{"pause"}, containers...)...) - pausedContainers := strings.Fields( - cli.DockerCmd(c, "ps", "-f", "status=paused", "-q", "-a").Combined(), - ) - c.Assert(len(pausedContainers), checker.Equals, len(containers)) - - cli.DockerCmd(c, append([]string{"unpause"}, containers...)...) - - out := cli.DockerCmd(c, "events", "--since=0", "--until", daemonUnixTime(c)).Combined() - events := strings.Split(strings.TrimSpace(out), "\n") - - for _, name := range containers { - actions := eventActionsByIDAndType(c, events, name, "container") - - c.Assert(actions[len(actions)-2], checker.Equals, "pause") - c.Assert(actions[len(actions)-1], checker.Equals, "unpause") - } -} - -func (s *DockerSuite) TestPauseFailsOnWindowsServerContainers(c *check.C) { - testRequires(c, DaemonIsWindows, NotPausable) - runSleepingContainer(c, "-d", "--name=test") - out, _, _ := dockerCmdWithError("pause", "test") - c.Assert(out, checker.Contains, "cannot pause Windows Server Containers") -} - -func (s *DockerSuite) TestStopPausedContainer(c *check.C) { - testRequires(c, DaemonIsLinux) - - id := runSleepingContainer(c) - cli.WaitRun(c, id) - cli.DockerCmd(c, "pause", id) - cli.DockerCmd(c, "stop", id) - cli.WaitForInspectResult(c, id, "{{.State.Running}}", "false", 30*time.Second) -} From 9bb3c2d897ab74aa1379726aa6dc9f9829296a54 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 10 Feb 2018 23:01:37 +0000 Subject: [PATCH 4/6] Update api tests to use the newly added container helper package This fix is a follow up to 36266 to update some api tests to use the newly added container helper package. Signed-off-by: Yong Tang Upstream-commit: e9f19df6a9d8ba682f3c9dcdaffed2ac4e0c6189 Component: engine --- .../engine/integration/container/stop_test.go | 43 +++---------- .../container/update_linux_test.go | 64 ++++++------------- .../integration/service/network_test.go | 39 ++++------- .../engine/integration/system/event_test.go | 24 ++----- 4 files changed, 46 insertions(+), 124 deletions(-) diff --git a/components/engine/integration/container/stop_test.go b/components/engine/integration/container/stop_test.go index 0a9daff2bd..305596ac85 100644 --- a/components/engine/integration/container/stop_test.go +++ b/components/engine/integration/container/stop_test.go @@ -8,9 +8,8 @@ import ( "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" + "github.com/docker/docker/integration/internal/container" "github.com/docker/docker/integration/internal/request" "github.com/gotestyourself/gotestyourself/icmd" "github.com/gotestyourself/gotestyourself/poll" @@ -25,23 +24,9 @@ func TestStopContainerWithRestartPolicyAlways(t *testing.T) { names := []string{"verifyRestart1", "verifyRestart2"} for _, name := range names { - resp, err := client.ContainerCreate(ctx, - &container.Config{ - Cmd: []string{"false"}, - Image: "busybox", - }, - &container.HostConfig{ - RestartPolicy: container.RestartPolicy{ - Name: "always", - }, - }, - &network.NetworkingConfig{}, - name, - ) - require.NoError(t, err) - - err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) - require.NoError(t, err) + container.Run(t, ctx, client, container.WithName(name), container.WithCmd("false"), func(c *container.TestContainerConfig) { + c.HostConfig.RestartPolicy.Name = "always" + }) } for _, name := range names { @@ -65,25 +50,13 @@ func TestDeleteDevicemapper(t *testing.T) { client := request.NewAPIClient(t) ctx := context.Background() - foo, err := client.ContainerCreate(ctx, - &container.Config{ - Cmd: []string{"echo"}, - Image: "busybox", - }, - &container.HostConfig{}, - &network.NetworkingConfig{}, - "foo", - ) - require.NoError(t, err) + id := container.Run(t, ctx, client, container.WithName("foo"), container.WithCmd("echo")) - err = client.ContainerStart(ctx, foo.ID, types.ContainerStartOptions{}) - require.NoError(t, err) + poll.WaitOn(t, containerIsStopped(ctx, client, id), poll.WithDelay(100*time.Millisecond)) - inspect, err := client.ContainerInspect(ctx, foo.ID) + inspect, err := client.ContainerInspect(ctx, id) require.NoError(t, err) - poll.WaitOn(t, containerIsStopped(ctx, client, foo.ID), poll.WithDelay(100*time.Millisecond)) - deviceID := inspect.GraphDriver.Data["DeviceId"] // Find pool name from device name @@ -94,7 +67,7 @@ func TestDeleteDevicemapper(t *testing.T) { result := icmd.RunCommand("dmsetup", "message", devicePool, "0", fmt.Sprintf("delete %s", deviceID)) result.Assert(t, icmd.Success) - err = client.ContainerRemove(ctx, foo.ID, types.ContainerRemoveOptions{}) + err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{}) require.NoError(t, err) } diff --git a/components/engine/integration/container/update_linux_test.go b/components/engine/integration/container/update_linux_test.go index f8138624b9..ef9397c209 100644 --- a/components/engine/integration/container/update_linux_test.go +++ b/components/engine/integration/container/update_linux_test.go @@ -3,7 +3,6 @@ package container // import "github.com/docker/docker/integration/container" import ( "bytes" "context" - "fmt" "io/ioutil" "strconv" "strings" @@ -11,9 +10,10 @@ import ( "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/strslice" "github.com/docker/docker/client" + "github.com/docker/docker/integration/internal/container" "github.com/docker/docker/integration/internal/request" "github.com/docker/docker/pkg/stdcopy" "github.com/gotestyourself/gotestyourself/poll" @@ -31,44 +31,32 @@ func TestUpdateMemory(t *testing.T) { client := request.NewAPIClient(t) ctx := context.Background() - c, err := client.ContainerCreate(ctx, - &container.Config{ - Cmd: []string{"top"}, - Image: "busybox", - }, - &container.HostConfig{ - Resources: container.Resources{ - Memory: 200 * 1024 * 1024, - }, - }, - nil, - "", - ) - require.NoError(t, err) + cID := container.Run(t, ctx, client, func(c *container.TestContainerConfig) { + c.HostConfig.Resources = containertypes.Resources{ + Memory: 200 * 1024 * 1024, + } + }) - err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}) - require.NoError(t, err) + poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) - poll.WaitOn(t, containerIsInState(ctx, client, c.ID, "running"), poll.WithDelay(100*time.Millisecond)) - - _, err = client.ContainerUpdate(ctx, c.ID, container.UpdateConfig{ - Resources: container.Resources{ + _, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{ + Resources: containertypes.Resources{ Memory: 314572800, MemorySwap: 524288000, }, }) require.NoError(t, err) - inspect, err := client.ContainerInspect(ctx, c.ID) + inspect, err := client.ContainerInspect(ctx, cID) require.NoError(t, err) assert.Equal(t, inspect.HostConfig.Memory, int64(314572800)) assert.Equal(t, inspect.HostConfig.MemorySwap, int64(524288000)) - body, err := getContainerSysFSValue(ctx, client, c.ID, "/sys/fs/cgroup/memory/memory.limit_in_bytes") + body, err := getContainerSysFSValue(ctx, client, cID, "/sys/fs/cgroup/memory/memory.limit_in_bytes") require.NoError(t, err) assert.Equal(t, strings.TrimSpace(body), "314572800") - body, err = getContainerSysFSValue(ctx, client, c.ID, "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") + body, err = getContainerSysFSValue(ctx, client, cID, "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes") require.NoError(t, err) assert.Equal(t, strings.TrimSpace(body), "524288000") } @@ -76,25 +64,11 @@ func TestUpdateMemory(t *testing.T) { func TestUpdateCPUQUota(t *testing.T) { t.Parallel() + defer setupTest(t)() client := request.NewAPIClient(t) ctx := context.Background() - c, err := client.ContainerCreate(ctx, &container.Config{ - Image: "busybox", - Cmd: []string{"top"}, - }, nil, nil, "") - if err != nil { - t.Fatal(err) - } - defer func() { - if err := client.ContainerRemove(ctx, c.ID, types.ContainerRemoveOptions{Force: true}); err != nil { - panic(fmt.Sprintf("failed to clean up after test: %v", err)) - } - }() - - if err := client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}); err != nil { - t.Fatal(err) - } + cID := container.Run(t, ctx, client) for _, test := range []struct { desc string @@ -105,15 +79,15 @@ func TestUpdateCPUQUota(t *testing.T) { {desc: "a lower value", update: 10000}, {desc: "unset value", update: -1}, } { - if _, err := client.ContainerUpdate(ctx, c.ID, container.UpdateConfig{ - Resources: container.Resources{ + if _, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{ + Resources: containertypes.Resources{ CPUQuota: test.update, }, }); err != nil { t.Fatal(err) } - inspect, err := client.ContainerInspect(ctx, c.ID) + inspect, err := client.ContainerInspect(ctx, cID) if err != nil { t.Fatal(err) } @@ -122,7 +96,7 @@ func TestUpdateCPUQUota(t *testing.T) { t.Fatalf("quota not updated in the API, expected %d, got: %d", test.update, inspect.HostConfig.CPUQuota) } - execCreate, err := client.ContainerExecCreate(ctx, c.ID, types.ExecConfig{ + execCreate, err := client.ContainerExecCreate(ctx, cID, types.ExecConfig{ Cmd: []string{"/bin/cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"}, AttachStdout: true, AttachStderr: true, diff --git a/components/engine/integration/service/network_test.go b/components/engine/integration/service/network_test.go index 22b271c8c2..09b0a1f12b 100644 --- a/components/engine/integration/service/network_test.go +++ b/components/engine/integration/service/network_test.go @@ -5,9 +5,9 @@ import ( "testing" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" + "github.com/docker/docker/integration/internal/container" "github.com/docker/docker/integration/internal/swarm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,20 +27,15 @@ func TestDockerNetworkConnectAlias(t *testing.T) { Attachable: true, }) require.NoError(t, err) - _, err = client.ContainerCreate(ctx, - &container.Config{ - Cmd: []string{"top"}, - Image: "busybox", - }, - &container.HostConfig{}, - &network.NetworkingConfig{ - EndpointsConfig: map[string]*network.EndpointSettings{ + + container.Create(t, ctx, client, container.WithName("ng1"), func(c *container.TestContainerConfig) { + c.NetworkingConfig = &network.NetworkingConfig{ + map[string]*network.EndpointSettings{ name: {}, }, - }, - "ng1", - ) - require.NoError(t, err) + } + }) + err = client.NetworkConnect(ctx, name, "ng1", &network.EndpointSettings{ Aliases: []string{ "aaa", @@ -56,20 +51,14 @@ func TestDockerNetworkConnectAlias(t *testing.T) { assert.Equal(t, len(ng1.NetworkSettings.Networks[name].Aliases), 2) assert.Equal(t, ng1.NetworkSettings.Networks[name].Aliases[0], "aaa") - _, err = client.ContainerCreate(ctx, - &container.Config{ - Cmd: []string{"top"}, - Image: "busybox", - }, - &container.HostConfig{}, - &network.NetworkingConfig{ - EndpointsConfig: map[string]*network.EndpointSettings{ + container.Create(t, ctx, client, container.WithName("ng2"), func(c *container.TestContainerConfig) { + c.NetworkingConfig = &network.NetworkingConfig{ + map[string]*network.EndpointSettings{ name: {}, }, - }, - "ng2", - ) - require.NoError(t, err) + } + }) + err = client.NetworkConnect(ctx, name, "ng2", &network.EndpointSettings{ Aliases: []string{ "bbb", diff --git a/components/engine/integration/system/event_test.go b/components/engine/integration/system/event_test.go index 5094e188ff..7f04195690 100644 --- a/components/engine/integration/system/event_test.go +++ b/components/engine/integration/system/event_test.go @@ -7,10 +7,9 @@ import ( "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/strslice" + "github.com/docker/docker/integration/internal/container" "github.com/docker/docker/integration/internal/request" "github.com/stretchr/testify/require" ) @@ -20,22 +19,9 @@ func TestEvents(t *testing.T) { ctx := context.Background() client := request.NewAPIClient(t) - container, err := client.ContainerCreate(ctx, - &container.Config{ - Image: "busybox", - Tty: true, - WorkingDir: "/root", - Cmd: strslice.StrSlice([]string{"top"}), - }, - &container.HostConfig{}, - &network.NetworkingConfig{}, - "foo", - ) - require.NoError(t, err) - err = client.ContainerStart(ctx, container.ID, types.ContainerStartOptions{}) - require.NoError(t, err) + cID := container.Run(t, ctx, client) - id, err := client.ContainerExecCreate(ctx, container.ID, + id, err := client.ContainerExecCreate(ctx, cID, types.ExecConfig{ Cmd: strslice.StrSlice([]string{"echo", "hello"}), }, @@ -43,7 +29,7 @@ func TestEvents(t *testing.T) { require.NoError(t, err) filters := filters.NewArgs( - filters.Arg("container", container.ID), + filters.Arg("container", cID), filters.Arg("event", "exec_die"), ) msg, errors := client.Events(ctx, types.EventsOptions{ @@ -61,7 +47,7 @@ func TestEvents(t *testing.T) { select { case m := <-msg: require.Equal(t, m.Type, "container") - require.Equal(t, m.Actor.ID, container.ID) + require.Equal(t, m.Actor.ID, cID) require.Equal(t, m.Action, "exec_die") require.Equal(t, m.Actor.Attributes["execID"], id.ID) require.Equal(t, m.Actor.Attributes["exitCode"], "0") From 5d80bc453f4d90b660c6cfe5fb8f3bdf8a4e5253 Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sun, 11 Feb 2018 23:14:39 +0000 Subject: [PATCH 5/6] Migrates session tests in integration-cli to api tests This fix migrates session tests in integration-cli to api tests in integration. Signed-off-by: Yong Tang Upstream-commit: 1d40c6a8999bdddbb57c199c2cf6e5d103153e69 Component: engine --- .../docker_api_session_test.go | 48 ------------------- .../engine/integration/session/main_test.go | 33 +++++++++++++ .../integration/session/session_test.go | 48 +++++++++++++++++++ 3 files changed, 81 insertions(+), 48 deletions(-) delete mode 100644 components/engine/integration-cli/docker_api_session_test.go create mode 100644 components/engine/integration/session/main_test.go create mode 100644 components/engine/integration/session/session_test.go diff --git a/components/engine/integration-cli/docker_api_session_test.go b/components/engine/integration-cli/docker_api_session_test.go deleted file mode 100644 index f1c44892d3..0000000000 --- a/components/engine/integration-cli/docker_api_session_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "net/http" - - "github.com/docker/docker/integration-cli/checker" - "github.com/docker/docker/integration-cli/request" - "github.com/go-check/check" -) - -func (s *DockerSuite) TestSessionCreate(c *check.C) { - testRequires(c, ExperimentalDaemon) - - res, body, err := request.Post("/session", func(r *http.Request) error { - r.Header.Set("X-Docker-Expose-Session-Uuid", "testsessioncreate") // so we don't block default name if something else is using it - r.Header.Set("Upgrade", "h2c") - return nil - }) - c.Assert(err, checker.IsNil) - c.Assert(res.StatusCode, checker.Equals, http.StatusSwitchingProtocols) - c.Assert(res.Header.Get("Upgrade"), checker.Equals, "h2c") - c.Assert(body.Close(), checker.IsNil) -} - -func (s *DockerSuite) TestSessionCreateWithBadUpgrade(c *check.C) { - testRequires(c, ExperimentalDaemon) - - res, body, err := request.Post("/session") - c.Assert(err, checker.IsNil) - c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) - buf, err := request.ReadBody(body) - c.Assert(err, checker.IsNil) - - out := string(buf) - c.Assert(out, checker.Contains, "no upgrade") - - res, body, err = request.Post("/session", func(r *http.Request) error { - r.Header.Set("Upgrade", "foo") - return nil - }) - c.Assert(err, checker.IsNil) - c.Assert(res.StatusCode, checker.Equals, http.StatusBadRequest) - buf, err = request.ReadBody(body) - c.Assert(err, checker.IsNil) - - out = string(buf) - c.Assert(out, checker.Contains, "not supported") -} diff --git a/components/engine/integration/session/main_test.go b/components/engine/integration/session/main_test.go new file mode 100644 index 0000000000..fc33025efe --- /dev/null +++ b/components/engine/integration/session/main_test.go @@ -0,0 +1,33 @@ +package session // import "github.com/docker/docker/integration/session" + +import ( + "fmt" + "os" + "testing" + + "github.com/docker/docker/internal/test/environment" +) + +var testEnv *environment.Execution + +func TestMain(m *testing.M) { + var err error + testEnv, err = environment.New() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + err = environment.EnsureFrozenImagesLinux(testEnv) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + testEnv.Print() + os.Exit(m.Run()) +} + +func setupTest(t *testing.T) func() { + environment.ProtectAll(t, testEnv) + return func() { testEnv.Clean(t) } +} diff --git a/components/engine/integration/session/session_test.go b/components/engine/integration/session/session_test.go new file mode 100644 index 0000000000..310f544554 --- /dev/null +++ b/components/engine/integration/session/session_test.go @@ -0,0 +1,48 @@ +package session // import "github.com/docker/docker/integration/session" + +import ( + "net/http" + "testing" + + req "github.com/docker/docker/integration-cli/request" + "github.com/gotestyourself/gotestyourself/skip" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSessionCreate(t *testing.T) { + skip.If(t, !testEnv.DaemonInfo.ExperimentalBuild) + + defer setupTest(t)() + + res, body, err := req.Post("/session", func(r *http.Request) error { + r.Header.Set("X-Docker-Expose-Session-Uuid", "testsessioncreate") // so we don't block default name if something else is using it + r.Header.Set("Upgrade", "h2c") + return nil + }) + require.NoError(t, err) + require.NoError(t, body.Close()) + assert.Equal(t, res.StatusCode, http.StatusSwitchingProtocols) + assert.Equal(t, res.Header.Get("Upgrade"), "h2c") +} + +func TestSessionCreateWithBadUpgrade(t *testing.T) { + skip.If(t, !testEnv.DaemonInfo.ExperimentalBuild) + + res, body, err := req.Post("/session") + require.NoError(t, err) + assert.Equal(t, res.StatusCode, http.StatusBadRequest) + buf, err := req.ReadBody(body) + require.NoError(t, err) + assert.Contains(t, string(buf), "no upgrade") + + res, body, err = req.Post("/session", func(r *http.Request) error { + r.Header.Set("Upgrade", "foo") + return nil + }) + require.NoError(t, err) + assert.Equal(t, res.StatusCode, http.StatusBadRequest) + buf, err = req.ReadBody(body) + require.NoError(t, err) + assert.Contains(t, string(buf), "not supported") +} From b61dd8e57f3081bd5dc86a00f317ebf753e1d06f Mon Sep 17 00:00:00 2001 From: Yong Tang Date: Sat, 10 Feb 2018 00:13:26 +0000 Subject: [PATCH 6/6] Migrate container pause tests to api tests This fix migrates container pause tests from integration-cli to api tests in integration/. Signed-off-by: Yong Tang Upstream-commit: ea2f076ca986038c48af37feaba6524f32761406 Component: engine --- .../integration/container/pause_test.go | 98 +++++++++++++++++++ .../integration/internal/request/client.go | 25 +++++ 2 files changed, 123 insertions(+) create mode 100644 components/engine/integration/container/pause_test.go diff --git a/components/engine/integration/container/pause_test.go b/components/engine/integration/container/pause_test.go new file mode 100644 index 0000000000..4e64357221 --- /dev/null +++ b/components/engine/integration/container/pause_test.go @@ -0,0 +1,98 @@ +package container // import "github.com/docker/docker/integration/container" + +import ( + "context" + "io" + "testing" + "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/integration/internal/container" + "github.com/docker/docker/integration/internal/request" + "github.com/docker/docker/internal/testutil" + "github.com/gotestyourself/gotestyourself/poll" + "github.com/gotestyourself/gotestyourself/skip" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPause(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType == "windows" && testEnv.DaemonInfo.Isolation == "process") + + defer setupTest(t)() + client := request.NewAPIClient(t) + ctx := context.Background() + + name := "testeventpause" + cID := container.Run(t, ctx, client, container.WithName(name)) + poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + + since := request.DaemonUnixTime(ctx, t, client, testEnv) + + err := client.ContainerPause(ctx, name) + require.NoError(t, err) + + inspect, err := client.ContainerInspect(ctx, cID) + require.NoError(t, err) + assert.Equal(t, inspect.State.Paused, true) + + err = client.ContainerUnpause(ctx, name) + require.NoError(t, err) + + until := request.DaemonUnixTime(ctx, t, client, testEnv) + + messages, errs := client.Events(ctx, types.EventsOptions{ + Since: since, + Until: until, + Filters: filters.NewArgs(filters.Arg("container", name)), + }) + assert.Equal(t, getEventActions(t, messages, errs), []string{"pause", "unpause"}) +} + +func TestPauseFailsOnWindowsServerContainers(t *testing.T) { + skip.If(t, (testEnv.DaemonInfo.OSType != "windows" || testEnv.DaemonInfo.Isolation != "process")) + + defer setupTest(t)() + client := request.NewAPIClient(t) + ctx := context.Background() + + cID := container.Run(t, ctx, client) + poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + + err := client.ContainerPause(ctx, cID) + testutil.ErrorContains(t, err, "cannot pause Windows Server Containers") +} + +func TestPauseStopPausedContainer(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + + defer setupTest(t)() + client := request.NewAPIClient(t) + ctx := context.Background() + + cID := container.Run(t, ctx, client) + poll.WaitOn(t, containerIsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + + err := client.ContainerPause(ctx, cID) + require.NoError(t, err) + + err = client.ContainerStop(ctx, cID, nil) + require.NoError(t, err) + + poll.WaitOn(t, containerIsStopped(ctx, client, cID), poll.WithDelay(100*time.Millisecond)) +} + +func getEventActions(t *testing.T, messages <-chan events.Message, errs <-chan error) []string { + actions := []string{} + for { + select { + case err := <-errs: + assert.Equal(t, err == nil || err == io.EOF, true) + return actions + case e := <-messages: + actions = append(actions, e.Status) + } + } +} diff --git a/components/engine/integration/internal/request/client.go b/components/engine/integration/internal/request/client.go index ca2486af4c..4bfb5c7673 100644 --- a/components/engine/integration/internal/request/client.go +++ b/components/engine/integration/internal/request/client.go @@ -1,12 +1,16 @@ package request // import "github.com/docker/docker/integration/internal/request" import ( + "fmt" "net" "net/http" "testing" "time" + "golang.org/x/net/context" + "github.com/docker/docker/client" + "github.com/docker/docker/internal/test/environment" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "github.com/stretchr/testify/require" @@ -49,3 +53,24 @@ func NewTLSAPIClient(t *testing.T, host, cacertPath, certPath, keyPath string) ( } return client.NewClientWithOpts(client.WithHost(host), client.WithHTTPClient(httpClient)) } + +// daemonTime provides the current time on the daemon host +func daemonTime(ctx context.Context, t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time { + if testEnv.IsLocalDaemon() { + return time.Now() + } + + info, err := client.Info(ctx) + require.NoError(t, err) + + dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) + require.NoError(t, err, "invalid time format in GET /info response") + return dt +} + +// DaemonUnixTime returns the current time on the daemon host with nanoseconds precision. +// It return the time formatted how the client sends timestamps to the server. +func DaemonUnixTime(ctx context.Context, t *testing.T, client client.APIClient, testEnv *environment.Execution) string { + dt := daemonTime(ctx, t, client, testEnv) + return fmt.Sprintf("%d.%09d", dt.Unix(), int64(dt.Nanosecond())) +}