diff --git a/command/agent/health_endpoint.go b/command/agent/health_endpoint.go index e77444e395..8462e0a4b2 100644 --- a/command/agent/health_endpoint.go +++ b/command/agent/health_endpoint.go @@ -106,5 +106,26 @@ func (s *HTTPServer) HealthServiceNodes(resp http.ResponseWriter, req *http.Requ if err := s.agent.RPC("Health.ServiceNodes", &args, &out); err != nil { return nil, err } + + // Filter to only passing if specified + if _, ok := params["passing"]; ok { + out.Nodes = filterNonPassing(out.Nodes) + } return out.Nodes, nil } + +// filterNonPassing is used to filter out any nodes that have check that are not passing +func filterNonPassing(nodes structs.CheckServiceNodes) structs.CheckServiceNodes { + n := len(nodes) + for i := 0; i < n; i++ { + node := nodes[i] + for _, check := range node.Checks { + if check.Status != structs.HealthPassing { + nodes[i], nodes[n-1] = nodes[n-1], structs.CheckServiceNode{} + n-- + i-- + } + } + } + return nodes[:n] +} diff --git a/command/agent/health_endpoint_test.go b/command/agent/health_endpoint_test.go index b8bf1bef5e..e51f260840 100644 --- a/command/agent/health_endpoint_test.go +++ b/command/agent/health_endpoint_test.go @@ -138,3 +138,48 @@ func TestHealthServiceNodes(t *testing.T) { t.Fatalf("bad: %v", obj) } } + +func TestHealthServiceNodes_PassingFilter(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Wait for a leader + time.Sleep(100 * time.Millisecond) + + // Create a failing service check + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: srv.agent.config.NodeName, + Address: "127.0.0.1", + Check: &structs.HealthCheck{ + Node: srv.agent.config.NodeName, + Name: "consul check", + ServiceID: "consul", + Status: structs.HealthCritical, + }, + } + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + req, err := http.NewRequest("GET", "/v1/health/service/consul?passing", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + resp := httptest.NewRecorder() + obj, err := srv.HealthServiceNodes(resp, req) + if err != nil { + t.Fatalf("err: %v", err) + } + assertIndex(t, resp) + + // Should be 0 health check for consul + nodes := obj.(structs.CheckServiceNodes) + if len(nodes) != 0 { + t.Fatalf("bad: %v", obj) + } +} diff --git a/website/source/docs/agent/http.html.markdown b/website/source/docs/agent/http.html.markdown index 8d4079422a..6f031564ab 100644 --- a/website/source/docs/agent/http.html.markdown +++ b/website/source/docs/agent/http.html.markdown @@ -681,6 +681,10 @@ endpoint automatically returns the status of the associated health check, as well as any system level health checks. This allows a client to avoid sending traffic to nodes failing health tests, or who are reporting warnings. +Providing the "?passing" query parameter will filter results to only nodes +with all checks in the passing state. This can be used to avoid some filtering +logic on the client side. (Added in Consul 0.2) + Users can also built in support for dynamic load balancing and other features by incorporating the use of health checks.