do not allow duration less than 1 ms in healthcheck parameters
| ... | ... |
@@ -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 |
} |