github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/moby/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/docker/docker/api/types/swarm"
    11  	"github.com/docker/docker/daemon/cluster/executor/container"
    12  	"github.com/docker/docker/integration-cli/checker"
    13  	"github.com/docker/docker/integration-cli/cli"
    14  	"github.com/docker/docker/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  }