From c74a3b00aaf7a658c2405cd9d6c4d45366ed868e Mon Sep 17 00:00:00 2001 From: Julien Pinsonneau Date: Tue, 26 Nov 2024 12:40:10 +0100 Subject: [PATCH 1/2] dev --- config/sample-config.yaml | 57 +++++++- pkg/handler/resources.go | 29 ++++ pkg/model/fields/fields.go | 1 + pkg/server/routes.go | 1 + scripts/update-config.sh | 3 + web/console-extensions.json | 34 +++++ web/locales/en/plugin__netobserv-plugin.json | 1 + web/src/api/ipfix.ts | 2 + web/src/api/loki.ts | 1 + web/src/api/routes.ts | 10 ++ web/src/app.tsx | 8 ++ .../drawer/element/element-panel-content.tsx | 136 +++++++++++------- .../components/drawer/record/record-field.tsx | 14 ++ web/src/utils/autocomplete-cache.ts | 9 ++ web/src/utils/columns.ts | 1 + web/src/utils/filter-definitions.ts | 3 + web/src/utils/filter-options.ts | 13 +- 17 files changed, 271 insertions(+), 52 deletions(-) diff --git a/config/sample-config.yaml b/config/sample-config.yaml index ab143251f..c6c47ab04 100644 --- a/config/sample-config.yaml +++ b/config/sample-config.yaml @@ -152,6 +152,20 @@ frontend: # flow on current scope - Flows - DnsFlows + # UDN + - UdnId_Bytes + - UdnId_Packets + - min_UdnId_TimeFlowRttNs + - max_UdnId_TimeFlowRttNs + - avg_UdnId_TimeFlowRttNs + - p90_UdnId_TimeFlowRttNs + - p99_UdnId_TimeFlowRttNs + - min_UdnId_DnsLatencyMs + - max_UdnId_DnsLatencyMs + - avg_UdnId_DnsLatencyMs + - p90_UdnId_DnsLatencyMs + - p99_UdnId_DnsLatencyMs + - UdnId_Flows columns: - id: StartTime name: Start Time @@ -586,6 +600,13 @@ frontend: field: Interfaces default: false width: 15 + - id: UDN + name: User Defined Network + tooltip: The user defined network identifier. + field: UdnId + filter: udn + default: false + width: 15 - id: Bytes name: Bytes tooltip: The total aggregated number of bytes. @@ -1022,6 +1043,10 @@ frontend: component: autocomplete placeholder: 'E.g: Ingress, Egress' hint: Specify the direction of the Flow observed at the network interface observation point. + - id: udn + name: User Defined Network + component: autocomplete + hint: Specify a user defined network name. - id: id name: Conversation Id component: text @@ -1086,6 +1111,14 @@ frontend: feature: multiCluster filter: cluster_name stepInto: zone + - id: udn + name: UDN + shortName: UDN + description: User Defined Network + labels: + - UdnId + filter: udn + stepInto: host - id: zone name: Zone shortName: AZ @@ -1099,17 +1132,21 @@ frontend: filters: - src_zone - dst_zone - stepInto: host + stepInto: namespace - id: host name: Node + shortName: Node description: Node on which the resources are running labels: - SrcK8S_HostName - DstK8S_HostName groups: - - clusters + - udns - zones + - clusters - clusters+zones + - clusters+udns + - udns+zones filters: - src_host_name - dst_host_name @@ -1128,6 +1165,9 @@ frontend: - zones - zones+hosts - hosts + - udns + - udns+zones + - udns+hosts filters: - src_namespace - dst_namespace @@ -1154,6 +1194,10 @@ frontend: - hosts - hosts+namespaces - namespaces + - udns + - udns+zones + - udns+hosts + - udns+namespaces filters: - src_owner_name - dst_owner_name @@ -1193,6 +1237,11 @@ frontend: - namespaces - namespaces+owners - owners + - udns + - udns+zones + - udns+hosts + - udns+namespaces + - udns+owners filters: - src_resource - dst_resource @@ -1366,6 +1415,10 @@ frontend: - name: K8S_ClusterName type: string description: Cluster name or identifier + - name: UdnId + type: string + description: User Defined Network + lokiLabel: true - name: _RecordType type: string description: "Type of record: 'flowLog' for regular flow logs, or 'newConnection', 'heartbeat', 'endConnection' for conversation tracking" diff --git a/pkg/handler/resources.go b/pkg/handler/resources.go index 7bcfa74ef..8bf0d9295 100644 --- a/pkg/handler/resources.go +++ b/pkg/handler/resources.go @@ -44,6 +44,35 @@ func (h *Handlers) GetClusters(ctx context.Context) func(w http.ResponseWriter, } } +func (h *Handlers) GetUDNs(ctx context.Context) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + params := r.URL.Query() + namespace := params.Get(namespaceKey) + isDev := namespace != "" + + clients, err := newClients(h.Cfg, r.Header, false, namespace) + if err != nil { + writeError(w, http.StatusInternalServerError, err.Error()) + return + } + var code int + startTime := time.Now() + defer func() { + metrics.ObserveHTTPCall("GetUDNs", code, startTime) + }() + + // Fetch and merge values for K8S_ClusterName + values, code, err := h.getLabelValues(ctx, clients, fields.UDN, isDev) + if err != nil { + writeError(w, code, err.Error()) + return + } + + code = http.StatusOK + writeJSON(w, code, utils.NonEmpty(utils.Dedup(values))) + } +} + func (h *Handlers) GetZones(ctx context.Context) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() diff --git a/pkg/model/fields/fields.go b/pkg/model/fields/fields.go index e54d86803..7963d1a3f 100644 --- a/pkg/model/fields/fields.go +++ b/pkg/model/fields/fields.go @@ -34,6 +34,7 @@ const ( SrcZone = Src + Zone DstZone = Dst + Zone Cluster = "K8S_ClusterName" + UDN = "UDN" Layer = "K8S_FlowLayer" Packets = "Packets" Proto = "Proto" diff --git a/pkg/server/routes.go b/pkg/server/routes.go index f2cb594dc..09e6b7c16 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -51,6 +51,7 @@ func setupRoutes(ctx context.Context, cfg *config.Config, authChecker auth.Check // Common endpoints api.HandleFunc("/flow/metrics", h.GetTopology(ctx)) api.HandleFunc("/resources/clusters", h.GetClusters(ctx)) + api.HandleFunc("/resources/udns", h.GetUDNs(ctx)) api.HandleFunc("/resources/zones", h.GetZones(ctx)) api.HandleFunc("/resources/namespaces", h.GetNamespaces(ctx)) api.HandleFunc("/resources/names", h.GetNames(ctx)) diff --git a/scripts/update-config.sh b/scripts/update-config.sh index c3d7dbcc3..a9a2adc8c 100755 --- a/scripts/update-config.sh +++ b/scripts/update-config.sh @@ -9,6 +9,9 @@ yq eval-all --inplace 'select(fileIndex==0).frontend.columns = select(fileIndex= echo " - filters..." yq eval-all --inplace 'select(fileIndex==0).frontend.filters = select(fileIndex==1).filters | select(fileIndex==0)' ./config/sample-config.yaml ./tmp/config.yaml +echo " - scopes..." +yq eval-all --inplace 'select(fileIndex==0).frontend.scopes = select(fileIndex==1).scopes | select(fileIndex==0)' ./config/sample-config.yaml ./tmp/config.yaml + echo " - fields..." yq eval-all --inplace 'select(fileIndex==0).frontend.fields = select(fileIndex==1).fields | select(fileIndex==0)' ./config/sample-config.yaml ./tmp/config.yaml diff --git a/web/console-extensions.json b/web/console-extensions.json index 36cadb0a5..ee76b1484 100644 --- a/web/console-extensions.json +++ b/web/console-extensions.json @@ -243,6 +243,40 @@ } } }, + { + "type": "console.tab/horizontalNav", + "properties": { + "model": { + "version": "v1", + "group": "k8s.ovn.org", + "kind": "ClusterUserDefinedNetwork" + }, + "component": { + "$codeRef": "netflowTab.default" + }, + "page": { + "name": "%plugin__netobserv-plugin~Network Traffic%", + "href": "netflow" + } + } + }, + { + "type": "console.tab/horizontalNav", + "properties": { + "model": { + "version": "v1", + "group": "k8s.ovn.org", + "kind": "UserDefinedNetwork" + }, + "component": { + "$codeRef": "netflowTab.default" + }, + "page": { + "name": "%plugin__netobserv-plugin~Network Traffic%", + "href": "netflow" + } + } + }, { "type": "console.tab", "properties": { diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 8462b37e7..0c09fab7f 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -9,6 +9,7 @@ "IP": "IP", "No information available for this content. Change scope to get more details.": "No information available for this content. Change scope to get more details.", "Cluster name": "Cluster name", + "UDN": "UDN", "Source": "Source", "Destination": "Destination", "Stats": "Stats", diff --git a/web/src/api/ipfix.ts b/web/src/api/ipfix.ts index 2072d946b..8f89c4aa1 100644 --- a/web/src/api/ipfix.ts +++ b/web/src/api/ipfix.ts @@ -179,6 +179,8 @@ export interface Fields { _IsFirst?: string; /** In conversation tracking, a counter of flow logs per conversation */ numFlowLogs?: number; + /** User Defined Network identifier */ + UdnId?: string; } export type Field = keyof Fields | keyof Labels; diff --git a/web/src/api/loki.ts b/web/src/api/loki.ts index 7a587d2e1..b535837f7 100644 --- a/web/src/api/loki.ts +++ b/web/src/api/loki.ts @@ -73,6 +73,7 @@ export interface TopologyMetricPeer { namespace?: string; host?: string; cluster?: string; + udn?: string; } export type GenericMetric = { diff --git a/web/src/api/routes.ts b/web/src/api/routes.ts index b5c27ede2..f8aa4c0be 100644 --- a/web/src/api/routes.ts +++ b/web/src/api/routes.ts @@ -75,6 +75,16 @@ export const getClusters = (forcedNamespace?: string): Promise => { }); }; +export const getUDNs = (forcedNamespace?: string): Promise => { + const params = { namespace: forcedNamespace }; + return axios.get(ContextSingleton.getHost() + '/api/resources/udns', { params }).then(r => { + if (r.status >= 400) { + throw new Error(`${r.statusText} [code=${r.status}]`); + } + return r.data; + }); +}; + export const getZones = (forcedNamespace?: string): Promise => { const params = { namespace: forcedNamespace }; return axios.get(ContextSingleton.getHost() + '/api/resources/zones', { params }).then(r => { diff --git a/web/src/app.tsx b/web/src/app.tsx index b4c8185d2..33354a764 100755 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -44,6 +44,10 @@ export const pages = [ { id: 'dev-tab', name: 'Dev tab' + }, + { + id: 'udn-tab', + name: 'UDN tab' } ]; @@ -94,6 +98,10 @@ export class App extends React.Component<{}, AppState> { }} /> ); + case 'udn-tab': + return ( + + ); default: return ; } diff --git a/web/src/components/drawer/element/element-panel-content.tsx b/web/src/components/drawer/element/element-panel-content.tsx index c57030902..558f12da1 100644 --- a/web/src/components/drawer/element/element-panel-content.tsx +++ b/web/src/components/drawer/element/element-panel-content.tsx @@ -75,10 +75,39 @@ export const ElementPanelContent: React.FC = ({ [filterDefinitions, filters, setFilters, t] ); + const udnName = React.useCallback( + (d: NodeData) => { + if (!d.peer.udn) { + return <>; + } + const fields = createPeer({ udn: d.peer.udn }); + const isFiltered = isElementFiltered(fields, filters, filterDefinitions); + return ( + + {t('UDN')} + + {d.peer.udn} + +