Support for connect native services in topology view. (#12098)

This commit is contained in:
Florian Apolloner 2022-02-16 22:51:54 +01:00 committed by GitHub
parent c17aa3720f
commit f01f00fc84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 237 additions and 8 deletions

3
.changelog/12098.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Support connect-native services in the Topology view.
```

View File

@ -3461,6 +3461,7 @@ func (s *Store) ServiceTopology(
err error err error
fullyTransparent bool fullyTransparent bool
hasTransparent bool hasTransparent bool
connectNative bool
) )
switch kind { switch kind {
case structs.ServiceKindIngressGateway: case structs.ServiceKindIngressGateway:
@ -3505,6 +3506,9 @@ func (s *Store) ServiceTopology(
// transparent proxy mode. If ANY instance isn't in the right mode then the warming applies. // transparent proxy mode. If ANY instance isn't in the right mode then the warming applies.
fullyTransparent = false fullyTransparent = false
} }
if proxy.ServiceConnect.Native {
connectNative = true
}
} }
default: default:
@ -3526,8 +3530,8 @@ func (s *Store) ServiceTopology(
upstreamDecisions := make(map[string]structs.IntentionDecisionSummary) upstreamDecisions := make(map[string]structs.IntentionDecisionSummary)
// Only transparent proxies have upstreams from intentions // Only transparent proxies / connect native services have upstreams from intentions
if hasTransparent { if hasTransparent || connectNative {
idx, intentionUpstreams, err := s.intentionTopologyTxn(tx, ws, sn, false, defaultAllow) idx, intentionUpstreams, err := s.intentionTopologyTxn(tx, ws, sn, false, defaultAllow)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
@ -3607,8 +3611,8 @@ func (s *Store) ServiceTopology(
sn = structs.NewServiceName(upstream.Service.Proxy.DestinationServiceName, &upstream.Service.EnterpriseMeta) sn = structs.NewServiceName(upstream.Service.Proxy.DestinationServiceName, &upstream.Service.EnterpriseMeta)
} }
// Avoid returning upstreams from intentions when none of the proxy instances of the target are in transparent mode. // Avoid returning upstreams from intentions when none of the proxy instances of the target are in transparent mode or connect native.
if !hasTransparent && upstreamSources[sn.String()] != structs.TopologySourceRegistration { if !hasTransparent && !connectNative && upstreamSources[sn.String()] != structs.TopologySourceRegistration {
continue continue
} }
upstreams = append(upstreams, upstream) upstreams = append(upstreams, upstream)
@ -3711,6 +3715,7 @@ func (s *Store) ServiceTopology(
} }
idx, unfilteredDownstreams, err := s.combinedServiceNodesTxn(tx, ws, downstreamNames) idx, unfilteredDownstreams, err := s.combinedServiceNodesTxn(tx, ws, downstreamNames)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed to get downstreams for %q: %v", sn.String(), err) return 0, nil, fmt.Errorf("failed to get downstreams for %q: %v", sn.String(), err)
} }
@ -3734,8 +3739,8 @@ func (s *Store) ServiceTopology(
if downstream.Service.Kind == structs.ServiceKindConnectProxy { if downstream.Service.Kind == structs.ServiceKindConnectProxy {
sn = structs.NewServiceName(downstream.Service.Proxy.DestinationServiceName, &downstream.Service.EnterpriseMeta) sn = structs.NewServiceName(downstream.Service.Proxy.DestinationServiceName, &downstream.Service.EnterpriseMeta)
} }
if _, ok := tproxyMap[sn]; !ok && downstreamSources[sn.String()] != structs.TopologySourceRegistration { if _, ok := tproxyMap[sn]; !ok && !downstream.Service.Connect.Native && downstreamSources[sn.String()] != structs.TopologySourceRegistration {
// If downstream is not a transparent proxy, remove references // If downstream is not a transparent proxy or connect native, remove references
delete(downstreamSources, sn.String()) delete(downstreamSources, sn.String())
delete(downstreamDecisions, sn.String()) delete(downstreamDecisions, sn.String())
continue continue

View File

@ -35,6 +35,7 @@ type ServiceSummary struct {
GatewayConfig GatewayConfig GatewayConfig GatewayConfig
TransparentProxy bool TransparentProxy bool
transparentProxySet bool transparentProxySet bool
ConnectNative bool
structs.EnterpriseMeta structs.EnterpriseMeta
} }
@ -422,6 +423,7 @@ func summarizeServices(dump structs.ServiceDump, cfg *config.RuntimeConfig, dc s
sum.Kind = svc.Kind sum.Kind = svc.Kind
sum.Datacenter = csn.Node.Datacenter sum.Datacenter = csn.Node.Datacenter
sum.InstanceCount += 1 sum.InstanceCount += 1
sum.ConnectNative = svc.Connect.Native
if svc.Kind == structs.ServiceKindConnectProxy { if svc.Kind == structs.ServiceKindConnectProxy {
sn := structs.NewServiceName(svc.Proxy.DestinationServiceName, &svc.EnterpriseMeta) sn := structs.NewServiceName(svc.Proxy.DestinationServiceName, &svc.EnterpriseMeta)
hasProxy[sn] = true hasProxy[sn] = true

View File

@ -1281,6 +1281,142 @@ func TestUIServiceTopology(t *testing.T) {
}, },
}, },
}, },
"Node cnative": {
Datacenter: "dc1",
Node: "cnative",
Address: "127.0.0.6",
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "cnative",
CheckID: "cnative:alive",
Name: "cnative-liveness",
Status: api.HealthPassing,
},
},
},
"Service cbackend on cnative": {
Datacenter: "dc1",
Node: "cnative",
SkipNodeUpdate: true,
Service: &structs.NodeService{
Kind: structs.ServiceKindTypical,
ID: "cbackend",
Service: "cbackend",
Port: 8080,
Address: "198.18.1.70",
},
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "cnative",
CheckID: "cnative:cbackend",
Name: "cbackend-liveness",
Status: api.HealthPassing,
ServiceID: "cbackend",
ServiceName: "cbackend",
},
},
},
"Service cbackend-proxy on cnative": {
Datacenter: "dc1",
Node: "cnative",
SkipNodeUpdate: true,
Service: &structs.NodeService{
Kind: structs.ServiceKindConnectProxy,
ID: "cbackend-proxy",
Service: "cbackend-proxy",
Port: 8443,
Address: "198.18.1.70",
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "cbackend",
},
},
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "cnative",
CheckID: "cnative:cbackend-proxy",
Name: "cbackend proxy listening",
Status: api.HealthCritical,
ServiceID: "cbackend-proxy",
ServiceName: "cbackend-proxy",
},
},
},
"Service cfrontend on cnative": {
Datacenter: "dc1",
Node: "cnative",
SkipNodeUpdate: true,
Service: &structs.NodeService{
Kind: structs.ServiceKindTypical,
ID: "cfrontend",
Service: "cfrontend",
Port: 9080,
Address: "198.18.1.70",
},
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "cnative",
CheckID: "cnative:cfrontend",
Name: "cfrontend-liveness",
Status: api.HealthPassing,
ServiceID: "cfrontend",
ServiceName: "cfrontend",
},
},
},
"Service cfrontend-proxy on cnative": {
Datacenter: "dc1",
Node: "cnative",
SkipNodeUpdate: true,
Service: &structs.NodeService{
Kind: structs.ServiceKindConnectProxy,
ID: "cfrontend-proxy",
Service: "cfrontend-proxy",
Port: 9443,
Address: "198.18.1.70",
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "cfrontend",
Upstreams: structs.Upstreams{
{
DestinationName: "cproxy",
LocalBindPort: 123,
},
},
},
},
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "cnative",
CheckID: "cnative:cfrontend-proxy",
Name: "cfrontend proxy listening",
Status: api.HealthCritical,
ServiceID: "cfrontend-proxy",
ServiceName: "cfrontend-proxy",
},
},
},
"Service cproxy on cnative": {
Datacenter: "dc1",
Node: "cnative",
SkipNodeUpdate: true,
Service: &structs.NodeService{
Kind: structs.ServiceKindTypical,
ID: "cproxy",
Service: "cproxy",
Port: 1111,
Address: "198.18.1.70",
Connect: structs.ServiceConnect{Native: true},
},
Checks: structs.HealthChecks{
&structs.HealthCheck{
Node: "cnative",
CheckID: "cnative:cproxy",
Name: "cproxy-liveness",
Status: api.HealthPassing,
ServiceID: "cproxy",
ServiceName: "cproxy",
},
},
},
} }
for _, args := range registrations { for _, args := range registrations {
var out struct{} var out struct{}
@ -1292,6 +1428,8 @@ func TestUIServiceTopology(t *testing.T) {
// wildcard deny intention // wildcard deny intention
// api -> web exact intention // api -> web exact intention
// web -> redis exact intention // web -> redis exact intention
// cfrontend -> cproxy exact intention
// cproxy -> cbackend exact intention
{ {
entries := []structs.ConfigEntryRequest{ entries := []structs.ConfigEntryRequest{
{ {
@ -1391,6 +1529,32 @@ func TestUIServiceTopology(t *testing.T) {
}, },
}, },
}, },
{
Datacenter: "dc1",
Entry: &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "cproxy",
Sources: []*structs.SourceIntention{
{
Action: structs.IntentionActionAllow,
Name: "cfrontend",
},
},
},
},
{
Datacenter: "dc1",
Entry: &structs.ServiceIntentionsConfigEntry{
Kind: structs.ServiceIntentions,
Name: "cbackend",
Sources: []*structs.SourceIntention{
{
Action: structs.IntentionActionAllow,
Name: "cproxy",
},
},
},
},
} }
for _, req := range entries { for _, req := range entries {
out := false out := false
@ -1620,6 +1784,60 @@ func TestUIServiceTopology(t *testing.T) {
FilteredByACLs: false, FilteredByACLs: false,
}, },
}, },
{
name: "cproxy",
httpReq: func() *http.Request {
req, _ := http.NewRequest("GET", "/v1/internal/ui/service-topology/cproxy?kind=", nil)
return req
}(),
want: &ServiceTopology{
Protocol: "http",
TransparentProxy: false,
Upstreams: []*ServiceTopologySummary{
{
ServiceSummary: ServiceSummary{
Name: "cbackend",
Datacenter: "dc1",
Nodes: []string{"cnative"},
InstanceCount: 1,
ChecksPassing: 2,
ChecksWarning: 0,
ChecksCritical: 1,
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
},
Intention: structs.IntentionDecisionSummary{
DefaultAllow: true,
Allowed: true,
HasPermissions: false,
HasExact: true,
},
Source: structs.TopologySourceSpecificIntention,
},
},
Downstreams: []*ServiceTopologySummary{
{
ServiceSummary: ServiceSummary{
Name: "cfrontend",
Datacenter: "dc1",
Nodes: []string{"cnative"},
InstanceCount: 1,
ChecksPassing: 2,
ChecksWarning: 0,
ChecksCritical: 1,
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
},
Intention: structs.IntentionDecisionSummary{
DefaultAllow: true,
Allowed: true,
HasPermissions: false,
HasExact: true,
},
Source: structs.TopologySourceRegistration,
},
},
FilteredByACLs: false,
},
},
} }
for _, tc := range tcs { for _, tc := range tcs {

View File

@ -92,7 +92,7 @@
@disabled={{disabled}} @disabled={{disabled}}
@oncreate={{action @oncreate item @service}} @oncreate={{action @oncreate item @service}}
/> />
{{else if (and (not-eq item.Datacenter '') item.Intention.Allowed (not item.TransparentProxy) (eq item.Source 'specific-intention'))}} {{else if (and (not-eq item.Datacenter '') item.Intention.Allowed (not item.TransparentProxy) (not item.ConnectNative) (eq item.Source 'specific-intention'))}}
<TopologyMetrics::Popover <TopologyMetrics::Popover
@type='not-defined' @type='not-defined'
@service={{@service}} @service={{@service}}

View File

@ -14,6 +14,7 @@ export default class Topology extends Model {
@attr('string') Protocol; @attr('string') Protocol;
@attr('boolean') FilteredByACLs; @attr('boolean') FilteredByACLs;
@attr('boolean') TransparentProxy; @attr('boolean') TransparentProxy;
@attr('boolean') ConnectNative;
@attr() Upstreams; // Service[] @attr() Upstreams; // Service[]
@attr() Downstreams; // Service[], @attr() Downstreams; // Service[],
@attr() meta; // {} @attr() meta; // {}
@ -25,7 +26,7 @@ export default class Topology extends Model {
undefinedDownstream = undefinedDownstream =
this.Downstreams.filter( this.Downstreams.filter(
item => item =>
item.Source === 'specific-intention' && !item.TransparentProxy && item.Intention.Allowed item.Source === 'specific-intention' && !item.TransparentProxy && !item.ConnectNative && item.Intention.Allowed
).length !== 0; ).length !== 0;
return undefinedDownstream; return undefinedDownstream;