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