github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/integration-cli/docker_cli_service_health_test.go (about)

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