Signed-off-by: Darren Stahl <darst@microsoft.com>
| ... | ... |
@@ -19,11 +19,12 @@ Options: |
| 19 | 19 |
--help Print usage |
| 20 | 20 |
``` |
| 21 | 21 |
|
| 22 |
-The `docker pause` command uses the cgroups freezer to suspend all processes in |
|
| 23 |
-a container. Traditionally, when suspending a process the `SIGSTOP` signal is |
|
| 24 |
-used, which is observable by the process being suspended. With the cgroups freezer |
|
| 25 |
-the process is unaware, and unable to capture, that it is being suspended, |
|
| 26 |
-and subsequently resumed. |
|
| 22 |
+The `docker pause` command suspends all processes in a container. On Linux, |
|
| 23 |
+this uses the cgroups freezer. Traditionally, when suspending a process the |
|
| 24 |
+`SIGSTOP` signal is used, which is observable by the process being suspended. |
|
| 25 |
+With the cgroups freezer the process is unaware, and unable to capture, |
|
| 26 |
+that it is being suspended, and subsequently resumed. On Windows, only Hyper-V |
|
| 27 |
+containers can be paused. |
|
| 27 | 28 |
|
| 28 | 29 |
See the |
| 29 | 30 |
[cgroups freezer documentation](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) |
| ... | ... |
@@ -19,8 +19,8 @@ Options: |
| 19 | 19 |
--help Print usage |
| 20 | 20 |
``` |
| 21 | 21 |
|
| 22 |
-The `docker unpause` command uses the cgroups freezer to un-suspend all |
|
| 23 |
-processes in a container. |
|
| 22 |
+The `docker unpause` command un-suspends all processes in a container. |
|
| 23 |
+On Linux, it does this using the cgroups freezer. |
|
| 24 | 24 |
|
| 25 | 25 |
See the |
| 26 | 26 |
[cgroups freezer documentation](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) |
| ... | ... |
@@ -154,9 +154,9 @@ func (s *DockerSuite) TestAttachDisconnect(c *check.C) {
|
| 154 | 154 |
} |
| 155 | 155 |
|
| 156 | 156 |
func (s *DockerSuite) TestAttachPausedContainer(c *check.C) {
|
| 157 |
- testRequires(c, DaemonIsLinux) // Containers cannot be paused on Windows |
|
| 157 |
+ testRequires(c, IsPausable) |
|
| 158 | 158 |
defer unpauseAllContainers() |
| 159 |
- dockerCmd(c, "run", "-d", "--name=test", "busybox", "top") |
|
| 159 |
+ runSleepingContainer(c, "-d", "--name=test") |
|
| 160 | 160 |
dockerCmd(c, "pause", "test") |
| 161 | 161 |
|
| 162 | 162 |
result := dockerCmdWithResult("attach", "test")
|
| ... | ... |
@@ -127,11 +127,10 @@ func (s *DockerSuite) TestExecExitStatus(c *check.C) {
|
| 127 | 127 |
} |
| 128 | 128 |
|
| 129 | 129 |
func (s *DockerSuite) TestExecPausedContainer(c *check.C) {
|
| 130 |
- // Windows does not support pause |
|
| 131 |
- testRequires(c, DaemonIsLinux) |
|
| 130 |
+ testRequires(c, IsPausable) |
|
| 132 | 131 |
defer unpauseAllContainers() |
| 133 | 132 |
|
| 134 |
- out, _ := dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") |
|
| 133 |
+ out, _ := runSleepingContainer(c, "-d", "--name", "testing") |
|
| 135 | 134 |
ContainerID := strings.TrimSpace(out) |
| 136 | 135 |
|
| 137 | 136 |
dockerCmd(c, "pause", "testing") |
| ... | ... |
@@ -138,9 +138,9 @@ func (s *DockerSuite) TestInfoDisplaysRunningContainers(c *check.C) {
|
| 138 | 138 |
} |
| 139 | 139 |
|
| 140 | 140 |
func (s *DockerSuite) TestInfoDisplaysPausedContainers(c *check.C) {
|
| 141 |
- testRequires(c, DaemonIsLinux) |
|
| 141 |
+ testRequires(c, IsPausable) |
|
| 142 | 142 |
|
| 143 |
- out, _ := dockerCmd(c, "run", "-d", "busybox", "top") |
|
| 143 |
+ out, _ := runSleepingContainer(c, "-d") |
|
| 144 | 144 |
cleanedContainerID := strings.TrimSpace(out) |
| 145 | 145 |
|
| 146 | 146 |
dockerCmd(c, "pause", cleanedContainerID) |
| ... | ... |
@@ -8,11 +8,11 @@ import ( |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 | 10 |
func (s *DockerSuite) TestPause(c *check.C) {
|
| 11 |
- testRequires(c, DaemonIsLinux) |
|
| 11 |
+ testRequires(c, IsPausable) |
|
| 12 | 12 |
defer unpauseAllContainers() |
| 13 | 13 |
|
| 14 | 14 |
name := "testeventpause" |
| 15 |
- dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") |
|
| 15 |
+ runSleepingContainer(c, "-d", "--name", name) |
|
| 16 | 16 |
|
| 17 | 17 |
dockerCmd(c, "pause", name) |
| 18 | 18 |
pausedContainers, err := getSliceOfPausedContainers() |
| ... | ... |
@@ -30,7 +30,7 @@ func (s *DockerSuite) TestPause(c *check.C) {
|
| 30 | 30 |
} |
| 31 | 31 |
|
| 32 | 32 |
func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
|
| 33 |
- testRequires(c, DaemonIsLinux) |
|
| 33 |
+ testRequires(c, IsPausable) |
|
| 34 | 34 |
defer unpauseAllContainers() |
| 35 | 35 |
|
| 36 | 36 |
containers := []string{
|
| ... | ... |
@@ -38,7 +38,7 @@ func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
|
| 38 | 38 |
"testpausewithmorecontainers2", |
| 39 | 39 |
} |
| 40 | 40 |
for _, name := range containers {
|
| 41 |
- dockerCmd(c, "run", "-d", "--name", name, "busybox", "top") |
|
| 41 |
+ runSleepingContainer(c, "-d", "--name", name) |
|
| 42 | 42 |
} |
| 43 | 43 |
dockerCmd(c, append([]string{"pause"}, containers...)...)
|
| 44 | 44 |
pausedContainers, err := getSliceOfPausedContainers() |
| ... | ... |
@@ -58,9 +58,9 @@ func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
|
| 58 | 58 |
} |
| 59 | 59 |
} |
| 60 | 60 |
|
| 61 |
-func (s *DockerSuite) TestPauseFailsOnWindows(c *check.C) {
|
|
| 62 |
- testRequires(c, DaemonIsWindows) |
|
| 63 |
- dockerCmd(c, "run", "-d", "--name=test", "busybox", "sleep 3") |
|
| 61 |
+func (s *DockerSuite) TestPauseFailsOnWindowsServerContainers(c *check.C) {
|
|
| 62 |
+ testRequires(c, DaemonIsWindows, NotPausable) |
|
| 63 |
+ runSleepingContainer(c, "-d", "--name=test") |
|
| 64 | 64 |
out, _, _ := dockerCmdWithError("pause", "test")
|
| 65 |
- c.Assert(out, checker.Contains, "Windows: Containers cannot be paused") |
|
| 65 |
+ c.Assert(out, checker.Contains, "cannot pause Windows Server Containers") |
|
| 66 | 66 |
} |
| ... | ... |
@@ -95,10 +95,10 @@ func (s *DockerSuite) TestStartRecordError(c *check.C) {
|
| 95 | 95 |
|
| 96 | 96 |
func (s *DockerSuite) TestStartPausedContainer(c *check.C) {
|
| 97 | 97 |
// Windows does not support pausing containers |
| 98 |
- testRequires(c, DaemonIsLinux) |
|
| 98 |
+ testRequires(c, IsPausable) |
|
| 99 | 99 |
defer unpauseAllContainers() |
| 100 | 100 |
|
| 101 |
- dockerCmd(c, "run", "-d", "--name", "testing", "busybox", "top") |
|
| 101 |
+ runSleepingContainer(c, "-d", "--name", "testing") |
|
| 102 | 102 |
|
| 103 | 103 |
dockerCmd(c, "pause", "testing") |
| 104 | 104 |
|
| ... | ... |
@@ -201,6 +201,24 @@ var ( |
| 201 | 201 |
}, |
| 202 | 202 |
"Test cannot be run when remapping root", |
| 203 | 203 |
} |
| 204 |
+ IsPausable = testRequirement{
|
|
| 205 |
+ func() bool {
|
|
| 206 |
+ if daemonPlatform == "windows" {
|
|
| 207 |
+ return isolation == "hyperv" |
|
| 208 |
+ } |
|
| 209 |
+ return true |
|
| 210 |
+ }, |
|
| 211 |
+ "Test requires containers are pausable.", |
|
| 212 |
+ } |
|
| 213 |
+ NotPausable = testRequirement{
|
|
| 214 |
+ func() bool {
|
|
| 215 |
+ if daemonPlatform == "windows" {
|
|
| 216 |
+ return isolation == "process" |
|
| 217 |
+ } |
|
| 218 |
+ return false |
|
| 219 |
+ }, |
|
| 220 |
+ "Test requires containers are not pausable.", |
|
| 221 |
+ } |
|
| 204 | 222 |
) |
| 205 | 223 |
|
| 206 | 224 |
// testRequires checks if the environment satisfies the requirements |
| ... | ... |
@@ -447,12 +447,81 @@ func (clnt *client) Resize(containerID, processFriendlyName string, width, heigh |
| 447 | 447 |
|
| 448 | 448 |
// Pause handles pause requests for containers |
| 449 | 449 |
func (clnt *client) Pause(containerID string) error {
|
| 450 |
- return errors.New("Windows: Containers cannot be paused")
|
|
| 450 |
+ unlockContainer := true |
|
| 451 |
+ // Get the libcontainerd container object |
|
| 452 |
+ clnt.lock(containerID) |
|
| 453 |
+ defer func() {
|
|
| 454 |
+ if unlockContainer {
|
|
| 455 |
+ clnt.unlock(containerID) |
|
| 456 |
+ } |
|
| 457 |
+ }() |
|
| 458 |
+ container, err := clnt.getContainer(containerID) |
|
| 459 |
+ if err != nil {
|
|
| 460 |
+ return err |
|
| 461 |
+ } |
|
| 462 |
+ |
|
| 463 |
+ for _, option := range container.options {
|
|
| 464 |
+ if h, ok := option.(*HyperVIsolationOption); ok {
|
|
| 465 |
+ if !h.IsHyperV {
|
|
| 466 |
+ return errors.New("cannot pause Windows Server Containers")
|
|
| 467 |
+ } |
|
| 468 |
+ break |
|
| 469 |
+ } |
|
| 470 |
+ } |
|
| 471 |
+ |
|
| 472 |
+ err = container.hcsContainer.Pause() |
|
| 473 |
+ if err != nil {
|
|
| 474 |
+ return err |
|
| 475 |
+ } |
|
| 476 |
+ |
|
| 477 |
+ // Unlock container before calling back into the daemon |
|
| 478 |
+ unlockContainer = false |
|
| 479 |
+ clnt.unlock(containerID) |
|
| 480 |
+ |
|
| 481 |
+ return clnt.backend.StateChanged(containerID, StateInfo{
|
|
| 482 |
+ CommonStateInfo: CommonStateInfo{
|
|
| 483 |
+ State: StatePause, |
|
| 484 |
+ }}) |
|
| 451 | 485 |
} |
| 452 | 486 |
|
| 453 | 487 |
// Resume handles resume requests for containers |
| 454 | 488 |
func (clnt *client) Resume(containerID string) error {
|
| 455 |
- return errors.New("Windows: Containers cannot be paused")
|
|
| 489 |
+ unlockContainer := true |
|
| 490 |
+ // Get the libcontainerd container object |
|
| 491 |
+ clnt.lock(containerID) |
|
| 492 |
+ defer func() {
|
|
| 493 |
+ if unlockContainer {
|
|
| 494 |
+ clnt.unlock(containerID) |
|
| 495 |
+ } |
|
| 496 |
+ }() |
|
| 497 |
+ container, err := clnt.getContainer(containerID) |
|
| 498 |
+ if err != nil {
|
|
| 499 |
+ return err |
|
| 500 |
+ } |
|
| 501 |
+ |
|
| 502 |
+ // This should never happen, since Windows Server Containers cannot be paused |
|
| 503 |
+ for _, option := range container.options {
|
|
| 504 |
+ if h, ok := option.(*HyperVIsolationOption); ok {
|
|
| 505 |
+ if !h.IsHyperV {
|
|
| 506 |
+ return errors.New("cannot resume Windows Server Containers")
|
|
| 507 |
+ } |
|
| 508 |
+ break |
|
| 509 |
+ } |
|
| 510 |
+ } |
|
| 511 |
+ |
|
| 512 |
+ err = container.hcsContainer.Resume() |
|
| 513 |
+ if err != nil {
|
|
| 514 |
+ return err |
|
| 515 |
+ } |
|
| 516 |
+ |
|
| 517 |
+ // Unlock container before calling back into the daemon |
|
| 518 |
+ unlockContainer = false |
|
| 519 |
+ clnt.unlock(containerID) |
|
| 520 |
+ |
|
| 521 |
+ return clnt.backend.StateChanged(containerID, StateInfo{
|
|
| 522 |
+ CommonStateInfo: CommonStateInfo{
|
|
| 523 |
+ State: StateResume, |
|
| 524 |
+ }}) |
|
| 456 | 525 |
} |
| 457 | 526 |
|
| 458 | 527 |
// Stats handles stats requests for containers |
| ... | ... |
@@ -10,11 +10,12 @@ CONTAINER [CONTAINER...] |
| 10 | 10 |
|
| 11 | 11 |
# DESCRIPTION |
| 12 | 12 |
|
| 13 |
-The `docker pause` command uses the cgroups freezer to suspend all processes in |
|
| 14 |
-a container. Traditionally when suspending a process the `SIGSTOP` signal is |
|
| 15 |
-used, which is observable by the process being suspended. With the cgroups freezer |
|
| 16 |
-the process is unaware, and unable to capture, that it is being suspended, |
|
| 17 |
-and subsequently resumed. |
|
| 13 |
+The `docker pause` command suspends all processes in a container. On Linux, |
|
| 14 |
+this uses the cgroups freezer. Traditionally, when suspending a process the |
|
| 15 |
+`SIGSTOP` signal is used, which is observable by the process being suspended. |
|
| 16 |
+With the cgroups freezer the process is unaware, and unable to capture, |
|
| 17 |
+that it is being suspended, and subsequently resumed. On Windows, only Hyper-V |
|
| 18 |
+containers can be paused. |
|
| 18 | 19 |
|
| 19 | 20 |
See the [cgroups freezer documentation] |
| 20 | 21 |
(https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt) for |
| ... | ... |
@@ -10,8 +10,8 @@ CONTAINER [CONTAINER...] |
| 10 | 10 |
|
| 11 | 11 |
# DESCRIPTION |
| 12 | 12 |
|
| 13 |
-The `docker unpause` command uses the cgroups freezer to un-suspend all |
|
| 14 |
-processes in a container. |
|
| 13 |
+The `docker unpause` command un-suspends all processes in a container. |
|
| 14 |
+On Linux, it does this using the cgroups freezer. |
|
| 15 | 15 |
|
| 16 | 16 |
See the [cgroups freezer documentation] |
| 17 | 17 |
(https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt) for |