mirror of https://github.com/status-im/consul.git
Add topology HTTP endpoint
This commit is contained in:
parent
dbbf6b2e46
commit
7c26a71b4b
|
@ -434,7 +434,7 @@ func (s *Store) discoveryChainSources(ws memdb.WatchSet, tx ReadTxn, dc string,
|
||||||
|
|
||||||
// Only return the services that directly target the destination
|
// Only return the services that directly target the destination
|
||||||
seenSource := make(map[structs.ServiceName]bool)
|
seenSource := make(map[structs.ServiceName]bool)
|
||||||
for sn, _ := range seenLink {
|
for sn := range seenLink {
|
||||||
req := discoverychain.CompileRequest{
|
req := discoverychain.CompileRequest{
|
||||||
ServiceName: sn.Name,
|
ServiceName: sn.Name,
|
||||||
EvaluateInNamespace: sn.NamespaceOrDefault(),
|
EvaluateInNamespace: sn.NamespaceOrDefault(),
|
||||||
|
|
|
@ -99,6 +99,7 @@ func init() {
|
||||||
registerEndpoint("/v1/internal/ui/services", []string{"GET"}, (*HTTPHandlers).UIServices)
|
registerEndpoint("/v1/internal/ui/services", []string{"GET"}, (*HTTPHandlers).UIServices)
|
||||||
registerEndpoint("/v1/internal/ui/gateway-services-nodes/", []string{"GET"}, (*HTTPHandlers).UIGatewayServicesNodes)
|
registerEndpoint("/v1/internal/ui/gateway-services-nodes/", []string{"GET"}, (*HTTPHandlers).UIGatewayServicesNodes)
|
||||||
registerEndpoint("/v1/internal/ui/gateway-intentions/", []string{"GET"}, (*HTTPHandlers).UIGatewayIntentions)
|
registerEndpoint("/v1/internal/ui/gateway-intentions/", []string{"GET"}, (*HTTPHandlers).UIGatewayIntentions)
|
||||||
|
registerEndpoint("/v1/internal/ui/service-topology/", []string{"GET"}, (*HTTPHandlers).UIServiceTopology)
|
||||||
registerEndpoint("/v1/internal/acl/authorize", []string{"POST"}, (*HTTPHandlers).ACLAuthorize)
|
registerEndpoint("/v1/internal/acl/authorize", []string{"POST"}, (*HTTPHandlers).ACLAuthorize)
|
||||||
registerEndpoint("/v1/kv/", []string{"GET", "PUT", "DELETE"}, (*HTTPHandlers).KVSEndpoint)
|
registerEndpoint("/v1/kv/", []string{"GET", "PUT", "DELETE"}, (*HTTPHandlers).KVSEndpoint)
|
||||||
registerEndpoint("/v1/operator/raft/configuration", []string{"GET"}, (*HTTPHandlers).OperatorRaftConfiguration)
|
registerEndpoint("/v1/operator/raft/configuration", []string{"GET"}, (*HTTPHandlers).OperatorRaftConfiguration)
|
||||||
|
|
|
@ -16,32 +16,53 @@ import (
|
||||||
// to extract this.
|
// to extract this.
|
||||||
const metaExternalSource = "external-source"
|
const metaExternalSource = "external-source"
|
||||||
|
|
||||||
type GatewayConfig struct {
|
|
||||||
AssociatedServiceCount int `json:",omitempty"`
|
|
||||||
Addresses []string `json:",omitempty"`
|
|
||||||
// internal to track uniqueness
|
|
||||||
addressesSet map[string]struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceSummary is used to summarize a service
|
// ServiceSummary is used to summarize a service
|
||||||
type ServiceSummary struct {
|
type ServiceSummary struct {
|
||||||
Kind structs.ServiceKind `json:",omitempty"`
|
Kind structs.ServiceKind `json:",omitempty"`
|
||||||
Name string
|
Name string
|
||||||
|
Datacenter string
|
||||||
Tags []string
|
Tags []string
|
||||||
Nodes []string
|
Nodes []string
|
||||||
|
ExternalSources []string
|
||||||
|
externalSourceSet map[string]struct{} // internal to track uniqueness
|
||||||
|
checks map[string]*structs.HealthCheck
|
||||||
InstanceCount int
|
InstanceCount int
|
||||||
ChecksPassing int
|
ChecksPassing int
|
||||||
ChecksWarning int
|
ChecksWarning int
|
||||||
ChecksCritical int
|
ChecksCritical int
|
||||||
ExternalSources []string
|
GatewayConfig GatewayConfig
|
||||||
externalSourceSet map[string]struct{} // internal to track uniqueness
|
|
||||||
GatewayConfig GatewayConfig `json:",omitempty"`
|
|
||||||
ConnectedWithProxy bool
|
|
||||||
ConnectedWithGateway bool
|
|
||||||
|
|
||||||
structs.EnterpriseMeta
|
structs.EnterpriseMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ServiceSummary) LessThan(other *ServiceSummary) bool {
|
||||||
|
if s.EnterpriseMeta.LessThan(&other.EnterpriseMeta) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return s.Name < other.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceListingSummary struct {
|
||||||
|
ServiceSummary
|
||||||
|
|
||||||
|
ConnectedWithProxy bool
|
||||||
|
ConnectedWithGateway bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type GatewayConfig struct {
|
||||||
|
AssociatedServiceCount int `json:",omitempty"`
|
||||||
|
Addresses []string `json:",omitempty"`
|
||||||
|
|
||||||
|
// internal to track uniqueness
|
||||||
|
addressesSet map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceTopology struct {
|
||||||
|
Upstreams []*ServiceSummary
|
||||||
|
Downstreams []*ServiceSummary
|
||||||
|
FilteredByACLs bool
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *HTTPHandlers) UINodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) UINodes(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
|
@ -163,9 +184,39 @@ RPC:
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the summary
|
// Store the names of the gateways associated with each service
|
||||||
// TODO (gateways) (freddy) Have Internal.ServiceDump return ServiceDump instead. Need to add bexpr filtering for type.
|
var (
|
||||||
return summarizeServices(out.Nodes.ToServiceDump(), out.Gateways, s.agent.config, args.Datacenter), nil
|
serviceGateways = make(map[structs.ServiceName][]structs.ServiceName)
|
||||||
|
numLinkedServices = make(map[structs.ServiceName]int)
|
||||||
|
)
|
||||||
|
for _, gs := range out.Gateways {
|
||||||
|
serviceGateways[gs.Service] = append(serviceGateways[gs.Service], gs.Gateway)
|
||||||
|
numLinkedServices[gs.Gateway] += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries, hasProxy := summarizeServices(out.Nodes.ToServiceDump(), nil, "")
|
||||||
|
sorted := prepSummaryOutput(summaries, false)
|
||||||
|
|
||||||
|
var result []*ServiceListingSummary
|
||||||
|
for _, svc := range sorted {
|
||||||
|
sum := ServiceListingSummary{ServiceSummary: *svc}
|
||||||
|
|
||||||
|
sn := structs.NewServiceName(svc.Name, &svc.EnterpriseMeta)
|
||||||
|
if hasProxy[sn] {
|
||||||
|
sum.ConnectedWithProxy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that at least one of the gateways linked by config entry has an instance registered in the catalog
|
||||||
|
for _, gw := range serviceGateways[sn] {
|
||||||
|
if s := summaries[gw]; s != nil && sum.InstanceCount > 0 {
|
||||||
|
sum.ConnectedWithGateway = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum.GatewayConfig.AssociatedServiceCount = numLinkedServices[sn]
|
||||||
|
|
||||||
|
result = append(result, &sum)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UIGatewayServices is used to query all the nodes for services associated with a gateway along with their gateway config
|
// UIGatewayServices is used to query all the nodes for services associated with a gateway along with their gateway config
|
||||||
|
@ -200,17 +251,56 @@ RPC:
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return summarizeServices(out.Dump, nil, s.agent.config, args.Datacenter), nil
|
summaries, _ := summarizeServices(out.Dump, s.agent.config, args.Datacenter)
|
||||||
|
return prepSummaryOutput(summaries, false), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (freddy): Refactor to split up for the two use cases
|
func (s *HTTPHandlers) UIServiceTopology(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
func summarizeServices(dump structs.ServiceDump, gateways structs.GatewayServices, cfg *config.RuntimeConfig, dc string) []*ServiceSummary {
|
// Parse arguments
|
||||||
// Collect the summary information
|
args := structs.ServiceSpecificRequest{}
|
||||||
var services []structs.ServiceName
|
if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
|
||||||
summary := make(map[structs.ServiceName]*ServiceSummary)
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err := s.parseEntMeta(req, &args.EnterpriseMeta); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
linkedGateways := make(map[structs.ServiceName][]structs.ServiceName)
|
args.ServiceName = strings.TrimPrefix(req.URL.Path, "/v1/internal/ui/service-topology/")
|
||||||
hasProxy := make(map[structs.ServiceName]bool)
|
if args.ServiceName == "" {
|
||||||
|
resp.WriteHeader(http.StatusBadRequest)
|
||||||
|
fmt.Fprint(resp, "Missing service name")
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the RPC request
|
||||||
|
var out structs.IndexedServiceTopology
|
||||||
|
defer setMeta(resp, &out.QueryMeta)
|
||||||
|
RPC:
|
||||||
|
if err := s.agent.RPC("Internal.ServiceTopology", &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
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreams, _ := summarizeServices(out.ServiceTopology.Upstreams.ToServiceDump(), nil, "")
|
||||||
|
downstreams, _ := summarizeServices(out.ServiceTopology.Downstreams.ToServiceDump(), nil, "")
|
||||||
|
|
||||||
|
sum := ServiceTopology{
|
||||||
|
Upstreams: prepSummaryOutput(upstreams, true),
|
||||||
|
Downstreams: prepSummaryOutput(downstreams, true),
|
||||||
|
FilteredByACLs: out.FilteredByACLs,
|
||||||
|
}
|
||||||
|
return sum, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func summarizeServices(dump structs.ServiceDump, cfg *config.RuntimeConfig, dc string) (map[structs.ServiceName]*ServiceSummary, map[structs.ServiceName]bool) {
|
||||||
|
var (
|
||||||
|
summary = make(map[structs.ServiceName]*ServiceSummary)
|
||||||
|
hasProxy = make(map[structs.ServiceName]bool)
|
||||||
|
)
|
||||||
|
|
||||||
getService := func(service structs.ServiceName) *ServiceSummary {
|
getService := func(service structs.ServiceName) *ServiceSummary {
|
||||||
serv, ok := summary[service]
|
serv, ok := summary[service]
|
||||||
|
@ -223,22 +313,12 @@ func summarizeServices(dump structs.ServiceDump, gateways structs.GatewayService
|
||||||
InstanceCount: 0,
|
InstanceCount: 0,
|
||||||
}
|
}
|
||||||
summary[service] = serv
|
summary[service] = serv
|
||||||
services = append(services, service)
|
|
||||||
}
|
}
|
||||||
return serv
|
return serv
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect the list of services linked to each gateway up front
|
|
||||||
// THis also allows tracking whether a service name is associated with a gateway
|
|
||||||
gsCount := make(map[structs.ServiceName]int)
|
|
||||||
|
|
||||||
for _, gs := range gateways {
|
|
||||||
gsCount[gs.Gateway] += 1
|
|
||||||
linkedGateways[gs.Service] = append(linkedGateways[gs.Service], gs.Gateway)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, csn := range dump {
|
for _, csn := range dump {
|
||||||
if csn.GatewayService != nil {
|
if cfg != nil && csn.GatewayService != nil {
|
||||||
gwsvc := csn.GatewayService
|
gwsvc := csn.GatewayService
|
||||||
sum := getService(gwsvc.Service)
|
sum := getService(gwsvc.Service)
|
||||||
modifySummaryForGatewayService(cfg, dc, sum, gwsvc)
|
modifySummaryForGatewayService(cfg, dc, sum, gwsvc)
|
||||||
|
@ -248,15 +328,27 @@ func summarizeServices(dump structs.ServiceDump, gateways structs.GatewayService
|
||||||
if csn.Service == nil {
|
if csn.Service == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sid := structs.NewServiceName(csn.Service.Service, &csn.Service.EnterpriseMeta)
|
sn := structs.NewServiceName(csn.Service.Service, &csn.Service.EnterpriseMeta)
|
||||||
sum := getService(sid)
|
sum := getService(sn)
|
||||||
|
|
||||||
svc := csn.Service
|
svc := csn.Service
|
||||||
sum.Nodes = append(sum.Nodes, csn.Node.Node)
|
sum.Nodes = append(sum.Nodes, csn.Node.Node)
|
||||||
sum.Kind = svc.Kind
|
sum.Kind = svc.Kind
|
||||||
|
sum.Datacenter = csn.Node.Datacenter
|
||||||
sum.InstanceCount += 1
|
sum.InstanceCount += 1
|
||||||
if svc.Kind == structs.ServiceKindConnectProxy {
|
if svc.Kind == structs.ServiceKindConnectProxy {
|
||||||
hasProxy[structs.NewServiceName(svc.Proxy.DestinationServiceName, &svc.EnterpriseMeta)] = true
|
sn := structs.NewServiceName(svc.Proxy.DestinationServiceName, &svc.EnterpriseMeta)
|
||||||
|
hasProxy[sn] = true
|
||||||
|
|
||||||
|
destination := getService(sn)
|
||||||
|
for _, check := range csn.Checks {
|
||||||
|
cid := structs.NewCheckID(check.CheckID, &check.EnterpriseMeta)
|
||||||
|
uid := structs.UniqueID(csn.Node.Node, cid.String())
|
||||||
|
if destination.checks == nil {
|
||||||
|
destination.checks = make(map[string]*structs.HealthCheck)
|
||||||
|
}
|
||||||
|
destination.checks[uid] = check
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, tag := range svc.Tags {
|
for _, tag := range svc.Tags {
|
||||||
found := false
|
found := false
|
||||||
|
@ -266,7 +358,6 @@ func summarizeServices(dump structs.ServiceDump, gateways structs.GatewayService
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
sum.Tags = append(sum.Tags, tag)
|
sum.Tags = append(sum.Tags, tag)
|
||||||
}
|
}
|
||||||
|
@ -288,7 +379,28 @@ func summarizeServices(dump structs.ServiceDump, gateways structs.GatewayService
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, check := range csn.Checks {
|
for _, check := range csn.Checks {
|
||||||
switch check.Status {
|
cid := structs.NewCheckID(check.CheckID, &check.EnterpriseMeta)
|
||||||
|
uid := structs.UniqueID(csn.Node.Node, cid.String())
|
||||||
|
if sum.checks == nil {
|
||||||
|
sum.checks = make(map[string]*structs.HealthCheck)
|
||||||
|
}
|
||||||
|
sum.checks[uid] = check
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return summary, hasProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepSummaryOutput(summaries map[structs.ServiceName]*ServiceSummary, excludeSidecars bool) []*ServiceSummary {
|
||||||
|
var resp []*ServiceSummary
|
||||||
|
|
||||||
|
// Collect and sort resp for display
|
||||||
|
for _, sum := range summaries {
|
||||||
|
sort.Strings(sum.Nodes)
|
||||||
|
sort.Strings(sum.Tags)
|
||||||
|
|
||||||
|
for _, chk := range sum.checks {
|
||||||
|
switch chk.Status {
|
||||||
case api.HealthPassing:
|
case api.HealthPassing:
|
||||||
sum.ChecksPassing++
|
sum.ChecksPassing++
|
||||||
case api.HealthWarning:
|
case api.HealthWarning:
|
||||||
|
@ -297,34 +409,15 @@ func summarizeServices(dump structs.ServiceDump, gateways structs.GatewayService
|
||||||
sum.ChecksCritical++
|
sum.ChecksCritical++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if excludeSidecars && sum.Kind != structs.ServiceKindTypical {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
resp = append(resp, sum)
|
||||||
// Return the services in sorted order
|
}
|
||||||
sort.Slice(services, func(i, j int) bool {
|
sort.Slice(resp, func(i, j int) bool {
|
||||||
return services[i].LessThan(&services[j])
|
return resp[i].LessThan(resp[j])
|
||||||
})
|
})
|
||||||
|
return resp
|
||||||
output := make([]*ServiceSummary, len(summary))
|
|
||||||
for idx, service := range services {
|
|
||||||
sum := summary[service]
|
|
||||||
if hasProxy[service] {
|
|
||||||
sum.ConnectedWithProxy = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that at least one of the gateways linked by config entry has an instance registered in the catalog
|
|
||||||
for _, gw := range linkedGateways[service] {
|
|
||||||
if s := summary[gw]; s != nil && s.InstanceCount > 0 {
|
|
||||||
sum.ConnectedWithGateway = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sum.GatewayConfig.AssociatedServiceCount = gsCount[service]
|
|
||||||
|
|
||||||
// Sort the nodes and tags
|
|
||||||
sort.Strings(sum.Nodes)
|
|
||||||
sort.Strings(sum.Tags)
|
|
||||||
output[idx] = sum
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifySummaryForGatewayService(
|
func modifySummaryForGatewayService(
|
||||||
|
|
|
@ -223,6 +223,7 @@ func TestUiServices(t *testing.T) {
|
||||||
Service: &structs.NodeService{
|
Service: &structs.NodeService{
|
||||||
Kind: structs.ServiceKindTypical,
|
Kind: structs.ServiceKindTypical,
|
||||||
Service: "api",
|
Service: "api",
|
||||||
|
ID: "api-1",
|
||||||
Tags: []string{"tag1", "tag2"},
|
Tags: []string{"tag1", "tag2"},
|
||||||
},
|
},
|
||||||
Checks: structs.HealthChecks{
|
Checks: structs.HealthChecks{
|
||||||
|
@ -230,18 +231,20 @@ func TestUiServices(t *testing.T) {
|
||||||
Node: "foo",
|
Node: "foo",
|
||||||
Name: "api svc check",
|
Name: "api svc check",
|
||||||
ServiceName: "api",
|
ServiceName: "api",
|
||||||
|
ServiceID: "api-1",
|
||||||
Status: api.HealthWarning,
|
Status: api.HealthWarning,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// register web svc on node foo
|
// register api-proxy svc on node foo
|
||||||
{
|
{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Node: "foo",
|
Node: "foo",
|
||||||
SkipNodeUpdate: true,
|
SkipNodeUpdate: true,
|
||||||
Service: &structs.NodeService{
|
Service: &structs.NodeService{
|
||||||
Kind: structs.ServiceKindConnectProxy,
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
Service: "web",
|
Service: "api-proxy",
|
||||||
|
ID: "api-proxy-1",
|
||||||
Tags: []string{},
|
Tags: []string{},
|
||||||
Meta: map[string]string{metaExternalSource: "k8s"},
|
Meta: map[string]string{metaExternalSource: "k8s"},
|
||||||
Port: 1234,
|
Port: 1234,
|
||||||
|
@ -252,8 +255,9 @@ func TestUiServices(t *testing.T) {
|
||||||
Checks: structs.HealthChecks{
|
Checks: structs.HealthChecks{
|
||||||
&structs.HealthCheck{
|
&structs.HealthCheck{
|
||||||
Node: "foo",
|
Node: "foo",
|
||||||
Name: "web svc check",
|
Name: "api proxy listening",
|
||||||
ServiceName: "web",
|
ServiceName: "api-proxy",
|
||||||
|
ServiceID: "api-proxy-1",
|
||||||
Status: api.HealthPassing,
|
Status: api.HealthPassing,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -264,14 +268,12 @@ func TestUiServices(t *testing.T) {
|
||||||
Node: "bar",
|
Node: "bar",
|
||||||
Address: "127.0.0.2",
|
Address: "127.0.0.2",
|
||||||
Service: &structs.NodeService{
|
Service: &structs.NodeService{
|
||||||
Kind: structs.ServiceKindConnectProxy,
|
Kind: structs.ServiceKindTypical,
|
||||||
Service: "web",
|
Service: "web",
|
||||||
|
ID: "web-1",
|
||||||
Tags: []string{},
|
Tags: []string{},
|
||||||
Meta: map[string]string{metaExternalSource: "k8s"},
|
Meta: map[string]string{metaExternalSource: "k8s"},
|
||||||
Port: 1234,
|
Port: 1234,
|
||||||
Proxy: structs.ConnectProxyConfig{
|
|
||||||
DestinationServiceName: "api",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Checks: []*structs.HealthCheck{
|
Checks: []*structs.HealthCheck{
|
||||||
{
|
{
|
||||||
|
@ -279,6 +281,7 @@ func TestUiServices(t *testing.T) {
|
||||||
Name: "web svc check",
|
Name: "web svc check",
|
||||||
Status: api.HealthCritical,
|
Status: api.HealthCritical,
|
||||||
ServiceName: "web",
|
ServiceName: "web",
|
||||||
|
ServiceID: "web-1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -366,55 +369,67 @@ func TestUiServices(t *testing.T) {
|
||||||
assertIndex(t, resp)
|
assertIndex(t, resp)
|
||||||
|
|
||||||
// Should be 2 nodes, and all the empty lists should be non-nil
|
// Should be 2 nodes, and all the empty lists should be non-nil
|
||||||
summary := obj.([]*ServiceSummary)
|
summary := obj.([]*ServiceListingSummary)
|
||||||
require.Len(t, summary, 5)
|
require.Len(t, summary, 6)
|
||||||
|
|
||||||
// internal accounting that users don't see can be blown away
|
// internal accounting that users don't see can be blown away
|
||||||
for _, sum := range summary {
|
for _, sum := range summary {
|
||||||
sum.externalSourceSet = nil
|
sum.externalSourceSet = nil
|
||||||
|
sum.checks = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []*ServiceSummary{
|
expected := []*ServiceListingSummary{
|
||||||
{
|
{
|
||||||
|
ServiceSummary: ServiceSummary{
|
||||||
Kind: structs.ServiceKindTypical,
|
Kind: structs.ServiceKindTypical,
|
||||||
Name: "api",
|
Name: "api",
|
||||||
|
Datacenter: "dc1",
|
||||||
Tags: []string{"tag1", "tag2"},
|
Tags: []string{"tag1", "tag2"},
|
||||||
Nodes: []string{"foo"},
|
Nodes: []string{"foo"},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 2,
|
ChecksPassing: 2,
|
||||||
ChecksWarning: 1,
|
ChecksWarning: 1,
|
||||||
ChecksCritical: 0,
|
ChecksCritical: 0,
|
||||||
ConnectedWithProxy: true,
|
|
||||||
ConnectedWithGateway: true,
|
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
},
|
},
|
||||||
|
ConnectedWithProxy: true,
|
||||||
|
ConnectedWithGateway: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
|
ServiceSummary: ServiceSummary{
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
Name: "api-proxy",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Tags: nil,
|
||||||
|
Nodes: []string{"foo"},
|
||||||
|
InstanceCount: 1,
|
||||||
|
ChecksPassing: 2,
|
||||||
|
ChecksWarning: 0,
|
||||||
|
ChecksCritical: 0,
|
||||||
|
ExternalSources: []string{"k8s"},
|
||||||
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ServiceSummary: ServiceSummary{
|
||||||
Kind: structs.ServiceKindTypical,
|
Kind: structs.ServiceKindTypical,
|
||||||
Name: "cache",
|
Name: "cache",
|
||||||
|
Datacenter: "dc1",
|
||||||
Tags: nil,
|
Tags: nil,
|
||||||
Nodes: []string{"zip"},
|
Nodes: []string{"zip"},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 0,
|
ChecksPassing: 0,
|
||||||
ChecksWarning: 0,
|
ChecksWarning: 0,
|
||||||
ChecksCritical: 0,
|
ChecksCritical: 0,
|
||||||
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
},
|
||||||
ConnectedWithGateway: true,
|
ConnectedWithGateway: true,
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Kind: structs.ServiceKindConnectProxy,
|
|
||||||
Name: "web",
|
|
||||||
Tags: nil,
|
|
||||||
Nodes: []string{"bar", "foo"},
|
|
||||||
InstanceCount: 2,
|
|
||||||
ChecksPassing: 2,
|
|
||||||
ChecksWarning: 1,
|
|
||||||
ChecksCritical: 1,
|
|
||||||
ExternalSources: []string{"k8s"},
|
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
ServiceSummary: ServiceSummary{
|
||||||
Kind: structs.ServiceKindTypical,
|
Kind: structs.ServiceKindTypical,
|
||||||
Name: "consul",
|
Name: "consul",
|
||||||
|
Datacenter: "dc1",
|
||||||
Tags: nil,
|
Tags: nil,
|
||||||
Nodes: []string{a.Config.NodeName},
|
Nodes: []string{a.Config.NodeName},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
|
@ -423,19 +438,38 @@ func TestUiServices(t *testing.T) {
|
||||||
ChecksCritical: 0,
|
ChecksCritical: 0,
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
|
ServiceSummary: ServiceSummary{
|
||||||
Kind: structs.ServiceKindTerminatingGateway,
|
Kind: structs.ServiceKindTerminatingGateway,
|
||||||
Name: "terminating-gateway",
|
Name: "terminating-gateway",
|
||||||
|
Datacenter: "dc1",
|
||||||
Tags: nil,
|
Tags: nil,
|
||||||
Nodes: []string{"foo"},
|
Nodes: []string{"foo"},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 2,
|
ChecksPassing: 1,
|
||||||
ChecksWarning: 1,
|
ChecksWarning: 0,
|
||||||
|
ChecksCritical: 0,
|
||||||
GatewayConfig: GatewayConfig{AssociatedServiceCount: 2},
|
GatewayConfig: GatewayConfig{AssociatedServiceCount: 2},
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ServiceSummary: ServiceSummary{
|
||||||
|
Kind: structs.ServiceKindTypical,
|
||||||
|
Name: "web",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Tags: nil,
|
||||||
|
Nodes: []string{"bar"},
|
||||||
|
InstanceCount: 1,
|
||||||
|
ChecksPassing: 0,
|
||||||
|
ChecksWarning: 0,
|
||||||
|
ChecksCritical: 1,
|
||||||
|
ExternalSources: []string{"k8s"},
|
||||||
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
require.ElementsMatch(t, expected, summary)
|
require.ElementsMatch(t, expected, summary)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -448,40 +482,47 @@ func TestUiServices(t *testing.T) {
|
||||||
assertIndex(t, resp)
|
assertIndex(t, resp)
|
||||||
|
|
||||||
// Should be 2 nodes, and all the empty lists should be non-nil
|
// Should be 2 nodes, and all the empty lists should be non-nil
|
||||||
summary := obj.([]*ServiceSummary)
|
summary := obj.([]*ServiceListingSummary)
|
||||||
require.Len(t, summary, 2)
|
require.Len(t, summary, 2)
|
||||||
|
|
||||||
// internal accounting that users don't see can be blown away
|
// internal accounting that users don't see can be blown away
|
||||||
for _, sum := range summary {
|
for _, sum := range summary {
|
||||||
sum.externalSourceSet = nil
|
sum.externalSourceSet = nil
|
||||||
|
sum.checks = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := []*ServiceSummary{
|
expected := []*ServiceListingSummary{
|
||||||
{
|
{
|
||||||
|
ServiceSummary: ServiceSummary{
|
||||||
Kind: structs.ServiceKindTypical,
|
Kind: structs.ServiceKindTypical,
|
||||||
Name: "api",
|
Name: "api",
|
||||||
|
Datacenter: "dc1",
|
||||||
Tags: []string{"tag1", "tag2"},
|
Tags: []string{"tag1", "tag2"},
|
||||||
Nodes: []string{"foo"},
|
Nodes: []string{"foo"},
|
||||||
InstanceCount: 1,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 2,
|
ChecksPassing: 1,
|
||||||
ChecksWarning: 1,
|
ChecksWarning: 1,
|
||||||
ChecksCritical: 0,
|
ChecksCritical: 0,
|
||||||
ConnectedWithProxy: true,
|
|
||||||
ConnectedWithGateway: false,
|
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
},
|
},
|
||||||
|
ConnectedWithProxy: false,
|
||||||
|
ConnectedWithGateway: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Kind: structs.ServiceKindConnectProxy,
|
ServiceSummary: ServiceSummary{
|
||||||
|
Kind: structs.ServiceKindTypical,
|
||||||
Name: "web",
|
Name: "web",
|
||||||
|
Datacenter: "dc1",
|
||||||
Tags: nil,
|
Tags: nil,
|
||||||
Nodes: []string{"bar", "foo"},
|
Nodes: []string{"bar"},
|
||||||
InstanceCount: 2,
|
InstanceCount: 1,
|
||||||
ChecksPassing: 2,
|
ChecksPassing: 0,
|
||||||
ChecksWarning: 1,
|
ChecksWarning: 0,
|
||||||
ChecksCritical: 1,
|
ChecksCritical: 1,
|
||||||
ExternalSources: []string{"k8s"},
|
ExternalSources: []string{"k8s"},
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
require.ElementsMatch(t, expected, summary)
|
require.ElementsMatch(t, expected, summary)
|
||||||
})
|
})
|
||||||
|
@ -582,7 +623,14 @@ func TestUIGatewayServiceNodes_Terminating(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assertIndex(t, resp)
|
assertIndex(t, resp)
|
||||||
|
|
||||||
dump := obj.([]*ServiceSummary)
|
summary := obj.([]*ServiceSummary)
|
||||||
|
|
||||||
|
// internal accounting that users don't see can be blown away
|
||||||
|
for _, sum := range summary {
|
||||||
|
sum.externalSourceSet = nil
|
||||||
|
sum.checks = nil
|
||||||
|
}
|
||||||
|
|
||||||
expect := []*ServiceSummary{
|
expect := []*ServiceSummary{
|
||||||
{
|
{
|
||||||
Name: "redis",
|
Name: "redis",
|
||||||
|
@ -590,6 +638,7 @@ func TestUIGatewayServiceNodes_Terminating(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "db",
|
Name: "db",
|
||||||
|
Datacenter: "dc1",
|
||||||
Tags: []string{"backup", "primary"},
|
Tags: []string{"backup", "primary"},
|
||||||
Nodes: []string{"bar", "baz"},
|
Nodes: []string{"bar", "baz"},
|
||||||
InstanceCount: 2,
|
InstanceCount: 2,
|
||||||
|
@ -599,7 +648,7 @@ func TestUIGatewayServiceNodes_Terminating(t *testing.T) {
|
||||||
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert.ElementsMatch(t, expect, dump)
|
assert.ElementsMatch(t, expect, summary)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUIGatewayServiceNodes_Ingress(t *testing.T) {
|
func TestUIGatewayServiceNodes_Ingress(t *testing.T) {
|
||||||
|
@ -748,6 +797,7 @@ func TestUIGatewayServiceNodes_Ingress(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "db",
|
Name: "db",
|
||||||
|
Datacenter: "dc1",
|
||||||
Tags: []string{"backup", "primary"},
|
Tags: []string{"backup", "primary"},
|
||||||
Nodes: []string{"bar", "baz"},
|
Nodes: []string{"bar", "baz"},
|
||||||
InstanceCount: 2,
|
InstanceCount: 2,
|
||||||
|
@ -767,6 +817,7 @@ func TestUIGatewayServiceNodes_Ingress(t *testing.T) {
|
||||||
// internal accounting that users don't see can be blown away
|
// internal accounting that users don't see can be blown away
|
||||||
for _, sum := range dump {
|
for _, sum := range dump {
|
||||||
sum.GatewayConfig.addressesSet = nil
|
sum.GatewayConfig.addressesSet = nil
|
||||||
|
sum.checks = nil
|
||||||
}
|
}
|
||||||
assert.ElementsMatch(t, expect, dump)
|
assert.ElementsMatch(t, expect, dump)
|
||||||
}
|
}
|
||||||
|
@ -878,3 +929,386 @@ func TestUIEndpoint_modifySummaryForGatewayService_UseRequestedDCInsteadOfConfig
|
||||||
expected := serviceCanonicalDNSName("test", "ingress", "dc2", "consul", nil) + ":42"
|
expected := serviceCanonicalDNSName("test", "ingress", "dc2", "consul", nil) + ":42"
|
||||||
require.Equal(t, expected, sum.GatewayConfig.Addresses[0])
|
require.Equal(t, expected, sum.GatewayConfig.Addresses[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUIServiceTopology(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
a := NewTestAgent(t, "")
|
||||||
|
defer a.Shutdown()
|
||||||
|
|
||||||
|
// Register terminating gateway and config entry linking it to postgres + redis
|
||||||
|
{
|
||||||
|
registrations := map[string]*structs.RegisterRequest{
|
||||||
|
"Node foo": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
Address: "127.0.0.2",
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "foo",
|
||||||
|
CheckID: "foo:alive",
|
||||||
|
Name: "foo-liveness",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Service api on foo": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
SkipNodeUpdate: true,
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindTypical,
|
||||||
|
ID: "api",
|
||||||
|
Service: "api",
|
||||||
|
Port: 9090,
|
||||||
|
Address: "198.18.1.2",
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "foo",
|
||||||
|
CheckID: "foo:api",
|
||||||
|
Name: "api-liveness",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
ServiceID: "api",
|
||||||
|
ServiceName: "api",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Service api-proxy": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
SkipNodeUpdate: true,
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
ID: "api-proxy",
|
||||||
|
Service: "api-proxy",
|
||||||
|
Port: 8443,
|
||||||
|
Address: "198.18.1.2",
|
||||||
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
DestinationServiceName: "api",
|
||||||
|
Upstreams: structs.Upstreams{
|
||||||
|
{
|
||||||
|
DestinationName: "web",
|
||||||
|
LocalBindPort: 8080,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "foo",
|
||||||
|
CheckID: "foo:api-proxy",
|
||||||
|
Name: "api proxy listening",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
ServiceID: "api-proxy",
|
||||||
|
ServiceName: "api-proxy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Node bar": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "bar",
|
||||||
|
Address: "127.0.0.3",
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "bar",
|
||||||
|
CheckID: "bar:alive",
|
||||||
|
Name: "bar-liveness",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Service web on bar": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "bar",
|
||||||
|
SkipNodeUpdate: true,
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindTypical,
|
||||||
|
ID: "web",
|
||||||
|
Service: "web",
|
||||||
|
Port: 80,
|
||||||
|
Address: "198.18.1.20",
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "bar",
|
||||||
|
CheckID: "bar:web",
|
||||||
|
Name: "web-liveness",
|
||||||
|
Status: api.HealthWarning,
|
||||||
|
ServiceID: "web",
|
||||||
|
ServiceName: "web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Service web-proxy on bar": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "bar",
|
||||||
|
SkipNodeUpdate: true,
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
ID: "web-proxy",
|
||||||
|
Service: "web-proxy",
|
||||||
|
Port: 8443,
|
||||||
|
Address: "198.18.1.20",
|
||||||
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
DestinationServiceName: "web",
|
||||||
|
Upstreams: structs.Upstreams{
|
||||||
|
{
|
||||||
|
DestinationName: "redis",
|
||||||
|
LocalBindPort: 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "bar",
|
||||||
|
CheckID: "bar:web-proxy",
|
||||||
|
Name: "web proxy listening",
|
||||||
|
Status: api.HealthCritical,
|
||||||
|
ServiceID: "web-proxy",
|
||||||
|
ServiceName: "web-proxy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Node baz": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "baz",
|
||||||
|
Address: "127.0.0.4",
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "baz",
|
||||||
|
CheckID: "baz:alive",
|
||||||
|
Name: "baz-liveness",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Service web on baz": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "baz",
|
||||||
|
SkipNodeUpdate: true,
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindTypical,
|
||||||
|
ID: "web",
|
||||||
|
Service: "web",
|
||||||
|
Port: 80,
|
||||||
|
Address: "198.18.1.40",
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "baz",
|
||||||
|
CheckID: "baz:web",
|
||||||
|
Name: "web-liveness",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
ServiceID: "web",
|
||||||
|
ServiceName: "web",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Service web-proxy on baz": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "baz",
|
||||||
|
SkipNodeUpdate: true,
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
ID: "web-proxy",
|
||||||
|
Service: "web-proxy",
|
||||||
|
Port: 8443,
|
||||||
|
Address: "198.18.1.40",
|
||||||
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
DestinationServiceName: "web",
|
||||||
|
Upstreams: structs.Upstreams{
|
||||||
|
{
|
||||||
|
DestinationName: "redis",
|
||||||
|
LocalBindPort: 123,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "baz",
|
||||||
|
CheckID: "baz:web-proxy",
|
||||||
|
Name: "web proxy listening",
|
||||||
|
Status: api.HealthCritical,
|
||||||
|
ServiceID: "web-proxy",
|
||||||
|
ServiceName: "web-proxy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Node zip": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "zip",
|
||||||
|
Address: "127.0.0.5",
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "zip",
|
||||||
|
CheckID: "zip:alive",
|
||||||
|
Name: "zip-liveness",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Service redis on zip": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "zip",
|
||||||
|
SkipNodeUpdate: true,
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindTypical,
|
||||||
|
ID: "redis",
|
||||||
|
Service: "redis",
|
||||||
|
Port: 6379,
|
||||||
|
Address: "198.18.1.60",
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "zip",
|
||||||
|
CheckID: "zip:redis",
|
||||||
|
Name: "redis-liveness",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
ServiceID: "redis",
|
||||||
|
ServiceName: "redis",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Service redis-proxy on zip": {
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "zip",
|
||||||
|
SkipNodeUpdate: true,
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
ID: "redis-proxy",
|
||||||
|
Service: "redis-proxy",
|
||||||
|
Port: 8443,
|
||||||
|
Address: "198.18.1.60",
|
||||||
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
DestinationServiceName: "redis",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
&structs.HealthCheck{
|
||||||
|
Node: "zip",
|
||||||
|
CheckID: "zip:redis-proxy",
|
||||||
|
Name: "redis proxy listening",
|
||||||
|
Status: api.HealthCritical,
|
||||||
|
ServiceID: "redis-proxy",
|
||||||
|
ServiceName: "redis-proxy",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, args := range registrations {
|
||||||
|
var out struct{}
|
||||||
|
require.NoError(t, a.RPC("Catalog.Register", args, &out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("api", func(t *testing.T) {
|
||||||
|
// Request topology for api
|
||||||
|
req, _ := http.NewRequest("GET", "/v1/internal/ui/service-topology/api", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.UIServiceTopology(resp, req)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assertIndex(t, resp)
|
||||||
|
|
||||||
|
expect := ServiceTopology{
|
||||||
|
Upstreams: []*ServiceSummary{
|
||||||
|
{
|
||||||
|
Name: "web",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Nodes: []string{"bar", "baz"},
|
||||||
|
InstanceCount: 2,
|
||||||
|
ChecksPassing: 3,
|
||||||
|
ChecksWarning: 1,
|
||||||
|
ChecksCritical: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FilteredByACLs: false,
|
||||||
|
}
|
||||||
|
result := obj.(ServiceTopology)
|
||||||
|
|
||||||
|
// Internal accounting that is not returned in JSON response
|
||||||
|
for _, u := range result.Upstreams {
|
||||||
|
u.externalSourceSet = nil
|
||||||
|
u.checks = nil
|
||||||
|
}
|
||||||
|
require.Equal(t, expect, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("web", func(t *testing.T) {
|
||||||
|
// Request topology for web
|
||||||
|
req, _ := http.NewRequest("GET", "/v1/internal/ui/service-topology/web", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.UIServiceTopology(resp, req)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assertIndex(t, resp)
|
||||||
|
|
||||||
|
expect := ServiceTopology{
|
||||||
|
Upstreams: []*ServiceSummary{
|
||||||
|
{
|
||||||
|
Name: "redis",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Nodes: []string{"zip"},
|
||||||
|
InstanceCount: 1,
|
||||||
|
ChecksPassing: 2,
|
||||||
|
ChecksCritical: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Downstreams: []*ServiceSummary{
|
||||||
|
{
|
||||||
|
Name: "api",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Nodes: []string{"foo"},
|
||||||
|
InstanceCount: 1,
|
||||||
|
ChecksPassing: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FilteredByACLs: false,
|
||||||
|
}
|
||||||
|
result := obj.(ServiceTopology)
|
||||||
|
|
||||||
|
// Internal accounting that is not returned in JSON response
|
||||||
|
for _, u := range result.Upstreams {
|
||||||
|
u.externalSourceSet = nil
|
||||||
|
u.checks = nil
|
||||||
|
}
|
||||||
|
for _, d := range result.Downstreams {
|
||||||
|
d.externalSourceSet = nil
|
||||||
|
d.checks = nil
|
||||||
|
}
|
||||||
|
require.Equal(t, expect, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("redis", func(t *testing.T) {
|
||||||
|
// Request topology for redis
|
||||||
|
req, _ := http.NewRequest("GET", "/v1/internal/ui/service-topology/redis", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := a.srv.UIServiceTopology(resp, req)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assertIndex(t, resp)
|
||||||
|
|
||||||
|
expect := ServiceTopology{
|
||||||
|
Downstreams: []*ServiceSummary{
|
||||||
|
{
|
||||||
|
Name: "web",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Nodes: []string{"bar", "baz"},
|
||||||
|
InstanceCount: 2,
|
||||||
|
ChecksPassing: 3,
|
||||||
|
ChecksWarning: 1,
|
||||||
|
ChecksCritical: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
FilteredByACLs: false,
|
||||||
|
}
|
||||||
|
result := obj.(ServiceTopology)
|
||||||
|
|
||||||
|
// Internal accounting that is not returned in JSON response
|
||||||
|
for _, d := range result.Downstreams {
|
||||||
|
d.externalSourceSet = nil
|
||||||
|
d.checks = nil
|
||||||
|
}
|
||||||
|
require.Equal(t, expect, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue