6ade136abf
The Init method provided the same functionality as the New constructor. The constructor is both more widely used, and more idiomatic, so remove the Init method. This change is in preparation for fixing printing of these IDs.
250 lines
7 KiB
Go
250 lines
7 KiB
Go
package agent
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/api"
|
|
)
|
|
|
|
// metaExternalSource is the key name for the service instance meta that
|
|
// defines the external syncing source. This is used by the UI APIs below
|
|
// to extract this.
|
|
const metaExternalSource = "external-source"
|
|
|
|
// ServiceSummary is used to summarize a service
|
|
type ServiceSummary struct {
|
|
Kind structs.ServiceKind `json:",omitempty"`
|
|
Name string
|
|
Tags []string
|
|
Nodes []string
|
|
InstanceCount int
|
|
ProxyFor []string `json:",omitempty"`
|
|
proxyForSet map[string]struct{} // internal to track uniqueness
|
|
ChecksPassing int
|
|
ChecksWarning int
|
|
ChecksCritical int
|
|
ExternalSources []string
|
|
externalSourceSet map[string]struct{} // internal to track uniqueness
|
|
|
|
structs.EnterpriseMeta
|
|
}
|
|
|
|
// 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) {
|
|
// Parse arguments
|
|
args := structs.DCSpecificRequest{}
|
|
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
|
return nil, nil
|
|
}
|
|
|
|
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.parseFilter(req, &args.Filter)
|
|
|
|
// 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
|
|
}
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Verify we have some DC, or use the default
|
|
args.Node = strings.TrimPrefix(req.URL.Path, "/v1/internal/ui/node/")
|
|
if args.Node == "" {
|
|
resp.WriteHeader(http.StatusBadRequest)
|
|
fmt.Fprint(resp, "Missing node name")
|
|
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
|
|
}
|
|
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
|
|
}
|
|
|
|
resp.WriteHeader(http.StatusNotFound)
|
|
return nil, 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) {
|
|
// Parse arguments
|
|
args := structs.ServiceDumpRequest{}
|
|
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
|
return nil, nil
|
|
}
|
|
|
|
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s.parseFilter(req, &args.Filter)
|
|
|
|
// Make the RPC request
|
|
var out structs.IndexedCheckServiceNodes
|
|
defer setMeta(resp, &out.QueryMeta)
|
|
RPC:
|
|
if err := s.agent.RPC("Internal.ServiceDump", &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
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Generate the summary
|
|
return summarizeServices(out.Nodes), nil
|
|
}
|
|
|
|
func summarizeServices(dump structs.CheckServiceNodes) []*ServiceSummary {
|
|
// Collect the summary information
|
|
var services []structs.ServiceID
|
|
summary := make(map[structs.ServiceID]*ServiceSummary)
|
|
getService := func(service structs.ServiceID) *ServiceSummary {
|
|
serv, ok := summary[service]
|
|
if !ok {
|
|
serv = &ServiceSummary{
|
|
Name: service.ID,
|
|
EnterpriseMeta: service.EnterpriseMeta,
|
|
// the other code will increment this unconditionally so we
|
|
// shouldn't initialize it to 1
|
|
InstanceCount: 0,
|
|
}
|
|
summary[service] = serv
|
|
services = append(services, service)
|
|
}
|
|
return serv
|
|
}
|
|
|
|
for _, csn := range dump {
|
|
svc := csn.Service
|
|
sum := getService(structs.NewServiceID(svc.Service, &svc.EnterpriseMeta))
|
|
sum.Nodes = append(sum.Nodes, csn.Node.Node)
|
|
sum.Kind = svc.Kind
|
|
sum.InstanceCount += 1
|
|
if svc.Kind == structs.ServiceKindConnectProxy {
|
|
if _, ok := sum.proxyForSet[svc.Proxy.DestinationServiceName]; !ok {
|
|
if sum.proxyForSet == nil {
|
|
sum.proxyForSet = make(map[string]struct{})
|
|
}
|
|
sum.proxyForSet[svc.Proxy.DestinationServiceName] = struct{}{}
|
|
sum.ProxyFor = append(sum.ProxyFor, svc.Proxy.DestinationServiceName)
|
|
}
|
|
}
|
|
for _, tag := range svc.Tags {
|
|
found := false
|
|
for _, existing := range sum.Tags {
|
|
if existing == tag {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
sum.Tags = append(sum.Tags, tag)
|
|
}
|
|
}
|
|
|
|
// If there is an external source, add it to the list of external
|
|
// sources. We only want to add unique sources so there is extra
|
|
// accounting here with an unexported field to maintain the set
|
|
// of sources.
|
|
if len(svc.Meta) > 0 && svc.Meta[metaExternalSource] != "" {
|
|
source := svc.Meta[metaExternalSource]
|
|
if sum.externalSourceSet == nil {
|
|
sum.externalSourceSet = make(map[string]struct{})
|
|
}
|
|
if _, ok := sum.externalSourceSet[source]; !ok {
|
|
sum.externalSourceSet[source] = struct{}{}
|
|
sum.ExternalSources = append(sum.ExternalSources, source)
|
|
}
|
|
}
|
|
|
|
for _, check := range csn.Checks {
|
|
switch check.Status {
|
|
case api.HealthPassing:
|
|
sum.ChecksPassing++
|
|
case api.HealthWarning:
|
|
sum.ChecksWarning++
|
|
case api.HealthCritical:
|
|
sum.ChecksCritical++
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the services in sorted order
|
|
sort.Slice(services, func(i, j int) bool {
|
|
return services[i].LessThan(&services[j])
|
|
})
|
|
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
|
|
}
|