kmesh: map: Add traffic map for Waypoints, Namespaces, Services - #1215
kmesh: map: Add traffic map for Waypoints, Namespaces, Services#1215itvi-1234 wants to merge 1 commit into
Conversation
Registers a KMesh source into Headlamp's shared Map view showing Namespace -> Waypoint -> Service relationships. Edges are derived from the istio.io/use-waypoint label (Service label takes priority over its Namespace's label), not ownerReferences, since KMesh/Istio ambient resources are routed to a waypoint rather than owned by it. Only namespaces/services actually enrolled with a waypoint are shown, so the map doesn't balloon to the whole cluster. The Waypoint node reuses the existing WaypointDetail component for its side-panel, via the same makeDetailsComponent adapter pattern used by the Strimzi and Volcano plugins' map views. The istio.io/use-waypoint label logic and the Gateway condition-to-map-status mapping live in pure, unit-tested helpers (src/map/mapUtils.ts), following the existing waypointUtils.ts convention so they're verifiable in isolation from the map source hooks that consume them. Pod/backend-level nodes from live eBPF data are left for a follow-up, since sourcing them needs the daemon proxy rather than a plain useList() hook. Signed-off-by: Sumit Goyal <rjsumit71@gmail.com>
Ralthos
left a comment
There was a problem hiding this comment.
The edge building is careful in the place it usually goes wrong. serviceSource matches the
waypoint on name and namespace and returns null when nothing matches, so an unresolvable
reference produces no edge instead of a broken one. That is the right call and it is not the
default outcome when relationships come from labels.
Two things.
A namespace that hosts the waypoint it uses gets two edges.
waypointSource draws namespace to waypoint for every KMesh waypoint:
const ns = namespaces.find(n => n.metadata.name === waypoint.metadata.namespace);
return ns ? makeKubeToKubeEdge(ns, waypoint, 'hosts') : null;namespaceSource draws the same pair again when the namespace carries the label:
const target = kmeshWaypoints.find(
w => w.metadata.name === waypointName && w.metadata.namespace === ns.metadata.name
);
return target ? makeKubeToKubeEdge(ns, target, 'uses') : null;The second lookup requires the waypoint to be in that same namespace, which is the case the
first one already covers. So a namespace labelled istio.io/use-waypoint: <waypoint in itself>,
which I understand to be the ordinary ambient layout, ends up with a hosts edge and a uses
edge between the same two nodes.
If both facts are worth showing, one edge carrying both labels reads better than two lines. If
hosts is really about placement and uses about routing, it may be clearer to let
namespaceSource handle only waypoints outside its own namespace, so the two sources stop
overlapping.
An enrolled service with no matching waypoint becomes a node with nothing attached.
enrolledServices decides membership from the label alone:
const enrolledServices = services.filter(svc => {
const ns = namespacesByName.get(svc.metadata.namespace);
return isWaypointEnrolled(svc.metadata.labels, ns?.metadata.labels);
});Every one of those becomes a node, but the edge is only drawn when a waypoint of that name
exists in the same namespace. A service labelled for a waypoint that is missing, misspelled, or
deployed elsewhere still lands on the map, floating, with no indication of why.
The related question: can istio.io/use-waypoint name a waypoint in another namespace? If
Istio's cross-namespace form is in play, then services legitimately enrolled against a waypoint
elsewhere are exactly the ones that appear unconnected here. Either way the node and the edge
should agree: include the service when it resolves, or keep it and make the unresolved state
visible.
One note on testing, and not a change request. mapUtils.test.ts covers
getWaypointNodeStatus across all five phases and resolveEffectiveWaypointName including the
precedence case. The part with the bugs in it
is the edge building inside useData, which cannot be reached from a test while it sits in the
hook. On #1079 I moved KEDA's edge logic into its own module for that reason and the tests went
from impossible to ten cases. Worth considering if you extend this map later.
There was a problem hiding this comment.
Pull request overview
Adds KMesh topology support to Headlamp’s shared Map view.
Changes:
- Registers Namespace, Waypoint, and Service graph sources.
- Derives waypoint relationships and status indicators.
- Adds unit tests for map helper logic.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
kmesh/src/mapView.tsx |
Builds KMesh topology nodes and edges. |
kmesh/src/mapRegistration.tsx |
Registers the KMesh map source. |
kmesh/src/map/mapUtils.ts |
Provides enrollment and status helpers. |
kmesh/src/map/mapUtils.test.ts |
Tests map helper behavior. |
kmesh/src/index.tsx |
Initializes map registration. |
Suppressed comments (1)
kmesh/src/map/mapUtils.ts:48
istio.io/use-waypoint=noneis the explicit opt-out value, but this resolver returns it as a waypoint name. ConsequentlyisWaypointEnrolledincludes an opted-out Service (including one overriding a namespace-level waypoint), leaving an isolated node in the map. Normalizenone(and an empty label value) toundefinedafter applying resource-over-namespace precedence.
return resourceLabels?.[USE_WAYPOINT_LABEL] ?? namespaceLabels?.[USE_WAYPOINT_LABEL];
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| export function resolveEffectiveWaypointName( | ||
| resourceLabels: Record<string, string> | undefined, | ||
| namespaceLabels: Record<string, string> | undefined | ||
| ): string | undefined { | ||
| return resourceLabels?.[USE_WAYPOINT_LABEL] ?? namespaceLabels?.[USE_WAYPOINT_LABEL]; |
| * (see waypointUtils.getWaypointCurrentStatus) to a Map node status badge. | ||
| * | ||
| * @param currentStatus - Result of getWaypointCurrentStatus(). | ||
| * @returns The Map node status, or undefined for an indeterminate state. |
|
|
||
| function makeKubeToKubeEdge(from: KubeObject, to: KubeObject, label?: string): GraphEdge { | ||
| return { | ||
| id: `${from.metadata.uid}-${to.metadata.uid}`, |
| /** Label set on a Waypoint's own Gateway describing what traffic it handles. */ | ||
| export const WAYPOINT_FOR_LABEL = 'istio.io/waypoint-for'; |
Description
Registers a KMesh source into Headlamp's shared Map view showing Namespace -> Waypoint -> Service relationships. Edges are derived from the istio.io/use-waypoint label (Service label takes priority over its Namespace's label), not ownerReferences, since KMesh/Istio ambient resources are routed to a waypoint rather than owned by it.
Only namespaces/services actually enrolled with a waypoint are shown, so the map doesn't balloon to the whole cluster. The Waypoint node reuses the existing WaypointDetail component for its side-panel, via the same makeDetailsComponent adapter pattern used by the Strimzi and Volcano plugins' map views.
The istio.io/use-waypoint label logic and the Gateway condition-to-map-status mapping live in pure, unit-tested helpers (src/map/mapUtils.ts), following the existing waypointUtils.ts convention so they're verifiable in isolation from the map source hooks that consume them.
Pod/backend-level nodes from live eBPF data are left for a follow-up, since sourcing them needs the daemon proxy rather than a plain useList() hook.