github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+incompatible/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  }