Browse code

Implement Pause Resume support for Windows

Signed-off-by: Darren Stahl <darst@microsoft.com>

Darren Stahl authored on 2016/09/09 09:31:04
Showing 11 changed files
... ...
@@ -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