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 {
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
}