Adds container health support to docker ps filter

Signed-off-by: Josh Horwitz <horwitzja@gmail.com>
This commit is contained in:
Josh Horwitz 2016-07-15 14:21:19 -04:00 committed by Josh Horwitz
parent 2a2f183627
commit 1a149a0ea5
10 changed files with 116 additions and 8 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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 {

View File

@ -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`).

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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) {

View File

@ -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>