docker_cli_service_health_test.go 4.8 KB

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