Skip to content

Commit 4b8a08f

Browse files
fix: set rules as silenced if all alerts under them are silenced
1 parent 81a7e26 commit 4b8a08f

File tree

4 files changed

+159
-89
lines changed

4 files changed

+159
-89
lines changed

web/src/components/alerting/AlertRulesPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ const AlertRulesPage_: FC = () => {
204204
onFilterChange={onFilterChange}
205205
rowFilters={rowFilters}
206206
/>
207-
{silences.loadError && <SilencesNotLoadedWarning silencesLoadError={silences.loadError} />}
207+
{silences?.loadError && <SilencesNotLoadedWarning silencesLoadError={silences.loadError} />}
208208
<div id="alert-rules-table-scroll">
209209
<VirtualizedTable<Rule>
210210
aria-label={t('Alerting rules')}

web/src/components/alerting/AlertUtils.tsx

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ import {
55
Alert,
66
AlertSeverity,
77
AlertStates,
8+
PrometheusAlert,
89
PrometheusLabels,
10+
PrometheusRule,
911
RowFilter,
1012
Rule,
13+
RuleStates,
14+
Silence,
15+
SilenceStates,
1116
Timestamp,
1217
} from '@openshift-console/dynamic-plugin-sdk';
1318
import { AlertSource } from '../types';
@@ -420,3 +425,147 @@ export const NamespaceGroupVersionKind = {
420425
kind: NamespaceModel.kind,
421426
version: null,
422427
};
428+
429+
// This function looks to take a alerts and rules and then apply a set of silences to them
430+
// This function mutates the arrays in place and then returns them
431+
export const applySilences = ({
432+
alerts,
433+
silences,
434+
rules,
435+
}: {
436+
silences: Array<Silence>;
437+
alerts: Array<Alert>;
438+
rules: Array<Rule>;
439+
}): { silences: Array<Silence>; alerts: Array<Alert>; rules: Array<Rule> } => {
440+
// We only need to check alerts that are either firing or silenced for if they are still silenced
441+
const firingAlerts = alerts.filter(isAlertFiring);
442+
applySilencesToAlerts({ firingAlerts, silences });
443+
444+
// Only check rules that are firing, silenced or pending to see if they are still silenced
445+
const firingRules = rules.filter(isRuleFiring);
446+
applySilencesToRules({ firingRules, silences });
447+
448+
// Add each alert that is being effected by a silence to the firingAlerts list on the silence
449+
const appliedSilences = silences.map((silence) => {
450+
silence.firingAlerts = firingAlerts.filter((firingAlert) =>
451+
isAlertSilenced(firingAlert, silence),
452+
);
453+
return silence;
454+
});
455+
return { alerts, silences: appliedSilences, rules };
456+
};
457+
458+
// This fucntion mutates the firingAlerts parameter in place to set silence fields on each alert
459+
const applySilencesToAlerts = ({
460+
firingAlerts,
461+
silences,
462+
}: {
463+
silences: Array<Silence>;
464+
firingAlerts: Array<Alert>;
465+
}) => {
466+
// For each firing alert, store a list of the Silences that are silencing it
467+
// and set its state to show it is silenced
468+
firingAlerts.forEach((firingAlert) => {
469+
firingAlert.silencedBy = silences.filter(
470+
(silence) =>
471+
silence.status?.state === SilenceStates.Active && isAlertSilenced(firingAlert, silence),
472+
);
473+
474+
if (firingAlert.silencedBy.length) {
475+
firingAlert.state = AlertStates.Silenced;
476+
// Also set the state of Alerts in `rule.alerts`
477+
478+
firingAlert.rule.alerts.forEach((ruleAlert) => {
479+
if (firingAlert.silencedBy?.some((silence) => isAlertSilenced(ruleAlert, silence))) {
480+
ruleAlert.state = AlertStates.Silenced;
481+
}
482+
});
483+
484+
if (
485+
firingAlert.rule.alerts.length !== 0 &&
486+
firingAlert.rule.alerts.every((alert) => alert.state === AlertStates.Silenced)
487+
) {
488+
firingAlert.rule.state = RuleStates.Silenced;
489+
490+
firingAlert.rule.silencedBy = silences.filter(
491+
(silence) =>
492+
silence.status?.state === SilenceStates.Active &&
493+
firingAlert.rule.alerts.some((alert) => isAlertSilenced(alert, silence)),
494+
);
495+
}
496+
}
497+
});
498+
499+
return firingAlerts;
500+
};
501+
502+
// This fucntion mutates the firingRules parameter in place to set silence fields on each rule
503+
const applySilencesToRules = ({
504+
firingRules,
505+
silences,
506+
}: {
507+
silences: Array<Silence>;
508+
firingRules: Array<Rule>;
509+
}) => {
510+
// For each firing alert, store a list of the Silences that are silencing it
511+
// and set its state to show it is silenced
512+
firingRules.forEach((firingRule) => {
513+
firingRule.silencedBy = silences.filter(
514+
(silence) =>
515+
silence.status?.state === SilenceStates.Active && isRuleSilenced(firingRule, silence),
516+
);
517+
518+
if (firingRule.silencedBy.length) {
519+
firingRule.state = RuleStates.Silenced;
520+
521+
firingRule.alerts.forEach((ruleAlert) => {
522+
if (firingRule.silencedBy?.some((silence) => isAlertSilenced(ruleAlert, silence))) {
523+
ruleAlert.state = AlertStates.Silenced;
524+
}
525+
});
526+
527+
if (
528+
firingRule.alerts.length !== 0 &&
529+
firingRule.alerts.every((alert) => alert.state === AlertStates.Silenced)
530+
) {
531+
firingRule.state = RuleStates.Silenced;
532+
533+
firingRule.silencedBy = silences.filter(
534+
(silence) =>
535+
silence.status?.state === SilenceStates.Active &&
536+
firingRule.alerts.some((alert) => isAlertSilenced(alert, silence)),
537+
);
538+
}
539+
}
540+
});
541+
542+
return firingRules;
543+
};
544+
545+
const isAlertFiring = (alert: PrometheusAlert) =>
546+
alert?.state === AlertStates.Firing || alert?.state === AlertStates.Silenced;
547+
548+
const isRuleFiring = (rule: PrometheusRule) =>
549+
rule?.state === RuleStates.Firing ||
550+
rule?.state === RuleStates.Silenced ||
551+
rule?.state === RuleStates.Pending;
552+
553+
// Determine if an Alert is silenced by a Silence (if all of the Silence's matchers match one of the
554+
// Alert's labels)
555+
const isAlertSilenced = (alert: PrometheusAlert, silence: Silence): boolean => {
556+
return (
557+
isAlertFiring(alert) &&
558+
silence.matchers.every((matcher) => {
559+
const alertValue = alert.labels[matcher.name] ?? '';
560+
const isMatch = matcher.isRegex
561+
? new RegExp(`^${matcher.value}$`).test(alertValue)
562+
: alertValue === matcher.value;
563+
return matcher.isEqual === false && alertValue ? !isMatch : isMatch;
564+
})
565+
);
566+
};
567+
568+
// Determine if an Rule is silenced by a Silence (if all alerts for a rule are silenced)
569+
export const isRuleSilenced = (rule: PrometheusRule, silence: Silence): boolean => {
570+
return isRuleFiring(rule) && rule.alerts.every((alert) => isAlertSilenced(alert, silence));
571+
};

web/src/components/utils.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,6 @@ export const getSilenceName = (silence: Silence) => {
125125
export const alertDescription = (alert: PrometheusAlert | Rule): string =>
126126
alert.annotations?.description || alert.annotations?.message || alert.labels?.alertname;
127127

128-
// Determine if an Alert is silenced by a Silence (if all of the Silence's matchers match one of the
129-
// Alert's labels)
130-
export const isSilenced = (alert: PrometheusAlert, silence: Silence): boolean => {
131-
return (
132-
[AlertStates.Firing, AlertStates.Silenced].includes(alert.state) &&
133-
silence.matchers.every((matcher) => {
134-
const alertValue = alert.labels[matcher.name] ?? '';
135-
const isMatch = matcher.isRegex
136-
? new RegExp(`^${matcher.value}$`).test(alertValue)
137-
: alertValue === matcher.value;
138-
return matcher.isEqual === false && alertValue ? !isMatch : isMatch;
139-
})
140-
);
141-
};
142-
143128
export type ListOrder = (number | string)[];
144129

145130
// Severity sort order is "critical" > "warning" > (anything else in A-Z order) > "none"

web/src/store/reducers.ts

Lines changed: 9 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
import * as _ from 'lodash-es';
2-
import {
3-
Alert,
4-
AlertStates,
5-
RuleStates,
6-
Silence,
7-
SilenceStates,
8-
} from '@openshift-console/dynamic-plugin-sdk';
92

103
import { ActionType, ObserveAction } from './actions';
114

12-
import { isSilenced } from '../components/utils';
135
import { MONITORING_DASHBOARDS_VARIABLE_ALL_OPTION_KEY } from '../components/dashboards/legacy/utils';
146
import {
157
defaultObserveState,
@@ -19,6 +11,7 @@ import {
1911
QueryStructure,
2012
} from './store';
2113
import { produce } from 'immer';
14+
import { applySilences } from '../components/alerting/AlertUtils';
2215

2316
const monitoringReducer = produce((draft: ObserveState, action: ObserveAction): ObserveState => {
2417
if (!draft) {
@@ -179,23 +172,16 @@ const monitoringReducer = produce((draft: ObserveState, action: ObserveAction):
179172
loadError: null,
180173
};
181174
}
182-
183-
const alerts = draft.alerting[datasource][identifier].alerts;
184-
const silences = draft.alerting[datasource][identifier].silences;
185-
186-
const firingAlerts = alerts.filter(isAlertFiring);
187-
188-
const updatedAlerts = silenceFiringAlerts(firingAlerts, silences.data);
189-
draft.alerting[datasource][identifier].alerts = updatedAlerts;
190-
191-
silences.data = silences.data.map((silence) => {
192-
silence.firingAlerts = firingAlerts.filter((firingAlert) =>
193-
isSilenced(firingAlert, silence),
194-
);
195-
return silence;
175+
const { alerts, rules, silences } = applySilences({
176+
alerts: draft.alerting[datasource][identifier].alerts,
177+
silences: draft.alerting[datasource][identifier].silences.data,
178+
rules: draft.alerting[datasource][identifier].rules,
196179
});
197180

198-
draft.alerting[datasource][identifier].silences = silences;
181+
draft.alerting[datasource][identifier].alerts = alerts;
182+
draft.alerting[datasource][identifier].rules = rules;
183+
draft.alerting[datasource][identifier].silences.data = silences;
184+
199185
break;
200186
}
201187

@@ -406,54 +392,4 @@ const monitoringReducer = produce((draft: ObserveState, action: ObserveAction):
406392
return draft;
407393
});
408394

409-
export type NotificationAlerts = {
410-
data: Alert[];
411-
loaded: boolean;
412-
loadError?: {
413-
message?: string;
414-
};
415-
};
416-
417-
const isAlertFiring = (alert: Alert) =>
418-
alert?.state === AlertStates.Firing || alert?.state === AlertStates.Silenced;
419-
420-
const silenceFiringAlerts = (
421-
firingAlerts: Array<Alert>,
422-
silences: Array<Silence>,
423-
): Array<Alert> => {
424-
// For each firing alert, store a list of the Silences that are silencing it
425-
// and set its state to show it is silenced
426-
firingAlerts.forEach((firingAlert) => {
427-
firingAlert.silencedBy = silences.filter(
428-
(silence) =>
429-
silence.status?.state === SilenceStates.Active && isSilenced(firingAlert, silence),
430-
);
431-
432-
if (firingAlert.silencedBy.length) {
433-
firingAlert.state = AlertStates.Silenced;
434-
// Also set the state of Alerts in `rule.alerts`
435-
436-
firingAlert.rule.alerts.forEach((ruleAlert) => {
437-
if (firingAlert.silencedBy?.some((silence) => isSilenced(ruleAlert, silence))) {
438-
ruleAlert.state = AlertStates.Silenced;
439-
}
440-
});
441-
442-
if (
443-
firingAlert.rule.alerts.length !== 0 &&
444-
firingAlert.rule.alerts.every((alert) => alert.state === AlertStates.Silenced)
445-
) {
446-
firingAlert.rule.state = RuleStates.Silenced;
447-
448-
firingAlert.rule.silencedBy = silences.filter(
449-
(silence) =>
450-
silence.status?.state === SilenceStates.Active &&
451-
firingAlert.rule.alerts.some((alert) => isSilenced(alert, silence)),
452-
);
453-
}
454-
}
455-
});
456-
return firingAlerts;
457-
};
458-
459395
export default monitoringReducer;

0 commit comments

Comments
 (0)