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