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 |