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