diff --git a/docs/reference/commandline/pause.md b/docs/reference/commandline/pause.md index 629c6ed091..6e85ebbea0 100644 --- a/docs/reference/commandline/pause.md +++ b/docs/reference/commandline/pause.md @@ -19,11 +19,12 @@ Options: --help Print usage ``` -The `docker pause` command uses the cgroups freezer to suspend all processes in -a container. Traditionally, when suspending a process the `SIGSTOP` signal is -used, which is observable by the process being suspended. With the cgroups freezer -the process is unaware, and unable to capture, that it is being suspended, -and subsequently resumed. +The `docker pause` command suspends all processes in a container. On Linux, +this uses the cgroups freezer. Traditionally, when suspending a process the +`SIGSTOP` signal is used, which is observable by the process being suspended. +With the cgroups freezer the process is unaware, and unable to capture, +that it is being suspended, and subsequently resumed. On Windows, only Hyper-V +containers can be paused. See the [cgroups freezer documentation](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) diff --git a/docs/reference/commandline/unpause.md b/docs/reference/commandline/unpause.md index e5c9d506e0..9795f16482 100644 --- a/docs/reference/commandline/unpause.md +++ b/docs/reference/commandline/unpause.md @@ -19,8 +19,8 @@ Options: --help Print usage ``` -The `docker unpause` command uses the cgroups freezer to un-suspend all -processes in a container. +The `docker unpause` command un-suspends all processes in a container. +On Linux, it does this using the cgroups freezer. See the [cgroups freezer documentation](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 08f6119779..2df4fdc4d2 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -154,9 +154,9 @@ func (s *DockerSuite) TestAttachDisconnect(c *check.C) { } func (s *DockerSuite) TestAttachPausedContainer(c *check.C) { - testRequires(c, DaemonIsLinux) // Containers cannot be paused on Windows + testRequires(c, IsPausable) defer unpauseAllContainers() - dockerCmd(c, "run", "-d", "--name=test", "busybox", "top") + runSleepingContainer(c, "-d", "--name=test") dockerCmd(c, "pause", "test") result := dockerCmdWithResult("attach", "test") diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index d0be69bfa0..da815335b0 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -127,11 +127,10 @@ func (s *DockerSuite) TestExecExitStatus(c *check.C) { } func (s *DockerSuite) TestExecPausedContainer(c *check.C) { - // Windows does not support pause - testRequires(c, DaemonIsLinux) + testRequires(c, IsPausable) defer unpauseAllContainers() - out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") + out, _ := runSleepingContainer(c, "-d", "--name", "testing") ContainerID := strings.TrimSpace(out) dockerCmd(c, "pause", "testing") diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index 63171117b4..c0caa23556 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -138,9 +138,9 @@ func (s *DockerSuite) TestInfoDisplaysRunningContainers(c *check.C) { } func (s *DockerSuite) TestInfoDisplaysPausedContainers(c *check.C) { - testRequires(c, DaemonIsLinux) + testRequires(c, IsPausable) - out, _ := dockerCmd(c, "run", "-d", "busybox", "top") + out, _ := runSleepingContainer(c, "-d") cleanedContainerID := strings.TrimSpace(out) dockerCmd(c, "pause", cleanedContainerID) diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go index e546ad45d5..9217a69968 100644 --- a/integration-cli/docker_cli_pause_test.go +++ b/integration-cli/docker_cli_pause_test.go @@ -8,11 +8,11 @@ import ( ) func (s *DockerSuite) TestPause(c *check.C) { - testRequires(c, DaemonIsLinux) + testRequires(c, IsPausable) defer unpauseAllContainers() name := "testeventpause" - dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") + runSleepingContainer(c, "-d", "--name", name) dockerCmd(c, "pause", name) pausedContainers, err := getSliceOfPausedContainers() @@ -30,7 +30,7 @@ func (s *DockerSuite) TestPause(c *check.C) { } func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) { - testRequires(c, DaemonIsLinux) + testRequires(c, IsPausable) defer unpauseAllContainers() containers := []string{ @@ -38,7 +38,7 @@ func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) { "testpausewithmorecontainers2", } for _, name := range containers { - dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") + runSleepingContainer(c, "-d", "--name", name) } dockerCmd(c, append([]string{"pause"}, containers...)...) pausedContainers, err := getSliceOfPausedContainers() @@ -58,9 +58,9 @@ func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) { } } -func (s *DockerSuite) TestPauseFailsOnWindows(c *check.C) { - testRequires(c, DaemonIsWindows) - dockerCmd(c, "run", "-d", "--name=test", "busybox", "sleep 3") +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, "Windows: Containers cannot be paused") + c.Assert(out, checker.Contains, "cannot pause Windows Server Containers") } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index f96528f7d6..b1cea35872 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -95,10 +95,10 @@ func (s *DockerSuite) TestStartRecordError(c *check.C) { func (s *DockerSuite) TestStartPausedContainer(c *check.C) { // Windows does not support pausing containers - testRequires(c, DaemonIsLinux) + testRequires(c, IsPausable) defer unpauseAllContainers() - dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") + runSleepingContainer(c, "-d", "--name", "testing") dockerCmd(c, "pause", "testing") diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index bba6f815f8..53ec417f13 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -201,6 +201,24 @@ var ( }, "Test cannot be run when remapping root", } + IsPausable = testRequirement{ + func() bool { + if daemonPlatform == "windows" { + return isolation == "hyperv" + } + return true + }, + "Test requires containers are pausable.", + } + NotPausable = testRequirement{ + func() bool { + if daemonPlatform == "windows" { + return isolation == "process" + } + return false + }, + "Test requires containers are not pausable.", + } ) // testRequires checks if the environment satisfies the requirements diff --git a/libcontainerd/client_windows.go b/libcontainerd/client_windows.go index da30ced86b..a03ffd9595 100644 --- a/libcontainerd/client_windows.go +++ b/libcontainerd/client_windows.go @@ -447,12 +447,81 @@ func (clnt *client) Resize(containerID, processFriendlyName string, width, heigh // Pause handles pause requests for containers func (clnt *client) Pause(containerID string) error { - return errors.New("Windows: Containers cannot be paused") + unlockContainer := true + // Get the libcontainerd container object + clnt.lock(containerID) + defer func() { + if unlockContainer { + clnt.unlock(containerID) + } + }() + container, err := clnt.getContainer(containerID) + if err != nil { + return err + } + + for _, option := range container.options { + if h, ok := option.(*HyperVIsolationOption); ok { + if !h.IsHyperV { + return errors.New("cannot pause Windows Server Containers") + } + break + } + } + + err = container.hcsContainer.Pause() + if err != nil { + return err + } + + // Unlock container before calling back into the daemon + unlockContainer = false + clnt.unlock(containerID) + + return clnt.backend.StateChanged(containerID, StateInfo{ + CommonStateInfo: CommonStateInfo{ + State: StatePause, + }}) } // Resume handles resume requests for containers func (clnt *client) Resume(containerID string) error { - return errors.New("Windows: Containers cannot be paused") + unlockContainer := true + // Get the libcontainerd container object + clnt.lock(containerID) + defer func() { + if unlockContainer { + clnt.unlock(containerID) + } + }() + container, err := clnt.getContainer(containerID) + if err != nil { + return err + } + + // This should never happen, since Windows Server Containers cannot be paused + for _, option := range container.options { + if h, ok := option.(*HyperVIsolationOption); ok { + if !h.IsHyperV { + return errors.New("cannot resume Windows Server Containers") + } + break + } + } + + err = container.hcsContainer.Resume() + if err != nil { + return err + } + + // Unlock container before calling back into the daemon + unlockContainer = false + clnt.unlock(containerID) + + return clnt.backend.StateChanged(containerID, StateInfo{ + CommonStateInfo: CommonStateInfo{ + State: StateResume, + }}) } // Stats handles stats requests for containers diff --git a/man/docker-pause.1.md b/man/docker-pause.1.md index 5d2267af62..ffc3b35ca0 100644 --- a/man/docker-pause.1.md +++ b/man/docker-pause.1.md @@ -10,11 +10,12 @@ CONTAINER [CONTAINER...] # DESCRIPTION -The `docker pause` command uses the cgroups freezer to suspend all processes in -a container. Traditionally when suspending a process the `SIGSTOP` signal is -used, which is observable by the process being suspended. With the cgroups freezer -the process is unaware, and unable to capture, that it is being suspended, -and subsequently resumed. +The `docker pause` command suspends all processes in a container. On Linux, +this uses the cgroups freezer. Traditionally, when suspending a process the +`SIGSTOP` signal is used, which is observable by the process being suspended. +With the cgroups freezer the process is unaware, and unable to capture, +that it is being suspended, and subsequently resumed. On Windows, only Hyper-V +containers can be paused. See the [cgroups freezer documentation] (https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt) for diff --git a/man/docker-unpause.1.md b/man/docker-unpause.1.md index 466e1bb1a3..8c64e68de6 100644 --- a/man/docker-unpause.1.md +++ b/man/docker-unpause.1.md @@ -10,8 +10,8 @@ CONTAINER [CONTAINER...] # DESCRIPTION -The `docker unpause` command uses the cgroups freezer to un-suspend all -processes in a container. +The `docker unpause` command un-suspends all processes in a container. +On Linux, it does this using the cgroups freezer. See the [cgroups freezer documentation] (https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt) for