do not allow duration less than 1 ms in healthcheck parameters
Brian Goff authored on 2017/04/28 12:54:00... | ... |
@@ -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 |
} |