diff --git a/build/charts/theia/README.md b/build/charts/theia/README.md index 870ba3ee2..d0469ddce 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-chord-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..fa5eb98f7 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-chord-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..ade8ddf79 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-chord-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..809b3538d 100644 --- a/plugins/grafana-custom-plugins/grafana-dependency-plugin/README.md +++ b/plugins/grafana-custom-plugins/grafana-dependency-plugin/README.md @@ -42,18 +42,24 @@ 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: sourcePodNamespace value with name or alias of `sourcePodNamespace` +- field 4: sourceNodeName value with name or an alias of `sourceNodeName` +- field 5: destinationPodName value with name or an alias of `destinationPodName` +- field 6: destinationPodLabels value with name or an alias of `destinationPodLabels` +- field 7: destinationNodeName value with name or an alias of `destinationNodeName` +- field 8: destinationServicePortName value with name or an alias of `destinationServicePortName` +- field 9: octetDeltaCount value with name or an alias of `octetDeltaCount` ClickHouse query example: ```sql SELECT sourcePodName, +sourcePodLabels, +sourcePodNamespace, sourceNodeName, destinationPodName, +destinationPodLabels, destinationNodeName, destinationServicePortName, octetDeltaCount @@ -106,6 +112,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; +}