open-consul/agent/ui_endpoint.go

186 lines
5.0 KiB
Go
Raw Normal View History

2014-04-23 19:57:06 +00:00
package agent
2014-04-28 21:52:30 +00:00
import (
"fmt"
2014-04-28 21:52:30 +00:00
"net/http"
2014-04-28 22:52:37 +00:00
"sort"
2014-04-28 21:52:30 +00:00
"strings"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
2014-04-28 21:52:30 +00:00
)
2014-04-28 22:52:37 +00:00
// ServiceSummary is used to summarize a service
type ServiceSummary struct {
Name string
Tags []string
2014-04-28 22:52:37 +00:00
Nodes []string
ChecksPassing int
ChecksWarning int
ChecksCritical int
}
2014-04-28 21:52:30 +00:00
// UINodes is used to list the nodes in a given datacenter. We return a
2014-04-28 22:09:46 +00:00
// NodeDump which provides overview information for all the nodes
2014-04-28 21:52:30 +00:00
func (s *HTTPServer) UINodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// Parse arguments
args := structs.DCSpecificRequest{}
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
2014-04-28 21:52:30 +00:00
// Make the RPC request
var out structs.IndexedNodeDump
defer setMeta(resp, &out.QueryMeta)
RPC:
if err := s.agent.RPC("Internal.NodeDump", &args, &out); err != nil {
// Retry the request allowing stale data if no leader
if strings.Contains(err.Error(), structs.ErrNoLeader.Error()) && !args.AllowStale {
args.AllowStale = true
goto RPC
}
2014-04-28 21:52:30 +00:00
return nil, err
}
// Use empty list instead of nil
for _, info := range out.Dump {
if info.Services == nil {
info.Services = make([]*structs.NodeService, 0)
}
if info.Checks == nil {
info.Checks = make([]*structs.HealthCheck, 0)
}
}
if out.Dump == nil {
out.Dump = make(structs.NodeDump, 0)
}
return out.Dump, nil
2014-04-28 21:52:30 +00:00
}
2014-04-28 22:09:46 +00:00
// 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) {
// Parse arguments
args := structs.NodeSpecificRequest{}
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
2014-04-28 22:09:46 +00:00
// Verify we have some DC, or use the default
args.Node = strings.TrimPrefix(req.URL.Path, "/v1/internal/ui/node/")
if args.Node == "" {
2017-08-23 19:19:11 +00:00
resp.WriteHeader(http.StatusBadRequest)
fmt.Fprint(resp, "Missing node name")
2014-04-28 22:09:46 +00:00
return nil, nil
}
// Make the RPC request
var out structs.IndexedNodeDump
defer setMeta(resp, &out.QueryMeta)
RPC:
if err := s.agent.RPC("Internal.NodeInfo", &args, &out); err != nil {
// Retry the request allowing stale data if no leader
if strings.Contains(err.Error(), structs.ErrNoLeader.Error()) && !args.AllowStale {
args.AllowStale = true
goto RPC
}
2014-04-28 22:09:46 +00:00
return nil, err
}
// Return only the first entry
if len(out.Dump) > 0 {
info := out.Dump[0]
if info.Services == nil {
info.Services = make([]*structs.NodeService, 0)
}
if info.Checks == nil {
info.Checks = make([]*structs.HealthCheck, 0)
}
return info, nil
2014-04-28 22:09:46 +00:00
}
2017-07-11 14:13:29 +00:00
resp.WriteHeader(http.StatusNotFound)
2014-04-28 22:09:46 +00:00
return nil, nil
}
2014-04-28 22:52:37 +00:00
// 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) {
// Parse arguments
args := structs.DCSpecificRequest{}
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
return nil, nil
}
2014-04-28 22:52:37 +00:00
// Make the RPC request
var out structs.IndexedNodeDump
defer setMeta(resp, &out.QueryMeta)
RPC:
if err := s.agent.RPC("Internal.NodeDump", &args, &out); err != nil {
// Retry the request allowing stale data if no leader
if strings.Contains(err.Error(), structs.ErrNoLeader.Error()) && !args.AllowStale {
args.AllowStale = true
goto RPC
}
2014-04-28 22:52:37 +00:00
return nil, err
}
// Generate the summary
return summarizeServices(out.Dump), nil
2014-04-28 22:52:37 +00:00
}
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 {
2014-04-28 22:52:37 +00:00
sum := getService(service.Service)
sum.Tags = service.Tags
2014-04-28 22:52:37 +00:00
sum.Nodes = append(sum.Nodes, node.Node)
nodeServices[idx] = sum
2014-04-28 22:52:37 +00:00
}
for _, check := range node.Checks {
var services []*ServiceSummary
2014-04-28 22:52:37 +00:00
if check.ServiceName == "" {
services = nodeServices
} else {
services = []*ServiceSummary{getService(check.ServiceName)}
2014-04-28 22:52:37 +00:00
}
for _, sum := range services {
switch check.Status {
case api.HealthPassing:
sum.ChecksPassing++
case api.HealthWarning:
sum.ChecksWarning++
case api.HealthCritical:
sum.ChecksCritical++
}
2014-04-28 22:52:37 +00:00
}
}
}
// 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
}