Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(dataplanes): add filtered XDS config to drawers #3186

Merged
merged 6 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/config/src/eslint.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ function createEslintConfig(
},
rules: {
'comma-dangle': ['error', 'always-multiline'],
'multiline-ternary': 'off',
'space-before-function-paren': ['error', {
anonymous: 'always',
named: 'never',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ connections:
item:
navigation:
overview: 'Overview'
xds: 'XDS Configuration'
stats: 'Stats'
clusters: 'Clusters'
11 changes: 10 additions & 1 deletion packages/kuma-gui/src/app/connections/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export const routes = (): RouteRecordRaw[] => {
name: 'connection-inbound-summary-clusters-view',
component: () => import('@/app/connections/views/ConnectionInboundSummaryClustersView.vue'),
},
{
path: 'xds-config',
name: 'connection-inbound-summary-xds-config-view',
component: () => import('@/app/connections/views/ConnectionInboundSummaryXdsConfigView.vue'),
},
],
},
{
Expand All @@ -43,7 +48,11 @@ export const routes = (): RouteRecordRaw[] => {
name: 'connection-outbound-summary-clusters-view',
component: () => import('@/app/connections/views/ConnectionOutboundSummaryClustersView.vue'),
},

{
path: 'xds-config',
name: 'connection-outbound-summary-xds-config-view',
component: () => import('@/app/connections/views/ConnectionOutboundSummaryXdsConfigView.vue'),
},
],
},
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<RouteView
:params="{
codeSearch: '',
codeFilter: false,
codeRegExp: false,
mesh: '',
dataPlane: '',
connection: '',
}"
name="connection-inbound-summary-xds-config-view"
v-slot="{ route, uri }"
>
<RouteTitle
:render="false"
:title="`XDS Configuration`"
johncowen marked this conversation as resolved.
Show resolved Hide resolved
/>
<AppView>
<DataLoader
:src="uri(sources, '/meshes/:mesh/dataplanes/:dataplane/inbound/:inbound/xds', {
mesh: route.params.mesh,
dataplane: route.params.dataPlane,
inbound: `${props.data.port}`,
})"
v-slot="{ data: raw, refresh }"
>
<XCodeBlock
language="json"
:code="JSON.stringify(raw, null, 2)"
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>
</DataLoader>
</AppView>
</RouteView>
</template>
<script lang="ts" setup>
import type { DataplaneInbound, DataplaneOverview } from '@/app/data-planes/data/'
import { sources } from '@/app/data-planes/sources'

const props = defineProps<{
data: DataplaneInbound
dataplaneOverview: DataplaneOverview
}>()
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<RouteView
:params="{
codeSearch: '',
codeFilter: false,
codeRegExp: false,
mesh: '',
dataPlane: '',
connection: '',
includeEds: false,
}"
name="connection-outbound-summary-xds-config-view"
v-slot="{ route, uri }"
>
<RouteTitle
:render="false"
:title="`XDS Configuration`"
/>
<AppView>
<DataLoader
:src="uri(sources, '/meshes/:mesh/dataplanes/:dataplane/outbound/:outbound/xds/:endpoints', {
mesh: route.params.mesh,
dataplane: route.params.dataPlane,
outbound: route.params.connection,
endpoints: String(route.params.includeEds),
})"
v-slot="{ data: raw, refresh }"
>
<XCodeBlock
language="json"
:code="JSON.stringify(raw, null, 2)"
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>
<XCheckbox
v-model="route.params.includeEds"
label="Include Endpoints"
/>
<XAction
action="refresh"
appearance="primary"
@click="refresh"
>
Refresh
</XAction>
</template>
</XCodeBlock>
</DataLoader>
</AppView>
</RouteView>
</template>
<script lang="ts" setup>
import { sources } from '@/app/data-planes/sources'
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ export const DataplaneNetworking = {
listenerAddress: '',
}]
: inbounds.map((item) => {
const address = item.address ?? networking.address
// inbound address, advertisedAddress, networkingAddress because externally accessible address
const address = item.address ?? networking.advertisedAddress ?? networking.address
const port = item.servicePort ?? item.port
return {
...item,
Expand All @@ -91,8 +92,7 @@ export const DataplaneNetworking = {
service: item.tags['kuma.io/service'],
protocol: item.tags['kuma.io/protocol'] ?? 'tcp',
address,
// inbound address, advertisedAddress, networkingAddress because externally accessible address
addressPort: `${item.address ?? networking.advertisedAddress ?? networking.address}:${item.port}`,
addressPort: `${address}:${item.port}`,
// inbound serviceAddress, inbound address, networkingAddress because the internal services accessible address
serviceAddressPort: `${item.serviceAddress ?? address}:${port}`,
}
Expand Down
79 changes: 79 additions & 0 deletions packages/kuma-gui/src/app/data-planes/sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,37 @@ export type MeshGatewayDataplaneSource = DataSourceResponse<MeshGatewayDataplane
const includes = <T extends readonly string[]>(arr: T, item: string): item is T[number] => {
return arr.includes(item as T[number])
}
const prop = <K extends PropertyKey>(obj: unknown, key: K | null | undefined): obj is Record<K, unknown> => {
schogges marked this conversation as resolved.
Show resolved Hide resolved
return key != null && obj != null && typeof obj === 'object' && key in obj
}
const filter = (data: Record<string, unknown>, cb: (key: string, arr: unknown[]) => unknown[]) => {
const { configs } = data
if (!Array.isArray(configs)) {
return { configs: [] }
}
return {
configs: configs.reduce((prev, item) => {
const entries = Object.entries(item)

const found = entries.reduce((prev, [key, value]) => {
const found = cb(key, Array.isArray(value) ? value : [])
if (found.length > 0) {
if (typeof prev[key] === 'undefined') {
prev[key] = []
}
prev[key] = prev[key].concat(found)
}
return prev
}, {} as typeof configs[number])

if (Object.keys(found).length > 0) {
return prev.concat(found)
}
return prev

}, [] as typeof configs),
}
}
export const sources = (source: Source, api: KumaApi, can: Can) => {
return defineSources({
// always resolves and keeps polling until we have at least one dataplane and all dataplanes are online
Expand Down Expand Up @@ -87,6 +117,55 @@ export const sources = (source: Source, api: KumaApi, can: Can) => {
dataPath,
})
},
'/meshes/:mesh/dataplanes/:dataplane/inbound/:inbound/xds': async (params) => {
const { mesh, dataplane, inbound } = params

// we don't ask for endpoints because we don't need them for inbound filtering
const res = await api.getDataplaneXds({
mesh,
dppName: dataplane,
}, {
include_eds: false,
})
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 []
})
},
'/meshes/:mesh/dataplanes/:dataplane/outbound/:outbound/xds/:endpoints': async (params) => {
const { mesh, dataplane, outbound, endpoints } = params

// we don't ask for endpoints because we don't need them for inbound filtering
const res = await api.getDataplaneXds({
mesh,
dppName: dataplane,
}, {
include_eds: endpoints,
})
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 []
})
},
'/meshes/:mesh/dataplanes/:name/xds/:endpoints': async (params) => {
const { mesh, name, endpoints } = params

Expand Down
4 changes: 4 additions & 0 deletions packages/kuma-gui/src/app/kuma/services/kuma-api/KumaApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ export default class KumaApi extends Api {
return this.client.get(`/meshes/${mesh}/dataplanes/${dppName}/${dataPath}`, { params })
}

getDataplaneXds({ mesh, dppName }: { mesh: string, dppName: string, params?: any }, params?: any): Promise<Record<string, unknown>> {
return this.client.get(`/meshes/${mesh}/dataplanes/${dppName}/xds`, { params })
}

getAllMeshServicesFromMesh({ mesh }: { mesh: string }, params?: PaginationParameters): Promise<PaginatedApiListResponse<MeshService>> {
return this.client.get(`/meshes/${mesh}/meshservices`, { params })
}
Expand Down
3 changes: 3 additions & 0 deletions packages/kuma-gui/src/test-support/FakeKuma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ export class KumaModule {
}
}

/**
* @deprecated - please inline networking property instead
*/
dataplaneNetworking(
{
type = 'proxy',
Expand Down
1 change: 1 addition & 0 deletions packages/kuma-gui/src/test-support/fake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type MockEnvKeys = keyof {
KUMA_DATAPLANE_COUNT: string
KUMA_DATAPLANE_TYPE: string
KUMA_DATAPLANEINBOUND_COUNT: string
KUMA_DATAPLANEOUTBOUND_COUNT: string
KUMA_DATAPLANE_PROXY_RULE_ENABLED: string
KUMA_DATAPLANE_RULE_COUNT: string
KUMA_DATAPLANE_TO_RULE_COUNT: string
Expand Down
Loading