github.com/jwhonce/docker@v0.6.7-0.20190327063223-da823cf3a5a3/integration-cli/docker_cli_service_health_test.go (about) 1 // +build !windows 2 3 package main 4 5 import ( 6 "strconv" 7 "strings" 8 9 "github.com/docker/docker/api/types/swarm" 10 "github.com/docker/docker/daemon/cluster/executor/container" 11 "github.com/docker/docker/integration-cli/checker" 12 "github.com/docker/docker/integration-cli/cli" 13 "github.com/docker/docker/integration-cli/cli/build" 14 "github.com/go-check/check" 15 "gotest.tools/icmd" 16 ) 17 18 // start a service, and then make its task unhealthy during running 19 // finally, unhealthy task should be detected and killed 20 func (s *DockerSwarmSuite) TestServiceHealthRun(c *check.C) { 21 testRequires(c, DaemonIsLinux) // busybox doesn't work on Windows 22 23 d := s.AddDaemon(c, true, true) 24 25 // build image with health-check 26 imageName := "testhealth" 27 result := cli.BuildCmd(c, imageName, cli.Daemon(d), 28 build.WithDockerfile(`FROM busybox 29 RUN touch /status 30 HEALTHCHECK --interval=1s --timeout=1s --retries=1\ 31 CMD cat /status`), 32 ) 33 result.Assert(c, icmd.Success) 34 35 serviceName := "healthServiceRun" 36 out, err := d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--name", serviceName, imageName, "top") 37 c.Assert(err, checker.IsNil, check.Commentf("%s", out)) 38 id := strings.TrimSpace(out) 39 40 var tasks []swarm.Task 41 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 42 tasks = d.GetServiceTasks(c, id) 43 return tasks, nil 44 }, checker.HasLen, 1) 45 46 task := tasks[0] 47 48 // wait for task to start 49 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 50 task = d.GetTask(c, task.ID) 51 return task.Status.State, nil 52 }, checker.Equals, swarm.TaskStateRunning) 53 containerID := task.Status.ContainerStatus.ContainerID 54 55 // wait for container to be healthy 56 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 57 out, _ := d.Cmd("inspect", "--format={{.State.Health.Status}}", containerID) 58 return strings.TrimSpace(out), nil 59 }, checker.Equals, "healthy") 60 61 // make it fail 62 d.Cmd("exec", containerID, "rm", "/status") 63 // wait for container to be unhealthy 64 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 65 out, _ := d.Cmd("inspect", "--format={{.State.Health.Status}}", containerID) 66 return strings.TrimSpace(out), nil 67 }, checker.Equals, "unhealthy") 68 69 // Task should be terminated 70 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 71 task = d.GetTask(c, task.ID) 72 return task.Status.State, nil 73 }, checker.Equals, swarm.TaskStateFailed) 74 75 if !strings.Contains(task.Status.Err, container.ErrContainerUnhealthy.Error()) { 76 c.Fatal("unhealthy task exits because of other error") 77 } 78 } 79 80 // start a service whose task is unhealthy at beginning 81 // its tasks should be blocked in starting stage, until health check is passed 82 func (s *DockerSwarmSuite) TestServiceHealthStart(c *check.C) { 83 testRequires(c, DaemonIsLinux) // busybox doesn't work on Windows 84 85 d := s.AddDaemon(c, true, true) 86 87 // service started from this image won't pass health check 88 imageName := "testhealth" 89 result := cli.BuildCmd(c, imageName, cli.Daemon(d), 90 build.WithDockerfile(`FROM busybox 91 HEALTHCHECK --interval=1s --timeout=1s --retries=1024\ 92 CMD cat /status`), 93 ) 94 result.Assert(c, icmd.Success) 95 96 serviceName := "healthServiceStart" 97 out, err := d.Cmd("service", "create", "--no-resolve-image", "--detach=true", "--name", serviceName, imageName, "top") 98 c.Assert(err, checker.IsNil, check.Commentf("%s", out)) 99 id := strings.TrimSpace(out) 100 101 var tasks []swarm.Task 102 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 103 tasks = d.GetServiceTasks(c, id) 104 return tasks, nil 105 }, checker.HasLen, 1) 106 107 task := tasks[0] 108 109 // wait for task to start 110 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 111 task = d.GetTask(c, task.ID) 112 return task.Status.State, nil 113 }, checker.Equals, swarm.TaskStateStarting) 114 115 containerID := task.Status.ContainerStatus.ContainerID 116 117 // wait for health check to work 118 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 119 out, _ := d.Cmd("inspect", "--format={{.State.Health.FailingStreak}}", containerID) 120 failingStreak, _ := strconv.Atoi(strings.TrimSpace(out)) 121 return failingStreak, nil 122 }, checker.GreaterThan, 0) 123 124 // task should be blocked at starting status 125 task = d.GetTask(c, task.ID) 126 c.Assert(task.Status.State, check.Equals, swarm.TaskStateStarting) 127 128 // make it healthy 129 d.Cmd("exec", containerID, "touch", "/status") 130 131 // Task should be at running status 132 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 133 task = d.GetTask(c, task.ID) 134 return task.Status.State, nil 135 }, checker.Equals, swarm.TaskStateRunning) 136 }