Browse code

validate healthcheck params in daemon side

Signed-off-by: allencloud <allen.sun@daocloud.io>

allencloud authored on 2017/01/17 16:55:45
Showing 6 changed files
... ...
@@ -742,10 +742,10 @@ definitions:
742 742
             items:
743 743
               type: "string"
744 744
           Interval:
745
-            description: "The time to wait between checks in nanoseconds. 0 means inherit."
745
+            description: "The time to wait between checks in nanoseconds. It should be 0 or not less than 1000000000(1s). 0 means inherit."
746 746
             type: "integer"
747 747
           Timeout:
748
-            description: "The time to wait before considering the check to have hung. 0 means inherit."
748
+            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."
749 749
             type: "integer"
750 750
           Retries:
751 751
             description: "The number of consecutive failures needed to consider a container as unhealthy. 0 means inherit."
... ...
@@ -511,6 +511,9 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*container.Config, *c
511 511
 		if copts.healthTimeout < 0 {
512 512
 			return nil, nil, nil, fmt.Errorf("--health-timeout cannot be negative")
513 513
 		}
514
+		if copts.healthRetries < 0 {
515
+			return nil, nil, nil, fmt.Errorf("--health-retries cannot be negative")
516
+		}
514 517
 
515 518
 		healthConfig = &container.HealthConfig{
516 519
 			Test:     probe,
... ...
@@ -231,6 +231,21 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
231 231
 				return nil, err
232 232
 			}
233 233
 		}
234
+
235
+		// Validate the healthcheck params of Config
236
+		if config.Healthcheck != nil {
237
+			if config.Healthcheck.Interval != 0 && config.Healthcheck.Interval < time.Second {
238
+				return nil, fmt.Errorf("Interval in Healthcheck cannot be less than one second")
239
+			}
240
+
241
+			if config.Healthcheck.Timeout != 0 && config.Healthcheck.Timeout < time.Second {
242
+				return nil, fmt.Errorf("Timeout in Healthcheck cannot be less than one second")
243
+			}
244
+
245
+			if config.Healthcheck.Retries < 0 {
246
+				return nil, fmt.Errorf("Retries in Healthcheck cannot be negative")
247
+			}
248
+		}
234 249
 	}
235 250
 
236 251
 	if hostConfig == nil {
... ...
@@ -281,6 +281,12 @@ Create a container
281 281
            "Volumes": {
282 282
              "/volumes/data": {}
283 283
            },
284
+           "Healthcheck":{
285
+              "Test": ["CMD-SHELL", "curl localhost:3000"],
286
+              "Interval": 1000000000,
287
+              "Timeout": 10000000000,
288
+              "Retries": 10
289
+           },
284 290
            "WorkingDir": "",
285 291
            "NetworkDisabled": false,
286 292
            "MacAddress": "12:34:56:78:9a:bc",
... ...
@@ -385,6 +391,15 @@ Create a container
385 385
 -   **Image** - A string specifying the image name to use for the container.
386 386
 -   **Volumes** - An object mapping mount point paths (strings) inside the
387 387
       container to empty objects.
388
+-   **Healthcheck** - A test to perform to check that the container is healthy.
389
+    -     **Test** - The test to perform. Possible values are:
390
+              + `{}` inherit healthcheck from image or parent image
391
+              + `{"NONE"}` disable healthcheck
392
+              + `{"CMD", args...}` exec arguments directly
393
+              + `{"CMD-SHELL", command}` run command with system's default shell
394
+    -     **Interval** - The time to wait between checks in nanoseconds. It should be 0 or not less than 1000000000(1s). 0 means inherit.
395
+    -     **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.
396
+    -     **Retries** - The number of consecutive failures needed to consider a container as unhealthy. 0 means inherit.
388 397
 -   **WorkingDir** - A string specifying the working directory for commands to
389 398
       run in.
390 399
 -   **NetworkDisabled** - Boolean value, when true disables networking for the
... ...
@@ -2,6 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"net/http"
5
+	"time"
5 6
 
6 7
 	"github.com/docker/docker/integration-cli/checker"
7 8
 	"github.com/docker/docker/integration-cli/request"
... ...
@@ -83,3 +84,70 @@ func (s *DockerSuite) TestAPICreateEmptyEnv(c *check.C) {
83 83
 	expected = "invalid environment variable: =foo"
84 84
 	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
85 85
 }
86
+
87
+func (s *DockerSuite) TestAPICreateWithInvalidHealthcheckParams(c *check.C) {
88
+	// test invalid Interval in Healthcheck: less than 0s
89
+	name := "test1"
90
+	config := map[string]interface{}{
91
+		"Image": "busybox",
92
+		"Healthcheck": map[string]interface{}{
93
+			"Interval": time.Duration(-10000000),
94
+			"Timeout":  time.Duration(1000000000),
95
+			"Retries":  int(1000),
96
+		},
97
+	}
98
+
99
+	status, body, err := request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
100
+	c.Assert(err, check.IsNil)
101
+	c.Assert(status, check.Equals, http.StatusInternalServerError)
102
+	expected := "Interval in Healthcheck cannot be less than one second"
103
+	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
104
+
105
+	// test invalid Interval in Healthcheck: larger than 0s but less than 1s
106
+	name = "test2"
107
+	config = map[string]interface{}{
108
+		"Image": "busybox",
109
+		"Healthcheck": map[string]interface{}{
110
+			"Interval": time.Duration(500000000),
111
+			"Timeout":  time.Duration(1000000000),
112
+			"Retries":  int(1000),
113
+		},
114
+	}
115
+	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
116
+	c.Assert(err, check.IsNil)
117
+	c.Assert(status, check.Equals, http.StatusInternalServerError)
118
+	expected = "Interval in Healthcheck cannot be less than one second"
119
+	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
120
+
121
+	// test invalid Timeout in Healthcheck: less than 1s
122
+	name = "test3"
123
+	config = map[string]interface{}{
124
+		"Image": "busybox",
125
+		"Healthcheck": map[string]interface{}{
126
+			"Interval": time.Duration(1000000000),
127
+			"Timeout":  time.Duration(-100000000),
128
+			"Retries":  int(1000),
129
+		},
130
+	}
131
+	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
132
+	c.Assert(err, check.IsNil)
133
+	c.Assert(status, check.Equals, http.StatusInternalServerError)
134
+	expected = "Timeout in Healthcheck cannot be less than one second"
135
+	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
136
+
137
+	// test invalid Retries in Healthcheck: less than 0
138
+	name = "test4"
139
+	config = map[string]interface{}{
140
+		"Image": "busybox",
141
+		"Healthcheck": map[string]interface{}{
142
+			"Interval": time.Duration(1000000000),
143
+			"Timeout":  time.Duration(1000000000),
144
+			"Retries":  int(-10),
145
+		},
146
+	}
147
+	status, body, err = request.SockRequest("POST", "/containers/create?name="+name, config, daemonHost())
148
+	c.Assert(err, check.IsNil)
149
+	c.Assert(status, check.Equals, http.StatusInternalServerError)
150
+	expected = "Retries in Healthcheck cannot be negative"
151
+	c.Assert(getErrorMessage(c, body), checker.Contains, expected)
152
+}
... ...
@@ -95,7 +95,7 @@ func (s *DockerSuite) TestHealth(c *check.C) {
95 95
 
96 96
 	// Enable the checks from the CLI
97 97
 	_, _ = dockerCmd(c, "run", "-d", "--name=fatal_healthcheck",
98
-		"--health-interval=0.5s",
98
+		"--health-interval=1s",
99 99
 		"--health-retries=3",
100 100
 		"--health-cmd=cat /status",
101 101
 		"no_healthcheck")
... ...
@@ -121,13 +121,13 @@ func (s *DockerSuite) TestHealth(c *check.C) {
121 121
 	// Note: if the interval is too small, it seems that Docker spends all its time running health
122 122
 	// checks and never gets around to killing it.
123 123
 	_, _ = dockerCmd(c, "run", "-d", "--name=test",
124
-		"--health-interval=1s", "--health-cmd=sleep 5m", "--health-timeout=1ms", imageName)
124
+		"--health-interval=1s", "--health-cmd=sleep 5m", "--health-timeout=1s", imageName)
125 125
 	waitForHealthStatus(c, "test", "starting", "unhealthy")
126 126
 	health = getHealth(c, "test")
127 127
 	last = health.Log[len(health.Log)-1]
128 128
 	c.Check(health.Status, checker.Equals, "unhealthy")
129 129
 	c.Check(last.ExitCode, checker.Equals, -1)
130
-	c.Check(last.Output, checker.Equals, "Health check exceeded timeout (1ms)")
130
+	c.Check(last.Output, checker.Equals, "Health check exceeded timeout (1s)")
131 131
 	dockerCmd(c, "rm", "-f", "test")
132 132
 
133 133
 	// Check JSON-format