github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/integration-cli/docker_cli_health_test.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/docker/docker/api/types"
    12  	"github.com/docker/docker/integration-cli/cli"
    13  	"github.com/docker/docker/integration-cli/cli/build"
    14  	"gotest.tools/v3/assert"
    15  )
    16  
    17  type DockerCLIHealthSuite struct {
    18  	ds *DockerSuite
    19  }
    20  
    21  func (s *DockerCLIHealthSuite) TearDownTest(ctx context.Context, c *testing.T) {
    22  	s.ds.TearDownTest(ctx, c)
    23  }
    24  
    25  func (s *DockerCLIHealthSuite) OnTimeout(c *testing.T) {
    26  	s.ds.OnTimeout(c)
    27  }
    28  
    29  func waitForHealthStatus(c *testing.T, name string, prev string, expected string) {
    30  	prev = prev + "\n"
    31  	expected = expected + "\n"
    32  	for {
    33  		out := cli.DockerCmd(c, "inspect", "--format={{.State.Health.Status}}", name).Stdout()
    34  		if out == expected {
    35  			return
    36  		}
    37  		assert.Equal(c, out, prev)
    38  		if out != prev {
    39  			return
    40  		}
    41  		time.Sleep(100 * time.Millisecond)
    42  	}
    43  }
    44  
    45  func getHealth(c *testing.T, name string) *types.Health {
    46  	out := cli.DockerCmd(c, "inspect", "--format={{json .State.Health}}", name).Stdout()
    47  	var health types.Health
    48  	err := json.Unmarshal([]byte(out), &health)
    49  	assert.Equal(c, err, nil)
    50  	return &health
    51  }
    52  
    53  func (s *DockerCLIHealthSuite) TestHealth(c *testing.T) {
    54  	testRequires(c, DaemonIsLinux) // busybox doesn't work on Windows
    55  
    56  	existingContainers := ExistingContainerIDs(c)
    57  
    58  	imageName := "testhealth"
    59  	buildImageSuccessfully(c, imageName, build.WithDockerfile(`FROM busybox
    60  		RUN echo OK > /status
    61  		CMD ["/bin/sleep", "120"]
    62  		STOPSIGNAL SIGKILL
    63  		HEALTHCHECK --interval=1s --timeout=30s \
    64  		  CMD cat /status`))
    65  
    66  	// No health status before starting
    67  	name := "test_health"
    68  	cid := cli.DockerCmd(c, "create", "--name", name, imageName).Stdout()
    69  	out := cli.DockerCmd(c, "ps", "-a", "--format={{.ID}} {{.Status}}").Stdout()
    70  	out = RemoveOutputForExistingElements(out, existingContainers)
    71  	assert.Equal(c, out, cid[:12]+" Created\n")
    72  
    73  	// Inspect the options
    74  	out = cli.DockerCmd(c, "inspect", "--format=timeout={{.Config.Healthcheck.Timeout}} interval={{.Config.Healthcheck.Interval}} retries={{.Config.Healthcheck.Retries}} test={{.Config.Healthcheck.Test}}", name).Stdout()
    75  	assert.Equal(c, out, "timeout=30s interval=1s retries=0 test=[CMD-SHELL cat /status]\n")
    76  
    77  	// Start
    78  	cli.DockerCmd(c, "start", name)
    79  	waitForHealthStatus(c, name, "starting", "healthy")
    80  
    81  	// Make it fail
    82  	cli.DockerCmd(c, "exec", name, "rm", "/status")
    83  	waitForHealthStatus(c, name, "healthy", "unhealthy")
    84  
    85  	// Inspect the status
    86  	out = cli.DockerCmd(c, "inspect", "--format={{.State.Health.Status}}", name).Stdout()
    87  	assert.Equal(c, out, "unhealthy\n")
    88  
    89  	// Make it healthy again
    90  	cli.DockerCmd(c, "exec", name, "touch", "/status")
    91  	waitForHealthStatus(c, name, "unhealthy", "healthy")
    92  
    93  	// Remove container
    94  	cli.DockerCmd(c, "rm", "-f", name)
    95  
    96  	// Disable the check from the CLI
    97  	cli.DockerCmd(c, "create", "--name=noh", "--no-healthcheck", imageName)
    98  	out = cli.DockerCmd(c, "inspect", "--format={{.Config.Healthcheck.Test}}", "noh").Stdout()
    99  	assert.Equal(c, out, "[NONE]\n")
   100  	cli.DockerCmd(c, "rm", "noh")
   101  
   102  	// Disable the check with a new build
   103  	buildImageSuccessfully(c, "no_healthcheck", build.WithDockerfile(`FROM testhealth
   104  		HEALTHCHECK NONE`))
   105  
   106  	out = cli.DockerCmd(c, "inspect", "--format={{.Config.Healthcheck.Test}}", "no_healthcheck").Stdout()
   107  	assert.Equal(c, out, "[NONE]\n")
   108  
   109  	// Enable the checks from the CLI
   110  	cli.DockerCmd(c, "run", "-d", "--name=fatal_healthcheck",
   111  		"--health-interval=1s",
   112  		"--health-retries=3",
   113  		"--health-cmd=cat /status",
   114  		"no_healthcheck",
   115  	)
   116  	waitForHealthStatus(c, "fatal_healthcheck", "starting", "healthy")
   117  	health := getHealth(c, "fatal_healthcheck")
   118  	assert.Equal(c, health.Status, "healthy")
   119  	assert.Equal(c, health.FailingStreak, 0)
   120  	last := health.Log[len(health.Log)-1]
   121  	assert.Equal(c, last.ExitCode, 0)
   122  	assert.Equal(c, last.Output, "OK\n")
   123  
   124  	// Fail the check
   125  	cli.DockerCmd(c, "exec", "fatal_healthcheck", "rm", "/status")
   126  	waitForHealthStatus(c, "fatal_healthcheck", "healthy", "unhealthy")
   127  
   128  	failsStr := cli.DockerCmd(c, "inspect", "--format={{.State.Health.FailingStreak}}", "fatal_healthcheck").Combined()
   129  	fails, err := strconv.Atoi(strings.TrimSpace(failsStr))
   130  	assert.Check(c, err)
   131  	assert.Equal(c, fails >= 3, true)
   132  	cli.DockerCmd(c, "rm", "-f", "fatal_healthcheck")
   133  
   134  	// Check timeout
   135  	// Note: if the interval is too small, it seems that Docker spends all its time running health
   136  	// checks and never gets around to killing it.
   137  	cli.DockerCmd(c, "run", "-d", "--name=test", "--health-interval=1s", "--health-cmd=sleep 5m", "--health-timeout=1s", imageName)
   138  	waitForHealthStatus(c, "test", "starting", "unhealthy")
   139  	health = getHealth(c, "test")
   140  	last = health.Log[len(health.Log)-1]
   141  	assert.Equal(c, health.Status, "unhealthy")
   142  	assert.Equal(c, last.ExitCode, -1)
   143  	assert.Equal(c, last.Output, "Health check exceeded timeout (1s)")
   144  	cli.DockerCmd(c, "rm", "-f", "test")
   145  
   146  	// Check JSON-format
   147  	buildImageSuccessfully(c, imageName, build.WithDockerfile(`FROM busybox
   148  		RUN echo OK > /status
   149  		CMD ["/bin/sleep", "120"]
   150  		STOPSIGNAL SIGKILL
   151  		HEALTHCHECK --interval=1s --timeout=30s \
   152  		  CMD ["cat", "/my status"]`))
   153  	out = cli.DockerCmd(c, "inspect", "--format={{.Config.Healthcheck.Test}}", imageName).Stdout()
   154  	assert.Equal(c, out, "[CMD cat /my status]\n")
   155  }
   156  
   157  // GitHub #33021
   158  func (s *DockerCLIHealthSuite) TestUnsetEnvVarHealthCheck(c *testing.T) {
   159  	testRequires(c, DaemonIsLinux) // busybox doesn't work on Windows
   160  
   161  	imageName := "testhealth"
   162  	buildImageSuccessfully(c, imageName, build.WithDockerfile(`FROM busybox
   163  HEALTHCHECK --interval=1s --timeout=5s --retries=5 CMD /bin/sh -c "sleep 1"
   164  ENTRYPOINT /bin/sh -c "sleep 600"`))
   165  
   166  	name := "env_test_health"
   167  	// No health status before starting
   168  	cli.DockerCmd(c, "run", "-d", "--name", name, "-e", "FOO", imageName)
   169  	defer func() {
   170  		cli.DockerCmd(c, "rm", "-f", name)
   171  		cli.DockerCmd(c, "rmi", imageName)
   172  	}()
   173  
   174  	// Start
   175  	cli.DockerCmd(c, "start", name)
   176  	waitForHealthStatus(c, name, "starting", "healthy")
   177  }