Skip to content

Commit 55d119c

Browse files
heanlancursoragent
andcommitted
Add gRPC client to FlowAggregator and expose SSE endpoint
- Add a gRPC client (GRPCFlowStreamSubscriber) connecting to the FlowAggregator FlowStreamService on port 14740 over server-side TLS. The FlowAggregator CA cert is fetched from the flow-aggregator-ca ConfigMap at startup and used to verify the server certificate. - Add a cross-namespace RoleBinding so the antrea-ui ServiceAccount can read the flow-aggregator-ca ConfigMap from the flow-aggregator namespace. - Expose a Server-Sent Events (SSE) endpoint at /api/v1/flows/stream that streams live flow records from the FlowAggregator to the browser. - Add frontend Flow Visibility page (Service Map and Flow List views) with functional filtering by namespace, pod, service, IP, flow type, and direction. - Wire the frontend to consume the SSE stream using a FlowStreamClient backed by fetch() to support Authorization headers (EventSource does not support custom headers). - Add flowVisibilityEnabled feature flag to the frontend settings API so the UI can conditionally show the Flow Visibility nav item. - Add flowAggregator.enabled / address / caConfigMap / namespace to the antrea-ui Helm chart values; add SSE-specific nginx location block with extended proxy_read_timeout to avoid 504 timeouts on idle streams. Signed-off-by: Anlan He <anlan.he@broadcom.com> Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Anlan He <anlan.he@broadcom.com> Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Anlan He <anlan.he@broadcom.com> Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Anlan He <anlan.he@broadcom.com> Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Anlan He <anlan.he@broadcom.com> Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Anlan He <anlan.he@broadcom.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 7c1d073 commit 55d119c

38 files changed

Lines changed: 4805 additions & 163 deletions

apis/v1/flow.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2026 Antrea Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1
16+
17+
// JSON-serializable flow types for the SSE API, mirroring the protobuf Flow message.
18+
19+
type FlowType int
20+
21+
const (
22+
FlowTypeUnspecified FlowType = 0
23+
FlowTypeIntraNode FlowType = 1
24+
FlowTypeInterNode FlowType = 2
25+
FlowTypeToExternal FlowType = 3
26+
FlowTypeFromExternal FlowType = 4
27+
)
28+
29+
type NetworkPolicyType int
30+
31+
const (
32+
NetworkPolicyTypeUnspecified NetworkPolicyType = 0
33+
NetworkPolicyTypeK8s NetworkPolicyType = 1
34+
NetworkPolicyTypeANP NetworkPolicyType = 2
35+
NetworkPolicyTypeACNP NetworkPolicyType = 3
36+
)
37+
38+
type NetworkPolicyRuleAction int
39+
40+
const (
41+
NetworkPolicyRuleActionNoAction NetworkPolicyRuleAction = 0
42+
NetworkPolicyRuleActionAllow NetworkPolicyRuleAction = 1
43+
NetworkPolicyRuleActionDrop NetworkPolicyRuleAction = 2
44+
NetworkPolicyRuleActionReject NetworkPolicyRuleAction = 3
45+
)
46+
47+
type IPVersion int
48+
49+
const (
50+
IPVersionUnspecified IPVersion = 0
51+
IPVersionIPv4 IPVersion = 4
52+
IPVersionIPv6 IPVersion = 6
53+
)
54+
55+
type FlowEndReason int
56+
57+
const (
58+
FlowEndReasonUnspecified FlowEndReason = 0
59+
FlowEndReasonIdleTimeout FlowEndReason = 1
60+
FlowEndReasonActiveTimeout FlowEndReason = 2
61+
FlowEndReasonEndOfFlow FlowEndReason = 3
62+
FlowEndReasonForcedEnd FlowEndReason = 4
63+
FlowEndReasonLackOfResources FlowEndReason = 5
64+
)
65+
66+
type FlowStats struct {
67+
PacketTotalCount uint64 `json:"packetTotalCount"`
68+
PacketDeltaCount uint64 `json:"packetDeltaCount"`
69+
OctetTotalCount uint64 `json:"octetTotalCount"`
70+
OctetDeltaCount uint64 `json:"octetDeltaCount"`
71+
}
72+
73+
type FlowTCP struct {
74+
StateName string `json:"stateName"`
75+
}
76+
77+
type FlowTransport struct {
78+
ProtocolNumber uint32 `json:"protocolNumber"`
79+
SourcePort uint32 `json:"sourcePort"`
80+
DestinationPort uint32 `json:"destinationPort"`
81+
TCP *FlowTCP `json:"tcp,omitempty"`
82+
}
83+
84+
type FlowIP struct {
85+
Version IPVersion `json:"version"`
86+
Source string `json:"source"`
87+
Destination string `json:"destination"`
88+
}
89+
90+
type FlowKubernetes struct {
91+
FlowType FlowType `json:"flowType"`
92+
93+
SourcePodNamespace string `json:"sourcePodNamespace"`
94+
SourcePodName string `json:"sourcePodName"`
95+
SourcePodUid string `json:"sourcePodUid"`
96+
SourcePodLabels map[string]string `json:"sourcePodLabels,omitempty"`
97+
98+
SourceNodeName string `json:"sourceNodeName"`
99+
SourceNodeUid string `json:"sourceNodeUid"`
100+
101+
DestinationPodNamespace string `json:"destinationPodNamespace"`
102+
DestinationPodName string `json:"destinationPodName"`
103+
DestinationPodUid string `json:"destinationPodUid"`
104+
DestinationPodLabels map[string]string `json:"destinationPodLabels,omitempty"`
105+
106+
DestinationNodeName string `json:"destinationNodeName"`
107+
DestinationNodeUid string `json:"destinationNodeUid"`
108+
109+
DestinationClusterIp string `json:"destinationClusterIp"`
110+
DestinationServicePort uint32 `json:"destinationServicePort"`
111+
DestinationServicePortName string `json:"destinationServicePortName"`
112+
DestinationServiceUid string `json:"destinationServiceUid"`
113+
114+
IngressNetworkPolicyType NetworkPolicyType `json:"ingressNetworkPolicyType"`
115+
IngressNetworkPolicyNamespace string `json:"ingressNetworkPolicyNamespace"`
116+
IngressNetworkPolicyName string `json:"ingressNetworkPolicyName"`
117+
IngressNetworkPolicyUid string `json:"ingressNetworkPolicyUid"`
118+
IngressNetworkPolicyRuleName string `json:"ingressNetworkPolicyRuleName"`
119+
IngressNetworkPolicyRuleAction NetworkPolicyRuleAction `json:"ingressNetworkPolicyRuleAction"`
120+
121+
EgressNetworkPolicyType NetworkPolicyType `json:"egressNetworkPolicyType"`
122+
EgressNetworkPolicyNamespace string `json:"egressNetworkPolicyNamespace"`
123+
EgressNetworkPolicyName string `json:"egressNetworkPolicyName"`
124+
EgressNetworkPolicyUid string `json:"egressNetworkPolicyUid"`
125+
EgressNetworkPolicyRuleName string `json:"egressNetworkPolicyRuleName"`
126+
EgressNetworkPolicyRuleAction NetworkPolicyRuleAction `json:"egressNetworkPolicyRuleAction"`
127+
128+
EgressName string `json:"egressName,omitempty"`
129+
EgressIp string `json:"egressIp,omitempty"`
130+
EgressNodeName string `json:"egressNodeName,omitempty"`
131+
EgressNodeUid string `json:"egressNodeUid,omitempty"`
132+
EgressUid string `json:"egressUid,omitempty"`
133+
}
134+
135+
type Flow struct {
136+
ID string `json:"id"`
137+
StartTs string `json:"startTs"`
138+
EndTs string `json:"endTs"`
139+
EndReason FlowEndReason `json:"endReason"`
140+
IP FlowIP `json:"ip"`
141+
Transport FlowTransport `json:"transport"`
142+
K8s FlowKubernetes `json:"k8s"`
143+
Stats FlowStats `json:"stats"`
144+
ReverseStats FlowStats `json:"reverseStats"`
145+
}
146+
147+
// FlowStreamEvent carries flow data and/or a dropped count from the stream.
148+
// When Flows is non-empty, the SSE handler emits a "flow" event.
149+
// When DroppedCount is non-zero, the SSE handler emits a "dropped" event.
150+
type FlowStreamEvent struct {
151+
Flows []Flow `json:"flows,omitempty"`
152+
DroppedCount uint64 `json:"droppedCount,omitempty"`
153+
}
154+
155+
// FlowStreamDroppedEvent is the JSON payload for an SSE "dropped" event.
156+
type FlowStreamDroppedEvent struct {
157+
DroppedCount uint64 `json:"droppedCount"`
158+
}
159+
160+
// FlowStreamErrorEvent is the JSON payload for an SSE "error" event.
161+
type FlowStreamErrorEvent struct {
162+
Message string `json:"message"`
163+
}
164+
165+
// FlowFilterDirection matches antrea FlowFilter.direction (service.proto, FlowStreamService).
166+
// It is distinct from per-flow FlowDirection in flow.proto (ingress/egress), which is not
167+
// exposed on this JSON API yet.
168+
type FlowFilterDirection int
169+
170+
const (
171+
FlowFilterDirectionBoth FlowFilterDirection = 0
172+
FlowFilterDirectionFrom FlowFilterDirection = 1
173+
FlowFilterDirectionTo FlowFilterDirection = 2
174+
)
175+
176+
// FlowStreamFilter represents the query parameters for the flow stream endpoint.
177+
// All specified filters are AND-ed. Within each filter, values are OR-ed.
178+
type FlowStreamFilter struct {
179+
Namespaces []string `json:"namespaces,omitempty"`
180+
PodNames []string `json:"podNames,omitempty"`
181+
PodLabelSelector string `json:"podLabelSelector,omitempty"`
182+
ServiceNames []string `json:"serviceNames,omitempty"`
183+
FlowTypes []FlowType `json:"flowTypes,omitempty"`
184+
IPs []string `json:"ips,omitempty"`
185+
Direction FlowFilterDirection `json:"direction,omitempty"`
186+
// Follow is accepted from the client for completeness but is always overridden to true
187+
// in the backend SSE handler. The FlowStreamService gRPC stream requires follow mode to
188+
// keep the connection open; non-follow (one-shot) mode is not supported over SSE.
189+
Follow bool `json:"follow"`
190+
}

apis/v1/frontend_settings.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ type FrontendAuthSettings struct {
2020
OIDCProviderName string `json:"oidcProviderName,omitempty"`
2121
}
2222

23+
type FrontendFeatureSettings struct {
24+
FlowVisibilityEnabled bool `json:"flowVisibilityEnabled"`
25+
}
26+
2327
// FrontendSettings are global settings exposed to the frontend, which can be
2428
// used to render some pages appropriately. These settings are not user-specific
2529
// and not confidential (the API for these settings is not protected by any auth
2630
// mechanism).
2731
type FrontendSettings struct {
28-
Version string `json:"version"`
29-
Auth FrontendAuthSettings `json:"auth"`
32+
Version string `json:"version"`
33+
Auth FrontendAuthSettings `json:"auth"`
34+
Features FrontendFeatureSettings `json:"features"`
3035
}

build/charts/antrea-ui/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Kubernetes: `>= 1.16.0-0`
1919
| Key | Type | Default | Description |
2020
|-----|------|---------|-------------|
2121
| affinity | object | `{}` | Affinity for the Antrea UI Pod. |
22+
| antreaNamespace | string | `"kube-system"` | Namespace where Antrea is installed. |
2223
| auth.basic.enable | bool | `true` | Enable password-based authentication. |
2324
| auth.oidc.clientID | string | `""` | Application (client) ID to be used by the Antrea UI server to identify itself to the OIDC provider. |
2425
| auth.oidc.clientIDSecretRef.key | string | `"clientID"` | Name of the key field storing the application (client) ID in the referenced secret. |
@@ -40,6 +41,10 @@ Kubernetes: `>= 1.16.0-0`
4041
| dex.image | object | `{"pullPolicy":"IfNotPresent","repository":"ghcr.io/dexidp/dex","tag":"v2.36.0-distroless"}` | Container image to use for Dex. |
4142
| dex.resources | object | `{}` | Resource requests and limits for the Dex container. |
4243
| extraVolumes | list | `[]` | Additional volumes. |
44+
| flowAggregator.address | string | `"flow-aggregator.flow-aggregator.svc:14740"` | gRPC address (host:port) of the FlowStreamService. |
45+
| flowAggregator.caConfigMap | string | `"flow-aggregator-ca"` | Name of the ConfigMap (in namespace below) containing the CA certificate (key: ca.crt) used to verify the FlowStreamService server certificate. The FlowStreamService uses server-side TLS only (no client authentication). Leave empty to skip server certificate verification (dev/test only). |
46+
| flowAggregator.enabled | bool | `false` | When true, the backend connects to Flow Aggregator's FlowStreamService over gRPC. |
47+
| flowAggregator.namespace | string | `"flow-aggregator"` | Namespace where the Flow Aggregator is installed. |
4348
| frontend.extraVolumeMounts | list | `[]` | Additional volumeMounts. |
4449
| frontend.image | object | `{"pullPolicy":"IfNotPresent","repository":"antrea/antrea-ui-frontend","tag":""}` | Container image to use for the Antrea UI frontend. |
4550
| frontend.port | int | `3000` | Container port on which the frontend will listen. |

build/charts/antrea-ui/templates/_backend_conf.tpl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{{- define "antrea-ui.backend.conf" }}
22
addr: ":{{ .Values.backend.port }}"
3-
url: {{ .Values.url }}
3+
url: {{ .Values.url | quote }}
4+
antreaNamespace: {{ .Values.antreaNamespace | quote }}
45
auth:
56
basic:
67
enabled: {{ .Values.auth.basic.enable }}
@@ -12,5 +13,12 @@ auth:
1213
logoutURL: {{ .Values.auth.oidc.logoutURL | quote }}
1314
jwtKeyPath: "/app/jwt-key.pem"
1415
cookieSecure: {{ include "cookieSecure" . }}
15-
logVerbosity: {{ .Values.logVerbosity }}
16+
logVerbosity: {{ .Values.backend.logVerbosity }}
17+
flowAggregator:
18+
enabled: {{ .Values.flowAggregator.enabled }}
19+
address: {{ .Values.flowAggregator.address | quote }}
20+
{{- if .Values.flowAggregator.enabled }}
21+
caConfigMap: {{ .Values.flowAggregator.caConfigMap | quote }}
22+
namespace: {{ .Values.flowAggregator.namespace | default "flow-aggregator" | quote }}
23+
{{- end }}
1624
{{- end }}

build/charts/antrea-ui/templates/_nginx_conf.tpl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,25 @@ server {
2727
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
2828
proxy_set_header X-Real-IP $remote_addr;
2929

30+
# Flow SSE can be idle for a long time when the Flow Aggregator ring buffer has no matching
31+
# records; nginx's default proxy_read_timeout (~60s) then returns 504 to the browser.
32+
location /api/v1/flows/stream {
33+
proxy_http_version 1.1;
34+
proxy_pass_request_headers on;
35+
proxy_hide_header Access-Control-Allow-Origin;
36+
proxy_set_header Connection '';
37+
proxy_buffering off;
38+
proxy_read_timeout 86400s;
39+
proxy_send_timeout 86400s;
40+
proxy_pass http://127.0.0.1:{{ .Values.backend.port }};
41+
{{- $secure := include "cookieSecure" . -}}
42+
{{- if eq $secure "true" }}
43+
proxy_cookie_flags ~ httponly secure;
44+
{{- else }}
45+
proxy_cookie_flags ~ httponly;
46+
{{- end }}
47+
}
48+
3049
location /api {
3150
proxy_http_version 1.1;
3251
proxy_pass_request_headers on;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{{- if .Values.flowAggregator.enabled -}}
2+
kind: RoleBinding
3+
apiVersion: rbac.authorization.k8s.io/v1
4+
metadata:
5+
labels:
6+
app: antrea-ui
7+
name: antrea-ui-flow-aggregator-ca-reader
8+
# This RoleBinding is in the flow-aggregator namespace so the antrea-ui
9+
# ServiceAccount can read the flow-aggregator-ca ConfigMap across namespaces.
10+
# It references flow-aggregator-exporter-role which is created by the
11+
# flow-aggregator Helm chart. The flow-aggregator chart must be installed
12+
# before (or alongside) antrea-ui for this RoleBinding to be effective.
13+
namespace: {{ .Values.flowAggregator.namespace | default "flow-aggregator" }}
14+
subjects:
15+
- kind: ServiceAccount
16+
name: antrea-ui
17+
namespace: {{ .Release.Namespace }}
18+
roleRef:
19+
kind: Role
20+
name: flow-aggregator-exporter-role
21+
apiGroup: rbac.authorization.k8s.io
22+
{{- end }}

build/charts/antrea-ui/values.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ backend:
3131
# -- Address at which the Antrea UI is accessible. Not required for most configurations.
3232
url: ""
3333

34+
# -- Namespace where Antrea is installed.
35+
antreaNamespace: "kube-system"
36+
37+
# Flow Aggregator gRPC integration (live flow visibility in the UI).
38+
flowAggregator:
39+
# -- When true, the backend connects to Flow Aggregator's FlowStreamService over gRPC.
40+
enabled: false
41+
# -- gRPC address (host:port) of the FlowStreamService.
42+
address: "flow-aggregator.flow-aggregator.svc:14740"
43+
# -- Name of the ConfigMap (in namespace below) containing the CA certificate (key: ca.crt)
44+
# used to verify the FlowStreamService server certificate.
45+
# The FlowStreamService uses server-side TLS only (no client authentication).
46+
# Leave empty to skip server certificate verification (dev/test only).
47+
caConfigMap: flow-aggregator-ca
48+
# -- Namespace where the Flow Aggregator is installed.
49+
namespace: flow-aggregator
50+
3451
security:
3552
# -- (bool) Set the Secure attribute for Antrea UI cookies. The attribute is set by default when HTTPS is
3653
# enabled in Antrea UI (by setting https.enable to true). When using an Ingress to terminate TLS,
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
VITE_API_SERVER=http://localhost:8080
1+
# VITE_API_SERVER is intentionally empty in development. API requests (/api, /auth)
2+
# are proxied to http://localhost:8080 by the Vite dev server (see vite.config.ts),
3+
# matching the production nginx proxy setup.
4+
VITE_API_SERVER=

0 commit comments

Comments
 (0)