Skip to content

Commit fe3659e

Browse files
committed
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.
1 parent 2c107de commit fe3659e

File tree

8 files changed

+148
-69
lines changed

8 files changed

+148
-69
lines changed

src/common/promql.ts

+61-29
Original file line numberDiff line numberDiff line change
@@ -22,25 +22,6 @@ enum MatchingModifiers {
2222
ON = 'on',
2323
}
2424

25-
class PromQLMatchingModifier extends PromQLExpression {
26-
27-
constructor(private modifier: MatchingModifiers, private labels: string[], private left: PromQLExpression) {
28-
super();
29-
}
30-
31-
stringify() {
32-
return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')}) `;
33-
}
34-
35-
groupLeft(labels: string[], vectorExpr: PromQLVectorExpression) {
36-
return new PromQLGroupModifier(GroupModifiers.GROUP_LEFT, labels, this, vectorExpr);
37-
}
38-
39-
groupRight(labels: string[], vectorExpr: PromQLVectorExpression) {
40-
return new PromQLGroupModifier(GroupModifiers.GROUP_RIGHT, labels, this, vectorExpr);
41-
}
42-
}
43-
4425
enum BinaryOperators {
4526
ADD = '+',
4627
SUBTRACT = '-',
@@ -50,9 +31,6 @@ enum BinaryOperators {
5031
POW = '^',
5132
}
5233

53-
54-
55-
5634
export abstract class PromQLVectorExpression extends PromQLExpression {
5735

5836
add() {
@@ -79,19 +57,49 @@ export abstract class PromQLVectorExpression extends PromQLExpression {
7957
return new PromQLBinaryExpression(BinaryOperators.POW, this);
8058
}
8159

82-
or(vectorExpr: PromQLVectorExpression) {
83-
return new PromQLLogicalExpression(LogicalOperators.OR, this, vectorExpr);
60+
or() {
61+
return new PromQLLogicalExpression(LogicalOperators.OR, this);
8462
}
8563

86-
and(vectorExpr: PromQLVectorExpression) {
87-
return new PromQLLogicalExpression(LogicalOperators.AND, this, vectorExpr);
64+
and() {
65+
return new PromQLLogicalExpression(LogicalOperators.AND, this);
8866
}
8967

9068
equals(value: number) {
9169
return new PromQLComparisonExpression(ComparisonOperators.EQUALS, this, new PromQLScalarExpression(value));
9270
}
9371
}
9472

73+
class PromQLMatchingModifier extends PromQLVectorExpression {
74+
75+
private right?: PromQLExpression;
76+
77+
constructor(private modifier: MatchingModifiers, private labels: string[], private left: PromQLExpression) {
78+
super();
79+
}
80+
81+
stringify() {
82+
if (this.right) {
83+
return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')}) ${this.right.stringify()}`;
84+
} else {
85+
return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')})`;
86+
}
87+
}
88+
89+
groupLeft(labels: string[], vectorExpr: PromQLVectorExpression) {
90+
return new PromQLGroupModifier(GroupModifiers.GROUP_LEFT, labels, this, vectorExpr);
91+
}
92+
93+
groupRight(labels: string[], vectorExpr: PromQLVectorExpression) {
94+
return new PromQLGroupModifier(GroupModifiers.GROUP_RIGHT, labels, this, vectorExpr);
95+
}
96+
97+
withExpression(expr: PromQLExpression) {
98+
this.right = expr;
99+
return PromQL.parenthesis(this);
100+
}
101+
}
102+
95103
class PromQLBinaryExpression extends PromQLVectorExpression {
96104

97105
private right?: PromQLExpression;
@@ -142,12 +150,36 @@ enum LogicalOperators {
142150
}
143151

144152
class PromQLLogicalExpression extends PromQLVectorExpression {
145-
constructor(private operator: LogicalOperators, private left: PromQLExpression, private right: PromQLExpression) {
153+
154+
private right?: PromQLExpression;
155+
156+
constructor(private operator: LogicalOperators, private left: PromQLExpression) {
146157
super();
147158
}
148159

149160
stringify() {
150-
return `${this.left.stringify()} ${this.operator} (${this.right.stringify()}) `;
161+
if (this.right) {
162+
return `${this.left.stringify()} ${this.operator} ${this.right.stringify()}`;
163+
} else {
164+
return `${this.left.stringify()} ${this.operator}`;
165+
}
166+
}
167+
168+
ignoring(labels: string[]) {
169+
return new PromQLMatchingModifier(MatchingModifiers.IGNORING, labels, this);
170+
}
171+
172+
on(labels: string[]) {
173+
return new PromQLMatchingModifier(MatchingModifiers.ON, labels, this);
174+
}
175+
176+
withScalar(scalar: number) {
177+
return new PromQLScalarExpression(scalar, this);
178+
}
179+
180+
withExpression(expr: PromQLExpression) {
181+
this.right = expr;
182+
return PromQL.parenthesis(this);
151183
}
152184
}
153185

@@ -482,7 +514,7 @@ export class PromQL {
482514
}
483515

484516
static labelReplace(exp: PromQLVectorExpression, dest: string, sourceLabel: string, replacement: string, regex: string) {
485-
return new PromQLLabelReplaceFunction(exp, dest, sourceLabel, replacement, regex);
517+
return new PromQLLabelReplaceFunction(exp, dest, replacement, sourceLabel, regex);
486518
}
487519

488520
static parenthesis(expr: PromQLVectorExpression) {

src/metrics/metrics.ts

+7
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,13 @@ export const Metrics = {
371371
controller: 'controller',
372372
}
373373
},
374+
// Nginx Ingress Controller
375+
nginxIngressControllerBuildInfo: {
376+
name: 'nginx_ingress_controller_build_info',
377+
labels:{
378+
controllerClass: 'controller_class',
379+
}
380+
},
374381
// Services
375382
kubeServiceInfo: {
376383
name: 'kube_service_info',

src/pages/Clusters/tabs/Nodes/Queries.tsx

+15-5
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
5353
value: ''
5454
}
5555
})
56-
).or(
56+
)
57+
.or()
58+
.withExpression(
5759
baseQuery.multiply().withScalar(0)
5860
)
5961
)
@@ -70,7 +72,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
7072
...commonCarryOverLabels,
7173
],
7274
this.createCpuRequestsQuery('$cluster', {})
73-
).or(
75+
)
76+
.or()
77+
.withExpression(
7478
baseQuery.multiply().withScalar(0)
7579
)
7680
)
@@ -87,7 +91,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
8791
...commonCarryOverLabels,
8892
],
8993
this.createCoresQuery('$cluster', {})
90-
).or(
94+
)
95+
.or()
96+
.withExpression(
9197
baseQuery.multiply().withScalar(0)
9298
)
9399
)
@@ -104,7 +110,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
104110
...commonCarryOverLabels,
105111
],
106112
this.createPodCountQuery('$cluster', {})
107-
).or(
113+
)
114+
.or()
115+
.withExpression(
108116
baseQuery.multiply().withScalar(0)
109117
)
110118
)
@@ -121,7 +129,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
121129
...commonCarryOverLabels,
122130
],
123131
this.createNodeAgeQuery('$cluster', {})
124-
).or(
132+
)
133+
.or()
134+
.withExpression(
125135
baseQuery.multiply().withScalar(0)
126136
)
127137
)

src/pages/Network/pages/ingresses/index.tsx

+44-28
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ import { usePluginJsonData } from "utils/utils.plugin";
2222
import { createTimeRange, createTopLevelVariables } from "common/variableHelpers";
2323
import { AlertsTable } from "components/AlertsTable";
2424
import { createResourceLabels } from "pages/Workloads/components/ResourceLabels";
25-
import { PromQL } from "common/promql";
25+
import { MatchOperators, PromQL } from "common/promql";
2626
import { Metrics } from "metrics/metrics";
2727
import React, { useMemo } from "react";
28-
import { DataFrameView } from "@grafana/data";
2928
import { Spinner } from "@grafana/ui";
3029
import {
3130
getNginxFailureRatioPanel,
@@ -45,7 +44,7 @@ import Analytics from "components/Analytics";
4544
// Try connecting kube_ingress_path service_name to pods
4645

4746
interface ConditionalSceneObjectState extends SceneObjectState {
48-
builder: (data: string) => SceneObject<SceneObjectState>;
47+
builder: (rowCounts: Map<string, number>) => SceneObject<SceneObjectState>;
4948
children?: Array<SceneObject<SceneObjectState>>;
5049
}
5150

@@ -63,16 +62,16 @@ function ConditionalRenderer({ model }: SceneComponentProps<ConditionalSceneObje
6362
return [];
6463
}
6564

66-
const frame = data.series[0];
67-
const view = new DataFrameView<any>(frame);
68-
const rows = view.toArray();
69-
70-
const controller = rows && rows.length > 0 ? rows[0].controller : undefined;
65+
const rowCounts = new Map<string, number>();
66+
// result counts per query
67+
for (const serie of data.series) {
68+
rowCounts.set(serie.refId || 'unknown', serie.length);
69+
}
7170

7271
// By setting it via state we can trigger render but also grafana connects the model to the scene graph
7372
// so that all nested objects could use the variables ...
7473
model.setState({
75-
children: [builder(controller)]
74+
children: [builder(rowCounts)]
7675
});
7776

7877
return;
@@ -92,19 +91,33 @@ function ConditionalRenderer({ model }: SceneComponentProps<ConditionalSceneObje
9291
}
9392
}
9493

95-
function ingressInfoQuery(namespace: string, ingress: string) {
96-
return PromQL.metric(Metrics.kubeIngressInfo.name)
97-
.withLabelEquals('namespace', namespace)
98-
.withLabelEquals('ingress', ingress)
99-
.withLabelEquals('cluster', '$cluster')
100-
.multiply()
101-
.on([
102-
Metrics.kubeIngressInfo.labels.ingressClass
103-
])
104-
.groupLeft([
105-
Metrics.kubeIngressClassInfo.labels.controller
106-
], PromQL.metric(Metrics.kubeIngressClassInfo.name)
107-
.withLabelEquals('cluster', '$cluster')
94+
function nginxBuildInfoQuery(namespace: string, ingress: string) {
95+
96+
return PromQL.metric(Metrics.nginxIngressControllerBuildInfo.name)
97+
.withLabel('cluster', MatchOperators.EQUALS, '$cluster')
98+
.and()
99+
.on(['controller_class', 'cluster'])
100+
.withExpression(
101+
PromQL.labelReplace(
102+
PromQL.metric(
103+
Metrics.kubeIngressInfo.name
104+
)
105+
.withLabel('cluster', MatchOperators.EQUALS, '$cluster')
106+
.withLabel('ingress', MatchOperators.EQUALS, ingress)
107+
.multiply()
108+
.on(['ingressclass', 'cluster'])
109+
.groupLeft(
110+
[
111+
'controller'
112+
],
113+
PromQL.metric(Metrics.kubeIngressClassInfo.name)
114+
.withLabel('cluster', MatchOperators.EQUALS, '$cluster')
115+
),
116+
'controller_class',
117+
'controller',
118+
'$1',
119+
'(.*)'
120+
)
108121
)
109122
}
110123

@@ -201,8 +214,11 @@ function displayBasicNginxMetrics(ingress: string, namespace: string) {
201214
});
202215
}
203216

204-
function buildRequestsPanels(controller: string, ingress: string, namespace: string) {
205-
if (controller === 'k8s.io/ingress-nginx') {
217+
function buildRequestsPanels(rowCounts: Map<string, number>, ingress: string, namespace: string) {
218+
219+
const nginxBuildInfo = rowCounts.get('nginx_ingress_controller_build_info') || 0;
220+
221+
if (nginxBuildInfo > 0) {
206222
return displayBasicNginxMetrics(ingress, namespace);
207223
} else {
208224
return new SceneFlexLayout({
@@ -222,8 +238,8 @@ function getScene(namespace: string, ingress: string) {
222238
},
223239
queries: [
224240
{
225-
refId: 'ingresses',
226-
expr: ingressInfoQuery(namespace, ingress).stringify(),
241+
refId: 'nginx_ingress_controller_build_info',
242+
expr: nginxBuildInfoQuery(namespace, ingress).stringify(),
227243
instant: true,
228244
format: 'table'
229245
},
@@ -289,8 +305,8 @@ function getScene(namespace: string, ingress: string) {
289305
width: '100%',
290306
body: new ConditionalSceneObject({
291307
$data: ingressInfoData,
292-
builder: (controller: string) => {
293-
return buildRequestsPanels(controller, ingress, namespace)
308+
builder: (rowCounts: Map<string, number>) => {
309+
return buildRequestsPanels(rowCounts, ingress, namespace)
294310
}
295311
}),
296312
}),

src/pages/Workloads/tabs/DaemonSets/Queries.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@ export class DaemonSetsQueryBuilder implements QueryBuilder<TableRow> {
8080
}
8181
})
8282
).by(['namespace', 'daemonset'])
83-
).or(
83+
)
84+
.or()
85+
.withExpression(
8486
baseQuery.multiply().withScalar(0)
8587
)
8688
)
@@ -94,7 +96,9 @@ export class DaemonSetsQueryBuilder implements QueryBuilder<TableRow> {
9496
.groupRight(
9597
[],
9698
createReplicasQuery('$cluster', {})
97-
).or(
99+
)
100+
.or()
101+
.withExpression(
98102
baseQuery.multiply().withScalar(0)
99103
)
100104
)

src/pages/Workloads/tabs/Deployments/Queries.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ export class DeploymentQueryBuilder implements QueryBuilder<TableRow> {
114114
}
115115
})
116116
).by(['namespace', 'deployment'])
117-
).or(
117+
)
118+
.or()
119+
.withExpression(
118120
baseQuery.multiply().withScalar(0)
119121
)
120122
)
@@ -128,7 +130,9 @@ export class DeploymentQueryBuilder implements QueryBuilder<TableRow> {
128130
.groupRight(
129131
[],
130132
createReplicasQuery('$cluster', {})
131-
).or(
133+
)
134+
.or()
135+
.withExpression(
132136
baseQuery.multiply().withScalar(0)
133137
)
134138
)

src/pages/Workloads/tabs/Pods/Queries.ts

+3-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)