From 558967c1f03561f29a86746631567822d6a194cd Mon Sep 17 00:00:00 2001 From: Dhruv-J Date: Tue, 2 May 2023 11:48:36 -0700 Subject: [PATCH] Dependency Plugin Options This PR allows for users to edit the panel and choose to group pods by labels or not. The user can also change the color of the boxes to red, yellow, green, or blue. Fixes issue #164 Signed-off-by: Dhruv-J --- build/charts/theia/README.md | 2 +- .../network_topology_dashboard.json | 10 ++-- build/charts/theia/values.yaml | 2 +- build/yamls/flow-visibility.yml | 12 +++-- docs/network-flow-visibility.md | 7 +++ .../grafana-dependency-plugin/CHANGELOG.md | 4 ++ .../grafana-dependency-plugin/README.md | 24 +++++++-- .../src/DependencyPanel.tsx | 53 +++++++++++++++---- .../grafana-dependency-plugin/src/module.ts | 36 ++++++++++++- .../grafana-dependency-plugin/src/types.ts | 8 ++- 10 files changed, 131 insertions(+), 27 deletions(-) diff --git a/build/charts/theia/README.md b/build/charts/theia/README.md index 870ba3ee2..2dd75dbd3 100644 --- a/build/charts/theia/README.md +++ b/build/charts/theia/README.md @@ -52,7 +52,7 @@ Kubernetes: `>= 1.16.0-0` | grafana.enable | bool | `true` | Determine whether to install Grafana. It is used as a data visualization and monitoring tool. | | grafana.homeDashboard | string | `"homepage.json"` | Default home dashboard. | | grafana.image | object | `{"pullPolicy":"IfNotPresent","repository":"projects.registry.vmware.com/antrea/theia-grafana","tag":"8.3.3"}` | Container image used by Grafana. | -| grafana.installPlugins | list | `["https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-sankey-plugin-1.0.2.zip;theia-grafana-sankey-plugin","https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-chord-plugin-1.0.1.zip;theia-grafana-chord-plugin","https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-dependency-plugin-1.0.1.zip;theia-grafana-dependency-plugin","grafana-clickhouse-datasource 1.0.1"]` | Grafana plugins to install. | +| grafana.installPlugins | list | `["https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-sankey-plugin-1.0.2.zip;theia-grafana-sankey-plugin","https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-chord-plugin-1.0.1.zip;theia-grafana-chord-plugin","https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-dependency-plugin-1.0.2.zip;theia-grafana-dependency-plugin","grafana-clickhouse-datasource 1.0.1"]` | Grafana plugins to install. | | grafana.log | object | `{"daily_rotate":"true","level":"info","log_rotate":"true","max_days":"7","max_lines":"1000000","max_size_shift":"27","mode":"console file"}` | Grafana logging options. | | grafana.log.daily_rotate | string | `"true"` | Enable daily rotation of files, valid options are false or true. Default is true. Only applicable when “file” used in [log] mode. | | grafana.log.level | string | `"info"` | Logging level. Options are “debug”, “info”, “warn”, “error”, and “critical”. Default is info. | diff --git a/build/charts/theia/provisioning/dashboards/network_topology_dashboard.json b/build/charts/theia/provisioning/dashboards/network_topology_dashboard.json index b70f420ad..538677994 100644 --- a/build/charts/theia/provisioning/dashboards/network_topology_dashboard.json +++ b/build/charts/theia/provisioning/dashboards/network_topology_dashboard.json @@ -21,8 +21,8 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 4, - "iteration": 1677633025540, + "id": 8, + "iteration": 1682533463233, "links": [], "liveNow": false, "panels": [ @@ -39,6 +39,8 @@ }, "id": 2, "options": { + "color": "yellow", + "groupByLabel": false, "seriesCountSize": "sm", "showSeriesCount": false, "text": "Default value of text input option" @@ -58,7 +60,7 @@ } }, "queryType": "sql", - "rawSql": "SELECT sourcePodName, sourcePodNamespace, sourceNodeName, destinationPodName, destinationNodeName, destinationServicePortName, octetDeltaCount FROM flows\nWHERE sourcePodNamespace NOT IN ('kube-system', 'flow-visibility', 'flow-aggregator')\nAND destinationPodNamespace NOT IN ('kube-system', 'flow-visibility', 'flow-aggregator')\nAND destinationPodName != ''\nAND sourcePodName != ''\nAND octetDeltaCount != 0\nAND $__timeFilter(flowEndSeconds)\nORDER BY flowEndSeconds DESC", + "rawSql": "SELECT sourcePodName, sourcePodLabels, sourcePodNamespace, sourceNodeName, destinationPodName, destinationPodLabels, destinationNodeName, destinationServicePortName, octetDeltaCount FROM flows\nWHERE sourcePodNamespace NOT IN ('kube-system', 'flow-visibility', 'flow-aggregator')\nAND destinationPodNamespace NOT IN ('kube-system', 'flow-visibility', 'flow-aggregator')\nAND destinationPodName != ''\nAND sourcePodName != ''\nAND octetDeltaCount != 0\nAND $__timeFilter(flowEndSeconds)\nORDER BY flowEndSeconds DESC", "refId": "A" } ], @@ -100,6 +102,6 @@ "timezone": "", "title": "network_topology_dashboard", "uid": "yRVDEad4k", - "version": 1, + "version": 2, "weekStart": "" } diff --git a/build/charts/theia/values.yaml b/build/charts/theia/values.yaml index d17c0f285..bc88df2fd 100644 --- a/build/charts/theia/values.yaml +++ b/build/charts/theia/values.yaml @@ -182,7 +182,7 @@ grafana: installPlugins: - https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-sankey-plugin-1.0.2.zip;theia-grafana-sankey-plugin - https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-chord-plugin-1.0.1.zip;theia-grafana-chord-plugin - - https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-dependency-plugin-1.0.1.zip;theia-grafana-dependency-plugin + - https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-dependency-plugin-1.0.2.zip;theia-grafana-dependency-plugin - grafana-clickhouse-datasource 1.0.1 # -- The dashboards to be displayed in Grafana UI. The files must be put under # provisioning/dashboards. diff --git a/build/yamls/flow-visibility.yml b/build/yamls/flow-visibility.yml index 9548107b4..b69842c42 100644 --- a/build/yamls/flow-visibility.yml +++ b/build/yamls/flow-visibility.yml @@ -3083,8 +3083,8 @@ data: "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 4, - "iteration": 1677633025540, + "id": 8, + "iteration": 1682533463233, "links": [], "liveNow": false, "panels": [ @@ -3101,6 +3101,8 @@ data: }, "id": 2, "options": { + "color": "yellow", + "groupByLabel": false, "seriesCountSize": "sm", "showSeriesCount": false, "text": "Default value of text input option" @@ -3120,7 +3122,7 @@ data: } }, "queryType": "sql", - "rawSql": "SELECT sourcePodName, sourcePodNamespace, sourceNodeName, destinationPodName, destinationNodeName, destinationServicePortName, octetDeltaCount FROM flows\nWHERE sourcePodNamespace NOT IN ('kube-system', 'flow-visibility', 'flow-aggregator')\nAND destinationPodNamespace NOT IN ('kube-system', 'flow-visibility', 'flow-aggregator')\nAND destinationPodName != ''\nAND sourcePodName != ''\nAND octetDeltaCount != 0\nAND $__timeFilter(flowEndSeconds)\nORDER BY flowEndSeconds DESC", + "rawSql": "SELECT sourcePodName, sourcePodLabels, sourcePodNamespace, sourceNodeName, destinationPodName, destinationPodLabels, destinationNodeName, destinationServicePortName, octetDeltaCount FROM flows\nWHERE sourcePodNamespace NOT IN ('kube-system', 'flow-visibility', 'flow-aggregator')\nAND destinationPodNamespace NOT IN ('kube-system', 'flow-visibility', 'flow-aggregator')\nAND destinationPodName != ''\nAND sourcePodName != ''\nAND octetDeltaCount != 0\nAND $__timeFilter(flowEndSeconds)\nORDER BY flowEndSeconds DESC", "refId": "A" } ], @@ -3162,7 +3164,7 @@ data: "timezone": "", "title": "network_topology_dashboard", "uid": "yRVDEad4k", - "version": 1, + "version": 2, "weekStart": "" } networkpolicy_dashboard.json: | @@ -6366,7 +6368,7 @@ spec: containers: - env: - name: GF_INSTALL_PLUGINS - value: https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-sankey-plugin-1.0.2.zip;theia-grafana-sankey-plugin,https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-chord-plugin-1.0.1.zip;theia-grafana-chord-plugin,https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-dependency-plugin-1.0.1.zip;theia-grafana-dependency-plugin,grafana-clickhouse-datasource + value: https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-sankey-plugin-1.0.2.zip;theia-grafana-sankey-plugin,https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-chord-plugin-1.0.1.zip;theia-grafana-chord-plugin,https://downloads.antrea.io/artifacts/grafana-custom-plugins/theia-grafana-dependency-plugin-1.0.2.zip;theia-grafana-dependency-plugin,grafana-clickhouse-datasource 1.0.1 - name: CLICKHOUSE_USERNAME valueFrom: diff --git a/docs/network-flow-visibility.md b/docs/network-flow-visibility.md index b751b4d59..bda012c3d 100644 --- a/docs/network-flow-visibility.md +++ b/docs/network-flow-visibility.md @@ -764,6 +764,13 @@ the selected time range. Network Topology Dashboard service dependency graph +By editing the panel, users can group Pods by label, allowing Pod squares in +the diagram to represent a set of Pods with the same label value. It is also +possible to choose the color of the Pod squares to be red, yellow, green, or +blue. + +Network Topology Dashboard additional configuration options + ### Dashboard Customization If you would like to make any change to any of the pre-built dashboards, or build diff --git a/plugins/grafana-custom-plugins/grafana-dependency-plugin/CHANGELOG.md b/plugins/grafana-custom-plugins/grafana-dependency-plugin/CHANGELOG.md index 987acf081..188494c95 100644 --- a/plugins/grafana-custom-plugins/grafana-dependency-plugin/CHANGELOG.md +++ b/plugins/grafana-custom-plugins/grafana-dependency-plugin/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.0.2 - 05-02-2023 + +Added "Group by Pod Label" toggle and Pod square color choices. + ## 1.0.1 - 04-13-2023 Added theming for dependency plugin. diff --git a/plugins/grafana-custom-plugins/grafana-dependency-plugin/README.md b/plugins/grafana-custom-plugins/grafana-dependency-plugin/README.md index 0b34736f1..f9eb24cfc 100644 --- a/plugins/grafana-custom-plugins/grafana-dependency-plugin/README.md +++ b/plugins/grafana-custom-plugins/grafana-dependency-plugin/README.md @@ -42,18 +42,22 @@ loading of data for the Service Dependency Graph Plugin, the query is expected to return the following fields, in arbitrary order. - field 1: sourcePodName value with name or an alias of `sourcePodName` -- field 2: sourceNodeName value with name or an alias of `sourceNodeName` -- field 3: destinationPodName value with name or an alias of `destinationPodName` -- field 4: destinationNodeName value with name or an alias of `destinationNodeName` -- field 5: destinationServicePortName value with name or an alias of `destinationServicePortName` -- field 6: octetDeltaCount value with name or an alias of `octetDeltaCount` +- field 2: sourcePodLabels value with name or alias of `sourcePodLabels` +- field 3: sourceNodeName value with name or an alias of `sourceNodeName` +- field 4: destinationPodName value with name or an alias of `destinationPodName` +- field 5: destinationPodLabels value with name or an alias of `destinationPodLabels` +- field 6: destinationNodeName value with name or an alias of `destinationNodeName` +- field 7: destinationServicePortName value with name or an alias of `destinationServicePortName` +- field 8: octetDeltaCount value with name or an alias of `octetDeltaCount` ClickHouse query example: ```sql SELECT sourcePodName, +sourcePodLabels, sourceNodeName, destinationPodName, +destinationPodLabels, destinationNodeName, destinationServicePortName, octetDeltaCount @@ -106,6 +110,16 @@ installed panels will appear. For more information, visit the docs on [Grafana p ## Customization +### 3. Customize the Panel Options + +Users can customize the panel by editing its options and choosing to group the +diagram based on a chosen Pod label. It is also possible to change the color +of the Pod squares in the diagram. + +Panel Option Editor View + +### 4. Further Customization + This plugin is built with [@grafana/toolkit](https://www.npmjs.com/package/@grafana/toolkit), which is a CLI that enables efficient development of Grafana plugins. To customize the plugin and do local testings: diff --git a/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/DependencyPanel.tsx b/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/DependencyPanel.tsx index 3ecf05696..c11434f39 100644 --- a/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/DependencyPanel.tsx +++ b/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/DependencyPanel.tsx @@ -19,8 +19,10 @@ export const DependencyPanel: React.FC = ({ options, data, width, height const theme = useTheme2(); const frame = data.series[0]; const sourcePodNames = frame.fields.find((field) => field.name === 'sourcePodName'); + const sourcePodLabels = frame.fields.find((field) => field.name === 'sourcePodLabels'); const sourceNodeNames = frame.fields.find((field) => field.name === 'sourceNodeName'); const destinationPodNames = frame.fields.find((field) => field.name === 'destinationPodName'); + const destinationPodLabels = frame.fields.find((field) => field.name === 'destinationPodLabels'); const destinationNodeNames = frame.fields.find((field) => field.name === 'destinationNodeName'); const destinationServicePortNames = frame.fields.find((field) => field.name === 'destinationServicePortName'); const octetDeltaCounts = frame.fields.find((field) => field.name === 'octetDeltaCount'); @@ -29,12 +31,27 @@ export const DependencyPanel: React.FC = ({ options, data, width, height let srcToDestMap = new Map>(); let graphString = 'graph LR;\n'; + let boxColor; + switch(options.color) { + case 'red': + boxColor = theme.colors.error.main; + break; + case 'yellow': + boxColor = theme.colors.warning.main; + break; + case 'green': + boxColor = theme.colors.success.main; + break; + case 'blue': + boxColor = theme.colors.primary.main; + break; + } mermaid.initialize({ startOnLoad: true, theme: 'base', themeVariables: { - primaryColor: theme.colors.warning.main, + primaryColor: boxColor, secondaryColor: theme.colors.background.canvas, tertiaryColor: theme.colors.background.canvas, primaryTextColor: theme.colors.text.maxContrast, @@ -44,26 +61,44 @@ export const DependencyPanel: React.FC = ({ options, data, width, height for (let i = 0; i < frame.length; i++) { const sourcePodName = sourcePodNames?.values.get(i); + const sourcePodLabel = sourcePodLabels?.values.get(i); const sourceNodeName = sourceNodeNames?.values.get(i); const destinationPodName = destinationPodNames?.values.get(i); + const destinationPodLabel = destinationPodLabels?.values.get(i); const destinationNodeName = destinationNodeNames?.values.get(i); const destinationServicePortName = destinationServicePortNames?.values.get(i); const octetDeltaCount = octetDeltaCounts?.values.get(i); + function getName(groupByLabel: boolean, source: boolean, labelJSON: string) { + if(!groupByLabel || labelJSON === undefined || options.labelName === undefined) { + return source ? sourcePodName : destinationPodName; + } + let labels = JSON.parse(labelJSON); + if(labels[options.labelName] !== undefined) { + return labels[options.labelName]; + } + return sourcePodName; + } + + let groupByPodLabel = options.groupByPodLabel; + let srcName = getName(groupByPodLabel, true, sourcePodLabel); + let dstName = getName(groupByPodLabel, false, destinationPodLabel); + // determine which nodes contain which pods - if (nodeToPodMap.has(sourceNodeName) && !nodeToPodMap.get(sourceNodeName)?.includes(sourcePodName)) { - nodeToPodMap.get(sourceNodeName)?.push(sourcePodName); + if (nodeToPodMap.has(sourceNodeName) && !nodeToPodMap.get(sourceNodeName)?.includes(srcName)) { + nodeToPodMap.get(sourceNodeName)?.push(srcName); } else if (!nodeToPodMap.has(sourceNodeName)) { - nodeToPodMap.set(sourceNodeName, [sourcePodName]); + nodeToPodMap.set(sourceNodeName, [srcName]); } - if (nodeToPodMap.has(destinationNodeName) && !nodeToPodMap.get(destinationNodeName)?.includes(destinationPodName)) { - nodeToPodMap.get(destinationNodeName)?.push(destinationPodName); + if (nodeToPodMap.has(destinationNodeName) && !nodeToPodMap.get(destinationNodeName)?.includes(dstName)) { + nodeToPodMap.get(destinationNodeName)?.push(dstName); } else if (!nodeToPodMap.has(destinationNodeName)) { - nodeToPodMap.set(destinationNodeName, [destinationPodName]); + nodeToPodMap.set(destinationNodeName, [dstName]); } + // determine how much traffic is being sent - let pod_src = sourceNodeName+'_pod_'+sourcePodName; - let pod_dst = destinationNodeName+'_pod_'+destinationPodName; + let pod_src = sourceNodeName+'_pod_'+srcName; + let pod_dst = destinationNodeName+'_pod_'+dstName; let svc_dst = 'svc_'+destinationServicePortName; let dests = new Map(); dests.set(pod_dst, octetDeltaCount); diff --git a/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/module.ts b/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/module.ts index b45186ea5..1f291f7f4 100644 --- a/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/module.ts +++ b/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/module.ts @@ -2,4 +2,38 @@ import { PanelPlugin } from '@grafana/data'; import { DependencyOptions } from './types'; import { DependencyPanel } from './DependencyPanel'; -export const plugin = new PanelPlugin(DependencyPanel).setPanelOptions((builder) => builder); +export const plugin = new PanelPlugin(DependencyPanel).setPanelOptions((builder) => builder.addBooleanSwitch({ + path: 'groupByPodLabel', + name: 'Group by Pod Label', + defaultValue: false, +}).addTextInput({ + path: 'labelName', + name: 'Label Name', + settings: { + placeholder: 'app', + }, +}).addRadio({ + path: 'color', + name: 'Box Color', + defaultValue: 'yellow', + settings: { + options: [ + { + value: 'red', + label: 'Red', + }, + { + value: 'yellow', + label: 'Yellow', + }, + { + value: 'green', + label: 'Green', + }, + { + value: 'blue', + label: 'Blue', + } + ] + }, +})); diff --git a/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/types.ts b/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/types.ts index bfd93b897..613122782 100644 --- a/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/types.ts +++ b/plugins/grafana-custom-plugins/grafana-dependency-plugin/src/types.ts @@ -1 +1,7 @@ -export interface DependencyOptions {} +type BoxColor = 'red' | 'yellow' | 'green' | 'blue' + +export interface DependencyOptions { + groupByPodLabel: boolean; + labelName: string; + color: BoxColor; +}