mirror of https://github.com/hashicorp/consul
agent: Adding UI services endpoint
parent
52d9b3638e
commit
416ff8f7d6
|
@ -108,6 +108,7 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
|
||||||
// API's are under /internal/ui/ to avoid conflict
|
// API's are under /internal/ui/ to avoid conflict
|
||||||
s.mux.HandleFunc("/v1/internal/ui/nodes", s.wrap(s.UINodes))
|
s.mux.HandleFunc("/v1/internal/ui/nodes", s.wrap(s.UINodes))
|
||||||
s.mux.HandleFunc("/v1/internal/ui/node/", s.wrap(s.UINodeInfo))
|
s.mux.HandleFunc("/v1/internal/ui/node/", s.wrap(s.UINodeInfo))
|
||||||
|
s.mux.HandleFunc("/v1/internal/ui/services", s.wrap(s.UIServices))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,19 @@ package agent
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/consul/consul/structs"
|
"github.com/hashicorp/consul/consul/structs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strings"
|
"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
|
// UINodes is used to list the nodes in a given datacenter. We return a
|
||||||
// NodeDump which provides overview information for all the nodes
|
// NodeDump which provides overview information for all the nodes
|
||||||
func (s *HTTPServer) UINodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPServer) UINodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
|
@ -86,3 +96,68 @@ START:
|
||||||
*dump = out.Dump
|
*dump = out.Dump
|
||||||
return nil
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -109,3 +110,96 @@ func TestUiNodeInfo(t *testing.T) {
|
||||||
t.Fatalf("bad: %v", node)
|
t.Fatalf("bad: %v", node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSummarizeServices(t *testing.T) {
|
||||||
|
dump := structs.NodeDump{
|
||||||
|
&structs.NodeInfo{
|
||||||
|
Node: "foo",
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Services: []*structs.NodeService{
|
||||||
|
&structs.NodeService{
|
||||||
|
Service: "api",
|
||||||
|
},
|
||||||
|
&structs.NodeService{
|
||||||
|
Service: "web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checks: []*structs.HealthCheck{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Status: structs.HealthCritical,
|
||||||
|
ServiceName: "",
|
||||||
|
},
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Status: structs.HealthPassing,
|
||||||
|
ServiceName: "web",
|
||||||
|
},
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Status: structs.HealthWarning,
|
||||||
|
ServiceName: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.NodeInfo{
|
||||||
|
Node: "bar",
|
||||||
|
Address: "127.0.0.2",
|
||||||
|
Services: []*structs.NodeService{
|
||||||
|
&structs.NodeService{
|
||||||
|
Service: "web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checks: []*structs.HealthCheck{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Status: structs.HealthCritical,
|
||||||
|
ServiceName: "web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&structs.NodeInfo{
|
||||||
|
Node: "zip",
|
||||||
|
Address: "127.0.0.3",
|
||||||
|
Services: []*structs.NodeService{
|
||||||
|
&structs.NodeService{
|
||||||
|
Service: "cache",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
summary := summarizeServices(dump)
|
||||||
|
if len(summary) != 3 {
|
||||||
|
t.Fatalf("bad: %v", summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectAPI := &ServiceSummary{
|
||||||
|
Name: "api",
|
||||||
|
Nodes: []string{"foo"},
|
||||||
|
ChecksPassing: 0,
|
||||||
|
ChecksWarning: 1,
|
||||||
|
ChecksCritical: 0,
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(summary[0], expectAPI) {
|
||||||
|
t.Fatalf("bad: %v", summary[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
expectCache := &ServiceSummary{
|
||||||
|
Name: "cache",
|
||||||
|
Nodes: []string{"zip"},
|
||||||
|
ChecksPassing: 0,
|
||||||
|
ChecksWarning: 0,
|
||||||
|
ChecksCritical: 0,
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(summary[1], expectCache) {
|
||||||
|
t.Fatalf("bad: %v", summary[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
expectWeb := &ServiceSummary{
|
||||||
|
Name: "web",
|
||||||
|
Nodes: []string{"bar", "foo"},
|
||||||
|
ChecksPassing: 1,
|
||||||
|
ChecksWarning: 0,
|
||||||
|
ChecksCritical: 1,
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(summary[2], expectWeb) {
|
||||||
|
t.Fatalf("bad: %v", summary[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue