mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Adds container health support to docker ps filter
Signed-off-by: Josh Horwitz <horwitzja@gmail.com>
This commit is contained in:
parent
2a2f183627
commit
1a149a0ea5
10 changed files with 116 additions and 8 deletions
|
@ -280,9 +280,10 @@ type HealthcheckResult struct {
|
|||
|
||||
// Health states
|
||||
const (
|
||||
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
|
||||
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
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
|
@ -78,6 +79,7 @@ func (s *State) String() string {
|
|||
if h := s.Health; h != nil {
|
||||
return fmt.Sprintf("Up %s (%s)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)), h.String())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt)))
|
||||
}
|
||||
|
||||
|
@ -100,6 +102,23 @@ func (s *State) String() string {
|
|||
return fmt.Sprintf("Exited (%d) %s ago", s.ExitCodeValue, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt)))
|
||||
}
|
||||
|
||||
// HealthString returns a single string to describe health status.
|
||||
func (s *State) HealthString() string {
|
||||
if s.Health == nil {
|
||||
return types.NoHealthcheck
|
||||
}
|
||||
|
||||
return s.Health.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
|
||||
}
|
||||
|
||||
// StateString returns a single string to describe state
|
||||
func (s *State) StateString() string {
|
||||
if s.Running {
|
||||
|
|
|
@ -4,8 +4,30 @@ import (
|
|||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestIsValidHealthString(t *testing.T) {
|
||||
contexts := []struct {
|
||||
Health string
|
||||
Expected bool
|
||||
}{
|
||||
{types.Healthy, true},
|
||||
{types.Unhealthy, true},
|
||||
{types.Starting, true},
|
||||
{types.NoHealthcheck, true},
|
||||
{"fail", false},
|
||||
}
|
||||
|
||||
for _, c := range contexts {
|
||||
v := IsValidHealthString(c.Health)
|
||||
if v != c.Expected {
|
||||
t.Fatalf("Expected %t, but got %t", c.Expected, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStateRunStop(t *testing.T) {
|
||||
s := NewState()
|
||||
for i := 1; i < 3; i++ { // full lifecycle two times
|
||||
|
|
|
@ -33,6 +33,7 @@ var acceptedPsFilterTags = map[string]bool{
|
|||
"label": true,
|
||||
"name": true,
|
||||
"status": true,
|
||||
"health": true,
|
||||
"since": true,
|
||||
"volume": true,
|
||||
"network": true,
|
||||
|
@ -258,6 +259,17 @@ func (daemon *Daemon) foldFilter(config *types.ContainerListOptions) (*listConte
|
|||
}
|
||||
}
|
||||
|
||||
err = psFilters.WalkValues("health", func(value string) error {
|
||||
if !container.IsValidHealthString(value) {
|
||||
return fmt.Errorf("Unrecognised filter value for health: %s", value)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var beforeContFilter, sinceContFilter *container.Container
|
||||
|
||||
err = psFilters.WalkValues("before", func(value string) error {
|
||||
|
@ -384,6 +396,11 @@ func includeContainerInList(container *container.Container, ctx *listContext) it
|
|||
return excludeContainer
|
||||
}
|
||||
|
||||
// Do not include container if its health doesn't match the filter
|
||||
if !ctx.filters.ExactMatch("health", container.State.HealthString()) {
|
||||
return excludeContainer
|
||||
}
|
||||
|
||||
if ctx.filters.Include("volume") {
|
||||
volumesByName := make(map[string]*volume.MountPoint)
|
||||
for _, m := range container.MountPoints {
|
||||
|
|
|
@ -136,6 +136,7 @@ This section lists each version from latest to oldest. Each listing includes a
|
|||
* `POST /containers/create` now takes `AutoRemove` in HostConfig, to enable auto-removal of the container on daemon side when the container's process exits.
|
||||
* `GET /containers/json` and `GET /containers/(id or name)/json` now return `"removing"` as a value for the `State.Status` field if the container is being removed. Previously, "exited" was returned as status.
|
||||
* `GET /containers/json` now accepts `removing` as a valid value for the `status` filter.
|
||||
* `GET /containers/json` now supports filtering containers by `health` status.
|
||||
* `DELETE /volumes/(name)` now accepts a `force` query parameter to force removal of volumes that were already removed out of band by the volume driver plugin.
|
||||
* `POST /containers/create/` and `POST /containers/(name)/update` now validates restart policies.
|
||||
* `POST /containers/create` now validates IPAMConfig in NetworkingConfig, and returns error for invalid IPv4 and IPv6 addresses (`--ip` and `--ip6` in `docker create/run`).
|
||||
|
|
|
@ -241,7 +241,8 @@ List containers
|
|||
- `since`=(`<container id>` or `<container name>`)
|
||||
- `volume`=(`<volume name>` or `<mount point destination>`)
|
||||
- `network`=(`<network id>` or `<network name>`)
|
||||
|
||||
- `health`=(`starting`|`healthy`|`unhealthy`|`none`)
|
||||
|
||||
**Status codes**:
|
||||
|
||||
- **200** – no error
|
||||
|
|
|
@ -33,6 +33,7 @@ Options:
|
|||
- ancestor=(<image-name>[:tag]|<image-id>|<image@digest>)
|
||||
containers created from an image or a descendant.
|
||||
- is-task=(true|false)
|
||||
- health=(starting|healthy|unhealthy|none)
|
||||
--format string Pretty-print containers using a Go template
|
||||
--help Print usage
|
||||
-n, --last int Show n last created containers (includes all states) (default -1)
|
||||
|
@ -81,6 +82,7 @@ The currently supported filters are:
|
|||
* isolation (default|process|hyperv) (Windows daemon only)
|
||||
* volume (volume name or mount point) - filters containers that mount volumes.
|
||||
* network (network id or name) - filters containers connected to the provided network
|
||||
* health (starting|healthy|unhealthy|none) - filters containers based on healthcheck status
|
||||
|
||||
#### Label
|
||||
|
||||
|
|
|
@ -2,12 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/go-check/check"
|
||||
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
func waitForStatus(c *check.C, name string, prev string, expected string) {
|
||||
|
|
|
@ -227,6 +227,48 @@ func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestPsListContainersFilterHealth(c *check.C) {
|
||||
// Test legacy no health check
|
||||
out, _ := runSleepingContainer(c, "--name=none_legacy")
|
||||
containerID := strings.TrimSpace(out)
|
||||
|
||||
waitForContainer(containerID)
|
||||
|
||||
out, _ = dockerCmd(c, "ps", "-q", "-l", "--no-trunc", "--filter=health=none")
|
||||
containerOut := strings.TrimSpace(out)
|
||||
c.Assert(containerOut, checker.Equals, containerID, check.Commentf("Expected id %s, got %s for legacy none filter, output: %q", containerID, containerOut, out))
|
||||
|
||||
// Test no health check specified explicitly
|
||||
out, _ = runSleepingContainer(c, "--name=none", "--no-healthcheck")
|
||||
containerID = strings.TrimSpace(out)
|
||||
|
||||
waitForContainer(containerID)
|
||||
|
||||
out, _ = dockerCmd(c, "ps", "-q", "-l", "--no-trunc", "--filter=health=none")
|
||||
containerOut = strings.TrimSpace(out)
|
||||
c.Assert(containerOut, checker.Equals, containerID, check.Commentf("Expected id %s, got %s for none filter, output: %q", containerID, containerOut, out))
|
||||
|
||||
// Test failing health check
|
||||
out, _ = runSleepingContainer(c, "--name=failing_container", "--health-cmd=exit 1", "--health-interval=1s")
|
||||
containerID = strings.TrimSpace(out)
|
||||
|
||||
waitForHealthStatus(c, "failing_container", "starting", "unhealthy")
|
||||
|
||||
out, _ = dockerCmd(c, "ps", "-q", "--no-trunc", "--filter=health=unhealthy")
|
||||
containerOut = strings.TrimSpace(out)
|
||||
c.Assert(containerOut, checker.Equals, containerID, check.Commentf("Expected containerID %s, got %s for unhealthy filter, output: %q", containerID, containerOut, out))
|
||||
|
||||
// Check passing healthcheck
|
||||
out, _ = runSleepingContainer(c, "--name=passing_container", "--health-cmd=exit 0", "--health-interval=1s")
|
||||
containerID = strings.TrimSpace(out)
|
||||
|
||||
waitForHealthStatus(c, "passing_container", "starting", "healthy")
|
||||
|
||||
out, _ = dockerCmd(c, "ps", "-q", "--no-trunc", "--filter=health=healthy")
|
||||
containerOut = strings.TrimSpace(out)
|
||||
c.Assert(containerOut, checker.Equals, containerID, check.Commentf("Expected containerID %s, got %s for healthy filter, output: %q", containerID, containerOut, out))
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestPsListContainersFilterID(c *check.C) {
|
||||
// start container
|
||||
out, _ := dockerCmd(c, "run", "-d", "busybox")
|
||||
|
@ -239,7 +281,6 @@ func (s *DockerSuite) TestPsListContainersFilterID(c *check.C) {
|
|||
out, _ = dockerCmd(c, "ps", "-a", "-q", "--filter=id="+firstID)
|
||||
containerOut := strings.TrimSpace(out)
|
||||
c.Assert(containerOut, checker.Equals, firstID[:12], check.Commentf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out))
|
||||
|
||||
}
|
||||
|
||||
func (s *DockerSuite) TestPsListContainersFilterName(c *check.C) {
|
||||
|
|
|
@ -38,6 +38,7 @@ the running containers.
|
|||
- ancestor=(<image-name>[:tag]|<image-id>|<image@digest>) - containers created from an image or a descendant.
|
||||
- volume=(<volume-name>|<mount-point-destination>)
|
||||
- network=(<network-name>|<network-id>) - containers connected to the provided network
|
||||
- health=(starting|healthy|unhealthy|none) - filters containers based on healthcheck status
|
||||
|
||||
**--format**="*TEMPLATE*"
|
||||
Pretty-print containers using a Go template.
|
||||
|
@ -141,3 +142,4 @@ June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
|||
August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
November 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
|
||||
February 2015, updated by André Martins <martins@noironetworks.com>
|
||||
October 2016, updated by Josh Horwitz <horwitzja@gmail.com>
|
||||
|
|
Loading…
Reference in a new issue