Skip to content

Commit

Permalink
feature(zones): add zone traffic drawers (#3509)
Browse files Browse the repository at this point in the history
This PR replicates the Dataplane Traffic drawers from over in the
dataplanes section into both ZoneIngress and ZoneEgress.

I began by trying to make the code used for Dataplanes more reusable so
I coulduse it for both ZoneIngress and ZoneEgress. I found that for
several reasons this was overly complicated to do and would have
required quite a lot of change within Dataplanes also.

For instance:

- explicitly named route params such as `params.dataPlane`,
`params.zoneIngress` and `params.zoneEgress`
- explicitly named route props such as `props.zoneIngress` and
`props.zoneEgress`
- explicitly named source URIs `zone-ingress`/`zone-egress` (this one
was relatively easy to address and is addressed in this PR)
- Dataplane Proxy URIs require a `mesh` specifying whereas Zone Proxy
URIs don't
- ...probably more edge cases

Therefore I chose to mostly copy/paste over the approach from Dataplanes
to ZoneIngress, and then again to ZoneEgress (ZoneIngress and ZoneEgress
modules are mostly copy pasted anyway).

---------

Signed-off-by: John Cowen <[email protected]>
  • Loading branch information
johncowen authored Feb 5, 2025
1 parent 3bbe7ab commit e99bf6c
Show file tree
Hide file tree
Showing 32 changed files with 963 additions and 177 deletions.
49 changes: 48 additions & 1 deletion packages/kuma-gui/src/app/connections/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,54 @@ export const networking = (prefix: string) => {

]
}

export const zones = (prefix: string): RouteRecordRaw[] => {
return [
{
path: 'inbound/:connection',
name: `${prefix}-connection-inbound-summary-view`,
component: () => import('@/app/connections/views/zones/ConnectionInboundSummaryView.vue'),
children: [
{
path: 'stats',
name: `${prefix}-connection-inbound-summary-stats-view`,
component: () => import('@/app/connections/views/zones/ConnectionInboundSummaryStatsView.vue'),
},
{
path: 'clusters',
name: `${prefix}-connection-inbound-summary-clusters-view`,
component: () => import('@/app/connections/views/zones/ConnectionInboundSummaryClustersView.vue'),
},
{
path: 'xds-config',
name: `${prefix}-connection-inbound-summary-xds-config-view`,
component: () => import('@/app/connections/views/zones/ConnectionInboundSummaryXdsConfigView.vue'),
},
],
},
{
path: 'outbound/:connection',
name: `${prefix}-connection-outbound-summary-view`,
component: () => import('@/app/connections/views/zones/ConnectionOutboundSummaryView.vue'),
children: [
{
path: 'stats',
name: `${prefix}-connection-outbound-summary-stats-view`,
component: () => import('@/app/connections/views/zones/ConnectionOutboundSummaryStatsView.vue'),
},
{
path: 'clusters',
name: `${prefix}-connection-outbound-summary-clusters-view`,
component: () => import('@/app/connections/views/zones/ConnectionOutboundSummaryClustersView.vue'),
},
{
path: 'xds-config',
name: `${prefix}-connection-outbound-summary-xds-config-view`,
component: () => import('@/app/connections/views/zones/ConnectionOutboundSummaryXdsConfigView.vue'),
},
],
},
]
}
export const routes = (prefix: string): RouteRecordRaw[] => {
return [
{
Expand Down
126 changes: 108 additions & 18 deletions packages/kuma-gui/src/app/connections/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,23 @@ const filter = (data: Record<string, unknown>, cb: (key: string, arr: unknown[])
}
export const sources = (source: Source, api: KumaApi) => {
return defineSources({
'/connections/stats/for/zone-ingress/:name/:socketAddress': async (params) => {
const { name, socketAddress } = params
const res = await api.getZoneIngressData({
zoneIngressName: name,
dataPath: 'stats',
})
'/connections/stats/for/:proxyType/:name/:socketAddress': async (params) => {
const { name, socketAddress, proxyType } = params

const res = await (() => {
switch (proxyType) {
case 'zone-ingress':
return api.getZoneIngressStats({
name,
})
case 'zone-egress':
return api.getZoneEgressStats({
name,
})
default:
throw new Error('incorrect value for proxyType')
}
})()
const connections = ConnectionCollection.fromObject(Stat.fromCollection(res))
return {
inbounds: Object.fromEntries(Object.entries(connections.listener).filter(([key, _value]) => key.startsWith(socketAddress.replace(':', '_')))),
Expand All @@ -58,19 +69,98 @@ export const sources = (source: Source, api: KumaApi) => {
}
},

'/connections/stats/for/zone-egress/:name/:socketAddress': async (params) => {
const { name, socketAddress } = params
const res = await api.getZoneEgressData({
zoneEgressName: name,
dataPath: 'stats',
'/connections/clusters/for/:proxyType/:name': async (params) => {
const { name, proxyType } = params
const res = await (() => {
switch (proxyType) {
case 'zone-ingress':
return api.getZoneIngressClusters({
name,
})
case 'zone-egress':
return api.getZoneEgressClusters({
name,
})
default:
throw new Error('incorrect value for proxyType')
}
})()
return res
},

'/connections/xds/for/:proxyType/:name/outbound/:outbound/endpoints/:endpoints': async (params) => {
const { name, outbound, endpoints, proxyType } = params

const res = await (() => {
switch (proxyType) {
case 'zone-ingress':
return api.getZoneIngressXds({
name,
}, {
include_eds: endpoints,
})
case 'zone-egress':
return api.getZoneEgressXds({
name,
}, {
include_eds: endpoints,
})
default:
throw new Error('incorrect value for proxyType')
}
})()

return filter(res, (key: string, arr: unknown[]) => {
switch (key) {
case 'dynamic_listeners':
// this one won't work yet see
// https://github.com/kumahq/kuma/issues/12093
// dynamic_listeners[].name === 'outbound:<outbound>'
return arr.filter(item => prop(item, 'name') && item.name === `outbound:${outbound}`)
case 'dynamic_active_clusters':
// dynamic_active_clusters[].cluster.name === outbound
return arr.filter(item => prop(item, 'cluster') && prop(item.cluster, 'name') && item.cluster?.name === outbound)
case 'dynamic_endpoint_configs':
// dynamic_endpoint_configs[].endpoint_config.cluster_name === outbound
return arr.filter(item => prop(item, 'endpoint_config') && prop(item.endpoint_config, 'cluster_name') && item.endpoint_config?.cluster_name === outbound)
}
return []
})
},
'/connections/xds/for/:proxyType/:name/inbound/:inbound': async (params) => {
const { name, inbound, proxyType } = params

// we don't ask for endpoints because we don't need them for inbound filtering
const res = await (() => {
switch (proxyType) {
case 'zone-ingress':
return api.getZoneIngressXds({
name,
}, {
include_eds: false,
})
case 'zone-egress':
return api.getZoneEgressXds({
name,
}, {
include_eds: false,
})
default:
throw new Error('incorrect value for proxyType')
}
})()

return filter(res, (key: string, arr: unknown[]) => {
switch (key) {
case 'dynamic_listeners':
// dynamic_listeners[].name === 'inbound:<ignored>:0000'
return arr.filter((item = {}) => prop(item, 'name') && typeof item.name === 'string' && item.name.startsWith('inbound:') && item.name?.endsWith(`:${inbound}`))
case 'dynamic_active_clusters':
// dynamic_active_clusters[].cluster.name === '<ignored>:0000'
return arr.filter(item => prop(item, 'cluster') && prop(item.cluster, 'name') && typeof item.cluster.name === 'string' && item.cluster?.name?.endsWith(`:${inbound}`))
}
return []
})
const connections = ConnectionCollection.fromObject(Stat.fromCollection(res))
return {
inbounds: Object.fromEntries(Object.entries(connections.listener).filter(([key, _value]) => key.startsWith(socketAddress.replace(':', '_')))),
outbounds: connections.cluster,
$raw: res,
raw: res,
}
},

'/meshes/:mesh/dataplanes/:name/stats/:address': async (params) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<template>
<RouteView
:params="{
codeSearch: '',
codeFilter: false,
codeRegExp: false,
proxy: '',
proxyType: '',
connection: '',
}"
:name="props.routeName"
v-slot="{ route, uri }"
>
<RouteTitle
:render="false"
:title="`Clusters`"
/>
<AppView>
<DataLoader
:src="uri(sources, '/connections/clusters/for/:proxyType/:name', {
name: route.params.proxy,
proxyType: route.params.proxyType === 'ingresses' ? 'zone-ingress' : 'zone-egress',
})"
v-slot="{ data , refresh }"
>
<template
v-for="prefix in [route.params.connection.replace('_', ':')]"
:key="typeof prefix"
>
<DataCollection
:items="data.split('\n')"
:predicate="item => item.startsWith(`${prefix}::`)"
v-slot="{ items: lines }"
>
<XCodeBlock
language="json"
:code="lines.map(item => item.replace(`${prefix}::`, '')).join('\n')"
is-searchable
:query="route.params.codeSearch"
:is-filter-mode="route.params.codeFilter"
:is-reg-exp-mode="route.params.codeRegExp"
@query-change="route.update({ codeSearch: $event })"
@filter-mode-change="route.update({ codeFilter: $event })"
@reg-exp-mode-change="route.update({ codeRegExp: $event })"
>
<template #primary-actions>
<XAction
action="refresh"
appearance="primary"
@click="refresh"
>
Refresh
</XAction>
</template>
</XCodeBlock>
</DataCollection>
</template>
</DataLoader>
</AppView>
</RouteView>
</template>
<script lang="ts" setup>
import { sources } from '@/app/connections/sources'
const props = defineProps<{
routeName: string
}>()
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<RouteView
:params="{
codeSearch: '',
codeFilter: false,
codeRegExp: false,
proxy: '',
proxyType: '',
connection: '',
}"
:name="props.routeName"
v-slot="{ route, uri }"
>
<RouteTitle
:render="false"
:title="`Stats`"
/>
<AppView>
<DataLoader
:src="uri(sources, '/connections/stats/for/:proxyType/:name/:socketAddress', {
name: route.params.proxy,
socketAddress: props.networking.inboundAddress,
proxyType: route.params.proxyType === 'ingresses' ? 'zone-ingress' : 'zone-egress',
})"
v-slot="{ data: stats, refresh }"
>
<DataCollection
:items="stats!.raw.split('\n')"
:predicate="item => [
`listener.${route.params.connection}`,
].some(prefix => item.startsWith(prefix))"
v-slot="{ items: lines }"
>
<XCodeBlock
language="json"
:code="lines.map(item => item.replace(`${route.params.connection}.`, '').replace(`${props.data.name}.`, '')).join('\n')"
is-searchable
:query="route.params.codeSearch"
:is-filter-mode="route.params.codeFilter"
:is-reg-exp-mode="route.params.codeRegExp"
@query-change="route.update({ codeSearch: $event })"
@filter-mode-change="route.update({ codeFilter: $event })"
@reg-exp-mode-change="route.update({ codeRegExp: $event })"
>
<template #primary-actions>
<XAction
action="refresh"
appearance="primary"
@click="refresh"
>
Refresh
</XAction>
</template>
</XCodeBlock>
</DataCollection>
</DataLoader>
</AppView>
</RouteView>
</template>
<script lang="ts" setup>
import { sources } from '@/app/connections/sources'
import type { DataplaneInbound } from '@/app/data-planes/data/'
import type { ZoneEgress } from '@/app/zone-egresses/data/'
import type { ZoneIngress } from '@/app/zone-ingresses/data/'
const props = defineProps<{
data: DataplaneInbound
networking: ZoneIngress['networking'] | ZoneEgress['networking']
routeName: string
}>()
</script>
Loading

0 comments on commit e99bf6c

Please sign in to comment.