Skip to content

Commit

Permalink
Merge pull request hashicorp#6 from criteo-forks/agent_health_service
Browse files Browse the repository at this point in the history
Agent health service
  • Loading branch information
pierresouchay authored Sep 16, 2018
2 parents ba79642 + 0070e47 commit f845053
Show file tree
Hide file tree
Showing 22 changed files with 988 additions and 143 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## UNRELEASED

## 1.2.3 (September 13, 2018)

FEATURES:
Expand All @@ -6,13 +8,15 @@ FEATURES:
* http: Added support for "Authorization: Bearer" head in addition to the X-Consul-Token header. [[GH-4483](https://github.com/hashicorp/consul/issues/4483)]
* dns: Added a way to specify SRV weights for each service instance to allow weighted DNS load-balancing. [[GH-4198](https://github.com/hashicorp/consul/pull/4198)]
* dns: Include EDNS-ECS options in EDNS responses where appropriate: see [RFC 7871](https://tools.ietf.org/html/rfc7871#section-7.1.3) [[GH-4647](https://github.com/hashicorp/consul/pull/4647)]
* ui: Add markers/icons for external sources [[GH-4640]](https://github.com/hashicorp/consul/pull/4640)

IMPROVEMENTS:

* ui: Switch to fullscreen layout for lists and detail, left aligned forms [[GH-4435]](https://github.com/hashicorp/consul/pull/4435)
* connect: TLS certificate readiness now performs x509 certificate verification to determine whether the cert is usable. [[GH-4540](https://github.com/hashicorp/consul/pull/4540)]
* ui: The syntax highlighting/code editor is now on by default [[GH-4651]](https://github.com/hashicorp/consul/pull/4651)
* ui: Fallback to showing `Node.Address` if `Service.Address` is not set [[GH-4579]](https://github.com/hashicorp/consul/issues/4579)
* gossip: Improvements to Serf and memberlist improving gossip stability on very large clusters (over 35k tested) [[GH-4511](https://github.com/hashicorp/consul/pull/4511)]

BUG FIXES:
* agent: Avoid returning empty data on startup of a non-leader server [[GH-4554](https://github.com/hashicorp/consul/pull/4554)]
Expand Down
2 changes: 1 addition & 1 deletion GNUmakefile
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ test-internal:
@awk '/^[^[:space:]]/ {do_print=0} /--- SKIP/ {do_print=1} do_print==1 {print}' test.log
@awk '/^[^[:space:]]/ {do_print=0} /--- FAIL/ {do_print=1} do_print==1 {print}' test.log
@grep '^FAIL' test.log || true
@if [ "$$(cat exit-code)" == "0" ] ; then echo "PASS" ; exit 0 ; else exit 1 ; fi
@if [ "$$(cat exit-code)" == "0" ] ; then echo "PASS" ; exit 0 ; else echo FAILED; tail -n50 test.log; exit 1 ; fi

test-race:
$(MAKE) GOTEST_FLAGS=-race
Expand Down
120 changes: 120 additions & 0 deletions agent/agent_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,126 @@ func (s *HTTPServer) AgentCheckUpdate(resp http.ResponseWriter, req *http.Reques
return nil, nil
}

func AgentHealthService(serviceId string, s *HTTPServer) (int, string) {
checks := s.agent.State.Checks()
serviceChecks := make(api.HealthChecks, 0)
for _, c := range checks {
if c.ServiceID == serviceId || c.ServiceID == "" {
// TODO: harmonize struct.HealthCheck and api.HealthCheck (or at least extract conversion function)
healthCheck := &api.HealthCheck{
Node: c.Node,
CheckID: string(c.CheckID),
Name: c.Name,
Status: c.Status,
Notes: c.Notes,
Output: c.Output,
ServiceID: c.ServiceID,
ServiceName: c.ServiceName,
ServiceTags: c.ServiceTags,
}
serviceChecks = append(serviceChecks, healthCheck)
}
}
status := serviceChecks.AggregatedStatus()
switch status {
case api.HealthWarning:
return http.StatusTooManyRequests, status
case api.HealthPassing:
return http.StatusOK, status
default:
return http.StatusServiceUnavailable, status
}
}

func returnTextPlain(req *http.Request) bool {
if contentType := req.Header.Get("Accept"); strings.HasPrefix(contentType, "text/plain") {
return true
}
if format := req.URL.Query().Get("format"); format == "text" {
return true
}
return false
}

func (s *HTTPServer) AgentHealthServiceId(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Pull out the service id (service id since there may be several instance of the same service on this host)
serviceID := strings.TrimPrefix(req.URL.Path, "/v1/agent/health/service/id/")
if serviceID == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing service id")
return nil, nil
}
services := s.agent.State.Services()
for _, service := range services {
if service.ID == serviceID {
code, status := AgentHealthService(serviceID, s)
if returnTextPlain(req) {
resp.WriteHeader(code)
fmt.Fprint(resp, status)
return nil, nil
}
resp.Header().Add("Content-Type", "application/json")
resp.WriteHeader(code)
result := make(map[string]*structs.NodeService)
result[status] = service
return result, nil
}
}
if returnTextPlain(req) {
resp.WriteHeader(http.StatusNotFound)
fmt.Fprintf(resp, "ServiceId %s not found", serviceID)
return nil, nil
}
resp.Header().Add("Content-Type", "application/json")
resp.WriteHeader(http.StatusNotFound)
result := make(map[string]*structs.NodeService)
return result, nil
}

func (s *HTTPServer) AgentHealthServiceName(resp http.ResponseWriter, req *http.Request) (interface{}, error) {

// Pull out the service name
serviceName := strings.TrimPrefix(req.URL.Path, "/v1/agent/health/service/name/")
if serviceName == "" {
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing service name")
return nil, nil
}
code := http.StatusNotFound
status := fmt.Sprintf("ServiceName %s Not Found", serviceName)
services := s.agent.State.Services()
result := make(map[string][]*structs.NodeService)
for _, service := range services {
if service.Service == serviceName {
scode, sstatus := AgentHealthService(service.ID, s)
res, ok := result[sstatus]
if !ok {
res = make([]*structs.NodeService, 0, 4)
}
result[sstatus] = append(res, service)
// When service is not found, we ignore it and keep existing HTTP status
if code == http.StatusNotFound {
code = scode
status = sstatus
}
// We take the worst of all statuses, so we keep iterating
// passing: 200 < < warning: 429 < critical: 503
if code < scode {
code = scode
status = sstatus
}
}
}
if returnTextPlain(req) {
resp.WriteHeader(code)
fmt.Fprint(resp, status)
return nil, nil
}
resp.Header().Add("Content-Type", "application/json")
resp.WriteHeader(code)
return result, nil
}

func (s *HTTPServer) AgentRegisterService(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
var args structs.ServiceDefinition
// Fixup the type decode of TTL or Interval if a check if provided.
Expand Down
Loading

0 comments on commit f845053

Please sign in to comment.