Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ResponseOps][flapping] change action behavior when flapping #147810

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
e15d790
Trimming recovered alerts
doakalexi Dec 13, 2022
8cc6b7d
Trimming recovered alerts in the rule registry
doakalexi Dec 14, 2022
66a340e
Fixing types
doakalexi Dec 14, 2022
2723617
Fixing test failures
doakalexi Dec 14, 2022
31a92c6
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Dec 14, 2022
34faacb
Changing structure to reduce duplicate code
doakalexi Dec 15, 2022
44ff4ee
Removing change
doakalexi Dec 15, 2022
7c9ef29
Fix types
doakalexi Dec 15, 2022
634af49
Fixing types
doakalexi Dec 15, 2022
c68f4d8
Fixing types again
doakalexi Dec 15, 2022
9970e82
Fixing types failing
doakalexi Dec 15, 2022
9e68e2f
Merge branch 'main' of github.com:elastic/kibana into alerting/change…
doakalexi Dec 16, 2022
025df90
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Dec 19, 2022
4c95a67
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Dec 19, 2022
c9867e2
Changing action behavior
doakalexi Dec 19, 2022
cbc3a74
Fixing types
doakalexi Dec 20, 2022
b331f99
Fixing types again
doakalexi Dec 20, 2022
6ff7c4a
Fixing tests
doakalexi Dec 20, 2022
0a7667e
Fixing test failure
doakalexi Dec 20, 2022
6d25d55
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Dec 21, 2022
e3dfb03
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Dec 21, 2022
e1163ec
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Dec 22, 2022
ad07eaf
Remove * 1
doakalexi Dec 22, 2022
d827cd1
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Dec 22, 2022
e33b281
Adressing pr feedback
doakalexi Dec 22, 2022
7d2deba
Merge branch 'alerting/trim-recovered-alerts' of github.com:doakalexi…
doakalexi Dec 22, 2022
bc47d7e
Fixing early recovered alerts
doakalexi Dec 22, 2022
170c8fa
Fixing failed test
doakalexi Dec 22, 2022
6fa706d
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Dec 22, 2022
18745d4
Merge branch 'main' into alerting/change-action-behavior-when-flapping
doakalexi Jan 3, 2023
417b973
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Jan 3, 2023
dbe6f49
[CI] Auto-commit changed files from 'node scripts/ts_project_linter -…
kibanamachine Jan 3, 2023
fc1c479
[CI] Auto-commit changed files from 'node scripts/ts_project_linter -…
kibanamachine Jan 3, 2023
c00649a
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Jan 3, 2023
7eac104
Merge branch 'alerting/trim-recovered-alerts' of github.com:doakalexi…
doakalexi Jan 3, 2023
c631ece
Merge branch 'main' into alerting/change-action-behavior-when-flapping
doakalexi Jan 4, 2023
d3b8143
Merge branch 'main' of github.com:elastic/kibana into alerting/trim-r…
doakalexi Jan 4, 2023
b5d7135
Merge branch 'main' of github.com:elastic/kibana into alerting/trim-r…
doakalexi Jan 4, 2023
468a9b1
Merge branch 'alerting/trim-recovered-alerts' of github.com:doakalexi…
doakalexi Jan 4, 2023
2bf5720
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Jan 9, 2023
229e77c
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Jan 9, 2023
c7f4cb5
Update x-pack/plugins/alerting/server/lib/trim_recovered_alerts.ts
doakalexi Jan 9, 2023
3e8cd6c
Remove unused
doakalexi Jan 9, 2023
c89a7d7
Merge branch 'alerting/trim-recovered-alerts' of github.com:doakalexi…
doakalexi Jan 9, 2023
8786526
Fixing test failure
doakalexi Jan 9, 2023
0ad43e7
Remove rule registry changes
doakalexi Jan 9, 2023
f3269b7
Removing more rule registry
doakalexi Jan 9, 2023
7c2760d
Addressing pr feedback
doakalexi Jan 9, 2023
e85c759
Merge branch 'main' into alerting/trim-recovered-alerts
doakalexi Jan 11, 2023
9c469eb
Merge branch 'alerting/trim-recovered-alerts' of github.com:doakalexi…
doakalexi Jan 12, 2023
097b46b
Merge branch 'alerting/change-action-behavior-when-flapping' of githu…
doakalexi Jan 12, 2023
6874ace
Removing current alerts
doakalexi Jan 12, 2023
30c1094
Merge branch 'main' of github.com:elastic/kibana into alerting/change…
doakalexi Jan 17, 2023
2625c20
Merge branch 'main' of github.com:elastic/kibana into alerting/change…
doakalexi Jan 17, 2023
39e025f
Fixing test
doakalexi Jan 17, 2023
b4dff70
Fixing failure
doakalexi Jan 18, 2023
afcf9e0
Merge branch 'main' of github.com:elastic/kibana into alerting/change…
doakalexi Jan 18, 2023
efa8186
Merge branch 'main' into alerting/change-action-behavior-when-flapping
doakalexi Jan 23, 2023
d0490ec
Merge branch 'main' into alerting/change-action-behavior-when-flapping
doakalexi Jan 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/common/alert_instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const metaSchema = t.partial({
flappingHistory: t.array(t.boolean),
// flapping flag that indicates whether the alert is flapping
flapping: t.boolean,
pendingRecoveredCount: t.number,
});
export type AlertInstanceMeta = t.TypeOf<typeof metaSchema>;

Expand Down
44 changes: 43 additions & 1 deletion x-pack/plugins/alerting/server/alert/alert.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,11 +414,12 @@ describe('toJSON', () => {
},
flappingHistory: [false, true],
flapping: false,
pendingRecoveredCount: 2,
},
}
);
expect(JSON.stringify(alertInstance)).toEqual(
'{"state":{"foo":true},"meta":{"lastScheduledActions":{"date":"1970-01-01T00:00:00.000Z","group":"default"},"flappingHistory":[false,true],"flapping":false}}'
'{"state":{"foo":true},"meta":{"lastScheduledActions":{"date":"1970-01-01T00:00:00.000Z","group":"default"},"flappingHistory":[false,true],"flapping":false,"pendingRecoveredCount":2}}'
);
});
});
Expand All @@ -433,6 +434,7 @@ describe('toRaw', () => {
group: 'default',
},
flappingHistory: [false, true, true],
pendingRecoveredCount: 2,
},
};
const alertInstance = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>(
Expand Down Expand Up @@ -529,3 +531,43 @@ describe('getFlapping', () => {
expect(alert.getFlapping()).toEqual(true);
});
});

describe('incrementPendingRecoveredCount', () => {
test('correctly increments pendingRecoveredCount', () => {
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1', {
meta: { pendingRecoveredCount: 3 },
});
alert.incrementPendingRecoveredCount();
expect(alert.getPendingRecoveredCount()).toEqual(4);
});

test('correctly increments pendingRecoveredCount when it is not already defined', () => {
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1');
alert.incrementPendingRecoveredCount();
expect(alert.getPendingRecoveredCount()).toEqual(1);
});
});

describe('getPendingRecoveredCount', () => {
test('returns pendingRecoveredCount', () => {
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1', {
meta: { pendingRecoveredCount: 3 },
});
expect(alert.getPendingRecoveredCount()).toEqual(3);
});

test('defines and returns pendingRecoveredCount when it is not already defined', () => {
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1');
expect(alert.getPendingRecoveredCount()).toEqual(0);
});
});

describe('resetPendingRecoveredCount', () => {
test('resets pendingRecoveredCount to 0', () => {
const alert = new Alert<AlertInstanceState, AlertInstanceContext, DefaultActionGroupId>('1', {
meta: { pendingRecoveredCount: 3 },
});
alert.resetPendingRecoveredCount();
expect(alert.getPendingRecoveredCount()).toEqual(0);
});
});
15 changes: 15 additions & 0 deletions x-pack/plugins/alerting/server/alert/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,19 @@ export class Alert<
getFlapping() {
return this.meta.flapping || false;
}

incrementPendingRecoveredCount() {
if (!this.meta.pendingRecoveredCount) {
this.meta.pendingRecoveredCount = 0;
}
this.meta.pendingRecoveredCount++;
}

getPendingRecoveredCount() {
return this.meta.pendingRecoveredCount || 0;
}

resetPendingRecoveredCount() {
this.meta.pendingRecoveredCount = 0;
}
}
42 changes: 42 additions & 0 deletions x-pack/plugins/alerting/server/alert/create_alert_factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ jest.mock('../lib', () => ({

let clock: sinon.SinonFakeTimers;
const logger = loggingSystemMock.create().get();
const alert1 = {
index: 1,
flappingHistory: [true, true, true, true],
};
const alert2 = {
index: 2,
flappingHistory: new Array(20).fill(false),
};
const alert3 = {
index: 3,
flappingHistory: [true, true],
};

describe('createAlertFactory()', () => {
beforeAll(() => {
Expand Down Expand Up @@ -298,6 +310,34 @@ describe('createAlertFactory()', () => {
alertFactory.alertLimit.setLimitReached(false);
alertFactory.alertLimit.checkLimitUsage();
});

test('trimRecovered should return longest recovered alerts', () => {
const alertFactory = createAlertFactory({
alerts: {},
logger,
maxAlerts: 2,
});

const recoveredAlerts = [alert1, alert2, alert3];
const trimmedAlerts = alertFactory.alertLimit.trimRecovered(recoveredAlerts);
expect(trimmedAlerts).toEqual([alert2]);

expect(logger.warn).toBeCalledWith(
'Recovered alerts have exceeded the max alert limit: dropping 1 alert.'
);
});

test('trimRecovered should not return alerts if the num of recovered alerts is not at the limit', () => {
const alertFactory = createAlertFactory({
alerts: {},
logger,
maxAlerts: 2,
});

const recoveredAlerts = [alert1, alert2];
const trimmedAlerts = alertFactory.alertLimit.trimRecovered(recoveredAlerts);
expect(trimmedAlerts).toEqual([]);
});
});

describe('getPublicAlertFactory', () => {
Expand All @@ -312,6 +352,7 @@ describe('getPublicAlertFactory', () => {
expect(alertFactory.alertLimit.getValue).toBeDefined();
expect(alertFactory.alertLimit.setLimitReached).toBeDefined();
expect(alertFactory.alertLimit.checkLimitUsage).toBeDefined();
expect(alertFactory.alertLimit.trimRecovered).toBeDefined();
expect(alertFactory.hasReachedAlertLimit).toBeDefined();
expect(alertFactory.done).toBeDefined();

Expand All @@ -321,6 +362,7 @@ describe('getPublicAlertFactory', () => {
expect(publicAlertFactory.done).toBeDefined();
expect(publicAlertFactory.alertLimit.getValue).toBeDefined();
expect(publicAlertFactory.alertLimit.setLimitReached).toBeDefined();
expect(publicAlertFactory.alertLimit.trimRecovered).toBeDefined();

// @ts-expect-error
expect(publicAlertFactory.alertLimit.checkLimitUsage).not.toBeDefined();
Expand Down
27 changes: 26 additions & 1 deletion x-pack/plugins/alerting/server/alert/create_alert_factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import { AlertInstanceContext, AlertInstanceState } from '../types';
import { Alert, PublicAlert } from './alert';
import { processAlerts } from '../lib';

export interface TrimRecoveredOpts {
index: number | string;
flappingHistory: boolean[];
trackedEvents?: boolean;
}

export interface AlertFactory<
State extends AlertInstanceState,
Context extends AlertInstanceContext,
Expand All @@ -21,6 +27,7 @@ export interface AlertFactory<
getValue: () => number;
setLimitReached: (reached: boolean) => void;
checkLimitUsage: () => void;
trimRecovered: (recoveredAlerts: TrimRecoveredOpts[]) => TrimRecoveredOpts[];
};
hasReachedAlertLimit: () => boolean;
done: () => AlertFactoryDoneUtils<State, Context, ActionGroupIds>;
Expand All @@ -33,7 +40,7 @@ export type PublicAlertFactory<
> = Pick<AlertFactory<State, Context, ActionGroupIds>, 'create' | 'done'> & {
alertLimit: Pick<
AlertFactory<State, Context, ActionGroupIds>['alertLimit'],
'getValue' | 'setLimitReached'
'getValue' | 'setLimitReached' | 'trimRecovered'
>;
};

Expand Down Expand Up @@ -116,6 +123,22 @@ export function createAlertFactory<
);
}
},
trimRecovered: (recoveredAlerts: TrimRecoveredOpts[]) => {
let earlyRecoveredAlerts: TrimRecoveredOpts[] = [];
if (recoveredAlerts.length > maxAlerts) {
recoveredAlerts.sort((a, b) => {
return a.flappingHistory.length - b.flappingHistory.length;
});

earlyRecoveredAlerts = recoveredAlerts.splice(maxAlerts * 1);
logger.warn(
`Recovered alerts have exceeded the max alert limit: dropping ${
earlyRecoveredAlerts.length
} ${earlyRecoveredAlerts.length > 1 ? 'alerts' : 'alert'}.`
);
}
return earlyRecoveredAlerts;
},
},
hasReachedAlertLimit: (): boolean => hasReachedAlertLimit,
done: (): AlertFactoryDoneUtils<State, Context, ActionGroupIds> => {
Expand Down Expand Up @@ -164,6 +187,8 @@ export function getPublicAlertFactory<
alertLimit: {
getValue: (): number => alertFactory.alertLimit.getValue(),
setLimitReached: (...args): void => alertFactory.alertLimit.setLimitReached(...args),
trimRecovered: (...args): TrimRecoveredOpts[] =>
alertFactory.alertLimit.trimRecovered(...args),
},
done: (): AlertFactoryDoneUtils<State, Context, ActionGroupIds> => alertFactory.done(),
};
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/alerting/server/lib/flapping_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

const MAX_CAPACITY = 20;
const MAX_FLAP_COUNT = 4;
export const MAX_FLAP_COUNT = 4;

export function updateFlappingHistory(flappingHistory: boolean[], state: boolean) {
const updatedFlappingHistory = flappingHistory.concat(state).slice(MAX_CAPACITY * -1);
Expand Down
146 changes: 146 additions & 0 deletions x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { getAlertsForNotification } from '.';
import { Alert } from '../alert';

describe('getAlertsForNotification', () => {
test('should set pendingRecoveredCount to zero for all active alerts', () => {
const alert1 = new Alert('1', { meta: { flapping: true, pendingRecoveredCount: 3 } });
const alert2 = new Alert('2', { meta: { flapping: false } });

const { newAlerts, activeAlerts } = getAlertsForNotification(
'default',
{
'1': alert1,
},
{
'1': alert1,
'2': alert2,
},
{},
{}
);
expect(newAlerts).toMatchInlineSnapshot(`
Object {
"1": Object {
"meta": Object {
"flapping": true,
"flappingHistory": Array [],
"pendingRecoveredCount": 0,
},
"state": Object {},
},
}
`);
expect(activeAlerts).toMatchInlineSnapshot(`
Object {
"1": Object {
"meta": Object {
"flapping": true,
"flappingHistory": Array [],
"pendingRecoveredCount": 0,
},
"state": Object {},
},
"2": Object {
"meta": Object {
"flapping": false,
"flappingHistory": Array [],
"pendingRecoveredCount": 0,
},
"state": Object {},
},
}
`);
});

test('should return flapping pending recovered alerts as active alerts', () => {
const alert1 = new Alert('1', { meta: { flapping: true, pendingRecoveredCount: 3 } });
const alert2 = new Alert('2', { meta: { flapping: false } });
const alert3 = new Alert('3', { meta: { flapping: true } });

const { newAlerts, activeAlerts, recoveredAlerts, currentRecoveredAlerts } =
getAlertsForNotification(
'default',
{},
{},
{
'1': alert1,
'2': alert2,
'3': alert3,
},
{
'1': alert1,
'2': alert2,
'3': alert3,
}
);

expect(newAlerts).toMatchInlineSnapshot(`Object {}`);
expect(activeAlerts).toMatchInlineSnapshot(`
Object {
"3": Object {
"meta": Object {
"flapping": true,
"flappingHistory": Array [],
"pendingRecoveredCount": 1,
},
"state": Object {},
},
}
`);
expect(Object.values(activeAlerts).map((a) => a.getScheduledActionOptions()))
.toMatchInlineSnapshot(`
Array [
Object {
"actionGroup": "default",
"context": Object {},
"state": Object {},
},
]
`);
expect(recoveredAlerts).toMatchInlineSnapshot(`
Object {
"1": Object {
"meta": Object {
"flapping": true,
"flappingHistory": Array [],
"pendingRecoveredCount": 0,
},
"state": Object {},
},
"2": Object {
"meta": Object {
"flapping": false,
"flappingHistory": Array [],
},
"state": Object {},
},
}
`);
expect(currentRecoveredAlerts).toMatchInlineSnapshot(`
Object {
"1": Object {
"meta": Object {
"flapping": true,
"flappingHistory": Array [],
"pendingRecoveredCount": 0,
},
"state": Object {},
},
"2": Object {
"meta": Object {
"flapping": false,
"flappingHistory": Array [],
},
"state": Object {},
},
}
`);
});
});
Loading