consul/command/agent/ui_endpoint.go

170 lines
4.4 KiB
Go

package agent
import (
"github.com/hashicorp/consul/consul/structs"
"net/http"
"sort"
"strings"
)
// ServiceSummary is used to summarize a service
type ServiceSummary struct {
Name string
Nodes []string
ChecksPassing int
ChecksWarning int
ChecksCritical int
}
// UINodes is used to list the nodes in a given datacenter. We return a
// NodeDump which provides overview information for all the nodes
func (s *HTTPServer) UINodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Get the datacenter
var dc string
s.parseDC(req, &dc)
// Try to ge ta node dump
var dump structs.NodeDump
if err := s.getNodeDump(resp, dc, "", &dump); err != nil {
return nil, err
}
return dump, nil
}
// UINodeInfo is used to get info on a single node in a given datacenter. We return a
// NodeInfo which provides overview information for the node
func (s *HTTPServer) UINodeInfo(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Get the datacenter
var dc string
s.parseDC(req, &dc)
// Verify we have some DC, or use the default
node := strings.TrimPrefix(req.URL.Path, "/v1/internal/ui/node/")
if node == "" {
resp.WriteHeader(400)
resp.Write([]byte("Missing node name"))
return nil, nil
}
// Try to get a node dump
var dump structs.NodeDump
if err := s.getNodeDump(resp, dc, node, &dump); err != nil {
return nil, err
}
// Return only the first entry
if len(dump) > 0 {
return dump[0], nil
}
return nil, nil
}
// getNodeDump is used to get a dump of all node data. We make a best effort by
// reading stale data in the case of an availability outage.
func (s *HTTPServer) getNodeDump(resp http.ResponseWriter, dc, node string, dump *structs.NodeDump) error {
var args interface{}
var method string
var allowStale *bool
if node == "" {
raw := structs.DCSpecificRequest{Datacenter: dc}
method = "Internal.NodeDump"
allowStale = &raw.AllowStale
args = &raw
} else {
raw := &structs.NodeSpecificRequest{Datacenter: dc, Node: node}
method = "Internal.NodeInfo"
allowStale = &raw.AllowStale
args = &raw
}
var out structs.IndexedNodeDump
defer setMeta(resp, &out.QueryMeta)
START:
if err := s.agent.RPC(method, args, &out); err != nil {
// Retry the request allowing stale data if no leader. The UI should continue
// to function even during an outage
if strings.Contains(err.Error(), structs.ErrNoLeader.Error()) && !*allowStale {
*allowStale = true
goto START
}
return err
}
// Set the result
*dump = out.Dump
return nil
}
// UIServices is used to list the services in a given datacenter. We return a
// ServiceSummary which provides overview information for the service
func (s *HTTPServer) UIServices(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Get the datacenter
var dc string
s.parseDC(req, &dc)
// Get the full node dump...
var dump structs.NodeDump
if err := s.getNodeDump(resp, dc, "", &dump); err != nil {
return nil, err
}
// Generate the summary
return summarizeServices(dump), nil
}
func summarizeServices(dump structs.NodeDump) []*ServiceSummary {
// Collect the summary information
var services []string
summary := make(map[string]*ServiceSummary)
getService := func(service string) *ServiceSummary {
serv, ok := summary[service]
if !ok {
serv = &ServiceSummary{Name: service}
summary[service] = serv
services = append(services, service)
}
return serv
}
// Aggregate all the node information
for _, node := range dump {
nodeServices := make([]*ServiceSummary, len(node.Services))
for idx, service := range node.Services {
sum := getService(service.Service)
sum.Nodes = append(sum.Nodes, node.Node)
nodeServices[idx] = sum
}
for _, check := range node.Checks {
var services []*ServiceSummary
if check.ServiceName == "" {
services = nodeServices
} else {
services = []*ServiceSummary{getService(check.ServiceName)}
}
for _, sum := range services {
switch check.Status {
case structs.HealthPassing:
sum.ChecksPassing++
case structs.HealthWarning:
sum.ChecksWarning++
case structs.HealthCritical:
sum.ChecksCritical++
}
}
}
}
// Return the services in sorted order
sort.Strings(services)
output := make([]*ServiceSummary, len(summary))
for idx, service := range services {
// Sort the nodes
sum := summary[service]
sort.Strings(sum.Nodes)
output[idx] = sum
}
return output
}