Browse code

Add unless-stopped restart policy

Fixes #11008

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2015/08/06 06:09:08
Showing 12 changed files
... ...
@@ -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