Fixes #11008
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
... | ... |
@@ -79,6 +79,7 @@ type CommonContainer struct { |
79 | 79 |
MountLabel, ProcessLabel string |
80 | 80 |
RestartCount int |
81 | 81 |
HasBeenStartedBefore bool |
82 |
+ HasBeenManuallyStopped bool // used for unless-stopped restart policy |
|
82 | 83 |
hostConfig *runconfig.HostConfig |
83 | 84 |
command *execdriver.Command |
84 | 85 |
monitor *containerMonitor |
... | ... |
@@ -1063,6 +1064,7 @@ func copyEscapable(dst io.Writer, src io.ReadCloser) (written int64, err error) |
1063 | 1063 |
|
1064 | 1064 |
func (container *Container) shouldRestart() bool { |
1065 | 1065 |
return container.hostConfig.RestartPolicy.Name == "always" || |
1066 |
+ (container.hostConfig.RestartPolicy.Name == "unless-stopped" && !container.HasBeenManuallyStopped) || |
|
1066 | 1067 |
(container.hostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0) |
1067 | 1068 |
} |
1068 | 1069 |
|
... | ... |
@@ -103,6 +103,7 @@ type Daemon struct { |
103 | 103 |
EventsService *events.Events |
104 | 104 |
netController libnetwork.NetworkController |
105 | 105 |
root string |
106 |
+ shutdown bool |
|
106 | 107 |
} |
107 | 108 |
|
108 | 109 |
// Get looks for a container using the provided information, which could be |
... | ... |
@@ -744,6 +745,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo |
744 | 744 |
} |
745 | 745 |
|
746 | 746 |
func (daemon *Daemon) Shutdown() error { |
747 |
+ daemon.shutdown = true |
|
747 | 748 |
if daemon.containers != nil { |
748 | 749 |
group := sync.WaitGroup{} |
749 | 750 |
logrus.Debug("starting clean shutdown of all containers...") |
... | ... |
@@ -119,6 +119,10 @@ func (m *containerMonitor) Start() error { |
119 | 119 |
} |
120 | 120 |
m.Close() |
121 | 121 |
}() |
122 |
+ // reset stopped flag |
|
123 |
+ if m.container.HasBeenManuallyStopped { |
|
124 |
+ m.container.HasBeenManuallyStopped = false |
|
125 |
+ } |
|
122 | 126 |
|
123 | 127 |
// reset the restart count |
124 | 128 |
m.container.RestartCount = -1 |
... | ... |
@@ -223,11 +227,12 @@ func (m *containerMonitor) shouldRestart(exitCode int) bool { |
223 | 223 |
|
224 | 224 |
// do not restart if the user or docker has requested that this container be stopped |
225 | 225 |
if m.shouldStop { |
226 |
+ m.container.HasBeenManuallyStopped = !m.container.daemon.shutdown |
|
226 | 227 |
return false |
227 | 228 |
} |
228 | 229 |
|
229 | 230 |
switch { |
230 |
- case m.restartPolicy.IsAlways(): |
|
231 |
+ case m.restartPolicy.IsAlways(), m.restartPolicy.IsUnlessStopped(): |
|
231 | 232 |
return true |
232 | 233 |
case m.restartPolicy.IsOnFailure(): |
233 | 234 |
// the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count |
... | ... |
@@ -276,7 +276,8 @@ Json Parameters: |
276 | 276 |
- **Capdrop** - A list of kernel capabilities to drop from the container. |
277 | 277 |
- **RestartPolicy** – The behavior to apply when the container exits. The |
278 | 278 |
value is an object with a `Name` property of either `"always"` to |
279 |
- always restart or `"on-failure"` to restart only when the container |
|
279 |
+ always restart, `"unless-stopped"` to restart always except when |
|
280 |
+ user has manually stopped the container or `"on-failure"` to restart only when the container |
|
280 | 281 |
exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` |
281 | 282 |
controls the number of times to retry before giving up. |
282 | 283 |
The default is not to restart. (optional) |
... | ... |
@@ -58,7 +58,7 @@ Creates a new container. |
58 | 58 |
--pid="" PID namespace to use |
59 | 59 |
--privileged=false Give extended privileges to this container |
60 | 60 |
--read-only=false Mount the container's root filesystem as read only |
61 |
- --restart="no" Restart policy (no, on-failure[:max-retry], always) |
|
61 |
+ --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) |
|
62 | 62 |
--security-opt=[] Security options |
63 | 63 |
-t, --tty=false Allocate a pseudo-TTY |
64 | 64 |
--disable-content-trust=true Skip image verification |
... | ... |
@@ -58,7 +58,7 @@ weight=1 |
58 | 58 |
--pid="" PID namespace to use |
59 | 59 |
--privileged=false Give extended privileges to this container |
60 | 60 |
--read-only=false Mount the container's root filesystem as read only |
61 |
- --restart="no" Restart policy (no, on-failure[:max-retry], always) |
|
61 |
+ --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) |
|
62 | 62 |
--rm=false Automatically remove the container when it exits |
63 | 63 |
--security-opt=[] Security Options |
64 | 64 |
--sig-proxy=true Proxy received signals to the process |
... | ... |
@@ -440,7 +440,16 @@ Docker supports the following restart policies: |
440 | 440 |
<td> |
441 | 441 |
Always restart the container regardless of the exit status. |
442 | 442 |
When you specify always, the Docker daemon will try to restart |
443 |
- the container indefinitely. |
|
443 |
+ the container indefinitely. The container will also always start |
|
444 |
+ on daemon startup, regardless of the current state of the container. |
|
445 |
+ </td> |
|
446 |
+ </tr> |
|
447 |
+ <tr> |
|
448 |
+ <td><strong>unless-stopped</strong></td> |
|
449 |
+ <td> |
|
450 |
+ Always restart the container regardless of the exit status, but |
|
451 |
+ do not start it on daemon startup if the container has been put |
|
452 |
+ to a stopped state before. |
|
444 | 453 |
</td> |
445 | 454 |
</tr> |
446 | 455 |
</tbody> |
... | ... |
@@ -398,7 +398,16 @@ Docker supports the following restart policies: |
398 | 398 |
<td> |
399 | 399 |
Always restart the container regardless of the exit status. |
400 | 400 |
When you specify always, the Docker daemon will try to restart |
401 |
- the container indefinitely. |
|
401 |
+ the container indefinitely. The container will also always start |
|
402 |
+ on daemon startup, regardless of the current state of the container. |
|
403 |
+ </td> |
|
404 |
+ </tr> |
|
405 |
+ <tr> |
|
406 |
+ <td><strong>unless-stopped</strong></td> |
|
407 |
+ <td> |
|
408 |
+ Always restart the container regardless of the exit status, but |
|
409 |
+ do not start it on daemon startup if the container has been put |
|
410 |
+ to a stopped state before. |
|
402 | 411 |
</td> |
403 | 412 |
</tr> |
404 | 413 |
</tbody> |
... | ... |
@@ -87,6 +87,60 @@ func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { |
87 | 87 |
} |
88 | 88 |
} |
89 | 89 |
|
90 |
+// #11008 |
|
91 |
+func (s *DockerDaemonSuite) TestDaemonRestartUnlessStopped(c *check.C) { |
|
92 |
+ err := s.d.StartWithBusybox() |
|
93 |
+ c.Assert(err, check.IsNil) |
|
94 |
+ |
|
95 |
+ out, err := s.d.Cmd("run", "-d", "--name", "top1", "--restart", "always", "busybox:latest", "top") |
|
96 |
+ c.Assert(err, check.IsNil, check.Commentf("run top1: %v", out)) |
|
97 |
+ |
|
98 |
+ out, err = s.d.Cmd("run", "-d", "--name", "top2", "--restart", "unless-stopped", "busybox:latest", "top") |
|
99 |
+ c.Assert(err, check.IsNil, check.Commentf("run top2: %v", out)) |
|
100 |
+ |
|
101 |
+ testRun := func(m map[string]bool, prefix string) { |
|
102 |
+ var format string |
|
103 |
+ for name, shouldRun := range m { |
|
104 |
+ out, err := s.d.Cmd("ps") |
|
105 |
+ c.Assert(err, check.IsNil, check.Commentf("run ps: %v", out)) |
|
106 |
+ if shouldRun { |
|
107 |
+ format = "%scontainer %q is not running" |
|
108 |
+ } else { |
|
109 |
+ format = "%scontainer %q is running" |
|
110 |
+ } |
|
111 |
+ c.Assert(strings.Contains(out, name), check.Equals, shouldRun, check.Commentf(format, prefix, name)) |
|
112 |
+ } |
|
113 |
+ } |
|
114 |
+ |
|
115 |
+ // both running |
|
116 |
+ testRun(map[string]bool{"top1": true, "top2": true}, "") |
|
117 |
+ |
|
118 |
+ out, err = s.d.Cmd("stop", "top1") |
|
119 |
+ c.Assert(err, check.IsNil, check.Commentf(out)) |
|
120 |
+ |
|
121 |
+ out, err = s.d.Cmd("stop", "top2") |
|
122 |
+ c.Assert(err, check.IsNil, check.Commentf(out)) |
|
123 |
+ |
|
124 |
+ // both stopped |
|
125 |
+ testRun(map[string]bool{"top1": false, "top2": false}, "") |
|
126 |
+ |
|
127 |
+ err = s.d.Restart() |
|
128 |
+ c.Assert(err, check.IsNil) |
|
129 |
+ |
|
130 |
+ // restart=always running |
|
131 |
+ testRun(map[string]bool{"top1": true, "top2": false}, "After daemon restart: ") |
|
132 |
+ |
|
133 |
+ out, err = s.d.Cmd("start", "top2") |
|
134 |
+ c.Assert(err, check.IsNil, check.Commentf("start top2: %v", out)) |
|
135 |
+ |
|
136 |
+ err = s.d.Restart() |
|
137 |
+ c.Assert(err, check.IsNil) |
|
138 |
+ |
|
139 |
+ // both running |
|
140 |
+ testRun(map[string]bool{"top1": true, "top2": true}, "After second daemon restart: ") |
|
141 |
+ |
|
142 |
+} |
|
143 |
+ |
|
90 | 144 |
func (s *DockerDaemonSuite) TestDaemonStartIptablesFalse(c *check.C) { |
91 | 145 |
if err := s.d.Start("--iptables=false"); err != nil { |
92 | 146 |
c.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err) |
... | ... |
@@ -226,7 +226,7 @@ This value should always larger than **-m**, so you should always use this with |
226 | 226 |
Mount the container's root filesystem as read only. |
227 | 227 |
|
228 | 228 |
**--restart**="no" |
229 |
- Restart policy to apply when a container exits (no, on-failure[:max-retry], always) |
|
229 |
+ Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped). |
|
230 | 230 |
|
231 | 231 |
**--security-opt**=[] |
232 | 232 |
Security Options |
... | ... |
@@ -360,7 +360,7 @@ to write files anywhere. By specifying the `--read-only` flag the container wil |
360 | 360 |
its root filesystem mounted as read only prohibiting any writes. |
361 | 361 |
|
362 | 362 |
**--restart**="no" |
363 |
- Restart policy to apply when a container exits (no, on-failure[:max-retry], always) |
|
363 |
+ Restart policy to apply when a container exits (no, on-failure[:max-retry], always, unless-stopped). |
|
364 | 364 |
|
365 | 365 |
**--rm**=*true*|*false* |
366 | 366 |
Automatically remove the container when it exits (incompatible with -d). The default is *false*. |
... | ... |
@@ -140,6 +140,13 @@ func (rp *RestartPolicy) IsOnFailure() bool { |
140 | 140 |
return rp.Name == "on-failure" |
141 | 141 |
} |
142 | 142 |
|
143 |
+// IsUnlessStopped indicates whether the container has the |
|
144 |
+// "unless-stopped" restart policy. This means the container will |
|
145 |
+// automatically restart unless user has put it to stopped state. |
|
146 |
+func (rp *RestartPolicy) IsUnlessStopped() bool { |
|
147 |
+ return rp.Name == "unless-stopped" |
|
148 |
+} |
|
149 |
+ |
|
143 | 150 |
// LogConfig represents the logging configuration of the container. |
144 | 151 |
type LogConfig struct { |
145 | 152 |
Type string |
... | ... |
@@ -450,9 +450,9 @@ func ParseRestartPolicy(policy string) (RestartPolicy, error) { |
450 | 450 |
|
451 | 451 |
p.Name = name |
452 | 452 |
switch name { |
453 |
- case "always": |
|
453 |
+ case "always", "unless-stopped": |
|
454 | 454 |
if len(parts) > 1 { |
455 |
- return p, fmt.Errorf("maximum restart count not valid with restart policy of \"always\"") |
|
455 |
+ return p, fmt.Errorf("maximum restart count not valid with restart policy of \"%s\"", name) |
|
456 | 456 |
} |
457 | 457 |
case "no": |
458 | 458 |
// do nothing |