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…
	
	Add table
		Add a link
		
	
		Reference in a new issue