Browse code

Merge pull request #32504 from dongluochen/healthcheck_duration

do not allow duration less than 1 ms in healthcheck parameters

Brian Goff authored on 2017/04/28 12:54:00
Showing 11 changed files
... ...
@@ -499,16 +499,16 @@ definitions:
499 499
         items:
500 500
           type: "string"
501 501
       Interval:
502
-        description: "The time to wait between checks in nanoseconds. It should be 0 or not less than 1000000000(1s). 0 means inherit."
502
+        description: "The time to wait between checks in nanoseconds. It should be 0 or at least 1000000 (1 ms). 0 means inherit."
503 503
         type: "integer"
504 504
       Timeout:
505
-        description: "The time to wait before considering the check to have hung. It should be 0 or not less than 1000000000(1s). 0 means inherit."
505
+        description: "The time to wait before considering the check to have hung. It should be 0 or at least 1000000 (1 ms). 0 means inherit."
506 506
         type: "integer"
507 507
       Retries:
508 508
         description: "The number of consecutive failures needed to consider a container as unhealthy. 0 means inherit."
509 509
         type: "integer"
510 510
       StartPeriod:
511
-        description: "Start period for the container to initialize before starting health-retries countdown in nanoseconds. 0 means inherit."
511
+        description: "Start period for the container to initialize before starting health-retries countdown in nanoseconds. It should be 0 or at least 1000000 (1 ms). 0 means inherit."
512 512
         type: "integer"
513 513
 
514 514
   HostConfig:
... ...
@@ -7,6 +7,12 @@ import (
7 7
 	"github.com/docker/go-connections/nat"
8 8
 )
9 9
 
10
+// MinimumDuration puts a minimum on user configured duration.
11
+// This is to prevent API error on time unit. For example, API may
12
+// set 3 as healthcheck interval with intention of 3 seconds, but
13
+// Docker interprets it as 3 nanoseconds.
14
+const MinimumDuration = 1 * time.Millisecond
15
+
10 16
 // HealthConfig holds configuration settings for the HEALTHCHECK feature.
11 17
 type HealthConfig struct {
12 18
 	// Test is the test to perform to check that the container is healthy.
... ...
@@ -486,7 +486,7 @@ func cmd(req dispatchRequest) error {
486 486
 }
487 487
 
488 488
 // parseOptInterval(flag) is the duration of flag.Value, or 0 if
489
-// empty. An error is reported if the value is given and less than 1 second.
489
+// empty. An error is reported if the value is given and less than minimum duration.
490 490
 func parseOptInterval(f *Flag) (time.Duration, error) {
491 491
 	s := f.Value
492 492
 	if s == "" {
... ...
@@ -496,8 +496,8 @@ func parseOptInterval(f *Flag) (time.Duration, error) {
496 496
 	if err != nil {
497 497
 		return 0, err
498 498
 	}
499
-	if d < time.Duration(time.Second) {
500
-		return 0, fmt.Errorf("Interval %#v cannot be less than 1 second", f.name)
499
+	if d < time.Duration(container.MinimumDuration) {
500
+		return 0, fmt.Errorf("Interval %#v cannot be less than %s", f.name, container.MinimumDuration)
501 501
 	}
502 502
 	return d, nil
503 503
 }
... ...
@@ -401,3 +401,21 @@ func TestShell(t *testing.T) {
401 401
 	expectedShell := strslice.StrSlice([]string{shellCmd})
402 402
 	assert.Equal(t, expectedShell, req.runConfig.Shell)
403 403
 }
404
+
405
+func TestParseOptInterval(t *testing.T) {
406
+	flInterval := &Flag{
407
+		name:     "interval",
408
+		flagType: stringType,
409
+		Value:    "50ns",
410
+	}
411
+	_, err := parseOptInterval(flInterval)
412
+	if err == nil {
413
+		t.Fatalf("Error should be presented for interval %s", flInterval.Value)
414
+	}
415
+
416
+	flInterval.Value = "1ms"
417
+	_, err = parseOptInterval(flInterval)
418
+	if err != nil {
419
+		t.Fatalf("Unexpected error: %s", err.Error())
420
+	}
421
+}
... ...
@@ -229,10 +229,10 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
229 229
 
230 230
 	// Health-checking
231 231
 	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
232
-	flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)")
232
+	flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ms|s|m|h) (default 0s)")
233 233
 	flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
234
-	flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)")
235
-	flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ns|us|ms|s|m|h) (default 0s)")
234
+	flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)")
235
+	flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)")
236 236
 	flags.SetAnnotation("health-start-period", "version", []string{"1.29"})
237 237
 	flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
238 238
 
... ...
@@ -802,13 +802,13 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValu
802 802
 
803 803
 	flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health")
804 804
 	flags.SetAnnotation(flagHealthCmd, "version", []string{"1.25"})
805
-	flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ns|us|ms|s|m|h)")
805
+	flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ms|s|m|h)")
806 806
 	flags.SetAnnotation(flagHealthInterval, "version", []string{"1.25"})
807
-	flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ns|us|ms|s|m|h)")
807
+	flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ms|s|m|h)")
808 808
 	flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"})
809 809
 	flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy")
810 810
 	flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"})
811
-	flags.Var(&opts.healthcheck.startPeriod, flagHealthStartPeriod, "Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h)")
811
+	flags.Var(&opts.healthcheck.startPeriod, flagHealthStartPeriod, "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)")
812 812
 	flags.SetAnnotation(flagHealthStartPeriod, "version", []string{"1.29"})
813 813
 	flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
814 814
 	flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"})
... ...
@@ -244,20 +244,20 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
244 244
 
245 245
 		// Validate the healthcheck params of Config
246 246
 		if config.Healthcheck != nil {
247
-			if config.Healthcheck.Interval != 0 && config.Healthcheck.Interval < time.Second {
248
-				return nil, fmt.Errorf("Interval in Healthcheck cannot be less than one second")
247
+			if config.Healthcheck.Interval != 0 && config.Healthcheck.Interval < containertypes.MinimumDuration {
248
+				return nil, fmt.Errorf("Interval in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
249 249
 			}
250 250
 
251
-			if config.Healthcheck.Timeout != 0 && config.Healthcheck.Timeout < time.Second {
252
-				return nil, fmt.Errorf("Timeout in Healthcheck cannot be less than one second")
251
+			if config.Healthcheck.Timeout != 0 && config.Healthcheck.Timeout < containertypes.MinimumDuration {
252
+				return nil, fmt.Errorf("Timeout in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
253 253
 			}
254 254
 
255 255
 			if config.Healthcheck.Retries < 0 {
256 256
 				return nil, fmt.Errorf("Retries in Healthcheck cannot be negative")
257 257
 			}
258 258
 
259
-			if config.Healthcheck.StartPeriod < 0 {
260
-				return nil, fmt.Errorf("StartPeriod in Healthcheck cannot be negative")
259
+			if config.Healthcheck.StartPeriod != 0 && config.Healthcheck.StartPeriod < containertypes.MinimumDuration {
260
+				return nil, fmt.Errorf("StartPeriod in Healthcheck cannot be less than %s", containertypes.MinimumDuration)
261 261
 			}
262 262
 		}
263 263
 	}
... ...
@@ -285,7 +285,8 @@ Create a container
285 285
               "Test": ["CMD-SHELL", "curl localhost:3000"],
286 286
               "Interval": 1000000000,
287 287
               "Timeout": 10000000000,
288
-              "Retries": 10
288
+              "Retries": 10,
289
+              "StartPeriod": 60000000000
289 290
            },
290 291
            "WorkingDir": "",
291 292
            "NetworkDisabled": false,
... ...
@@ -397,9 +398,10 @@ Create a container
397 397
               + `{"NONE"}` disable healthcheck
398 398
               + `{"CMD", args...}` exec arguments directly
399 399
               + `{"CMD-SHELL", command}` run command with system's default shell
400
-    -     **Interval** - The time to wait between checks in nanoseconds. It should be 0 or not less than 1000000000(1s). 0 means inherit.
401
-    -     **Timeout** - The time to wait before considering the check to have hung. It should be 0 or not less than 1000000000(1s). 0 means inherit.
400
+    -     **Interval** - The time to wait between checks in nanoseconds. It should be 0 or at least 1000000 (1 ms). 0 means inherit.
401
+    -     **Timeout** - The time to wait before considering the check to have hung. It should be 0 or at least 1000000 (1 ms). 0 means inherit.
402 402
     -     **Retries** - The number of consecutive failures needed to consider a container as unhealthy. 0 means inherit.
403
+    -     **StartPeriod** - The time to wait for container initialization before starting health-retries countdown in nanoseconds. It should be 0 or at least 1000000 (1 ms). 0 means inherit.
403 404
 -   **WorkingDir** - A string specifying the working directory for commands to
404 405
       run in.
405 406
 -   **NetworkDisabled** - Boolean value, when true disables networking for the
... ...
@@ -33,10 +33,10 @@ Options:
33 33
       --env-file list                      Read in a file of environment variables
34 34
       --group list                         Set one or more supplementary user groups for the container
35 35
       --health-cmd string                  Command to run to check health
36
-      --health-interval duration           Time between running the check (ns|us|ms|s|m|h)
36
+      --health-interval duration           Time between running the check (ms|s|m|h)
37 37
       --health-retries int                 Consecutive failures needed to report unhealthy
38
-      --health-start-period duration       Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h)
39
-      --health-timeout duration            Maximum time to allow one check to run (ns|us|ms|s|m|h)
38
+      --health-start-period duration       Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)
39
+      --health-timeout duration            Maximum time to allow one check to run (ms|s|m|h)
40 40
       --help                               Print usage
41 41
       --host list                          Set one or more custom host-to-IP mappings (host:ip)
42 42
       --hostname string                    Container hostname
... ...
@@ -41,10 +41,10 @@ Options:
41 41
       --group-add list                     Add an additional supplementary user group to the container
42 42
       --group-rm list                      Remove a previously added supplementary user group from the container
43 43
       --health-cmd string                  Command to run to check health
44
-      --health-interval duration           Time between running the check (ns|us|ms|s|m|h)
44
+      --health-interval duration           Time between running the check (ms|s|m|h)
45 45
       --health-retries int                 Consecutive failures needed to report unhealthy
46
-      --health-start-period duration       Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h)
47
-      --health-timeout duration            Maximum time to allow one check to run (ns|us|ms|s|m|h)
46
+      --health-start-period duration       Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)
47
+      --health-timeout duration            Maximum time to allow one check to run (ms|s|m|h)
48 48
       --help                               Print usage
49 49
       --host-add list                      Add or update a custom host-to-IP mapping (host:ip)
50 50
       --host-rm list                       Remove a custom host-to-IP mapping (host:ip)
... ...
@@ -1,9 +1,11 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"net/http"
5 6
 	"time"
6 7
 
8
+	"github.com/docker/docker/api/types/container"
7 9
 	"github.com/docker/docker/integration-cli/checker"
8 10
 	"github.com/docker/docker/integration-cli/request"
9 11
 	"github.com/go-check/check"
... ...
@@ -91,8 +93,8 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
91 91
 	config := map[string]interface{}{
92 92
 		"Image": "busybox",
93 93
 		"Healthcheck": map[string]interface{}{
94
-			"Interval": time.Duration(-10000000),
95
-			"Timeout":  time.Duration(1000000000),
94
+			"Interval": -10 * time.Millisecond,
95
+			"Timeout":  time.Second,
96 96
 			"Retries":  int(1000),
97 97
 		},
98 98
 	}
... ...
@@ -100,39 +102,38 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
100 100
 	status, body, err := request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
101 101
 	c.Assert(err, check.IsNil)
102 102
 	c.Assert(status, check.Equals, http.StatusInternalServerError)
103
-	expected := "Interval in Healthcheck cannot be less than one second"
103
+	expected := fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration)
104 104
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
105 105
 
106
-	// test invalid Interval in Healthcheck: larger than 0s but less than 1s
106
+	// test invalid Interval in Healthcheck: larger than 0s but less than 1ms
107 107
 	name = "test2"
108 108
 	config = map[string]interface{}{
109 109
 		"Image": "busybox",
110 110
 		"Healthcheck": map[string]interface{}{
111
-			"Interval": time.Duration(500000000),
112
-			"Timeout":  time.Duration(1000000000),
111
+			"Interval": 500 * time.Microsecond,
112
+			"Timeout":  time.Second,
113 113
 			"Retries":  int(1000),
114 114
 		},
115 115
 	}
116 116
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
117 117
 	c.Assert(err, check.IsNil)
118 118
 	c.Assert(status, check.Equals, http.StatusInternalServerError)
119
-	expected = "Interval in Healthcheck cannot be less than one second"
120 119
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
121 120
 
122
-	// test invalid Timeout in Healthcheck: less than 1s
121
+	// test invalid Timeout in Healthcheck: less than 1ms
123 122
 	name = "test3"
124 123
 	config = map[string]interface{}{
125 124
 		"Image": "busybox",
126 125
 		"Healthcheck": map[string]interface{}{
127
-			"Interval": time.Duration(1000000000),
128
-			"Timeout":  time.Duration(-100000000),
126
+			"Interval": time.Second,
127
+			"Timeout":  -100 * time.Millisecond,
129 128
 			"Retries":  int(1000),
130 129
 		},
131 130
 	}
132 131
 	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
133 132
 	c.Assert(err, check.IsNil)
134 133
 	c.Assert(status, check.Equals, http.StatusInternalServerError)
135
-	expected = "Timeout in Healthcheck cannot be less than one second"
134
+	expected = fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration)
136 135
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
137 136
 
138 137
 	// test invalid Retries in Healthcheck: less than 0
... ...
@@ -140,8 +141,8 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
140 140
 	config = map[string]interface{}{
141 141
 		"Image": "busybox",
142 142
 		"Healthcheck": map[string]interface{}{
143
-			"Interval": time.Duration(1000000000),
144
-			"Timeout":  time.Duration(1000000000),
143
+			"Interval": time.Second,
144
+			"Timeout":  time.Second,
145 145
 			"Retries":  int(-10),
146 146
 		},
147 147
 	}
... ...
@@ -150,4 +151,21 @@ func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
150 150
 	c.Assert(status, check.Equals, http.StatusInternalServerError)
151 151
 	expected = "Retries in Healthcheck cannot be negative"
152 152
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
153
+
154
+	// test invalid StartPeriod in Healthcheck: not 0 and less than 1ms
155
+	name = "test3"
156
+	config = map[string]interface{}{
157
+		"Image": "busybox",
158
+		"Healthcheck": map[string]interface{}{
159
+			"Interval":    time.Second,
160
+			"Timeout":     time.Second,
161
+			"Retries":     int(1000),
162
+			"StartPeriod": 100 * time.Microsecond,
163
+		},
164
+	}
165
+	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
166
+	c.Assert(err, check.IsNil)
167
+	c.Assert(status, check.Equals, http.StatusInternalServerError)
168
+	expected = fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration)
169
+	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
153 170
 }