github.com/portworx/docker@v1.12.1/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/daemon/cluster/executor/container" 10 "github.com/docker/docker/pkg/integration/checker" 11 "github.com/docker/engine-api/types/swarm" 12 "github.com/go-check/check" 13 ) 14 15 // start a service, and then make its task unhealthy during running 16 // finally, unhealthy task should be detected and killed 17 func (s *DockerSwarmSuite) TestServiceHealthRun(c *check.C) { 18 testRequires(c, DaemonIsLinux) // busybox doesn't work on Windows 19 20 d := s.AddDaemon(c, true, true) 21 22 // build image with health-check 23 // note: use `daemon.buildImageWithOut` to build, do not use `buildImage` to build 24 imageName := "testhealth" 25 _, _, err := d.buildImageWithOut(imageName, 26 `FROM busybox 27 RUN touch /status 28 HEALTHCHECK --interval=1s --timeout=1s --retries=1\ 29 CMD cat /status`, 30 true) 31 c.Check(err, check.IsNil) 32 33 serviceName := "healthServiceRun" 34 out, err := d.Cmd("service", "create", "--name", serviceName, imageName, "top") 35 c.Assert(err, checker.IsNil, check.Commentf(out)) 36 id := strings.TrimSpace(out) 37 38 var tasks []swarm.Task 39 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 40 tasks = d.getServiceTasks(c, id) 41 return tasks, nil 42 }, checker.HasLen, 1) 43 44 task := tasks[0] 45 46 // wait for task to start 47 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 48 task = d.getTask(c, task.ID) 49 return task.Status.State, nil 50 }, checker.Equals, swarm.TaskStateStarting) 51 containerID := task.Status.ContainerStatus.ContainerID 52 53 // wait for container to be healthy 54 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 55 out, _ := d.Cmd("inspect", "--format={{.State.Health.Status}}", containerID) 56 return strings.TrimSpace(out), nil 57 }, checker.Equals, "healthy") 58 59 // make it fail 60 d.Cmd("exec", containerID, "rm", "/status") 61 // wait for container to be unhealthy 62 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 63 out, _ := d.Cmd("inspect", "--format={{.State.Health.Status}}", containerID) 64 return strings.TrimSpace(out), nil 65 }, checker.Equals, "unhealthy") 66 67 // Task should be terminated 68 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 69 task = d.getTask(c, task.ID) 70 return task.Status.State, nil 71 }, checker.Equals, swarm.TaskStateFailed) 72 73 if !strings.Contains(task.Status.Err, container.ErrContainerUnhealthy.Error()) { 74 c.Fatal("unhealthy task exits because of other error") 75 } 76 } 77 78 // start a service whose task is unhealthy at beginning 79 // its tasks should be blocked in starting stage, until health check is passed 80 func (s *DockerSwarmSuite) TestServiceHealthStart(c *check.C) { 81 testRequires(c, DaemonIsLinux) // busybox doesn't work on Windows 82 83 d := s.AddDaemon(c, true, true) 84 85 // service started from this image won't pass health check 86 imageName := "testhealth" 87 _, _, err := d.buildImageWithOut(imageName, 88 `FROM busybox 89 HEALTHCHECK --interval=1s --timeout=1s --retries=1024\ 90 CMD cat /status`, 91 true) 92 c.Check(err, check.IsNil) 93 94 serviceName := "healthServiceStart" 95 out, err := d.Cmd("service", "create", "--name", serviceName, imageName, "top") 96 c.Assert(err, checker.IsNil, check.Commentf(out)) 97 id := strings.TrimSpace(out) 98 99 var tasks []swarm.Task 100 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 101 tasks = d.getServiceTasks(c, id) 102 return tasks, nil 103 }, checker.HasLen, 1) 104 105 task := tasks[0] 106 107 // wait for task to start 108 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 109 task = d.getTask(c, task.ID) 110 return task.Status.State, nil 111 }, checker.Equals, swarm.TaskStateStarting) 112 113 containerID := task.Status.ContainerStatus.ContainerID 114 115 // wait for health check to work 116 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 117 out, _ := d.Cmd("inspect", "--format={{.State.Health.FailingStreak}}", containerID) 118 failingStreak, _ := strconv.Atoi(strings.TrimSpace(out)) 119 return failingStreak, nil 120 }, checker.GreaterThan, 0) 121 122 // task should be blocked at starting status 123 task = d.getTask(c, task.ID) 124 c.Assert(task.Status.State, check.Equals, swarm.TaskStateStarting) 125 126 // make it healthy 127 d.Cmd("exec", containerID, "touch", "/status") 128 129 // Task should be at running status 130 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 131 task = d.getTask(c, task.ID) 132 return task.Status.State, nil 133 }, checker.Equals, swarm.TaskStateRunning) 134 } 135 136 // start a service whose task is unhealthy at beginning 137 // its tasks should be blocked in starting stage, until health check is passed 138 func (s *DockerSwarmSuite) TestServiceHealthUpdate(c *check.C) { 139 testRequires(c, DaemonIsLinux) // busybox doesn't work on Windows 140 141 d := s.AddDaemon(c, true, true) 142 143 // service started from this image won't pass health check 144 imageName := "testhealth" 145 _, _, err := d.buildImageWithOut(imageName, 146 `FROM busybox 147 HEALTHCHECK --interval=1s --timeout=1s --retries=1024\ 148 CMD cat /status`, 149 true) 150 c.Check(err, check.IsNil) 151 152 serviceName := "healthServiceStart" 153 out, err := d.Cmd("service", "create", "--name", serviceName, imageName, "top") 154 c.Assert(err, checker.IsNil, check.Commentf(out)) 155 id := strings.TrimSpace(out) 156 157 var tasks []swarm.Task 158 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 159 tasks = d.getServiceTasks(c, id) 160 return tasks, nil 161 }, checker.HasLen, 1) 162 163 task := tasks[0] 164 165 // wait for task to start 166 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 167 task = d.getTask(c, task.ID) 168 return task.Status.State, nil 169 }, checker.Equals, swarm.TaskStateStarting) 170 171 containerID := task.Status.ContainerStatus.ContainerID 172 173 // wait for health check to work 174 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 175 out, _ := d.Cmd("inspect", "--format={{.State.Health.FailingStreak}}", containerID) 176 failingStreak, _ := strconv.Atoi(strings.TrimSpace(out)) 177 return failingStreak, nil 178 }, checker.GreaterThan, 0) 179 180 // task should be blocked at starting status 181 task = d.getTask(c, task.ID) 182 c.Assert(task.Status.State, check.Equals, swarm.TaskStateStarting) 183 184 // make it healthy 185 d.Cmd("exec", containerID, "touch", "/status") 186 // Task should be at running status 187 waitAndAssert(c, defaultReconciliationTimeout, func(c *check.C) (interface{}, check.CommentInterface) { 188 task = d.getTask(c, task.ID) 189 return task.Status.State, nil 190 }, checker.Equals, swarm.TaskStateRunning) 191 }