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