From fe3659eb676e94f39370dd8441d7123481ce76fe Mon Sep 17 00:00:00 2001 From: Tiit Hansen Date: Sun, 19 Jan 2025 12:19:52 +0200 Subject: [PATCH] fix: Fix ingress panels when multiple controllers present in the cluster When there are multiple ingress controllers in a cluster and ingressclasses are used then controller names must be different. Instead of detecting nginx based on controller name try to join controller class and nginx build info to detect ingress controller type. --- src/common/promql.ts | 90 +++++++++++++------ src/metrics/metrics.ts | 7 ++ src/pages/Clusters/tabs/Nodes/Queries.tsx | 20 +++-- src/pages/Network/pages/ingresses/index.tsx | 72 +++++++++------ .../Workloads/tabs/DaemonSets/Queries.tsx | 8 +- .../Workloads/tabs/Deployments/Queries.ts | 8 +- src/pages/Workloads/tabs/Pods/Queries.ts | 4 +- .../Workloads/tabs/StatefulSets/Queries.tsx | 8 +- 8 files changed, 148 insertions(+), 69 deletions(-) diff --git a/src/common/promql.ts b/src/common/promql.ts index ba59129..d64a4eb 100644 --- a/src/common/promql.ts +++ b/src/common/promql.ts @@ -22,25 +22,6 @@ enum MatchingModifiers { ON = 'on', } -class PromQLMatchingModifier extends PromQLExpression { - - constructor(private modifier: MatchingModifiers, private labels: string[], private left: PromQLExpression) { - super(); - } - - stringify() { - return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')}) `; - } - - groupLeft(labels: string[], vectorExpr: PromQLVectorExpression) { - return new PromQLGroupModifier(GroupModifiers.GROUP_LEFT, labels, this, vectorExpr); - } - - groupRight(labels: string[], vectorExpr: PromQLVectorExpression) { - return new PromQLGroupModifier(GroupModifiers.GROUP_RIGHT, labels, this, vectorExpr); - } -} - enum BinaryOperators { ADD = '+', SUBTRACT = '-', @@ -50,9 +31,6 @@ enum BinaryOperators { POW = '^', } - - - export abstract class PromQLVectorExpression extends PromQLExpression { add() { @@ -79,12 +57,12 @@ export abstract class PromQLVectorExpression extends PromQLExpression { return new PromQLBinaryExpression(BinaryOperators.POW, this); } - or(vectorExpr: PromQLVectorExpression) { - return new PromQLLogicalExpression(LogicalOperators.OR, this, vectorExpr); + or() { + return new PromQLLogicalExpression(LogicalOperators.OR, this); } - and(vectorExpr: PromQLVectorExpression) { - return new PromQLLogicalExpression(LogicalOperators.AND, this, vectorExpr); + and() { + return new PromQLLogicalExpression(LogicalOperators.AND, this); } equals(value: number) { @@ -92,6 +70,36 @@ export abstract class PromQLVectorExpression extends PromQLExpression { } } +class PromQLMatchingModifier extends PromQLVectorExpression { + + private right?: PromQLExpression; + + constructor(private modifier: MatchingModifiers, private labels: string[], private left: PromQLExpression) { + super(); + } + + stringify() { + if (this.right) { + return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')}) ${this.right.stringify()}`; + } else { + return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')})`; + } + } + + groupLeft(labels: string[], vectorExpr: PromQLVectorExpression) { + return new PromQLGroupModifier(GroupModifiers.GROUP_LEFT, labels, this, vectorExpr); + } + + groupRight(labels: string[], vectorExpr: PromQLVectorExpression) { + return new PromQLGroupModifier(GroupModifiers.GROUP_RIGHT, labels, this, vectorExpr); + } + + withExpression(expr: PromQLExpression) { + this.right = expr; + return PromQL.parenthesis(this); + } +} + class PromQLBinaryExpression extends PromQLVectorExpression { private right?: PromQLExpression; @@ -142,12 +150,36 @@ enum LogicalOperators { } class PromQLLogicalExpression extends PromQLVectorExpression { - constructor(private operator: LogicalOperators, private left: PromQLExpression, private right: PromQLExpression) { + + private right?: PromQLExpression; + + constructor(private operator: LogicalOperators, private left: PromQLExpression) { super(); } stringify() { - return `${this.left.stringify()} ${this.operator} (${this.right.stringify()}) `; + if (this.right) { + return `${this.left.stringify()} ${this.operator} ${this.right.stringify()}`; + } else { + return `${this.left.stringify()} ${this.operator}`; + } + } + + ignoring(labels: string[]) { + return new PromQLMatchingModifier(MatchingModifiers.IGNORING, labels, this); + } + + on(labels: string[]) { + return new PromQLMatchingModifier(MatchingModifiers.ON, labels, this); + } + + withScalar(scalar: number) { + return new PromQLScalarExpression(scalar, this); + } + + withExpression(expr: PromQLExpression) { + this.right = expr; + return PromQL.parenthesis(this); } } @@ -482,7 +514,7 @@ export class PromQL { } static labelReplace(exp: PromQLVectorExpression, dest: string, sourceLabel: string, replacement: string, regex: string) { - return new PromQLLabelReplaceFunction(exp, dest, sourceLabel, replacement, regex); + return new PromQLLabelReplaceFunction(exp, dest, replacement, sourceLabel, regex); } static parenthesis(expr: PromQLVectorExpression) { diff --git a/src/metrics/metrics.ts b/src/metrics/metrics.ts index f11d866..6e558d5 100644 --- a/src/metrics/metrics.ts +++ b/src/metrics/metrics.ts @@ -371,6 +371,13 @@ export const Metrics = { controller: 'controller', } }, + // Nginx Ingress Controller + nginxIngressControllerBuildInfo: { + name: 'nginx_ingress_controller_build_info', + labels:{ + controllerClass: 'controller_class', + } + }, // Services kubeServiceInfo: { name: 'kube_service_info', diff --git a/src/pages/Clusters/tabs/Nodes/Queries.tsx b/src/pages/Clusters/tabs/Nodes/Queries.tsx index 2b62483..0c22ac9 100644 --- a/src/pages/Clusters/tabs/Nodes/Queries.tsx +++ b/src/pages/Clusters/tabs/Nodes/Queries.tsx @@ -53,7 +53,9 @@ export class NodesQueryBuilder implements QueryBuilder { value: '' } }) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) @@ -70,7 +72,9 @@ export class NodesQueryBuilder implements QueryBuilder { ...commonCarryOverLabels, ], this.createCpuRequestsQuery('$cluster', {}) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) @@ -87,7 +91,9 @@ export class NodesQueryBuilder implements QueryBuilder { ...commonCarryOverLabels, ], this.createCoresQuery('$cluster', {}) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) @@ -104,7 +110,9 @@ export class NodesQueryBuilder implements QueryBuilder { ...commonCarryOverLabels, ], this.createPodCountQuery('$cluster', {}) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) @@ -121,7 +129,9 @@ export class NodesQueryBuilder implements QueryBuilder { ...commonCarryOverLabels, ], this.createNodeAgeQuery('$cluster', {}) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) diff --git a/src/pages/Network/pages/ingresses/index.tsx b/src/pages/Network/pages/ingresses/index.tsx index 124cb8c..d9e0724 100644 --- a/src/pages/Network/pages/ingresses/index.tsx +++ b/src/pages/Network/pages/ingresses/index.tsx @@ -22,10 +22,9 @@ import { usePluginJsonData } from "utils/utils.plugin"; import { createTimeRange, createTopLevelVariables } from "common/variableHelpers"; import { AlertsTable } from "components/AlertsTable"; import { createResourceLabels } from "pages/Workloads/components/ResourceLabels"; -import { PromQL } from "common/promql"; +import { MatchOperators, PromQL } from "common/promql"; import { Metrics } from "metrics/metrics"; import React, { useMemo } from "react"; -import { DataFrameView } from "@grafana/data"; import { Spinner } from "@grafana/ui"; import { getNginxFailureRatioPanel, @@ -45,7 +44,7 @@ import Analytics from "components/Analytics"; // Try connecting kube_ingress_path service_name to pods interface ConditionalSceneObjectState extends SceneObjectState { - builder: (data: string) => SceneObject; + builder: (rowCounts: Map) => SceneObject; children?: Array>; } @@ -63,16 +62,16 @@ function ConditionalRenderer({ model }: SceneComponentProps(frame); - const rows = view.toArray(); - - const controller = rows && rows.length > 0 ? rows[0].controller : undefined; + const rowCounts = new Map(); + // result counts per query + for (const serie of data.series) { + rowCounts.set(serie.refId || 'unknown', serie.length); + } // By setting it via state we can trigger render but also grafana connects the model to the scene graph // so that all nested objects could use the variables ... model.setState({ - children: [builder(controller)] + children: [builder(rowCounts)] }); return; @@ -92,19 +91,33 @@ function ConditionalRenderer({ model }: SceneComponentProps, ingress: string, namespace: string) { + + const nginxBuildInfo = rowCounts.get('nginx_ingress_controller_build_info') || 0; + + if (nginxBuildInfo > 0) { return displayBasicNginxMetrics(ingress, namespace); } else { return new SceneFlexLayout({ @@ -222,8 +238,8 @@ function getScene(namespace: string, ingress: string) { }, queries: [ { - refId: 'ingresses', - expr: ingressInfoQuery(namespace, ingress).stringify(), + refId: 'nginx_ingress_controller_build_info', + expr: nginxBuildInfoQuery(namespace, ingress).stringify(), instant: true, format: 'table' }, @@ -289,8 +305,8 @@ function getScene(namespace: string, ingress: string) { width: '100%', body: new ConditionalSceneObject({ $data: ingressInfoData, - builder: (controller: string) => { - return buildRequestsPanels(controller, ingress, namespace) + builder: (rowCounts: Map) => { + return buildRequestsPanels(rowCounts, ingress, namespace) } }), }), diff --git a/src/pages/Workloads/tabs/DaemonSets/Queries.tsx b/src/pages/Workloads/tabs/DaemonSets/Queries.tsx index 2760a0f..9fa4177 100644 --- a/src/pages/Workloads/tabs/DaemonSets/Queries.tsx +++ b/src/pages/Workloads/tabs/DaemonSets/Queries.tsx @@ -80,7 +80,9 @@ export class DaemonSetsQueryBuilder implements QueryBuilder { } }) ).by(['namespace', 'daemonset']) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) @@ -94,7 +96,9 @@ export class DaemonSetsQueryBuilder implements QueryBuilder { .groupRight( [], createReplicasQuery('$cluster', {}) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) diff --git a/src/pages/Workloads/tabs/Deployments/Queries.ts b/src/pages/Workloads/tabs/Deployments/Queries.ts index 4e6974e..fe7e5f5 100644 --- a/src/pages/Workloads/tabs/Deployments/Queries.ts +++ b/src/pages/Workloads/tabs/Deployments/Queries.ts @@ -114,7 +114,9 @@ export class DeploymentQueryBuilder implements QueryBuilder { } }) ).by(['namespace', 'deployment']) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) @@ -128,7 +130,9 @@ export class DeploymentQueryBuilder implements QueryBuilder { .groupRight( [], createReplicasQuery('$cluster', {}) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) diff --git a/src/pages/Workloads/tabs/Pods/Queries.ts b/src/pages/Workloads/tabs/Pods/Queries.ts index 18c26b4..76f4f91 100644 --- a/src/pages/Workloads/tabs/Pods/Queries.ts +++ b/src/pages/Workloads/tabs/Pods/Queries.ts @@ -381,7 +381,9 @@ export function createRootQuery( .groupRight( carryOverLabels, sortQuery - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) diff --git a/src/pages/Workloads/tabs/StatefulSets/Queries.tsx b/src/pages/Workloads/tabs/StatefulSets/Queries.tsx index 890101a..6df6050 100644 --- a/src/pages/Workloads/tabs/StatefulSets/Queries.tsx +++ b/src/pages/Workloads/tabs/StatefulSets/Queries.tsx @@ -80,7 +80,9 @@ export class StatefulSetQueryBuilder implements QueryBuilder { } }) ).by(['namespace', 'statefulset']) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) ) @@ -94,7 +96,9 @@ export class StatefulSetQueryBuilder implements QueryBuilder { .groupRight( [], createReplicasQuery('$cluster', {}) - ).or( + ) + .or() + .withExpression( baseQuery.multiply().withScalar(0) ) )