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