2014-04-23 19:57:06 +00:00
|
|
|
package agent
|
|
|
|
|
2014-04-28 21:52:30 +00:00
|
|
|
import (
|
|
|
|
"github.com/hashicorp/consul/consul/structs"
|
|
|
|
"net/http"
|
2014-04-28 22:52:37 +00:00
|
|
|
"sort"
|
2014-04-28 21:52:30 +00:00
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2014-04-28 22:52:37 +00:00
|
|
|
// ServiceSummary is used to summarize a service
|
|
|
|
type ServiceSummary struct {
|
|
|
|
Name string
|
|
|
|
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) {
|
2014-04-28 22:09:46 +00:00
|
|
|
// Get the datacenter
|
|
|
|
var dc string
|
|
|
|
s.parseDC(req, &dc)
|
2014-04-28 21:52:30 +00:00
|
|
|
|
|
|
|
// Try to ge ta node dump
|
|
|
|
var dump structs.NodeDump
|
2014-04-28 22:09:46 +00:00
|
|
|
if err := s.getNodeDump(resp, dc, "", &dump); err != nil {
|
2014-04-28 21:52:30 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return dump, nil
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2014-04-28 21:52:30 +00:00
|
|
|
// 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.
|
2014-04-28 22:09:46 +00:00
|
|
|
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
|
|
|
|
}
|
2014-04-28 21:52:30 +00:00
|
|
|
var out structs.IndexedNodeDump
|
|
|
|
defer setMeta(resp, &out.QueryMeta)
|
|
|
|
|
|
|
|
START:
|
2014-04-28 22:09:46 +00:00
|
|
|
if err := s.agent.RPC(method, args, &out); err != nil {
|
2014-04-28 21:52:30 +00:00
|
|
|
// Retry the request allowing stale data if no leader. The UI should continue
|
|
|
|
// to function even during an outage
|
2014-04-28 22:09:46 +00:00
|
|
|
if strings.Contains(err.Error(), structs.ErrNoLeader.Error()) && !*allowStale {
|
|
|
|
*allowStale = true
|
2014-04-28 21:52:30 +00:00
|
|
|
goto START
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the result
|
|
|
|
*dump = out.Dump
|
|
|
|
return 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) {
|
|
|
|
// 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 {
|
|
|
|
for _, service := range node.Services {
|
|
|
|
sum := getService(service.Service)
|
|
|
|
sum.Nodes = append(sum.Nodes, node.Node)
|
|
|
|
}
|
|
|
|
for _, check := range node.Checks {
|
|
|
|
if check.ServiceName == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
sum := getService(check.ServiceName)
|
|
|
|
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
|
|
|
|
}
|