forked from hashicorp/consul
/
ui_endpoint.go
169 lines (150 loc) · 4.38 KB
/
ui_endpoint.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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
}