api/types: move container Health types to api/types/container

This moves the `Health` and `HealthcheckResult` types to the container package,
as well as the related `NoHealthcheck`, `Starting`, `Healthy`, and `Unhealthy`
consts.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn
2024-06-25 19:34:29 +02:00
parent df22a511cb
commit c130ce1f5d
14 changed files with 93 additions and 76 deletions

View File

@@ -0,0 +1,26 @@
package container
import "time"
// Health states
const (
NoHealthcheck = "none" // Indicates there is no healthcheck
Starting = "starting" // Starting indicates that the container is not yet ready
Healthy = "healthy" // Healthy indicates that the container is running correctly
Unhealthy = "unhealthy" // Unhealthy indicates that the container has a problem
)
// Health stores information about the container's healthcheck results
type Health struct {
Status string // Status is one of [Starting], [Healthy] or [Unhealthy].
FailingStreak int // FailingStreak is the number of consecutive failures
Log []*HealthcheckResult // Log contains the last few results (oldest first)
}
// HealthcheckResult stores information about a single run of a healthcheck probe
type HealthcheckResult struct {
Start time.Time // Start is the time this check started
End time.Time // End is the time this check ended
ExitCode int // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe
Output string // Output from last check
}

View File

@@ -203,29 +203,6 @@ type Version struct {
BuildTime string `json:",omitempty"`
}
// HealthcheckResult stores information about a single run of a healthcheck probe
type HealthcheckResult struct {
Start time.Time // Start is the time this check started
End time.Time // End is the time this check ended
ExitCode int // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe
Output string // Output from last check
}
// Health states
const (
NoHealthcheck = "none" // Indicates there is no healthcheck
Starting = "starting" // Starting indicates that the container is not yet ready
Healthy = "healthy" // Healthy indicates that the container is running correctly
Unhealthy = "unhealthy" // Unhealthy indicates that the container has a problem
)
// Health stores information about the container's healthcheck results
type Health struct {
Status string // Status is one of Starting, Healthy or Unhealthy
FailingStreak int // FailingStreak is the number of consecutive failures
Log []*HealthcheckResult // Log contains the last few results (oldest first)
}
// ContainerState stores container's running state
// it's part of ContainerJSONBase and will return by "inspect" command
type ContainerState struct {
@@ -240,7 +217,7 @@ type ContainerState struct {
Error string
StartedAt string
FinishedAt string
Health *Health `json:",omitempty"`
Health *container.Health `json:",omitempty"`
}
// ContainerJSONBase contains response of Engine API:

View File

@@ -231,3 +231,21 @@ type DefaultNetworkSettings = container.DefaultNetworkSettings
//
// Deprecated: use [container.NetworkSettingsSummary].
type SummaryNetworkSettings = container.NetworkSettingsSummary
// Health states
const (
NoHealthcheck = container.NoHealthcheck // Deprecated: use [container.NoHealthcheck].
Starting = container.Starting // Deprecated: use [container.Starting].
Healthy = container.Healthy // Deprecated: use [container.Healthy].
Unhealthy = container.Unhealthy // Deprecated: use [container.Unhealthy].
)
// Health stores information about the container's healthcheck results.
//
// Deprecated: use [container.Health].
type Health = container.Health
// HealthcheckResult stores information about a single run of a healthcheck probe.
//
// Deprecated: use [container.HealthcheckResult].
type HealthcheckResult = container.HealthcheckResult

View File

@@ -5,12 +5,12 @@ import (
"sync"
"github.com/containerd/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
)
// Health holds the current container health-check state
type Health struct {
types.Health
container.Health
stop chan struct{} // Write struct{} to stop the monitor
mu sync.Mutex
}
@@ -20,7 +20,7 @@ func (s *Health) String() string {
status := s.Status()
switch status {
case types.Starting:
case container.Starting:
return "health: starting"
default: // Healthy and Unhealthy are clear on their own
return status
@@ -36,7 +36,7 @@ func (s *Health) Status() string {
// This happens when the monitor has yet to be setup.
if s.Health.Status == "" {
return types.Unhealthy
return container.Unhealthy
}
return s.Health.Status
@@ -77,7 +77,7 @@ func (s *Health) CloseMonitorChannel() {
close(s.stop)
s.stop = nil
// unhealthy when the monitor has stopped for compatibility reasons
s.Health.Status = types.Unhealthy
s.Health.Status = container.Unhealthy
log.G(context.TODO()).Debug("CloseMonitorChannel done")
}
}

View File

@@ -7,7 +7,7 @@ import (
"sync"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
units "github.com/docker/go-units"
)
@@ -110,10 +110,10 @@ func (s *State) String() string {
// IsValidHealthString checks if the provided string is a valid container health status or not.
func IsValidHealthString(s string) bool {
return s == types.Starting ||
s == types.Healthy ||
s == types.Unhealthy ||
s == types.NoHealthcheck
return s == container.Starting ||
s == container.Healthy ||
s == container.Unhealthy ||
s == container.NoHealthcheck
}
// StateString returns a single string to describe state

View File

@@ -5,7 +5,7 @@ import (
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
)
@@ -14,10 +14,10 @@ func TestIsValidHealthString(t *testing.T) {
Health string
Expected bool
}{
{types.Healthy, true},
{types.Unhealthy, true},
{types.Starting, true},
{types.NoHealthcheck, true},
{container.Healthy, true},
{container.Unhealthy, true},
{container.Starting, true},
{container.NoHealthcheck, true},
{"fail", false},
}

View File

@@ -301,7 +301,7 @@ func (v *View) GetAllNames() map[string][]string {
// transform maps a (deep) copied Container object to what queries need.
// A lock on the Container is not held because these are immutable deep copies.
func (v *View) transform(ctr *Container) *Snapshot {
health := types.NoHealthcheck
health := container.NoHealthcheck
if ctr.Health != nil {
health = ctr.Health.Status()
}

View File

@@ -7,8 +7,7 @@ import (
"path/filepath"
"testing"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/stringid"
"github.com/google/uuid"
"gotest.tools/v3/assert"
@@ -37,7 +36,7 @@ func newContainer(t *testing.T) *Container {
t.Fatal(err)
}
c := NewBaseContainer(id, cRoot)
c.HostConfig = &containertypes.HostConfig{}
c.HostConfig = &container.HostConfig{}
return c
}
@@ -171,7 +170,7 @@ func TestViewWithHealthCheck(t *testing.T) {
one = newContainer(t)
)
one.Health = &Health{
Health: types.Health{
Health: container.Health{
Status: "starting",
},
}

View File

@@ -10,8 +10,8 @@ import (
"time"
"github.com/containerd/log"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/backend"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/container"
@@ -55,7 +55,7 @@ const (
type probe interface {
// Perform one run of the check. Returns the exit code and an optional
// short diagnostic string.
run(context.Context, *Daemon, *container.Container) (*types.HealthcheckResult, error)
run(context.Context, *Daemon, *container.Container) (*containertypes.HealthcheckResult, error)
}
// cmdProbe implements the "CMD" probe type.
@@ -66,7 +66,7 @@ type cmdProbe struct {
// exec the healthcheck command in the container.
// Returns the exit code and probe output (if any)
func (p *cmdProbe) run(ctx context.Context, d *Daemon, cntr *container.Container) (*types.HealthcheckResult, error) {
func (p *cmdProbe) run(ctx context.Context, d *Daemon, cntr *container.Container) (*containertypes.HealthcheckResult, error) {
startTime := time.Now()
cmdSlice := strslice.StrSlice(cntr.Config.Healthcheck.Test)[1:]
if p.shell {
@@ -145,7 +145,7 @@ func (p *cmdProbe) run(ctx context.Context, d *Daemon, cntr *container.Container
} else {
msg = fmt.Sprintf("Health check exceeded timeout (%v)", probeTimeout)
}
return &types.HealthcheckResult{
return &containertypes.HealthcheckResult{
ExitCode: -1,
Output: msg,
End: time.Now(),
@@ -173,7 +173,7 @@ func (p *cmdProbe) run(ctx context.Context, d *Daemon, cntr *container.Container
}
// Note: Go's json package will handle invalid UTF-8 for us
out := output.String()
return &types.HealthcheckResult{
return &containertypes.HealthcheckResult{
End: time.Now(),
ExitCode: exitCode,
Output: out,
@@ -181,7 +181,7 @@ func (p *cmdProbe) run(ctx context.Context, d *Daemon, cntr *container.Container
}
// Update the container's Status.Health struct based on the latest probe's result.
func handleProbeResult(d *Daemon, c *container.Container, result *types.HealthcheckResult, done chan struct{}) {
func handleProbeResult(d *Daemon, c *container.Container, result *containertypes.HealthcheckResult, done chan struct{}) {
c.Lock()
defer c.Unlock()
@@ -208,14 +208,14 @@ func handleProbeResult(d *Daemon, c *container.Container, result *types.Healthch
if result.ExitCode == exitStatusHealthy {
h.FailingStreak = 0
h.SetStatus(types.Healthy)
h.SetStatus(containertypes.Healthy)
} else { // Failure (including invalid exit code)
shouldIncrementStreak := true
// If the container is starting (i.e. we never had a successful health check)
// then we check if we are within the start period of the container in which
// case we do not increment the failure streak.
if h.Status() == types.Starting {
if h.Status() == containertypes.Starting {
startPeriod := timeoutWithDefault(c.Config.Healthcheck.StartPeriod, defaultStartPeriod)
timeSinceStart := result.Start.Sub(c.State.StartedAt)
@@ -229,7 +229,7 @@ func handleProbeResult(d *Daemon, c *container.Container, result *types.Healthch
h.FailingStreak++
if h.FailingStreak >= retries {
h.SetStatus(types.Unhealthy)
h.SetStatus(containertypes.Unhealthy)
}
}
// Else we're starting or healthy. Stay in that state.
@@ -270,7 +270,7 @@ func monitor(d *Daemon, c *container.Container, stop chan struct{}, probe probe)
status := c.Health.Health.Status
c.Unlock()
if status == types.Starting {
if status == containertypes.Starting {
return startInterval
}
return probeInterval
@@ -288,14 +288,14 @@ func monitor(d *Daemon, c *container.Container, stop chan struct{}, probe probe)
log.G(context.TODO()).Debugf("Running health check for container %s ...", c.ID)
startTime := time.Now()
ctx, cancelProbe := context.WithCancel(context.Background())
results := make(chan *types.HealthcheckResult, 1)
results := make(chan *containertypes.HealthcheckResult, 1)
go func() {
healthChecksCounter.Inc()
result, err := probe.run(ctx, d, c)
if err != nil {
healthChecksFailedCounter.Inc()
log.G(ctx).Warnf("Health check for container %s error: %v", c.ID, err)
results <- &types.HealthcheckResult{
results <- &containertypes.HealthcheckResult{
ExitCode: -1,
Output: err.Error(),
Start: startTime,
@@ -379,11 +379,11 @@ func (daemon *Daemon) initHealthMonitor(c *container.Container) {
daemon.stopHealthchecks(c)
if h := c.State.Health; h != nil {
h.SetStatus(types.Starting)
h.SetStatus(containertypes.Starting)
h.FailingStreak = 0
} else {
h := &container.Health{}
h.SetStatus(types.Starting)
h.SetStatus(containertypes.Starting)
c.State.Health = h
}

View File

@@ -4,7 +4,6 @@ import (
"testing"
"time"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/container"
@@ -14,7 +13,7 @@ import (
func reset(c *container.Container) {
c.State = &container.State{}
c.State.Health = &container.Health{}
c.State.Health.SetStatus(types.Starting)
c.State.Health.SetStatus(containertypes.Starting)
}
func TestNoneHealthcheck(t *testing.T) {
@@ -87,7 +86,7 @@ func TestHealthStates(t *testing.T) {
reset(c)
handleResult := func(startTime time.Time, exitCode int) {
handleProbeResult(daemon, c, &types.HealthcheckResult{
handleProbeResult(daemon, c, &containertypes.HealthcheckResult{
Start: startTime,
End: startTime,
ExitCode: exitCode,
@@ -112,7 +111,7 @@ func TestHealthStates(t *testing.T) {
handleResult(c.State.StartedAt.Add(20*time.Second), 1)
handleResult(c.State.StartedAt.Add(40*time.Second), 1)
if status := c.State.Health.Status(); status != types.Starting {
if status := c.State.Health.Status(); status != containertypes.Starting {
t.Errorf("Expecting starting, but got %#v\n", status)
}
if c.State.Health.FailingStreak != 2 {
@@ -134,14 +133,14 @@ func TestHealthStates(t *testing.T) {
c.Config.Healthcheck.StartPeriod = 30 * time.Second
handleResult(c.State.StartedAt.Add(20*time.Second), 1)
if status := c.State.Health.Status(); status != types.Starting {
if status := c.State.Health.Status(); status != containertypes.Starting {
t.Errorf("Expecting starting, but got %#v\n", status)
}
if c.State.Health.FailingStreak != 0 {
t.Errorf("Expecting FailingStreak=0, but got %d\n", c.State.Health.FailingStreak)
}
handleResult(c.State.StartedAt.Add(50*time.Second), 1)
if status := c.State.Health.Status(); status != types.Starting {
if status := c.State.Health.Status(); status != containertypes.Starting {
t.Errorf("Expecting starting, but got %#v\n", status)
}
if c.State.Health.FailingStreak != 1 {

View File

@@ -137,12 +137,12 @@ func (daemon *Daemon) getInspectData(daemonCfg *config.Config, container *contai
}
}
var containerHealth *types.Health
var containerHealth *containertypes.Health
if container.State.Health != nil {
containerHealth = &types.Health{
containerHealth = &containertypes.Health{
Status: container.State.Health.Status(),
FailingStreak: container.State.Health.FailingStreak,
Log: append([]*types.HealthcheckResult{}, container.State.Health.Log...),
Log: append([]*containertypes.HealthcheckResult{}, container.State.Health.Log...),
}
}

View File

@@ -8,7 +8,7 @@ import (
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/cli/build"
"gotest.tools/v3/assert"
@@ -42,9 +42,9 @@ func waitForHealthStatus(c *testing.T, name string, prev string, expected string
}
}
func getHealth(c *testing.T, name string) *types.Health {
func getHealth(c *testing.T, name string) *container.Health {
out := cli.DockerCmd(c, "inspect", "--format={{json .State.Health}}", name).Stdout()
var health types.Health
var health container.Health
err := json.Unmarshal([]byte(out), &health)
assert.Equal(c, err, nil)
return &health

View File

@@ -6,7 +6,6 @@ import (
"testing"
"time"
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
@@ -30,7 +29,7 @@ func TestHealthCheckWorkdir(t *testing.T) {
}
})
poll.WaitOn(t, pollForHealthStatus(ctx, apiClient, cID, types.Healthy), poll.WithDelay(100*time.Millisecond))
poll.WaitOn(t, pollForHealthStatus(ctx, apiClient, cID, containertypes.Healthy), poll.WithDelay(100*time.Millisecond))
}
// GitHub #37263

View File

@@ -7,7 +7,6 @@ import (
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
@@ -111,7 +110,7 @@ func TestDaemonRestartKillContainers(t *testing.T) {
err = apiClient.ContainerStart(ctx, resp.ID, container.StartOptions{})
assert.NilError(t, err)
if tc.xHealthCheck {
poll.WaitOn(t, pollForHealthStatus(ctx, apiClient, resp.ID, types.Healthy), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(30*time.Second))
poll.WaitOn(t, pollForHealthStatus(ctx, apiClient, resp.ID, container.Healthy), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(30*time.Second))
testContainer.ExecT(ctx, t, apiClient, resp.ID, []string{"touch", "/tmp/unhealthy"}).AssertSuccess(t)
}
}
@@ -132,11 +131,11 @@ func TestDaemonRestartKillContainers(t *testing.T) {
// to become healthy, which gives us the entire StartPeriod (60s) to assert that
// the container's health state is Starting before we have to worry about racing
// the health monitor.
assert.Equal(t, testContainer.Inspect(ctx, t, apiClient, resp.ID).State.Health.Status, types.Starting)
assert.Equal(t, testContainer.Inspect(ctx, t, apiClient, resp.ID).State.Health.Status, container.Starting)
poll.WaitOn(t, pollForNewHealthCheck(ctx, apiClient, startTime, resp.ID), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(30*time.Second))
testContainer.ExecT(ctx, t, apiClient, resp.ID, []string{"rm", "/tmp/unhealthy"}).AssertSuccess(t)
poll.WaitOn(t, pollForHealthStatus(ctx, apiClient, resp.ID, types.Healthy), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(30*time.Second))
poll.WaitOn(t, pollForHealthStatus(ctx, apiClient, resp.ID, container.Healthy), poll.WithDelay(100*time.Millisecond), poll.WithTimeout(30*time.Second))
}
// TODO(cpuguy83): test pause states... this seems to be rather undefined currently
})