mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[8.x] [Response Ops][Alerting] Only load maintenance windows when there are alerts during rule execution and caching loaded maintenance windows (#192573) (#194191)
# Backport This will backport the following commits from `main` to `8.x`: - [[Response Ops][Alerting] Only load maintenance windows when there are alerts during rule execution and caching loaded maintenance windows (#192573)](https://github.com/elastic/kibana/pull/192573) <!--- Backport version: 8.9.8 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Ying Mao","email":"ying.mao@elastic.co"},"sourceCommit":{"committedDate":"2024-09-26T12:59:36Z","message":"[Response Ops][Alerting] Only load maintenance windows when there are alerts during rule execution and caching loaded maintenance windows (#192573)\n\nResolves https://github.com/elastic/kibana/issues/184324\r\n\r\n## Summary\r\n\r\nThis PR moves the loading of maintenance windows further down in rule\r\nexecution so maintenance windows are only loaded when a rule execution\r\ngenerates alerts. Also caches maintenance windows per space to reduce\r\nthe number of requests.\r\n\r\n## To Verify\r\n\r\n1. Add some logging to\r\nx-pack/plugins/alerting/server/task_runner/maintenance_windows/maintenance_windows_service.ts\r\nto indicate when windows are being fetched and when they're returning\r\nfrom the cache.\r\n2. Create and run some rules in different spaces with and without alerts\r\nto see that the maintenance windows are only loaded when there are\r\nalerts and that the windows are returned from the cache when the cache\r\nhas not expired.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"93414a672c2767b035110fa2d811cc040af57727","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Alerting","release_note:skip","Team:ResponseOps","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-management","v8.16.0"],"number":192573,"url":"https://github.com/elastic/kibana/pull/192573","mergeCommit":{"message":"[Response Ops][Alerting] Only load maintenance windows when there are alerts during rule execution and caching loaded maintenance windows (#192573)\n\nResolves https://github.com/elastic/kibana/issues/184324\r\n\r\n## Summary\r\n\r\nThis PR moves the loading of maintenance windows further down in rule\r\nexecution so maintenance windows are only loaded when a rule execution\r\ngenerates alerts. Also caches maintenance windows per space to reduce\r\nthe number of requests.\r\n\r\n## To Verify\r\n\r\n1. Add some logging to\r\nx-pack/plugins/alerting/server/task_runner/maintenance_windows/maintenance_windows_service.ts\r\nto indicate when windows are being fetched and when they're returning\r\nfrom the cache.\r\n2. Create and run some rules in different spaces with and without alerts\r\nto see that the maintenance windows are only loaded when there are\r\nalerts and that the windows are returned from the cache when the cache\r\nhas not expired.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"93414a672c2767b035110fa2d811cc040af57727"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","labelRegex":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/192573","number":192573,"mergeCommit":{"message":"[Response Ops][Alerting] Only load maintenance windows when there are alerts during rule execution and caching loaded maintenance windows (#192573)\n\nResolves https://github.com/elastic/kibana/issues/184324\r\n\r\n## Summary\r\n\r\nThis PR moves the loading of maintenance windows further down in rule\r\nexecution so maintenance windows are only loaded when a rule execution\r\ngenerates alerts. Also caches maintenance windows per space to reduce\r\nthe number of requests.\r\n\r\n## To Verify\r\n\r\n1. Add some logging to\r\nx-pack/plugins/alerting/server/task_runner/maintenance_windows/maintenance_windows_service.ts\r\nto indicate when windows are being fetched and when they're returning\r\nfrom the cache.\r\n2. Create and run some rules in different spaces with and without alerts\r\nto see that the maintenance windows are only loaded when there are\r\nalerts and that the windows are returned from the cache when the cache\r\nhas not expired.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"93414a672c2767b035110fa2d811cc040af57727"}},{"branch":"8.x","label":"v8.16.0","labelRegex":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT-->
This commit is contained in:
parent
4a3d831695
commit
3358772208
49 changed files with 1627 additions and 642 deletions
|
@ -156,8 +156,6 @@ export function createAlertFactory<
|
|||
autoRecoverAlerts,
|
||||
// flappingSettings.enabled is false, as we only want to use this function to get the recovered alerts
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
// no maintenance window IDs are passed as we only want to use this function to get recovered alerts
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
return Object.keys(currentRecoveredAlerts ?? {}).map(
|
||||
(alertId: string) => currentRecoveredAlerts[alertId]
|
||||
|
|
|
@ -11,6 +11,7 @@ import { UntypedNormalizedRuleType } from '../rule_type_registry';
|
|||
import {
|
||||
AlertsFilter,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
MaintenanceWindowStatus,
|
||||
RecoveredActionGroup,
|
||||
RuleAlertData,
|
||||
} from '../types';
|
||||
|
@ -54,8 +55,9 @@ import { Alert } from '../alert/alert';
|
|||
import { AlertsClient, AlertsClientParams } from './alerts_client';
|
||||
import {
|
||||
GetSummarizedAlertsParams,
|
||||
ProcessAndLogAlertsOpts,
|
||||
GetMaintenanceWindowScopedQueryAlertsParams,
|
||||
ProcessAlertsOpts,
|
||||
LogAlertsOpts,
|
||||
} from './types';
|
||||
import { legacyAlertsClientMock } from './legacy_alerts_client.mock';
|
||||
import { keys, range } from 'lodash';
|
||||
|
@ -74,6 +76,9 @@ import {
|
|||
} from './alerts_client_fixtures';
|
||||
import { getDataStreamAdapter } from '../alerts_service/lib/data_stream_adapter';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
import { maintenanceWindowsServiceMock } from '../task_runner/maintenance_windows/maintenance_windows_service.mock';
|
||||
import { getMockMaintenanceWindow } from '../data/maintenance_window/test_helpers';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
|
||||
const date = '2023-03-28T22:27:28.159Z';
|
||||
const startedAtDate = '2023-03-28T13:00:00.000Z';
|
||||
|
@ -81,6 +86,7 @@ const maxAlerts = 1000;
|
|||
let logger: ReturnType<(typeof loggingSystemMock)['createLogger']>;
|
||||
const clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
const ruleRunMetricsStore = ruleRunMetricsStoreMock.create();
|
||||
|
||||
const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
||||
|
@ -226,7 +232,7 @@ const getNewIndexedAlertDoc = (overrides = {}) => ({
|
|||
[ALERT_FLAPPING]: false,
|
||||
[ALERT_FLAPPING_HISTORY]: [true],
|
||||
[ALERT_INSTANCE_ID]: '1',
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: [],
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: ['test-id1', 'test-id2'],
|
||||
[ALERT_RULE_CATEGORY]: 'My test rule',
|
||||
[ALERT_RULE_CONSUMER]: 'bar',
|
||||
[ALERT_RULE_EXECUTION_UUID]: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -259,6 +265,7 @@ const getOngoingIndexedAlertDoc = (overrides = {}) => ({
|
|||
[ALERT_TIME_RANGE]: { gte: '2023-03-28T12:27:28.159Z' },
|
||||
[ALERT_PREVIOUS_ACTION_GROUP]: 'default',
|
||||
[ALERT_SEVERITY_IMPROVING]: undefined,
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
|
@ -275,6 +282,7 @@ const getRecoveredIndexedAlertDoc = (overrides = {}) => ({
|
|||
[ALERT_CONSECUTIVE_MATCHES]: 0,
|
||||
[ALERT_PREVIOUS_ACTION_GROUP]: 'default',
|
||||
[ALERT_SEVERITY_IMPROVING]: true,
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
|
@ -287,12 +295,29 @@ const defaultExecutionOpts = {
|
|||
startedAt: null,
|
||||
};
|
||||
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
getSavedObjectsClient: jest.fn(),
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
const ruleInfo = `for test.rule-type:1 'rule-name'`;
|
||||
const logTags = { tags: ['test.rule-type', '1', 'alerts-client'] };
|
||||
|
||||
describe('Alerts Client', () => {
|
||||
let alertsClientParams: AlertsClientParams;
|
||||
let processAndLogAlertsOpts: ProcessAndLogAlertsOpts;
|
||||
let processAlertsOpts: ProcessAlertsOpts;
|
||||
let logAlertsOpts: LogAlertsOpts;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
|
@ -311,22 +336,43 @@ describe('Alerts Client', () => {
|
|||
jest.clearAllMocks();
|
||||
logger = loggingSystemMock.createLogger();
|
||||
alertsClientParams = {
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
elasticsearchClientPromise: Promise.resolve(clusterClient),
|
||||
request: fakeRequest,
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
rule: alertRuleData,
|
||||
kibanaVersion: '8.9.0',
|
||||
spaceId: 'space1',
|
||||
dataStreamAdapter: getDataStreamAdapter({ useDataStreamForAlerts }),
|
||||
};
|
||||
processAndLogAlertsOpts = {
|
||||
eventLogger: alertingEventLogger,
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockReturnValue({
|
||||
maintenanceWindows: [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id1',
|
||||
},
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id2',
|
||||
},
|
||||
],
|
||||
maintenanceWindowsWithoutScopedQueryIds: ['test-id1', 'test-id2'],
|
||||
});
|
||||
processAlertsOpts = {
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: false,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
};
|
||||
logAlertsOpts = { shouldLogAlerts: false, ruleRunMetricsStore };
|
||||
});
|
||||
|
||||
describe('initializeExecution()', () => {
|
||||
|
@ -508,7 +554,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -533,6 +580,12 @@ describe('Alerts Client', () => {
|
|||
getNewIndexedAlertDoc({ [ALERT_UUID]: uuid2, [ALERT_INSTANCE_ID]: '2' }),
|
||||
],
|
||||
});
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should not index new alerts if the activeCount is less than the rule alertDelay', async () => {
|
||||
|
@ -547,11 +600,18 @@ describe('Alerts Client', () => {
|
|||
const alertExecutorService = alertsClient.factory();
|
||||
alertExecutorService.create('1').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
expect(clusterClient.bulk).not.toHaveBeenCalled();
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should update ongoing alerts in existing index', async () => {
|
||||
|
@ -588,7 +648,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -618,6 +679,12 @@ describe('Alerts Client', () => {
|
|||
getNewIndexedAlertDoc({ [ALERT_UUID]: uuid2, [ALERT_INSTANCE_ID]: '2' }),
|
||||
],
|
||||
});
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should update unflattened ongoing alerts in existing index', async () => {
|
||||
|
@ -654,7 +721,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -719,6 +787,12 @@ describe('Alerts Client', () => {
|
|||
getNewIndexedAlertDoc({ [ALERT_UUID]: uuid2, [ALERT_INSTANCE_ID]: '2' }),
|
||||
],
|
||||
});
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should not update ongoing alerts in existing index when they are not in the processed alerts', async () => {
|
||||
|
@ -740,6 +814,7 @@ describe('Alerts Client', () => {
|
|||
.mockReturnValueOnce({
|
||||
'1': activeAlertObj, // return only the first (tracked) alert
|
||||
})
|
||||
.mockReturnValueOnce({})
|
||||
.mockReturnValueOnce({});
|
||||
|
||||
clusterClient.search.mockResolvedValue({
|
||||
|
@ -773,13 +848,15 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default'); // will be skipped as getProcessedAlerts does not return it
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(spy).toHaveBeenCalledTimes(5);
|
||||
expect(spy).toHaveBeenNthCalledWith(1, 'active');
|
||||
expect(spy).toHaveBeenNthCalledWith(2, 'recoveredCurrent');
|
||||
expect(spy).toHaveBeenNthCalledWith(3, 'new');
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
`Error writing alert(2) to .alerts-test.alerts-default - alert(2) doesn't exist in active alerts ${ruleInfo}.`,
|
||||
|
@ -802,6 +879,12 @@ describe('Alerts Client', () => {
|
|||
getOngoingIndexedAlertDoc({ [ALERT_UUID]: 'abc', [ALERT_CONSECUTIVE_MATCHES]: 0 }),
|
||||
],
|
||||
});
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should recover recovered alerts in existing index', async () => {
|
||||
|
@ -846,7 +929,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('2').scheduleActions('default');
|
||||
alertExecutorService.create('3').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -894,6 +978,12 @@ describe('Alerts Client', () => {
|
|||
getRecoveredIndexedAlertDoc({ [ALERT_UUID]: 'abc' }),
|
||||
],
|
||||
});
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should recover unflattened recovered alerts in existing index', async () => {
|
||||
|
@ -938,7 +1028,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('2').scheduleActions('default');
|
||||
alertExecutorService.create('3').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -1051,6 +1142,12 @@ describe('Alerts Client', () => {
|
|||
},
|
||||
],
|
||||
});
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should use startedAt time if provided', async () => {
|
||||
|
@ -1096,7 +1193,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('2').scheduleActions('default');
|
||||
alertExecutorService.create('3').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -1160,6 +1258,12 @@ describe('Alerts Client', () => {
|
|||
}),
|
||||
],
|
||||
});
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should use runTimestamp time if provided', async () => {
|
||||
|
@ -1207,7 +1311,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('2').scheduleActions('default');
|
||||
alertExecutorService.create('3').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -1271,6 +1376,12 @@ describe('Alerts Client', () => {
|
|||
}),
|
||||
],
|
||||
});
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should not try to index if no alerts', async () => {
|
||||
|
@ -1282,11 +1393,13 @@ describe('Alerts Client', () => {
|
|||
|
||||
// Report no alerts
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
expect(clusterClient.bulk).not.toHaveBeenCalled();
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should log if bulk indexing fails for some alerts', async () => {
|
||||
|
@ -1345,7 +1458,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -1354,6 +1468,12 @@ describe('Alerts Client', () => {
|
|||
`Error writing alerts ${ruleInfo}: 1 successful, 0 conflicts, 2 errors: Validation Failed: 1: index is missing;2: type is missing;; failed to parse field [process.command_line] of type [wildcard] in document with id 'f0c9805be95fedbc3c99c663f7f02cc15826c122'.`,
|
||||
{ tags: ['test.rule-type', '1', 'resolve-alert-conflicts'] }
|
||||
);
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should log if alert to update belongs to a non-standard index', async () => {
|
||||
|
@ -1398,7 +1518,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -1432,6 +1553,13 @@ describe('Alerts Client', () => {
|
|||
`Could not update alert abc in partial-.internal.alerts-test.alerts-default-000001. Partial and restored alert indices are not supported ${ruleInfo}.`,
|
||||
logTags
|
||||
);
|
||||
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should log and swallow error if bulk indexing throws error', async () => {
|
||||
|
@ -1449,7 +1577,8 @@ describe('Alerts Client', () => {
|
|||
alertExecutorService.create('1').scheduleActions('default');
|
||||
alertExecutorService.create('2').scheduleActions('default');
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -1458,12 +1587,21 @@ describe('Alerts Client', () => {
|
|||
`Error writing 2 alerts to .alerts-test.alerts-default ${ruleInfo} - fail`,
|
||||
logTags
|
||||
);
|
||||
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('should not persist alerts if shouldWrite is false', async () => {
|
||||
alertsClientParams = {
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
elasticsearchClientPromise: Promise.resolve(clusterClient),
|
||||
maintenanceWindowsService,
|
||||
ruleType: {
|
||||
...ruleType,
|
||||
alerts: {
|
||||
|
@ -1471,10 +1609,12 @@ describe('Alerts Client', () => {
|
|||
shouldWrite: false,
|
||||
},
|
||||
},
|
||||
request: fakeRequest,
|
||||
namespace: 'default',
|
||||
rule: alertRuleData,
|
||||
kibanaVersion: '8.9.0',
|
||||
dataStreamAdapter: getDataStreamAdapter({ useDataStreamForAlerts }),
|
||||
spaceId: 'space1',
|
||||
};
|
||||
const alertsClient = new AlertsClient<{}, {}, {}, 'default', 'recovered'>(
|
||||
alertsClientParams
|
||||
|
@ -1490,6 +1630,7 @@ describe('Alerts Client', () => {
|
|||
logTags
|
||||
);
|
||||
expect(clusterClient.bulk).not.toHaveBeenCalled();
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2041,8 +2182,15 @@ describe('Alerts Client', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('updateAlertsMaintenanceWindowIdByScopedQuery', () => {
|
||||
describe('updatePersistedAlertsWithMaintenanceWindowIds', () => {
|
||||
test('should update alerts with MW ids when provided with maintenance windows', async () => {
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockReturnValueOnce({
|
||||
maintenanceWindows: [
|
||||
...getParamsByUpdateMaintenanceWindowIds.maintenanceWindows,
|
||||
{ id: 'mw3' } as unknown as MaintenanceWindow,
|
||||
],
|
||||
maintenanceWindowsWithoutScopedQueryIds: [],
|
||||
});
|
||||
const alertsClient = new AlertsClient(alertsClientParams);
|
||||
|
||||
const alert1 = new Alert('1');
|
||||
|
@ -2072,11 +2220,8 @@ describe('Alerts Client', () => {
|
|||
// @ts-ignore
|
||||
.mockResolvedValueOnce({});
|
||||
|
||||
// @ts-expect-error
|
||||
const result = await alertsClient.updateAlertsMaintenanceWindowIdByScopedQuery([
|
||||
...getParamsByUpdateMaintenanceWindowIds.maintenanceWindows,
|
||||
{ id: 'mw3' } as unknown as MaintenanceWindow,
|
||||
]);
|
||||
// @ts-ignore - accessing private function
|
||||
const result = await alertsClient.updatePersistedAlertsWithMaintenanceWindowIds();
|
||||
|
||||
expect(alert1.getMaintenanceWindowIds()).toEqual(['mw3', 'mw1']);
|
||||
expect(alert2.getMaintenanceWindowIds()).toEqual(['mw3', 'mw1']);
|
||||
|
@ -2302,7 +2447,8 @@ describe('Alerts Client', () => {
|
|||
payload: { count: 2, url: `https://url2` },
|
||||
});
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -2330,7 +2476,7 @@ describe('Alerts Client', () => {
|
|||
[ALERT_FLAPPING]: false,
|
||||
[ALERT_FLAPPING_HISTORY]: [true],
|
||||
[ALERT_INSTANCE_ID]: '1',
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: [],
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: ['test-id1', 'test-id2'],
|
||||
[ALERT_RULE_CATEGORY]: 'My test rule',
|
||||
[ALERT_RULE_CONSUMER]: 'bar',
|
||||
[ALERT_RULE_EXECUTION_UUID]: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -2365,7 +2511,7 @@ describe('Alerts Client', () => {
|
|||
[ALERT_FLAPPING]: false,
|
||||
[ALERT_FLAPPING_HISTORY]: [true],
|
||||
[ALERT_INSTANCE_ID]: '2',
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: [],
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: ['test-id1', 'test-id2'],
|
||||
[ALERT_RULE_CATEGORY]: 'My test rule',
|
||||
[ALERT_RULE_CONSUMER]: 'bar',
|
||||
[ALERT_RULE_EXECUTION_UUID]: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -2579,7 +2725,8 @@ describe('Alerts Client', () => {
|
|||
payload: { count: 100, url: `https://elastic.co` },
|
||||
});
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -2605,7 +2752,7 @@ describe('Alerts Client', () => {
|
|||
[ALERT_FLAPPING]: false,
|
||||
[ALERT_FLAPPING_HISTORY]: [true],
|
||||
[ALERT_INSTANCE_ID]: '1',
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: [],
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: ['test-id1', 'test-id2'],
|
||||
[ALERT_RULE_CATEGORY]: 'My test rule',
|
||||
[ALERT_RULE_CONSUMER]: 'bar',
|
||||
[ALERT_RULE_EXECUTION_UUID]: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -2679,7 +2826,8 @@ describe('Alerts Client', () => {
|
|||
payload: { count: 100, url: `https://elastic.co` },
|
||||
});
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
@ -2775,7 +2923,8 @@ describe('Alerts Client', () => {
|
|||
payload: { count: 100, url: `https://elastic.co` },
|
||||
});
|
||||
|
||||
alertsClient.processAndLogAlerts(processAndLogAlertsOpts);
|
||||
await alertsClient.processAlerts(processAlertsOpts);
|
||||
alertsClient.logAlerts(logAlertsOpts);
|
||||
|
||||
await alertsClient.persistAlerts();
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ import type { AlertRule, LogAlertsOpts, ProcessAlertsOpts, SearchResult } from '
|
|||
import {
|
||||
IAlertsClient,
|
||||
InitializeExecutionOpts,
|
||||
ProcessAndLogAlertsOpts,
|
||||
TrackedAlerts,
|
||||
ReportedAlert,
|
||||
ReportedAlertData,
|
||||
|
@ -62,11 +61,10 @@ import {
|
|||
} from './lib';
|
||||
import { isValidAlertIndexName } from '../alerts_service';
|
||||
import { resolveAlertConflicts } from './lib/alert_conflict_resolver';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
import {
|
||||
filterMaintenanceWindows,
|
||||
filterMaintenanceWindowsIds,
|
||||
} from '../task_runner/get_maintenance_windows';
|
||||
} from '../task_runner/maintenance_windows';
|
||||
|
||||
// Term queries can take up to 10,000 terms
|
||||
const CHUNK_SIZE = 10000;
|
||||
|
@ -77,6 +75,10 @@ export interface AlertsClientParams extends CreateAlertsClientParams {
|
|||
dataStreamAdapter: DataStreamAdapter;
|
||||
}
|
||||
|
||||
interface AlertsAffectedByMaintenanceWindows {
|
||||
alertIds: string[];
|
||||
maintenanceWindowIds: string[];
|
||||
}
|
||||
export class AlertsClient<
|
||||
AlertData extends RuleAlertData,
|
||||
LegacyState extends AlertInstanceState,
|
||||
|
@ -121,7 +123,14 @@ export class AlertsClient<
|
|||
LegacyContext,
|
||||
ActionGroupIds,
|
||||
RecoveryActionGroupId
|
||||
>({ logger: this.options.logger, ruleType: this.options.ruleType });
|
||||
>({
|
||||
alertingEventLogger: this.options.alertingEventLogger,
|
||||
logger: this.options.logger,
|
||||
maintenanceWindowsService: this.options.maintenanceWindowsService,
|
||||
request: this.options.request,
|
||||
ruleType: this.options.ruleType,
|
||||
spaceId: this.options.spaceId,
|
||||
});
|
||||
this.indexTemplateAndPattern = getIndexTemplateAndPattern({
|
||||
context: this.options.ruleType.alerts?.context!,
|
||||
namespace: this.options.ruleType.alerts?.isSpaceAware
|
||||
|
@ -301,45 +310,25 @@ export class AlertsClient<
|
|||
return this.legacyAlertsClient.checkLimitUsage();
|
||||
}
|
||||
|
||||
public processAlerts(opts: ProcessAlertsOpts) {
|
||||
this.legacyAlertsClient.processAlerts(opts);
|
||||
public async processAlerts(opts: ProcessAlertsOpts) {
|
||||
await this.legacyAlertsClient.processAlerts(opts);
|
||||
}
|
||||
|
||||
public logAlerts(opts: LogAlertsOpts) {
|
||||
this.legacyAlertsClient.logAlerts(opts);
|
||||
}
|
||||
|
||||
public processAndLogAlerts(opts: ProcessAndLogAlertsOpts) {
|
||||
this.legacyAlertsClient.processAndLogAlerts(opts);
|
||||
}
|
||||
|
||||
public getProcessedAlerts(
|
||||
type: 'new' | 'active' | 'activeCurrent' | 'recovered' | 'recoveredCurrent'
|
||||
) {
|
||||
return this.legacyAlertsClient.getProcessedAlerts(type);
|
||||
}
|
||||
|
||||
public async persistAlerts(maintenanceWindows?: MaintenanceWindow[]): Promise<{
|
||||
alertIds: string[];
|
||||
maintenanceWindowIds: string[];
|
||||
} | null> {
|
||||
public async persistAlerts(): Promise<AlertsAffectedByMaintenanceWindows> {
|
||||
// Persist alerts first
|
||||
await this.persistAlertsHelper();
|
||||
|
||||
// Try to update the persisted alerts with maintenance windows with a scoped query
|
||||
let updateAlertsMaintenanceWindowResult = null;
|
||||
try {
|
||||
updateAlertsMaintenanceWindowResult = await this.updateAlertsMaintenanceWindowIdByScopedQuery(
|
||||
maintenanceWindows ?? []
|
||||
);
|
||||
} catch (e) {
|
||||
this.options.logger.debug(
|
||||
`Failed to update alert matched by maintenance window scoped query ${this.ruleInfoMessage}`,
|
||||
this.logTags
|
||||
);
|
||||
}
|
||||
|
||||
return updateAlertsMaintenanceWindowResult;
|
||||
return await this.updatePersistedAlertsWithMaintenanceWindowIds();
|
||||
}
|
||||
|
||||
public getAlertsToSerialize() {
|
||||
|
@ -692,18 +681,39 @@ export class AlertsClient<
|
|||
}
|
||||
}
|
||||
|
||||
private async updateAlertsMaintenanceWindowIdByScopedQuery(
|
||||
maintenanceWindows: MaintenanceWindow[]
|
||||
) {
|
||||
private async updatePersistedAlertsWithMaintenanceWindowIds(): Promise<AlertsAffectedByMaintenanceWindows> {
|
||||
// check if there are any alerts
|
||||
const newAlerts = Object.values(this.legacyAlertsClient.getProcessedAlerts('new'));
|
||||
const activeAlerts = Object.values(this.legacyAlertsClient.getProcessedAlerts('active'));
|
||||
const recoveredAlerts = Object.values(this.legacyAlertsClient.getProcessedAlerts('recovered'));
|
||||
|
||||
// return if there are no alerts written
|
||||
if (
|
||||
(!newAlerts.length && !activeAlerts.length && !recoveredAlerts.length) ||
|
||||
!this.options.maintenanceWindowsService
|
||||
) {
|
||||
return {
|
||||
alertIds: [],
|
||||
maintenanceWindowIds: [],
|
||||
};
|
||||
}
|
||||
|
||||
const { maintenanceWindows } =
|
||||
await this.options.maintenanceWindowsService.getMaintenanceWindows({
|
||||
eventLogger: this.options.alertingEventLogger,
|
||||
request: this.options.request,
|
||||
ruleTypeCategory: this.ruleType.category,
|
||||
spaceId: this.options.spaceId,
|
||||
});
|
||||
|
||||
const maintenanceWindowsWithScopedQuery = filterMaintenanceWindows({
|
||||
maintenanceWindows,
|
||||
maintenanceWindows: maintenanceWindows ?? [],
|
||||
withScopedQuery: true,
|
||||
});
|
||||
const maintenanceWindowsWithoutScopedQueryIds = filterMaintenanceWindowsIds({
|
||||
maintenanceWindows,
|
||||
maintenanceWindows: maintenanceWindows ?? [],
|
||||
withScopedQuery: false,
|
||||
});
|
||||
|
||||
if (maintenanceWindowsWithScopedQuery.length === 0) {
|
||||
return {
|
||||
alertIds: [],
|
||||
|
@ -723,8 +733,6 @@ export class AlertsClient<
|
|||
const alertsAffectedByScopedQuery: string[] = [];
|
||||
const appliedMaintenanceWindowIds: string[] = [];
|
||||
|
||||
const newAlerts = Object.values(this.getProcessedAlerts('new'));
|
||||
|
||||
for (const [scopedQueryMaintenanceWindowId, alertIds] of Object.entries(aggsResult)) {
|
||||
// Go through matched alerts, find the in memory object
|
||||
alertIds.forEach((alertId) => {
|
||||
|
|
|
@ -6,18 +6,21 @@
|
|||
*/
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { UntypedNormalizedRuleType } from '../rule_type_registry';
|
||||
import { AlertInstanceContext, RecoveredActionGroup } from '../types';
|
||||
import { AlertInstanceContext, MaintenanceWindowStatus, RecoveredActionGroup } from '../types';
|
||||
import { LegacyAlertsClient } from './legacy_alerts_client';
|
||||
import { createAlertFactory, getPublicAlertFactory } from '../alert/create_alert_factory';
|
||||
import { Alert } from '../alert/alert';
|
||||
import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock';
|
||||
import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock';
|
||||
import { getAlertsForNotification, processAlerts } from '../lib';
|
||||
import { trimRecoveredAlerts } from '../lib/trim_recovered_alerts';
|
||||
import { logAlerts } from '../task_runner/log_alerts';
|
||||
import { DEFAULT_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { maintenanceWindowsServiceMock } from '../task_runner/maintenance_windows/maintenance_windows_service.mock';
|
||||
import { getMockMaintenanceWindow } from '../data/maintenance_window/test_helpers';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock';
|
||||
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
const scheduleActions = jest.fn();
|
||||
const replaceState = jest.fn(() => ({ scheduleActions }));
|
||||
const mockCreateAlert = jest.fn(() => ({ replaceState, scheduleActions }));
|
||||
|
@ -81,8 +84,8 @@ jest.mock('../lib/get_alerts_for_notification', () => {
|
|||
jest.mock('../task_runner/log_alerts', () => ({ logAlerts: jest.fn() }));
|
||||
|
||||
let logger: ReturnType<(typeof loggingSystemMock)['createLogger']>;
|
||||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
const ruleRunMetricsStore = ruleRunMetricsStoreMock.create();
|
||||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
|
||||
const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
||||
id: 'test',
|
||||
|
@ -118,6 +121,22 @@ const testAlert2 = {
|
|||
},
|
||||
};
|
||||
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
getSavedObjectsClient: jest.fn(),
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
const defaultExecutionOpts = {
|
||||
maxAlerts: 1000,
|
||||
ruleLabel: `test: rule-name`,
|
||||
|
@ -138,8 +157,12 @@ describe('Legacy Alerts Client', () => {
|
|||
|
||||
test('initializeExecution() should create alert factory with given alerts', async () => {
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
@ -158,8 +181,12 @@ describe('Legacy Alerts Client', () => {
|
|||
|
||||
test('factory() should call getPublicAlertFactory on alert factory', async () => {
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
@ -170,8 +197,12 @@ describe('Legacy Alerts Client', () => {
|
|||
|
||||
test('getAlert() should pass through to alert factory function', async () => {
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
@ -182,8 +213,12 @@ describe('Legacy Alerts Client', () => {
|
|||
|
||||
test('checkLimitUsage() should pass through to alert factory function', async () => {
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
@ -194,8 +229,12 @@ describe('Legacy Alerts Client', () => {
|
|||
|
||||
test('hasReachedAlertLimit() should pass through to alert factory function', async () => {
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
@ -204,7 +243,26 @@ describe('Legacy Alerts Client', () => {
|
|||
expect(mockCreateAlertFactory.hasReachedAlertLimit).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('processAndLogAlerts() should call processAlerts, trimRecoveredAlerts, getAlertsForNotification and logAlerts and store results', async () => {
|
||||
test('processAlerts() should call processAlerts, trimRecoveredAlerts and getAlertsForNotifications', async () => {
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockReturnValue({
|
||||
maintenanceWindows: [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id1',
|
||||
},
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id2',
|
||||
},
|
||||
],
|
||||
maintenanceWindowsWithoutScopedQueryIds: ['test-id1', 'test-id2'],
|
||||
});
|
||||
(processAlerts as jest.Mock).mockReturnValue({
|
||||
newAlerts: {},
|
||||
activeAlerts: {
|
||||
|
@ -232,18 +290,19 @@ describe('Legacy Alerts Client', () => {
|
|||
recoveredAlerts: {},
|
||||
});
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
||||
alertsClient.processAndLogAlerts({
|
||||
eventLogger: alertingEventLogger,
|
||||
await alertsClient.processAlerts({
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: ['window-id1', 'window-id2'],
|
||||
alertDelay: 5,
|
||||
});
|
||||
|
||||
|
@ -261,7 +320,6 @@ describe('Legacy Alerts Client', () => {
|
|||
alertLimit: 1000,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: ['window-id1', 'window-id2'],
|
||||
startedAt: null,
|
||||
});
|
||||
|
||||
|
@ -285,31 +343,126 @@ describe('Legacy Alerts Client', () => {
|
|||
null
|
||||
);
|
||||
|
||||
expect(logAlerts).toHaveBeenCalledWith({
|
||||
logger,
|
||||
alertingEventLogger,
|
||||
newAlerts: {},
|
||||
activeAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
recoveredAlerts: {},
|
||||
ruleLogPrefix: 'test: rule-name',
|
||||
ruleRunMetricsStore,
|
||||
canSetRecoveryContext: false,
|
||||
shouldPersistAlerts: true,
|
||||
});
|
||||
|
||||
expect(alertsClient.getProcessedAlerts('active')).toEqual({
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
});
|
||||
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
});
|
||||
|
||||
test('processAlerts() should set maintenance windows IDs on new alerts', async () => {
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockReturnValue({
|
||||
maintenanceWindows: [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id1',
|
||||
},
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id2',
|
||||
},
|
||||
],
|
||||
maintenanceWindowsWithoutScopedQueryIds: ['test-id1', 'test-id2'],
|
||||
});
|
||||
(processAlerts as jest.Mock).mockReturnValue({
|
||||
newAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
},
|
||||
activeAlerts: {
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentRecoveredAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
(trimRecoveredAlerts as jest.Mock).mockReturnValue({
|
||||
trimmedAlertsRecovered: {},
|
||||
earlyRecoveredAlerts: {},
|
||||
});
|
||||
(getAlertsForNotification as jest.Mock).mockReturnValue({
|
||||
newAlerts: {
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', testAlert1),
|
||||
},
|
||||
activeAlerts: {
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentActiveAlerts: {
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
currentRecoveredAlerts: {},
|
||||
recoveredAlerts: {},
|
||||
});
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution({
|
||||
...defaultExecutionOpts,
|
||||
activeAlertsFromState: {
|
||||
'2': testAlert2,
|
||||
},
|
||||
});
|
||||
|
||||
await alertsClient.processAlerts({
|
||||
ruleRunMetricsStore,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
alertDelay: 5,
|
||||
});
|
||||
|
||||
expect(maintenanceWindowsService.getMaintenanceWindows).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
request: fakeRequest,
|
||||
ruleTypeCategory: 'test',
|
||||
spaceId: 'space1',
|
||||
});
|
||||
|
||||
expect(getAlertsForNotification).toHaveBeenCalledWith(
|
||||
{
|
||||
enabled: true,
|
||||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
},
|
||||
'default',
|
||||
5,
|
||||
{
|
||||
'1': new Alert<AlertInstanceContext, AlertInstanceContext>('1', {
|
||||
...testAlert1,
|
||||
meta: { ...testAlert1.meta, maintenanceWindowIds: ['test-id1', 'test-id2'] },
|
||||
}),
|
||||
},
|
||||
{
|
||||
'2': new Alert<AlertInstanceContext, AlertInstanceContext>('2', testAlert2),
|
||||
},
|
||||
{},
|
||||
{},
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
test('isTrackedAlert() should return true if alert was active in a previous execution, false otherwise', async () => {
|
||||
const alertsClient = new LegacyAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
spaceId: 'space1',
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
await alertsClient.initializeExecution(defaultExecutionOpts);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import { cloneDeep, keys, merge } from 'lodash';
|
||||
import { Alert } from '../alert/alert';
|
||||
import {
|
||||
|
@ -21,7 +21,6 @@ import {
|
|||
import { trimRecoveredAlerts } from '../lib/trim_recovered_alerts';
|
||||
import { logAlerts } from '../task_runner/log_alerts';
|
||||
import { AlertInstanceContext, AlertInstanceState, WithoutReservedActionGroups } from '../types';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
import {
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
RulesSettingsFlappingProperties,
|
||||
|
@ -29,17 +28,22 @@ import {
|
|||
import {
|
||||
IAlertsClient,
|
||||
InitializeExecutionOpts,
|
||||
ProcessAndLogAlertsOpts,
|
||||
ProcessAlertsOpts,
|
||||
LogAlertsOpts,
|
||||
TrackedAlerts,
|
||||
} from './types';
|
||||
import { DEFAULT_MAX_ALERTS } from '../config';
|
||||
import { UntypedNormalizedRuleType } from '../rule_type_registry';
|
||||
import { MaintenanceWindowsService } from '../task_runner/maintenance_windows';
|
||||
import { AlertingEventLogger } from '../lib/alerting_event_logger/alerting_event_logger';
|
||||
|
||||
export interface LegacyAlertsClientParams {
|
||||
alertingEventLogger: AlertingEventLogger;
|
||||
logger: Logger;
|
||||
maintenanceWindowsService?: MaintenanceWindowsService;
|
||||
request: KibanaRequest;
|
||||
ruleType: UntypedNormalizedRuleType;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
export class LegacyAlertsClient<
|
||||
|
@ -141,9 +145,8 @@ export class LegacyAlertsClient<
|
|||
return !!this.trackedAlerts.active[id];
|
||||
}
|
||||
|
||||
public processAlerts({
|
||||
public async processAlerts({
|
||||
flappingSettings,
|
||||
maintenanceWindowIds,
|
||||
alertDelay,
|
||||
ruleRunMetricsStore,
|
||||
}: ProcessAlertsOpts) {
|
||||
|
@ -160,10 +163,34 @@ export class LegacyAlertsClient<
|
|||
alertLimit: this.maxAlerts,
|
||||
autoRecoverAlerts: this.options.ruleType.autoRecoverAlerts ?? true,
|
||||
flappingSettings,
|
||||
maintenanceWindowIds,
|
||||
startedAt: this.startedAtString,
|
||||
});
|
||||
|
||||
if (this.options.maintenanceWindowsService) {
|
||||
// load maintenance windows if there are any any alerts (new, active, recovered)
|
||||
// this is because we need the MW IDs for any active or recovered alerts that may
|
||||
// have started during the MW period.
|
||||
if (
|
||||
keys(processedAlertsNew).length > 0 ||
|
||||
keys(processedAlertsActive).length > 0 ||
|
||||
keys(processedAlertsRecovered).length > 0
|
||||
) {
|
||||
const { maintenanceWindowsWithoutScopedQueryIds } =
|
||||
await this.options.maintenanceWindowsService.getMaintenanceWindows({
|
||||
eventLogger: this.options.alertingEventLogger,
|
||||
request: this.options.request,
|
||||
ruleTypeCategory: this.options.ruleType.category,
|
||||
spaceId: this.options.spaceId,
|
||||
});
|
||||
|
||||
for (const id in processedAlertsNew) {
|
||||
if (Object.hasOwn(processedAlertsNew, id)) {
|
||||
processedAlertsNew[id].setMaintenanceWindowIds(maintenanceWindowsWithoutScopedQueryIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { trimmedAlertsRecovered, earlyRecoveredAlerts } = trimRecoveredAlerts(
|
||||
this.options.logger,
|
||||
processedAlertsRecovered,
|
||||
|
@ -190,10 +217,10 @@ export class LegacyAlertsClient<
|
|||
this.processedAlerts.recoveredCurrent = alerts.currentRecoveredAlerts;
|
||||
}
|
||||
|
||||
public logAlerts({ eventLogger, ruleRunMetricsStore, shouldLogAlerts }: LogAlertsOpts) {
|
||||
public logAlerts({ ruleRunMetricsStore, shouldLogAlerts }: LogAlertsOpts) {
|
||||
logAlerts({
|
||||
logger: this.options.logger,
|
||||
alertingEventLogger: eventLogger,
|
||||
alertingEventLogger: this.options.alertingEventLogger,
|
||||
newAlerts: this.processedAlerts.new,
|
||||
activeAlerts: this.processedAlerts.activeCurrent,
|
||||
recoveredAlerts: this.processedAlerts.recoveredCurrent,
|
||||
|
@ -204,28 +231,6 @@ export class LegacyAlertsClient<
|
|||
});
|
||||
}
|
||||
|
||||
public processAndLogAlerts({
|
||||
eventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts,
|
||||
flappingSettings,
|
||||
maintenanceWindowIds,
|
||||
alertDelay,
|
||||
}: ProcessAndLogAlertsOpts) {
|
||||
this.processAlerts({
|
||||
flappingSettings,
|
||||
maintenanceWindowIds,
|
||||
alertDelay,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
||||
this.logAlerts({
|
||||
eventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts,
|
||||
});
|
||||
}
|
||||
|
||||
public getProcessedAlerts(
|
||||
type: 'new' | 'active' | 'activeCurrent' | 'recovered' | 'recoveredCurrent'
|
||||
) {
|
||||
|
@ -267,7 +272,7 @@ export class LegacyAlertsClient<
|
|||
return null;
|
||||
}
|
||||
|
||||
public async persistAlerts(maintenanceWindows?: MaintenanceWindow[]) {
|
||||
public async persistAlerts() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ import { alertsClientMock } from '../alerts_client.mock';
|
|||
import { UntypedNormalizedRuleType } from '../../rule_type_registry';
|
||||
import { legacyAlertsClientMock } from '../legacy_alerts_client.mock';
|
||||
import { initializeAlertsClient, RuleData } from './initialize_alerts_client';
|
||||
import { maintenanceWindowsServiceMock } from '../../task_runner/maintenance_windows/maintenance_windows_service.mock';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
|
||||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
const ruleRunMetricsStore = ruleRunMetricsStoreMock.create();
|
||||
|
@ -29,6 +31,23 @@ const alertsService = alertsServiceMock.create();
|
|||
const alertsClient = alertsClientMock.create();
|
||||
const legacyAlertsClient = legacyAlertsClientMock.create();
|
||||
const logger = loggingSystemMock.create().get();
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
getSavedObjectsClient: jest.fn(),
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
const ruleTypeWithAlerts: jest.Mocked<UntypedNormalizedRuleType> = {
|
||||
...ruleType,
|
||||
|
@ -75,6 +94,8 @@ describe('initializeAlertsClient', () => {
|
|||
context: {
|
||||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowsService,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -90,9 +111,13 @@ describe('initializeAlertsClient', () => {
|
|||
});
|
||||
|
||||
expect(alertsService.createAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
alertDelay: 0,
|
||||
consumer: 'bar',
|
||||
|
@ -128,6 +153,8 @@ describe('initializeAlertsClient', () => {
|
|||
alertsService,
|
||||
context: {
|
||||
alertingEventLogger,
|
||||
maintenanceWindowsService,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -143,9 +170,13 @@ describe('initializeAlertsClient', () => {
|
|||
});
|
||||
|
||||
expect(alertsService.createAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
alertDelay: 0,
|
||||
consumer: 'bar',
|
||||
|
@ -182,6 +213,8 @@ describe('initializeAlertsClient', () => {
|
|||
context: {
|
||||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowsService,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -197,9 +230,13 @@ describe('initializeAlertsClient', () => {
|
|||
});
|
||||
|
||||
expect(alertsService.createAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
alertDelay: 0,
|
||||
consumer: 'bar',
|
||||
|
@ -215,8 +252,12 @@ describe('initializeAlertsClient', () => {
|
|||
},
|
||||
});
|
||||
expect(LegacyAlertsClientModule.LegacyAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
spaceId: 'default',
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
expect(legacyAlertsClient.initializeExecution).toHaveBeenCalledWith({
|
||||
activeAlertsFromState: {},
|
||||
|
@ -241,6 +282,8 @@ describe('initializeAlertsClient', () => {
|
|||
context: {
|
||||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowsService,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -256,9 +299,13 @@ describe('initializeAlertsClient', () => {
|
|||
});
|
||||
|
||||
expect(alertsService.createAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
alertDelay: 0,
|
||||
consumer: 'bar',
|
||||
|
@ -274,8 +321,12 @@ describe('initializeAlertsClient', () => {
|
|||
},
|
||||
});
|
||||
expect(LegacyAlertsClientModule.LegacyAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
spaceId: 'default',
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
`Error initializing AlertsClient for context test. Using legacy alerts client instead. - fail fail`
|
||||
|
|
|
@ -65,7 +65,14 @@ export const initializeAlertsClient = async <
|
|||
},
|
||||
} = taskInstance;
|
||||
|
||||
const alertsClientParams = { logger, ruleType };
|
||||
const alertsClientParams = {
|
||||
alertingEventLogger: context.alertingEventLogger,
|
||||
logger,
|
||||
maintenanceWindowsService: context.maintenanceWindowsService,
|
||||
request: context.request,
|
||||
ruleType,
|
||||
spaceId: context.spaceId,
|
||||
};
|
||||
|
||||
// Create AlertsClient if rule type has registered an alerts context
|
||||
// with the framework. The AlertsClient will handle reading and
|
||||
|
|
|
@ -74,16 +74,12 @@ export interface IAlertsClient<
|
|||
initializeExecution(opts: InitializeExecutionOpts): Promise<void>;
|
||||
hasReachedAlertLimit(): boolean;
|
||||
checkLimitUsage(): void;
|
||||
processAndLogAlerts(opts: ProcessAndLogAlertsOpts): void;
|
||||
processAlerts(opts: ProcessAlertsOpts): void;
|
||||
logAlerts(opts: LogAlertsOpts): void;
|
||||
getProcessedAlerts(
|
||||
type: 'new' | 'active' | 'activeCurrent' | 'recovered' | 'recoveredCurrent'
|
||||
): Record<string, LegacyAlert<State, Context, ActionGroupIds | RecoveryActionGroupId>>;
|
||||
persistAlerts(maintenanceWindows?: MaintenanceWindow[]): Promise<{
|
||||
alertIds: string[];
|
||||
maintenanceWindowIds: string[];
|
||||
} | null>;
|
||||
persistAlerts(): Promise<{ alertIds: string[]; maintenanceWindowIds: string[] } | null>;
|
||||
isTrackedAlert(id: string): boolean;
|
||||
getSummarizedAlerts?(params: GetSummarizedAlertsParams): Promise<SummarizedAlerts>;
|
||||
getAlertsToSerialize(): {
|
||||
|
@ -114,13 +110,11 @@ export interface ProcessAndLogAlertsOpts {
|
|||
|
||||
export interface ProcessAlertsOpts {
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
maintenanceWindowIds: string[];
|
||||
alertDelay: number;
|
||||
ruleRunMetricsStore: RuleRunMetricsStore;
|
||||
}
|
||||
|
||||
export interface LogAlertsOpts {
|
||||
eventLogger: AlertingEventLogger;
|
||||
shouldLogAlerts: boolean;
|
||||
ruleRunMetricsStore: RuleRunMetricsStore;
|
||||
}
|
||||
|
|
|
@ -21,12 +21,34 @@ import { UntypedNormalizedRuleType } from '../rule_type_registry';
|
|||
import { AlertsClient } from '../alerts_client';
|
||||
import { alertsClientMock } from '../alerts_client/alerts_client.mock';
|
||||
import { getDataStreamAdapter } from './lib/data_stream_adapter';
|
||||
import { maintenanceWindowsServiceMock } from '../task_runner/maintenance_windows/maintenance_windows_service.mock';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock';
|
||||
|
||||
jest.mock('../alerts_client');
|
||||
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
|
||||
let logger: ReturnType<(typeof loggingSystemMock)['createLogger']>;
|
||||
const clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
getSavedObjectsClient: jest.fn(),
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
const SimulateTemplateResponse = {
|
||||
template: {
|
||||
aliases: {
|
||||
|
@ -1507,9 +1529,13 @@ describe('Alerts Service', () => {
|
|||
);
|
||||
|
||||
await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1526,11 +1552,15 @@ describe('Alerts Service', () => {
|
|||
});
|
||||
|
||||
expect(AlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
elasticsearchClientPromise: Promise.resolve(clusterClient),
|
||||
dataStreamAdapter,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1563,9 +1593,13 @@ describe('Alerts Service', () => {
|
|||
async () => alertsService.isInitialized() === true
|
||||
);
|
||||
const result = await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1613,9 +1647,13 @@ describe('Alerts Service', () => {
|
|||
expect(clusterClient.indices.create).not.toHaveBeenCalled();
|
||||
|
||||
const result = await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1646,11 +1684,15 @@ describe('Alerts Service', () => {
|
|||
}
|
||||
|
||||
expect(AlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
elasticsearchClientPromise: Promise.resolve(clusterClient),
|
||||
dataStreamAdapter,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1714,9 +1756,13 @@ describe('Alerts Service', () => {
|
|||
// call createAlertsClient at the same time which will trigger the retries
|
||||
const result = await Promise.all([
|
||||
alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1732,9 +1778,13 @@ describe('Alerts Service', () => {
|
|||
},
|
||||
}),
|
||||
alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1765,11 +1815,15 @@ describe('Alerts Service', () => {
|
|||
expect(clusterClient.indices.getAlias).toHaveBeenCalled();
|
||||
}
|
||||
expect(AlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
elasticsearchClientPromise: Promise.resolve(clusterClient),
|
||||
dataStreamAdapter,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1825,9 +1879,13 @@ describe('Alerts Service', () => {
|
|||
);
|
||||
|
||||
const result = await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1844,11 +1902,15 @@ describe('Alerts Service', () => {
|
|||
});
|
||||
|
||||
expect(AlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
elasticsearchClientPromise: Promise.resolve(clusterClient),
|
||||
dataStreamAdapter,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1912,9 +1974,13 @@ describe('Alerts Service', () => {
|
|||
}
|
||||
|
||||
return await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -1938,11 +2004,15 @@ describe('Alerts Service', () => {
|
|||
|
||||
expect(AlertsClient).toHaveBeenCalledTimes(2);
|
||||
expect(AlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
elasticsearchClientPromise: Promise.resolve(clusterClient),
|
||||
dataStreamAdapter,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -2011,9 +2081,13 @@ describe('Alerts Service', () => {
|
|||
}
|
||||
|
||||
return await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -2078,9 +2152,13 @@ describe('Alerts Service', () => {
|
|||
expect(clusterClient.indices.create).not.toHaveBeenCalled();
|
||||
|
||||
const result = await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -2145,9 +2223,13 @@ describe('Alerts Service', () => {
|
|||
expect(clusterClient.indices.create).not.toHaveBeenCalled();
|
||||
|
||||
const result = await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -2210,9 +2292,13 @@ describe('Alerts Service', () => {
|
|||
);
|
||||
|
||||
const result = await alertsService.createAlertsClient({
|
||||
alertingEventLogger,
|
||||
logger,
|
||||
request: fakeRequest,
|
||||
ruleType: ruleTypeWithAlertDefinition,
|
||||
maintenanceWindowsService,
|
||||
namespace: 'default',
|
||||
spaceId: 'default',
|
||||
rule: {
|
||||
consumer: 'bar',
|
||||
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
|
|
@ -234,11 +234,15 @@ export class AlertsService implements IAlertsService {
|
|||
ActionGroupIds,
|
||||
RecoveryActionGroupId
|
||||
>({
|
||||
alertingEventLogger: opts.alertingEventLogger,
|
||||
logger: this.options.logger,
|
||||
elasticsearchClientPromise: this.options.elasticsearchClientPromise,
|
||||
ruleType: opts.ruleType,
|
||||
maintenanceWindowsService: opts.maintenanceWindowsService,
|
||||
namespace: opts.namespace,
|
||||
request: opts.request,
|
||||
rule: opts.rule,
|
||||
spaceId: opts.spaceId,
|
||||
kibanaVersion: this.options.kibanaVersion,
|
||||
dataStreamAdapter: this.dataStreamAdapter,
|
||||
});
|
||||
|
|
|
@ -103,6 +103,90 @@ describe('MaintenanceWindowClient - getActiveMaintenanceWindows', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should use cacheInterval if provided', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z'));
|
||||
|
||||
savedObjectsClient.find.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
attributes: getMockMaintenanceWindow({ expirationDate: new Date().toISOString() }),
|
||||
id: 'test-1',
|
||||
},
|
||||
{
|
||||
attributes: getMockMaintenanceWindow({ expirationDate: new Date().toISOString() }),
|
||||
id: 'test-2',
|
||||
},
|
||||
],
|
||||
} as unknown as SavedObjectsFindResponse);
|
||||
|
||||
const result = await getActiveMaintenanceWindows(mockContext, 60000);
|
||||
|
||||
const findCallParams = savedObjectsClient.find.mock.calls[0][0];
|
||||
|
||||
expect(findCallParams.type).toEqual(MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE);
|
||||
|
||||
expect(toElasticsearchQuery(findCallParams.filter)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"bool": Object {
|
||||
"filter": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"maintenance-window.attributes.events": "2023-02-26T00:00:00.000Z",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"maintenance-window.attributes.events": "2023-02-26T00:01:00.000Z",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"bool": Object {
|
||||
"minimum_should_match": 1,
|
||||
"should": Array [
|
||||
Object {
|
||||
"match": Object {
|
||||
"maintenance-window.attributes.enabled": "true",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(result).toEqual([
|
||||
expect.objectContaining({
|
||||
id: 'test-1',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'test-2',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return empty array if there are no active maintenance windows', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z'));
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import Boom from '@hapi/boom';
|
||||
import { nodeBuilder } from '@kbn/es-query';
|
||||
import { KueryNode, nodeBuilder } from '@kbn/es-query';
|
||||
import type { MaintenanceWindowClientContext } from '../../../../../common';
|
||||
import type { MaintenanceWindow } from '../../types';
|
||||
import { transformMaintenanceWindowAttributesToMaintenanceWindow } from '../../transforms';
|
||||
|
@ -23,15 +23,29 @@ export interface MaintenanceWindowAggregationResult {
|
|||
}
|
||||
|
||||
export async function getActiveMaintenanceWindows(
|
||||
context: MaintenanceWindowClientContext
|
||||
context: MaintenanceWindowClientContext,
|
||||
cacheIntervalMs?: number
|
||||
): Promise<MaintenanceWindow[]> {
|
||||
const { savedObjectsClient, logger } = context;
|
||||
|
||||
const startDate = new Date();
|
||||
const startDateISO = startDate.toISOString();
|
||||
|
||||
let eventsKuery: KueryNode;
|
||||
if (cacheIntervalMs) {
|
||||
// add offset to startDate
|
||||
const startDateWithCacheOffset = new Date(startDate.getTime() + cacheIntervalMs);
|
||||
const startDateWithCacheOffsetISO = startDateWithCacheOffset.toISOString();
|
||||
eventsKuery = nodeBuilder.or([
|
||||
nodeBuilder.is('maintenance-window.attributes.events', startDateISO),
|
||||
nodeBuilder.is('maintenance-window.attributes.events', startDateWithCacheOffsetISO),
|
||||
]);
|
||||
} else {
|
||||
eventsKuery = nodeBuilder.is('maintenance-window.attributes.events', startDateISO);
|
||||
}
|
||||
|
||||
const filter = nodeBuilder.and([
|
||||
nodeBuilder.is('maintenance-window.attributes.events', startDateISO),
|
||||
eventsKuery,
|
||||
nodeBuilder.is('maintenance-window.attributes.enabled', 'true'),
|
||||
]);
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@ import { Alert } from '../alert';
|
|||
import { AlertInstanceState, AlertInstanceContext } from '../types';
|
||||
import { DEFAULT_FLAPPING_SETTINGS, DISABLE_FLAPPING_SETTINGS } from '../../common/rules_settings';
|
||||
|
||||
const maintenanceWindowIds = ['test-id-1', 'test-id-2'];
|
||||
|
||||
describe('processAlerts', () => {
|
||||
let clock: sinon.SinonFakeTimers;
|
||||
|
||||
|
@ -60,7 +58,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(newAlerts).toEqual({ '1': newAlert });
|
||||
|
@ -99,7 +96,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(newAlerts).toEqual({ '1': newAlert1, '2': newAlert2 });
|
||||
|
@ -150,7 +146,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
startedAt: '2023-10-03T20:03:08.716Z',
|
||||
});
|
||||
|
||||
|
@ -168,46 +163,6 @@ describe('processAlerts', () => {
|
|||
expect(newAlert1State.end).not.toBeDefined();
|
||||
expect(newAlert2State.end).not.toBeDefined();
|
||||
});
|
||||
|
||||
test('sets maintenance window IDs in new alert state', () => {
|
||||
const newAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('1');
|
||||
const newAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('2');
|
||||
const existingAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('3');
|
||||
const existingAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('4');
|
||||
|
||||
const existingAlerts = {
|
||||
'3': existingAlert1,
|
||||
'4': existingAlert2,
|
||||
};
|
||||
|
||||
const updatedAlerts = {
|
||||
...cloneDeep(existingAlerts),
|
||||
'1': newAlert1,
|
||||
'2': newAlert2,
|
||||
};
|
||||
|
||||
updatedAlerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
updatedAlerts['2'].scheduleActions('default' as never, { foo: '1' });
|
||||
updatedAlerts['3'].scheduleActions('default' as never, { foo: '1' });
|
||||
updatedAlerts['4'].scheduleActions('default' as never, { foo: '2' });
|
||||
|
||||
expect(newAlert1.getState()).toStrictEqual({});
|
||||
expect(newAlert2.getState()).toStrictEqual({});
|
||||
|
||||
const { newAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds,
|
||||
});
|
||||
|
||||
expect(newAlerts['1'].getMaintenanceWindowIds()).toEqual(maintenanceWindowIds);
|
||||
expect(newAlerts['2'].getMaintenanceWindowIds()).toEqual(maintenanceWindowIds);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeAlerts', () => {
|
||||
|
@ -238,7 +193,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -277,7 +231,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -326,7 +279,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -385,7 +337,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -451,7 +402,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -513,7 +463,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(
|
||||
|
@ -570,7 +519,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
startedAt: '2023-10-03T20:03:08.716Z',
|
||||
});
|
||||
|
||||
|
@ -590,37 +538,6 @@ describe('processAlerts', () => {
|
|||
expect(previouslyRecoveredAlert1State.end).not.toBeDefined();
|
||||
expect(previouslyRecoveredAlert2State.end).not.toBeDefined();
|
||||
});
|
||||
|
||||
test('should not set maintenance window IDs for active alerts', () => {
|
||||
const newAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1');
|
||||
const existingAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('2');
|
||||
|
||||
const existingAlerts = {
|
||||
'2': existingAlert1,
|
||||
};
|
||||
existingAlerts['2'].replaceState({ start: '1969-12-30T00:00:00.000Z', duration: 33000 });
|
||||
|
||||
const updatedAlerts = {
|
||||
...cloneDeep(existingAlerts),
|
||||
'1': newAlert,
|
||||
};
|
||||
|
||||
updatedAlerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
updatedAlerts['2'].scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const { activeAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds,
|
||||
});
|
||||
|
||||
expect(activeAlerts['2'].getMaintenanceWindowIds()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('recoveredAlerts', () => {
|
||||
|
@ -646,7 +563,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'] });
|
||||
|
@ -675,7 +591,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
|
@ -706,7 +621,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'], '3': updatedAlerts['3'] });
|
||||
|
@ -749,7 +663,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
startedAt: '2023-10-03T20:03:08.716Z',
|
||||
});
|
||||
|
||||
|
@ -790,7 +703,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({ '2': updatedAlerts['2'], '3': updatedAlerts['3'] });
|
||||
|
@ -830,7 +742,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual(updatedAlerts);
|
||||
|
@ -861,39 +772,10 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: false,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
});
|
||||
|
||||
test('should not set maintenance window IDs for recovered alerts', () => {
|
||||
const activeAlert = new Alert<AlertInstanceState, AlertInstanceContext>('1');
|
||||
const recoveredAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('2');
|
||||
|
||||
const existingAlerts = {
|
||||
'1': activeAlert,
|
||||
'2': recoveredAlert1,
|
||||
};
|
||||
existingAlerts['2'].replaceState({ start: '1969-12-30T00:00:00.000Z', duration: 33000 });
|
||||
|
||||
const updatedAlerts = cloneDeep(existingAlerts);
|
||||
|
||||
updatedAlerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
|
||||
const { recoveredAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: false,
|
||||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds,
|
||||
});
|
||||
|
||||
expect(recoveredAlerts['2'].getMaintenanceWindowIds()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when hasReachedAlertLimit is true', () => {
|
||||
|
@ -936,7 +818,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 7,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(recoveredAlerts).toEqual({});
|
||||
|
@ -973,7 +854,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 7,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toEqual({
|
||||
|
@ -1034,7 +914,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: MAX_ALERTS,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(Object.keys(activeAlerts).length).toEqual(MAX_ALERTS);
|
||||
|
@ -1052,68 +931,6 @@ describe('processAlerts', () => {
|
|||
'7': newAlert7,
|
||||
});
|
||||
});
|
||||
|
||||
test('should set maintenance window IDs for new alerts when reached alert limit', () => {
|
||||
const MAX_ALERTS = 7;
|
||||
const existingAlert1 = new Alert<AlertInstanceState, AlertInstanceContext>('1');
|
||||
const existingAlert2 = new Alert<AlertInstanceState, AlertInstanceContext>('2');
|
||||
const existingAlert3 = new Alert<AlertInstanceState, AlertInstanceContext>('3');
|
||||
const existingAlert4 = new Alert<AlertInstanceState, AlertInstanceContext>('4');
|
||||
const existingAlert5 = new Alert<AlertInstanceState, AlertInstanceContext>('5');
|
||||
const newAlert6 = new Alert<AlertInstanceState, AlertInstanceContext>('6');
|
||||
const newAlert7 = new Alert<AlertInstanceState, AlertInstanceContext>('7');
|
||||
const newAlert8 = new Alert<AlertInstanceState, AlertInstanceContext>('8');
|
||||
const newAlert9 = new Alert<AlertInstanceState, AlertInstanceContext>('9');
|
||||
const newAlert10 = new Alert<AlertInstanceState, AlertInstanceContext>('10');
|
||||
|
||||
const existingAlerts = {
|
||||
'1': existingAlert1,
|
||||
'2': existingAlert2,
|
||||
'3': existingAlert3,
|
||||
'4': existingAlert4,
|
||||
'5': existingAlert5,
|
||||
};
|
||||
|
||||
const updatedAlerts = {
|
||||
...cloneDeep(existingAlerts),
|
||||
'6': newAlert6,
|
||||
'7': newAlert7,
|
||||
'8': newAlert8,
|
||||
'9': newAlert9,
|
||||
'10': newAlert10,
|
||||
};
|
||||
|
||||
updatedAlerts['1'].scheduleActions('default' as never, { foo: '1' });
|
||||
updatedAlerts['2'].scheduleActions('default' as never, { foo: '1' });
|
||||
updatedAlerts['3'].scheduleActions('default' as never, { foo: '2' });
|
||||
updatedAlerts['4'].scheduleActions('default' as never, { foo: '2' });
|
||||
// intentionally not scheduling actions for alert "5"
|
||||
updatedAlerts['6'].scheduleActions('default' as never, { foo: '2' });
|
||||
updatedAlerts['7'].scheduleActions('default' as never, { foo: '2' });
|
||||
updatedAlerts['8'].scheduleActions('default' as never, { foo: '2' });
|
||||
updatedAlerts['9'].scheduleActions('default' as never, { foo: '2' });
|
||||
updatedAlerts['10'].scheduleActions('default' as never, { foo: '2' });
|
||||
|
||||
const { activeAlerts, newAlerts } = processAlerts({
|
||||
alerts: updatedAlerts,
|
||||
existingAlerts,
|
||||
previouslyRecoveredAlerts: {},
|
||||
hasReachedAlertLimit: true,
|
||||
alertLimit: MAX_ALERTS,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds,
|
||||
});
|
||||
|
||||
expect(Object.keys(activeAlerts).length).toEqual(MAX_ALERTS);
|
||||
expect(newAlerts['6'].getMaintenanceWindowIds()).toEqual(maintenanceWindowIds);
|
||||
expect(newAlerts['7'].getMaintenanceWindowIds()).toEqual(maintenanceWindowIds);
|
||||
expect(activeAlerts['1'].getMaintenanceWindowIds()).toEqual([]);
|
||||
expect(activeAlerts['2'].getMaintenanceWindowIds()).toEqual([]);
|
||||
expect(activeAlerts['3'].getMaintenanceWindowIds()).toEqual([]);
|
||||
expect(activeAlerts['4'].getMaintenanceWindowIds()).toEqual([]);
|
||||
expect(activeAlerts['5'].getMaintenanceWindowIds()).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updating flappingHistory', () => {
|
||||
|
@ -1133,7 +950,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1189,7 +1005,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1231,7 +1046,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1292,7 +1106,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
|
@ -1329,7 +1142,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`Object {}`);
|
||||
|
@ -1376,7 +1188,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1452,7 +1263,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1494,7 +1304,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1567,7 +1376,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
@ -1655,7 +1463,6 @@ describe('processAlerts', () => {
|
|||
alertLimit: 10,
|
||||
autoRecoverAlerts: true,
|
||||
flappingSettings: DISABLE_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
});
|
||||
|
||||
expect(activeAlerts).toMatchInlineSnapshot(`
|
||||
|
|
|
@ -24,7 +24,6 @@ interface ProcessAlertsOpts<
|
|||
autoRecoverAlerts: boolean;
|
||||
startedAt?: string | null;
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
maintenanceWindowIds: string[];
|
||||
}
|
||||
interface ProcessAlertsResult<
|
||||
State extends AlertInstanceState,
|
||||
|
@ -52,7 +51,6 @@ export function processAlerts<
|
|||
alertLimit,
|
||||
autoRecoverAlerts,
|
||||
flappingSettings,
|
||||
maintenanceWindowIds,
|
||||
startedAt,
|
||||
}: ProcessAlertsOpts<State, Context>): ProcessAlertsResult<
|
||||
State,
|
||||
|
@ -67,7 +65,6 @@ export function processAlerts<
|
|||
previouslyRecoveredAlerts,
|
||||
alertLimit,
|
||||
flappingSettings,
|
||||
maintenanceWindowIds,
|
||||
startedAt
|
||||
)
|
||||
: processAlertsHelper(
|
||||
|
@ -76,7 +73,6 @@ export function processAlerts<
|
|||
previouslyRecoveredAlerts,
|
||||
autoRecoverAlerts,
|
||||
flappingSettings,
|
||||
maintenanceWindowIds,
|
||||
startedAt
|
||||
);
|
||||
}
|
||||
|
@ -92,7 +88,6 @@ function processAlertsHelper<
|
|||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>>,
|
||||
autoRecoverAlerts: boolean,
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
maintenanceWindowIds: string[],
|
||||
startedAt?: string | null
|
||||
): ProcessAlertsResult<State, Context, ActionGroupIds, RecoveryActionGroupId> {
|
||||
const existingAlertIds = new Set(Object.keys(existingAlerts));
|
||||
|
@ -124,7 +119,6 @@ function processAlertsHelper<
|
|||
}
|
||||
updateAlertFlappingHistory(flappingSettings, newAlerts[id], true);
|
||||
}
|
||||
newAlerts[id].setMaintenanceWindowIds(maintenanceWindowIds);
|
||||
} else {
|
||||
// this alert did exist in previous run
|
||||
// calculate duration to date for active alerts
|
||||
|
@ -188,7 +182,6 @@ function processAlertsLimitReached<
|
|||
previouslyRecoveredAlerts: Record<string, Alert<State, Context>>,
|
||||
alertLimit: number,
|
||||
flappingSettings: RulesSettingsFlappingProperties,
|
||||
maintenanceWindowIds: string[],
|
||||
startedAt?: string | null
|
||||
): ProcessAlertsResult<State, Context, ActionGroupIds, RecoveryActionGroupId> {
|
||||
const existingAlertIds = new Set(Object.keys(existingAlerts));
|
||||
|
@ -258,8 +251,6 @@ function processAlertsLimitReached<
|
|||
updateAlertFlappingHistory(flappingSettings, newAlerts[id], true);
|
||||
}
|
||||
|
||||
newAlerts[id].setMaintenanceWindowIds(maintenanceWindowIds);
|
||||
|
||||
if (!hasCapacityForNewAlerts()) {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -85,6 +85,6 @@ export class MaintenanceWindowClient {
|
|||
public bulkGet = (
|
||||
params: BulkGetMaintenanceWindowsParams
|
||||
): Promise<BulkGetMaintenanceWindowsResult> => bulkGetMaintenanceWindows(this.context, params);
|
||||
public getActiveMaintenanceWindows = (): Promise<MaintenanceWindow[]> =>
|
||||
getActiveMaintenanceWindows(this.context);
|
||||
public getActiveMaintenanceWindows = (cacheIntervalMs?: number): Promise<MaintenanceWindow[]> =>
|
||||
getActiveMaintenanceWindows(this.context, cacheIntervalMs);
|
||||
}
|
||||
|
|
|
@ -168,16 +168,17 @@ const createRuleExecutorServicesMock = <
|
|||
done: jest.fn().mockReturnValue(alertFactoryMockDone),
|
||||
},
|
||||
alertsClient: publicAlertsClientMock.create(),
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
uiSettingsClient: uiSettingsServiceMock.createClient(),
|
||||
scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
|
||||
shouldWriteAlerts: () => true,
|
||||
shouldStopExecution: () => true,
|
||||
search: createAbortableSearchServiceMock(),
|
||||
getDataViews: jest.fn().mockResolvedValue(dataViewPluginMocks.createStartContract()),
|
||||
getMaintenanceWindowIds: jest.fn().mockResolvedValue([]),
|
||||
getSearchSourceClient: jest.fn().mockResolvedValue(searchSourceCommonMock),
|
||||
ruleMonitoringService: createRuleMonitoringServiceMock(),
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
|
||||
search: createAbortableSearchServiceMock(),
|
||||
share: createShareStartMock(),
|
||||
getDataViews: jest.fn().mockResolvedValue(dataViewPluginMocks.createStartContract()),
|
||||
shouldStopExecution: () => true,
|
||||
shouldWriteAlerts: () => true,
|
||||
uiSettingsClient: uiSettingsServiceMock.createClient(),
|
||||
};
|
||||
};
|
||||
export type RuleExecutorServicesMock = ReturnType<typeof createRuleExecutorServicesMock>;
|
||||
|
|
|
@ -116,6 +116,7 @@ import { ConnectorAdapter, ConnectorAdapterParams } from './connector_adapters/t
|
|||
import { DataStreamAdapter, getDataStreamAdapter } from './alerts_service/lib/data_stream_adapter';
|
||||
import { createGetAlertIndicesAliasFn, GetAlertIndicesAlias } from './lib';
|
||||
import { BackfillClient } from './backfill_client/backfill_client';
|
||||
import { MaintenanceWindowsService } from './task_runner/maintenance_windows';
|
||||
|
||||
export const EVENT_LOG_PROVIDER = 'alerting';
|
||||
export const EVENT_LOG_ACTIONS = {
|
||||
|
@ -605,10 +606,14 @@ export class AlertingPlugin {
|
|||
encryptedSavedObjectsClient,
|
||||
eventLogger: this.eventLogger!,
|
||||
executionContext: core.executionContext,
|
||||
getMaintenanceWindowClientWithRequest,
|
||||
getRulesClientWithRequest,
|
||||
kibanaBaseUrl: this.kibanaBaseUrl,
|
||||
logger,
|
||||
maintenanceWindowsService: new MaintenanceWindowsService({
|
||||
cacheInterval: this.config.rulesSettings.cacheInterval,
|
||||
getMaintenanceWindowClientWithRequest,
|
||||
logger,
|
||||
}),
|
||||
maxAlerts: this.config.rules.run.alerts.max,
|
||||
maxEphemeralActionsPerRule: this.config.maxEphemeralActionsPerAlert,
|
||||
ruleTypeRegistry: this.ruleTypeRegistry!,
|
||||
|
|
|
@ -30,7 +30,6 @@ import { usageCountersServiceMock } from '@kbn/usage-collection-plugin/server/us
|
|||
import { AdHocTaskRunner } from './ad_hoc_task_runner';
|
||||
import { TaskRunnerContext } from './types';
|
||||
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
|
||||
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
|
||||
import { rulesClientMock } from '../rules_client.mock';
|
||||
import { ruleTypeRegistryMock } from '../rule_type_registry.mock';
|
||||
import {
|
||||
|
@ -94,6 +93,7 @@ import { ruleRunMetricsStoreMock } from '../lib/rule_run_metrics_store.mock';
|
|||
import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store';
|
||||
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
|
||||
import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
|
||||
import { maintenanceWindowsServiceMock } from './maintenance_windows/maintenance_windows_service.mock';
|
||||
|
||||
const UUID = '5f6aa57d-3e22-484e-bae8-cbed868f4d28';
|
||||
|
||||
|
@ -142,7 +142,7 @@ const dataViewsMock = {
|
|||
const elasticsearchService = elasticsearchServiceMock.createInternalStart();
|
||||
const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient();
|
||||
const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
||||
const maintenanceWindowClient = maintenanceWindowClientMock.create();
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
const rulesClient = rulesClientMock.create();
|
||||
const ruleRunMetricsStore = ruleRunMetricsStoreMock.create();
|
||||
const rulesSettingsService = rulesSettingsServiceMock.create();
|
||||
|
@ -165,7 +165,7 @@ const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType
|
|||
encryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
executionContext: executionContextServiceMock.createInternalStartContract(),
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
maintenanceWindowsService,
|
||||
getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
|
||||
kibanaBaseUrl: 'https://localhost:5601',
|
||||
logger,
|
||||
|
@ -459,7 +459,6 @@ describe('Ad Hoc Task Runner', () => {
|
|||
expect(call.rule.ruleTypeName).toBe('My test rule');
|
||||
expect(call.rule.actions).toEqual([]);
|
||||
expect(call.flappingSettings).toEqual(DEFAULT_FLAPPING_SETTINGS);
|
||||
expect(call.maintenanceWindowIds).toBe(undefined);
|
||||
|
||||
expect(clusterClient.bulk).toHaveBeenCalledWith({
|
||||
index: '.alerts-test.alerts-default',
|
||||
|
|
|
@ -180,6 +180,7 @@ export class AdHocTaskRunner implements CancellableTask {
|
|||
const ruleTypeRunnerContext = {
|
||||
alertingEventLogger: this.alertingEventLogger,
|
||||
namespace: this.context.spaceIdToNamespace(adHocRunData.spaceId),
|
||||
request: fakeRequest,
|
||||
ruleId: rule.id,
|
||||
ruleLogPrefix: ruleLabel,
|
||||
ruleRunMetricsStore,
|
||||
|
|
|
@ -7,19 +7,19 @@
|
|||
|
||||
import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { maintenanceWindowCategoryIdTypes } from '../application/maintenance_window/constants';
|
||||
import { getMockMaintenanceWindow } from '../data/maintenance_window/test_helpers';
|
||||
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
|
||||
import { MaintenanceWindowStatus } from '../types';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
import { mockedRawRuleSO, mockedRule } from './fixtures';
|
||||
import { maintenanceWindowCategoryIdTypes } from '../../application/maintenance_window/constants';
|
||||
import { getMockMaintenanceWindow } from '../../data/maintenance_window/test_helpers';
|
||||
import { maintenanceWindowClientMock } from '../../maintenance_window_client.mock';
|
||||
import { MaintenanceWindowStatus } from '../../types';
|
||||
import { MaintenanceWindow } from '../../application/maintenance_window/types';
|
||||
import { mockedRawRuleSO, mockedRule } from '../fixtures';
|
||||
import {
|
||||
filterMaintenanceWindows,
|
||||
filterMaintenanceWindowsIds,
|
||||
getMaintenanceWindows,
|
||||
} from './get_maintenance_windows';
|
||||
import { getFakeKibanaRequest } from './rule_loader';
|
||||
import { TaskRunnerContext } from './types';
|
||||
import { getFakeKibanaRequest } from '../rule_loader';
|
||||
import { TaskRunnerContext } from '../types';
|
||||
import { FilterStateStore } from '@kbn/es-query';
|
||||
|
||||
const logger = loggingSystemMock.create().get();
|
||||
|
@ -64,8 +64,8 @@ describe('getMaintenanceWindows', () => {
|
|||
);
|
||||
expect(
|
||||
await getMaintenanceWindows({
|
||||
context,
|
||||
fakeRequest,
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
ruleTypeId,
|
||||
ruleTypeCategory: 'observability',
|
||||
|
@ -98,8 +98,8 @@ describe('getMaintenanceWindows', () => {
|
|||
);
|
||||
expect(
|
||||
await getMaintenanceWindows({
|
||||
context,
|
||||
fakeRequest,
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
ruleTypeId,
|
||||
ruleTypeCategory: 'observability',
|
||||
|
@ -139,8 +139,8 @@ describe('getMaintenanceWindows', () => {
|
|||
);
|
||||
expect(
|
||||
await getMaintenanceWindows({
|
||||
context,
|
||||
fakeRequest,
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
ruleTypeId,
|
||||
ruleTypeCategory: 'observability',
|
||||
|
@ -153,8 +153,8 @@ describe('getMaintenanceWindows', () => {
|
|||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce([]);
|
||||
expect(
|
||||
await getMaintenanceWindows({
|
||||
context,
|
||||
fakeRequest,
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
ruleTypeId,
|
||||
ruleTypeCategory: 'observability',
|
||||
|
@ -169,8 +169,8 @@ describe('getMaintenanceWindows', () => {
|
|||
});
|
||||
expect(
|
||||
await getMaintenanceWindows({
|
||||
context,
|
||||
fakeRequest,
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
ruleTypeId,
|
||||
ruleTypeCategory: 'observability',
|
|
@ -6,16 +6,17 @@
|
|||
*/
|
||||
|
||||
import { KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
import { TaskRunnerContext } from './types';
|
||||
import { MaintenanceWindow } from '../../application/maintenance_window/types';
|
||||
import { MaintenanceWindowClientApi } from '../../types';
|
||||
import { withAlertingSpan } from '../lib';
|
||||
|
||||
interface GetMaintenanceWindowsOpts {
|
||||
context: TaskRunnerContext;
|
||||
fakeRequest: KibanaRequest;
|
||||
getMaintenanceWindowClientWithRequest(request: KibanaRequest): MaintenanceWindowClientApi;
|
||||
logger: Logger;
|
||||
ruleId: string;
|
||||
ruleTypeId: string;
|
||||
ruleTypeCategory: string;
|
||||
ruleId: string;
|
||||
}
|
||||
|
||||
interface FilterMaintenanceWindowsOpts {
|
||||
|
@ -55,29 +56,38 @@ export const filterMaintenanceWindowsIds = ({
|
|||
export const getMaintenanceWindows = async (
|
||||
opts: GetMaintenanceWindowsOpts
|
||||
): Promise<MaintenanceWindow[]> => {
|
||||
const { context, fakeRequest, logger, ruleTypeId, ruleId, ruleTypeCategory } = opts;
|
||||
const maintenanceWindowClient = context.getMaintenanceWindowClientWithRequest(fakeRequest);
|
||||
return await withAlertingSpan('alerting:load-maintenance-windows', async () => {
|
||||
const {
|
||||
getMaintenanceWindowClientWithRequest,
|
||||
fakeRequest,
|
||||
logger,
|
||||
ruleTypeId,
|
||||
ruleId,
|
||||
ruleTypeCategory,
|
||||
} = opts;
|
||||
const maintenanceWindowClient = getMaintenanceWindowClientWithRequest(fakeRequest);
|
||||
|
||||
let activeMaintenanceWindows: MaintenanceWindow[] = [];
|
||||
try {
|
||||
activeMaintenanceWindows = await maintenanceWindowClient.getActiveMaintenanceWindows();
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`error getting active maintenance window for ${ruleTypeId}:${ruleId} ${err.message}`
|
||||
);
|
||||
}
|
||||
let activeMaintenanceWindows: MaintenanceWindow[] = [];
|
||||
try {
|
||||
activeMaintenanceWindows = await maintenanceWindowClient.getActiveMaintenanceWindows();
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`error getting active maintenance window for ${ruleTypeId}:${ruleId} ${err.message}`
|
||||
);
|
||||
}
|
||||
|
||||
const maintenanceWindows = activeMaintenanceWindows.filter(({ categoryIds }) => {
|
||||
// If category IDs array doesn't exist: allow all
|
||||
if (!Array.isArray(categoryIds)) {
|
||||
return true;
|
||||
}
|
||||
// If category IDs array exist: check category
|
||||
if ((categoryIds as string[]).includes(ruleTypeCategory)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const maintenanceWindows = activeMaintenanceWindows.filter(({ categoryIds }) => {
|
||||
// If category IDs array doesn't exist: allow all
|
||||
if (!Array.isArray(categoryIds)) {
|
||||
return true;
|
||||
}
|
||||
// If category IDs array exist: check category
|
||||
if ((categoryIds as string[]).includes(ruleTypeCategory)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return maintenanceWindows;
|
||||
});
|
||||
|
||||
return maintenanceWindows;
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { filterMaintenanceWindows, filterMaintenanceWindowsIds } from './get_maintenance_windows';
|
||||
export { MaintenanceWindowsService } from './maintenance_windows_service';
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const createMaintenanceWindowsServiceMock = () => {
|
||||
return jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getMaintenanceWindows: jest.fn(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const maintenanceWindowsServiceMock = {
|
||||
create: createMaintenanceWindowsServiceMock(),
|
||||
};
|
|
@ -0,0 +1,425 @@
|
|||
/*
|
||||
* 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 sinon from 'sinon';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
import { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { alertingEventLoggerMock } from '../../lib/alerting_event_logger/alerting_event_logger.mock';
|
||||
import { MaintenanceWindowsService } from './maintenance_windows_service';
|
||||
import { maintenanceWindowClientMock } from '../../maintenance_window_client.mock';
|
||||
import { getMockMaintenanceWindow } from '../../data/maintenance_window/test_helpers';
|
||||
import { MaintenanceWindowStatus } from '../../../common';
|
||||
import { MaintenanceWindowCategoryIds } from '../../../common/routes/maintenance_window/shared';
|
||||
import { FilterStateStore } from '@kbn/es-query';
|
||||
|
||||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
const logger = loggingSystemMock.createLogger();
|
||||
const maintenanceWindowClient = maintenanceWindowClientMock.create();
|
||||
let fakeTimer: sinon.SinonFakeTimers;
|
||||
|
||||
const maintenanceWindows = [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id1',
|
||||
},
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id2',
|
||||
},
|
||||
];
|
||||
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
getSavedObjectsClient: jest.fn(),
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
describe('MaintenanceWindowsService', () => {
|
||||
beforeAll(() => {
|
||||
fakeTimer = sinon.useFakeTimers(new Date('2023-02-27T08:15:00.000Z'));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fakeTimer.reset();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => fakeTimer.restore());
|
||||
|
||||
test('should load maintenance windows if none in cache', async () => {
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(maintenanceWindows);
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
// @ts-ignore - accessing private variable
|
||||
expect(maintenanceWindowsService.windows.get('default')).toBeUndefined();
|
||||
|
||||
const windows = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(1);
|
||||
|
||||
// @ts-ignore - accessing private variable
|
||||
expect(maintenanceWindowsService.windows.get('default')).toEqual({
|
||||
lastUpdated: 1677485700000,
|
||||
activeMaintenanceWindows: maintenanceWindows,
|
||||
});
|
||||
|
||||
expect(windows.maintenanceWindows).toEqual(maintenanceWindows);
|
||||
expect(windows.maintenanceWindowsWithoutScopedQueryIds).toEqual(['test-id1', 'test-id2']);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith([
|
||||
'test-id1',
|
||||
'test-id2',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return empty arrays if fetch settings errors and nothing in cache', async () => {
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockImplementationOnce(() => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
// @ts-ignore - accessing private variable
|
||||
expect(maintenanceWindowsService.windows.get('default')).toBeUndefined();
|
||||
|
||||
const windows = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(1);
|
||||
|
||||
// @ts-ignore - accessing private variable
|
||||
expect(maintenanceWindowsService.windows.get('default')).toBeUndefined();
|
||||
|
||||
expect(windows.maintenanceWindows).toEqual([]);
|
||||
expect(windows.maintenanceWindowsWithoutScopedQueryIds).toEqual([]);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should fetch maintenance windows per space', async () => {
|
||||
const newSpaceMW = [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-new-space-id2',
|
||||
},
|
||||
];
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(maintenanceWindows);
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(newSpaceMW);
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
// @ts-ignore - accessing private variable
|
||||
expect(maintenanceWindowsService.windows.get('default')).toBeUndefined();
|
||||
// @ts-ignore - accessing private variable
|
||||
expect(maintenanceWindowsService.windows.get('new-space')).toBeUndefined();
|
||||
|
||||
const windowsDefault = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
const windowsNewSpace = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'new-space',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(2);
|
||||
|
||||
// @ts-ignore - accessing private variable
|
||||
expect(maintenanceWindowsService.windows.get('default')).toEqual({
|
||||
lastUpdated: 1677485700000,
|
||||
activeMaintenanceWindows: maintenanceWindows,
|
||||
});
|
||||
// @ts-ignore - accessing private variable
|
||||
expect(maintenanceWindowsService.windows.get('new-space')).toEqual({
|
||||
lastUpdated: 1677485700000,
|
||||
activeMaintenanceWindows: newSpaceMW,
|
||||
});
|
||||
|
||||
expect(windowsDefault.maintenanceWindows).toEqual(maintenanceWindows);
|
||||
expect(windowsDefault.maintenanceWindowsWithoutScopedQueryIds).toEqual([
|
||||
'test-id1',
|
||||
'test-id2',
|
||||
]);
|
||||
|
||||
expect(windowsNewSpace.maintenanceWindows).toEqual(newSpaceMW);
|
||||
expect(windowsNewSpace.maintenanceWindowsWithoutScopedQueryIds).toEqual(['test-new-space-id2']);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith([
|
||||
'test-id1',
|
||||
'test-id2',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should use cached windows if cache has not expired', async () => {
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(maintenanceWindows);
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
|
||||
const windows1 = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
fakeTimer.tick(30000);
|
||||
const windows2 = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(windows1.maintenanceWindows).toEqual(maintenanceWindows);
|
||||
expect(windows1.maintenanceWindowsWithoutScopedQueryIds).toEqual(['test-id1', 'test-id2']);
|
||||
expect(windows1).toEqual(windows2);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith([
|
||||
'test-id1',
|
||||
'test-id2',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should refetch windows if cache has expired', async () => {
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(maintenanceWindows);
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce([
|
||||
maintenanceWindows[0],
|
||||
]);
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
|
||||
const windows1 = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
fakeTimer.tick(61000);
|
||||
const windows2 = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(windows1.maintenanceWindows).toEqual(maintenanceWindows);
|
||||
expect(windows1.maintenanceWindowsWithoutScopedQueryIds).toEqual(['test-id1', 'test-id2']);
|
||||
expect(windows2.maintenanceWindows).toEqual([maintenanceWindows[0]]);
|
||||
expect(windows2.maintenanceWindowsWithoutScopedQueryIds).toEqual(['test-id1']);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith([
|
||||
'test-id1',
|
||||
'test-id2',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return cached windows if refetching throws an error', async () => {
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(maintenanceWindows);
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockImplementationOnce(() => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
|
||||
const windows1 = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
fakeTimer.tick(61000);
|
||||
const windows2 = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'rule-category',
|
||||
});
|
||||
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(windows1.maintenanceWindows).toEqual(maintenanceWindows);
|
||||
expect(windows1.maintenanceWindowsWithoutScopedQueryIds).toEqual(['test-id1', 'test-id2']);
|
||||
expect(windows1).toEqual(windows2);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith([
|
||||
'test-id1',
|
||||
'test-id2',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should filter by rule category', async () => {
|
||||
const mw = [
|
||||
{
|
||||
...maintenanceWindows[0],
|
||||
categoryIds: ['observability', 'management'] as MaintenanceWindowCategoryIds,
|
||||
},
|
||||
maintenanceWindows[1],
|
||||
];
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(mw);
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
|
||||
const windows = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'securitySolution',
|
||||
});
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(windows.maintenanceWindows).toEqual([maintenanceWindows[1]]);
|
||||
expect(windows.maintenanceWindowsWithoutScopedQueryIds).toEqual(['test-id2']);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith(['test-id2']);
|
||||
});
|
||||
|
||||
test('should not call alertingEventLogger.setMaintenanceWindowIds if all maintenance windows have scoped queries', async () => {
|
||||
const mw = maintenanceWindows.map((window) => ({
|
||||
...window,
|
||||
scopedQuery: {
|
||||
kql: "_id: '1234'",
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'kibana.alert.action_group',
|
||||
field: 'kibana.alert.action_group',
|
||||
params: {
|
||||
query: 'test',
|
||||
},
|
||||
type: 'phrase',
|
||||
},
|
||||
$state: {
|
||||
store: FilterStateStore.APP_STATE,
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
'kibana.alert.action_group': 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(mw);
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
|
||||
const windows = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'securitySolution',
|
||||
});
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(windows.maintenanceWindows).toEqual(mw);
|
||||
expect(windows.maintenanceWindowsWithoutScopedQueryIds).toEqual([]);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should filter active maintenance windows on current time', async () => {
|
||||
const mw = [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
events: [
|
||||
{
|
||||
gte: '2023-02-27T00:00:00.000Z',
|
||||
lte: '2023-02-28T00:00:00.000Z',
|
||||
},
|
||||
{
|
||||
gte: '2023-03-01T00:00:00.000Z',
|
||||
lte: '2023-03-02T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id1',
|
||||
},
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
events: [
|
||||
{
|
||||
// maintenance window starts one minute in the future
|
||||
gte: '2023-02-27T08:16:00.000Z',
|
||||
lte: '2023-02-28T00:00:00.000Z',
|
||||
},
|
||||
],
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id2',
|
||||
},
|
||||
];
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(mw);
|
||||
const maintenanceWindowsService = new MaintenanceWindowsService({
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
logger,
|
||||
});
|
||||
|
||||
const windows = await maintenanceWindowsService.getMaintenanceWindows({
|
||||
request: fakeRequest,
|
||||
spaceId: 'default',
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleTypeCategory: 'securitySolution',
|
||||
});
|
||||
expect(maintenanceWindowClient.getActiveMaintenanceWindows).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(windows.maintenanceWindows).toEqual([mw[0]]);
|
||||
expect(windows.maintenanceWindowsWithoutScopedQueryIds).toEqual(['test-id1']);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 { KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import { MaintenanceWindow } from '../../application/maintenance_window/types';
|
||||
import { filterMaintenanceWindowsIds } from './get_maintenance_windows';
|
||||
import { MaintenanceWindowClientApi } from '../../types';
|
||||
import { AlertingEventLogger } from '../../lib/alerting_event_logger/alerting_event_logger';
|
||||
import { withAlertingSpan } from '../lib';
|
||||
|
||||
export const DEFAULT_CACHE_INTERVAL_MS = 60000; // 1 minute cache
|
||||
|
||||
interface MaintenanceWindowServiceOpts {
|
||||
cacheInterval?: number;
|
||||
getMaintenanceWindowClientWithRequest(request: KibanaRequest): MaintenanceWindowClientApi;
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
interface MaintenanceWindowData {
|
||||
maintenanceWindows: MaintenanceWindow[];
|
||||
maintenanceWindowsWithoutScopedQueryIds: string[];
|
||||
}
|
||||
|
||||
interface LoadMaintenanceWindowsOpts {
|
||||
request: KibanaRequest;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
type GetMaintenanceWindowsOpts = LoadMaintenanceWindowsOpts & {
|
||||
eventLogger: AlertingEventLogger;
|
||||
ruleTypeCategory: string;
|
||||
};
|
||||
|
||||
interface LastUpdatedWindows {
|
||||
lastUpdated: number;
|
||||
activeMaintenanceWindows: MaintenanceWindow[];
|
||||
}
|
||||
|
||||
export class MaintenanceWindowsService {
|
||||
private cacheIntervalMs = DEFAULT_CACHE_INTERVAL_MS;
|
||||
|
||||
private windows: Map<string, LastUpdatedWindows> = new Map();
|
||||
|
||||
constructor(private readonly options: MaintenanceWindowServiceOpts) {
|
||||
if (options.cacheInterval) {
|
||||
this.cacheIntervalMs = options.cacheInterval;
|
||||
}
|
||||
}
|
||||
|
||||
public async getMaintenanceWindows(
|
||||
opts: GetMaintenanceWindowsOpts
|
||||
): Promise<MaintenanceWindowData> {
|
||||
const activeMaintenanceWindows = await this.loadMaintenanceWindows({
|
||||
request: opts.request,
|
||||
spaceId: opts.spaceId,
|
||||
});
|
||||
|
||||
// Filter maintenance windows on current time
|
||||
const now = Date.now();
|
||||
const currentlyActiveMaintenanceWindows = activeMaintenanceWindows.filter((mw) => {
|
||||
return mw.events.some((event) => {
|
||||
return new Date(event.gte).getTime() <= now && new Date(event.lte).getTime;
|
||||
});
|
||||
});
|
||||
|
||||
// Only look at maintenance windows for this rule category
|
||||
const maintenanceWindows = currentlyActiveMaintenanceWindows.filter(({ categoryIds }) => {
|
||||
// If category IDs array doesn't exist: allow all
|
||||
if (!Array.isArray(categoryIds)) {
|
||||
return true;
|
||||
}
|
||||
// If category IDs array exist: check category
|
||||
if ((categoryIds as string[]).includes(opts.ruleTypeCategory)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Set the event log MW Id field the first time with MWs without scoped queries
|
||||
const maintenanceWindowsWithoutScopedQueryIds = filterMaintenanceWindowsIds({
|
||||
maintenanceWindows,
|
||||
withScopedQuery: false,
|
||||
});
|
||||
|
||||
if (maintenanceWindowsWithoutScopedQueryIds.length) {
|
||||
opts.eventLogger.setMaintenanceWindowIds(maintenanceWindowsWithoutScopedQueryIds);
|
||||
}
|
||||
|
||||
return { maintenanceWindows, maintenanceWindowsWithoutScopedQueryIds };
|
||||
}
|
||||
|
||||
private async loadMaintenanceWindows(
|
||||
opts: LoadMaintenanceWindowsOpts
|
||||
): Promise<MaintenanceWindow[]> {
|
||||
const now = Date.now();
|
||||
if (this.windows.has(opts.spaceId)) {
|
||||
const windowsFromLastUpdate = this.windows.get(opts.spaceId)!;
|
||||
const lastUpdated = new Date(windowsFromLastUpdate.lastUpdated).getTime();
|
||||
|
||||
if (now - lastUpdated >= this.cacheIntervalMs) {
|
||||
// cache expired, refetch settings
|
||||
try {
|
||||
return await this.fetchMaintenanceWindows(opts.request, opts.spaceId, now);
|
||||
} catch (err) {
|
||||
// return cached settings on error
|
||||
this.options.logger.debug(
|
||||
`Failed to fetch maintenance windows after cache expiration, using cached windows: ${err.message}`
|
||||
);
|
||||
return windowsFromLastUpdate.activeMaintenanceWindows;
|
||||
}
|
||||
} else {
|
||||
return windowsFromLastUpdate.activeMaintenanceWindows;
|
||||
}
|
||||
} else {
|
||||
// nothing in cache, fetch settings
|
||||
try {
|
||||
return await this.fetchMaintenanceWindows(opts.request, opts.spaceId, now);
|
||||
} catch (err) {
|
||||
// return default settings on error
|
||||
this.options.logger.debug(`Failed to fetch initial maintenance windows: ${err.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchMaintenanceWindows(
|
||||
request: KibanaRequest,
|
||||
spaceId: string,
|
||||
now: number
|
||||
): Promise<MaintenanceWindow[]> {
|
||||
return await withAlertingSpan('alerting:load-maintenance-windows', async () => {
|
||||
const maintenanceWindowClient = this.options.getMaintenanceWindowClientWithRequest(request);
|
||||
const activeMaintenanceWindows = await maintenanceWindowClient.getActiveMaintenanceWindows(
|
||||
this.cacheIntervalMs
|
||||
);
|
||||
this.windows.set(spaceId, {
|
||||
lastUpdated: now,
|
||||
activeMaintenanceWindows,
|
||||
});
|
||||
return activeMaintenanceWindows;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -29,11 +29,14 @@ import {
|
|||
TaskStatus,
|
||||
} from '@kbn/task-manager-plugin/server';
|
||||
import { getErrorSource } from '@kbn/task-manager-plugin/server/task_running';
|
||||
import { maintenanceWindowsServiceMock } from './maintenance_windows/maintenance_windows_service.mock';
|
||||
import { KibanaRequest } from '@kbn/core/server';
|
||||
|
||||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
const alertsClient = alertsClientMock.create();
|
||||
const dataViews = dataViewPluginMocks.createStartContract();
|
||||
const logger = loggingSystemMock.create().get();
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
const publicRuleMonitoringService = publicRuleMonitoringServiceMock.create();
|
||||
const publicRuleResultService = publicRuleResultServiceMock.create();
|
||||
const ruleRunMetricsStore = ruleRunMetricsStoreMock.create();
|
||||
|
@ -43,6 +46,22 @@ const wrappedScopedClusterClient = wrappedScopedClusterClientMock.create();
|
|||
const getDataViews = jest.fn().mockResolvedValue(dataViews);
|
||||
const getWrappedSearchSourceClient = jest.fn();
|
||||
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
getSavedObjectsClient: jest.fn(),
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
const timer = new TaskRunnerTimer({ logger });
|
||||
const ruleType: jest.Mocked<
|
||||
NormalizedRuleType<{}, {}, { foo: string }, {}, {}, 'default', 'recovered', {}>
|
||||
|
@ -162,8 +181,10 @@ describe('RuleTypeRunner', () => {
|
|||
const { state, error, stackTrace } = await ruleTypeRunner.run({
|
||||
context: {
|
||||
alertingEventLogger,
|
||||
maintenanceWindowsService,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -193,6 +214,7 @@ describe('RuleTypeRunner', () => {
|
|||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: expect.any(Function),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
savedObjectsClient,
|
||||
|
@ -247,14 +269,12 @@ describe('RuleTypeRunner', () => {
|
|||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalledWith([]);
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalled();
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).not.toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: true,
|
||||
});
|
||||
|
@ -269,6 +289,8 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
request: fakeRequest,
|
||||
maintenanceWindowsService,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -298,6 +320,7 @@ describe('RuleTypeRunner', () => {
|
|||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: expect.any(Function),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
savedObjectsClient,
|
||||
|
@ -352,14 +375,12 @@ describe('RuleTypeRunner', () => {
|
|||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalledWith([]);
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalled();
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).not.toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: true,
|
||||
});
|
||||
|
@ -377,6 +398,8 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
maintenanceWindowsService,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -413,14 +436,12 @@ describe('RuleTypeRunner', () => {
|
|||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalledWith([]);
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalled();
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith(['abc']);
|
||||
expect(alertsClient.logAlerts).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: true,
|
||||
});
|
||||
|
@ -438,6 +459,8 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
request: fakeRequest,
|
||||
maintenanceWindowsService,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -467,6 +490,7 @@ describe('RuleTypeRunner', () => {
|
|||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: expect.any(Function),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
savedObjectsClient,
|
||||
|
@ -537,6 +561,8 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
maintenanceWindowsService,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -566,6 +592,7 @@ describe('RuleTypeRunner', () => {
|
|||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: expect.any(Function),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
savedObjectsClient,
|
||||
|
@ -636,6 +663,8 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
maintenanceWindowsService,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -671,7 +700,9 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
maintenanceWindowsService,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
spaceId: 'default',
|
||||
|
@ -700,6 +731,7 @@ describe('RuleTypeRunner', () => {
|
|||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: expect.any(Function),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
savedObjectsClient,
|
||||
|
@ -758,13 +790,11 @@ describe('RuleTypeRunner', () => {
|
|||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalledWith([]);
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: true,
|
||||
});
|
||||
|
@ -783,7 +813,9 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
request: fakeRequest,
|
||||
ruleId: RULE_ID,
|
||||
maintenanceWindowsService,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
spaceId: 'default',
|
||||
|
@ -812,6 +844,7 @@ describe('RuleTypeRunner', () => {
|
|||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: expect.any(Function),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
savedObjectsClient,
|
||||
|
@ -870,13 +903,11 @@ describe('RuleTypeRunner', () => {
|
|||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalledWith([]);
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: true,
|
||||
});
|
||||
|
@ -895,6 +926,8 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
request: fakeRequest,
|
||||
maintenanceWindowsService,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -925,6 +958,7 @@ describe('RuleTypeRunner', () => {
|
|||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: expect.any(Function),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
savedObjectsClient,
|
||||
|
@ -976,7 +1010,6 @@ describe('RuleTypeRunner', () => {
|
|||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
@ -996,8 +1029,10 @@ describe('RuleTypeRunner', () => {
|
|||
context: {
|
||||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
request: fakeRequest,
|
||||
queryDelaySec: 0,
|
||||
ruleId: RULE_ID,
|
||||
maintenanceWindowsService,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
spaceId: 'default',
|
||||
|
@ -1027,6 +1062,7 @@ describe('RuleTypeRunner', () => {
|
|||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: expect.any(Function),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
savedObjectsClient,
|
||||
|
@ -1078,11 +1114,10 @@ describe('RuleTypeRunner', () => {
|
|||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalledWith([]);
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -1099,6 +1134,8 @@ describe('RuleTypeRunner', () => {
|
|||
alertingEventLogger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySec: 0,
|
||||
request: fakeRequest,
|
||||
maintenanceWindowsService,
|
||||
ruleId: RULE_ID,
|
||||
ruleLogPrefix: `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -1128,6 +1165,7 @@ describe('RuleTypeRunner', () => {
|
|||
services: {
|
||||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getMaintenanceWindowIds: expect.any(Function),
|
||||
getDataViews: expect.any(Function),
|
||||
ruleMonitoringService: publicRuleMonitoringService,
|
||||
ruleResultService: publicRuleResultService,
|
||||
|
@ -1180,13 +1218,11 @@ describe('RuleTypeRunner', () => {
|
|||
expect(ruleRunMetricsStore.setSearchMetrics).toHaveBeenCalled();
|
||||
expect(alertsClient.processAlerts).toHaveBeenCalledWith({
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: [],
|
||||
alertDelay: 0,
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalledWith([]);
|
||||
expect(alertsClient.persistAlerts).toHaveBeenCalled();
|
||||
expect(alertsClient.logAlerts).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: true,
|
||||
});
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
} from '@kbn/task-manager-plugin/server';
|
||||
import { getErrorSource } from '@kbn/task-manager-plugin/server/task_running';
|
||||
import { IAlertsClient } from '../alerts_client/types';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
import { ErrorWithReason } from '../lib';
|
||||
import { getTimeRange } from '../lib/get_time_range';
|
||||
import { NormalizedRuleType } from '../rule_type_registry';
|
||||
|
@ -89,8 +88,6 @@ interface RunOpts<
|
|||
nowDate?: string
|
||||
) => { dateStart: string; dateEnd: string };
|
||||
};
|
||||
maintenanceWindows?: MaintenanceWindow[];
|
||||
maintenanceWindowsWithoutScopedQueryIds?: string[];
|
||||
rule: RuleData<Params>;
|
||||
ruleType: NormalizedRuleType<
|
||||
Params,
|
||||
|
@ -147,8 +144,6 @@ export class RuleTypeRunner<
|
|||
alertsClient,
|
||||
executionId,
|
||||
executorServices,
|
||||
maintenanceWindows = [],
|
||||
maintenanceWindowsWithoutScopedQueryIds = [],
|
||||
rule,
|
||||
ruleType,
|
||||
startedAt,
|
||||
|
@ -221,6 +216,27 @@ export class RuleTypeRunner<
|
|||
services: {
|
||||
alertFactory: alertsClient.factory(),
|
||||
alertsClient: alertsClient.client(),
|
||||
getDataViews: executorServices.getDataViews,
|
||||
getMaintenanceWindowIds: async () => {
|
||||
if (context.maintenanceWindowsService) {
|
||||
const { maintenanceWindowsWithoutScopedQueryIds } =
|
||||
await context.maintenanceWindowsService.getMaintenanceWindows({
|
||||
eventLogger: context.alertingEventLogger,
|
||||
request: context.request,
|
||||
ruleTypeCategory: ruleType.category,
|
||||
spaceId: context.spaceId,
|
||||
});
|
||||
return maintenanceWindowsWithoutScopedQueryIds ?? [];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
getSearchSourceClient: async () => {
|
||||
if (!wrappedSearchSourceClient) {
|
||||
wrappedSearchSourceClient =
|
||||
await executorServices.getWrappedSearchSourceClient();
|
||||
}
|
||||
return wrappedSearchSourceClient.searchSourceClient;
|
||||
},
|
||||
ruleMonitoringService: executorServices.ruleMonitoringService,
|
||||
ruleResultService: executorServices.ruleResultService,
|
||||
savedObjectsClient: executorServices.savedObjectsClient,
|
||||
|
@ -230,14 +246,6 @@ export class RuleTypeRunner<
|
|||
shouldWriteAlerts: () =>
|
||||
this.shouldLogAndScheduleActionsForAlerts(ruleType.cancelAlertsOnRuleTimeout),
|
||||
uiSettingsClient: executorServices.uiSettingsClient,
|
||||
getDataViews: executorServices.getDataViews,
|
||||
getSearchSourceClient: async () => {
|
||||
if (!wrappedSearchSourceClient) {
|
||||
wrappedSearchSourceClient =
|
||||
await executorServices.getWrappedSearchSourceClient();
|
||||
}
|
||||
return wrappedSearchSourceClient.searchSourceClient;
|
||||
},
|
||||
},
|
||||
params: validatedParams,
|
||||
state: ruleTypeState as RuleState,
|
||||
|
@ -270,10 +278,6 @@ export class RuleTypeRunner<
|
|||
},
|
||||
logger: this.options.logger,
|
||||
flappingSettings: context.flappingSettings ?? DEFAULT_FLAPPING_SETTINGS,
|
||||
// passed in so the rule registry knows about maintenance windows
|
||||
...(maintenanceWindowsWithoutScopedQueryIds.length
|
||||
? { maintenanceWindowIds: maintenanceWindowsWithoutScopedQueryIds }
|
||||
: {}),
|
||||
getTimeRange: (timeWindow) =>
|
||||
getTimeRange({
|
||||
logger: this.options.logger,
|
||||
|
@ -333,9 +337,8 @@ export class RuleTypeRunner<
|
|||
|
||||
await withAlertingSpan('alerting:process-alerts', () =>
|
||||
this.options.timer.runWithTimer(TaskRunnerTimerSpan.ProcessAlerts, async () => {
|
||||
alertsClient.processAlerts({
|
||||
await alertsClient.processAlerts({
|
||||
flappingSettings: context.flappingSettings ?? DEFAULT_FLAPPING_SETTINGS,
|
||||
maintenanceWindowIds: maintenanceWindowsWithoutScopedQueryIds,
|
||||
alertDelay: alertDelay?.active ?? 0,
|
||||
ruleRunMetricsStore: context.ruleRunMetricsStore,
|
||||
});
|
||||
|
@ -344,9 +347,7 @@ export class RuleTypeRunner<
|
|||
|
||||
await withAlertingSpan('alerting:index-alerts-as-data', () =>
|
||||
this.options.timer.runWithTimer(TaskRunnerTimerSpan.PersistAlerts, async () => {
|
||||
const updateAlertsMaintenanceWindowResult = await alertsClient.persistAlerts(
|
||||
maintenanceWindows
|
||||
);
|
||||
const updateAlertsMaintenanceWindowResult = await alertsClient.persistAlerts();
|
||||
|
||||
// Set the event log MW ids again, this time including the ids that matched alerts with
|
||||
// scoped query
|
||||
|
@ -362,7 +363,6 @@ export class RuleTypeRunner<
|
|||
);
|
||||
|
||||
alertsClient.logAlerts({
|
||||
eventLogger: context.alertingEventLogger,
|
||||
ruleRunMetricsStore: context.ruleRunMetricsStore,
|
||||
shouldLogAlerts: this.shouldLogAndScheduleActionsForAlerts(
|
||||
ruleType.cancelAlertsOnRuleTimeout
|
||||
|
|
|
@ -83,12 +83,10 @@ import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_e
|
|||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
|
||||
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
|
||||
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
|
||||
import { getMockMaintenanceWindow } from '../data/maintenance_window/test_helpers';
|
||||
import { alertsClientMock } from '../alerts_client/alerts_client.mock';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../saved_objects';
|
||||
import { getErrorSource } from '@kbn/task-manager-plugin/server/task_running';
|
||||
import { RuleResultService } from '../monitoring/rule_result_service';
|
||||
|
@ -97,6 +95,8 @@ import { backfillClientMock } from '../backfill_client/backfill_client.mock';
|
|||
import { UntypedNormalizedRuleType } from '../rule_type_registry';
|
||||
import * as getExecutorServicesModule from './get_executor_services';
|
||||
import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
|
||||
import { maintenanceWindowsServiceMock } from './maintenance_windows/maintenance_windows_service.mock';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -107,7 +107,6 @@ jest.mock('../lib/wrap_scoped_cluster_client', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('../lib/alerting_event_logger/alerting_event_logger');
|
||||
|
||||
jest.mock('../monitoring/rule_result_service');
|
||||
|
||||
jest.spyOn(getExecutorServicesModule, 'getExecutorServices');
|
||||
|
@ -120,6 +119,7 @@ const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
|||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
const alertsClient = alertsClientMock.create();
|
||||
const ruleResultService = ruleResultServiceMock.create();
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
|
||||
describe('Task Runner', () => {
|
||||
let mockedTaskInstance: ConcreteTaskInstance;
|
||||
|
@ -157,7 +157,6 @@ describe('Task Runner', () => {
|
|||
getScriptedFieldsEnabled: jest.fn().mockReturnValue(true),
|
||||
} as DataViewsServerPluginStart;
|
||||
const alertsService = alertsServiceMock.create();
|
||||
const maintenanceWindowClient = maintenanceWindowClientMock.create();
|
||||
const connectorAdapterRegistry = new ConnectorAdapterRegistry();
|
||||
const rulesSettingsService = rulesSettingsServiceMock.create();
|
||||
|
||||
|
@ -181,12 +180,10 @@ describe('Task Runner', () => {
|
|||
encryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
executionContext: executionContextServiceMock.createInternalStartContract(),
|
||||
getMaintenanceWindowClientWithRequest: jest
|
||||
.fn()
|
||||
.mockReturnValue(maintenanceWindowClientMock.create()),
|
||||
getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
|
||||
kibanaBaseUrl: 'https://localhost:5601',
|
||||
logger,
|
||||
maintenanceWindowsService,
|
||||
maxAlerts: 1000,
|
||||
maxEphemeralActionsPerRule: 10,
|
||||
ruleTypeRegistry,
|
||||
|
@ -235,7 +232,6 @@ describe('Task Runner', () => {
|
|||
});
|
||||
savedObjectsService.getScopedClient.mockReturnValue(services.savedObjectsClient);
|
||||
elasticsearchService.client.asScoped.mockReturnValue(services.scopedClusterClient);
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValue([]);
|
||||
taskRunnerFactoryInitializerParams.getRulesClientWithRequest.mockReturnValue(rulesClient);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.getActionsClientWithRequest.mockResolvedValue(
|
||||
actionsClient
|
||||
|
@ -247,13 +243,14 @@ describe('Task Runner', () => {
|
|||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySettings: DEFAULT_QUERY_DELAY_SETTINGS,
|
||||
});
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockResolvedValue({
|
||||
maintenanceWindows: [],
|
||||
maintenanceWindowsWithoutScopedQueryIds: [],
|
||||
});
|
||||
ruleTypeRegistry.get.mockReturnValue(ruleType);
|
||||
taskRunnerFactoryInitializerParams.executionContext.withContext.mockImplementation((ctx, fn) =>
|
||||
fn()
|
||||
);
|
||||
taskRunnerFactoryInitializerParams.getMaintenanceWindowClientWithRequest.mockReturnValue(
|
||||
maintenanceWindowClient
|
||||
);
|
||||
mockedRuleTypeSavedObject.monitoring!.run.history = [];
|
||||
mockedRuleTypeSavedObject.monitoring!.run.calculated_metrics.success_ratio = 0;
|
||||
|
||||
|
@ -646,6 +643,21 @@ describe('Task Runner', () => {
|
|||
);
|
||||
|
||||
test('skips alert notification if there are active maintenance windows', async () => {
|
||||
const mw = [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
id: 'test-id-1',
|
||||
} as MaintenanceWindow,
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
id: 'test-id-2',
|
||||
} as MaintenanceWindow,
|
||||
];
|
||||
const maintenanceWindowIds = ['test-id-1', 'test-id-2'];
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockResolvedValue({
|
||||
maintenanceWindows: mw,
|
||||
maintenanceWindowsWithoutScopedQueryIds: maintenanceWindowIds,
|
||||
});
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
ruleType.executor.mockImplementation(
|
||||
|
@ -672,29 +684,16 @@ describe('Task Runner', () => {
|
|||
});
|
||||
expect(AlertingEventLogger).toHaveBeenCalledTimes(1);
|
||||
rulesClient.getAlertFromRaw.mockReturnValue(mockedRuleTypeSavedObject as Rule);
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce([
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
id: 'test-id-1',
|
||||
} as MaintenanceWindow,
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
id: 'test-id-2',
|
||||
} as MaintenanceWindow,
|
||||
]);
|
||||
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(mockedRawRuleSO);
|
||||
await taskRunner.run();
|
||||
expect(actionsClient.ephemeralEnqueuedExecution).toHaveBeenCalledTimes(0);
|
||||
|
||||
const maintenanceWindowIds = ['test-id-1', 'test-id-2'];
|
||||
|
||||
testAlertingEventLogCalls({
|
||||
activeAlerts: 1,
|
||||
newAlerts: 1,
|
||||
status: 'active',
|
||||
logAlert: 2,
|
||||
maintenanceWindowIds,
|
||||
});
|
||||
expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
|
@ -719,6 +718,18 @@ describe('Task Runner', () => {
|
|||
});
|
||||
|
||||
test('skips alert notification if active maintenance window contains the rule type category', async () => {
|
||||
const mw = [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
categoryIds: ['test'] as unknown as MaintenanceWindow['categoryIds'],
|
||||
id: 'test-id-1',
|
||||
} as MaintenanceWindow,
|
||||
];
|
||||
const maintenanceWindowIds = ['test-id-1'];
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockResolvedValue({
|
||||
maintenanceWindows: mw,
|
||||
maintenanceWindowsWithoutScopedQueryIds: maintenanceWindowIds,
|
||||
});
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
ruleType.executor.mockImplementation(
|
||||
|
@ -745,26 +756,16 @@ describe('Task Runner', () => {
|
|||
});
|
||||
expect(AlertingEventLogger).toHaveBeenCalledTimes(1);
|
||||
rulesClient.getAlertFromRaw.mockReturnValue(mockedRuleTypeSavedObject as Rule);
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce([
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
categoryIds: ['test'] as unknown as MaintenanceWindow['categoryIds'],
|
||||
id: 'test-id-1',
|
||||
} as MaintenanceWindow,
|
||||
]);
|
||||
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(mockedRawRuleSO);
|
||||
await taskRunner.run();
|
||||
expect(actionsClient.ephemeralEnqueuedExecution).toHaveBeenCalledTimes(0);
|
||||
|
||||
const maintenanceWindowIds = ['test-id-1'];
|
||||
|
||||
testAlertingEventLogCalls({
|
||||
activeAlerts: 1,
|
||||
newAlerts: 1,
|
||||
status: 'active',
|
||||
logAlert: 2,
|
||||
maintenanceWindowIds,
|
||||
});
|
||||
expect(alertingEventLogger.logAlert).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
|
@ -788,6 +789,10 @@ describe('Task Runner', () => {
|
|||
});
|
||||
|
||||
test('allows alert notification if active maintenance window does not contain the rule type category', async () => {
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockResolvedValue({
|
||||
maintenanceWindows: [],
|
||||
maintenanceWindowsWithoutScopedQueryIds: [],
|
||||
});
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
ruleType.executor.mockImplementation(
|
||||
|
@ -814,13 +819,6 @@ describe('Task Runner', () => {
|
|||
});
|
||||
expect(AlertingEventLogger).toHaveBeenCalledTimes(1);
|
||||
rulesClient.getAlertFromRaw.mockReturnValue(mockedRuleTypeSavedObject as Rule);
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce([
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
categoryIds: ['something-else'] as unknown as MaintenanceWindow['categoryIds'],
|
||||
id: 'test-id-1',
|
||||
} as MaintenanceWindow,
|
||||
]);
|
||||
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(mockedRawRuleSO);
|
||||
await taskRunner.run();
|
||||
|
@ -855,67 +853,6 @@ describe('Task Runner', () => {
|
|||
expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should update alerts with maintenance window if scoped query matches said alerts', async () => {
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
|
||||
ruleType.executor.mockImplementation(async () => {
|
||||
return { state: {} };
|
||||
});
|
||||
|
||||
const mockMaintenanceWindows = [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
categoryIds: ['test'] as unknown as MaintenanceWindow['categoryIds'],
|
||||
id: 'test-id-1',
|
||||
} as unknown as MaintenanceWindow,
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
categoryIds: ['test'] as unknown as MaintenanceWindow['categoryIds'],
|
||||
scopedQuery: {
|
||||
kql: "kibana.alert.rule.name: 'test123'",
|
||||
filters: [],
|
||||
dsl: '{"bool":{"must":[],"filter":[{"bool":{"should":[{"match_phrase":{"kibana.alert.rule.name":"test123"}}],"minimum_should_match":1}}],"should":[],"must_not":[]}}',
|
||||
},
|
||||
id: 'test-id-2',
|
||||
} as unknown as MaintenanceWindow,
|
||||
];
|
||||
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValueOnce(
|
||||
mockMaintenanceWindows
|
||||
);
|
||||
|
||||
alertsClient.persistAlerts.mockResolvedValue({
|
||||
alertIds: [],
|
||||
maintenanceWindowIds: ['test-id-1', 'test-id-2'],
|
||||
});
|
||||
|
||||
alertsService.createAlertsClient.mockImplementation(() => alertsClient);
|
||||
|
||||
const taskRunner = new TaskRunner({
|
||||
ruleType,
|
||||
taskInstance: mockedTaskInstance,
|
||||
context: taskRunnerFactoryInitializerParams,
|
||||
inMemoryMetrics,
|
||||
internalSavedObjectsRepository,
|
||||
});
|
||||
|
||||
expect(AlertingEventLogger).toHaveBeenCalledTimes(1);
|
||||
rulesClient.getAlertFromRaw.mockReturnValue(mockedRuleTypeSavedObject as Rule);
|
||||
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(mockedRawRuleSO);
|
||||
await taskRunner.run();
|
||||
expect(actionsClient.ephemeralEnqueuedExecution).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(alertsClient.persistAlerts).toHaveBeenLastCalledWith(mockMaintenanceWindows);
|
||||
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith(['test-id-1']);
|
||||
expect(alertingEventLogger.setMaintenanceWindowIds).toHaveBeenCalledWith([
|
||||
'test-id-1',
|
||||
'test-id-2',
|
||||
]);
|
||||
});
|
||||
|
||||
test.each(ephemeralTestParams)(
|
||||
'skips firing actions for active alert if alert is muted %s',
|
||||
async (nameExtension, customTaskRunnerFactoryInitializerParams, enqueueFunction) => {
|
||||
|
|
|
@ -64,8 +64,6 @@ import { RuleMonitoringService } from '../monitoring/rule_monitoring_service';
|
|||
import { lastRunToRaw } from '../lib/last_run_status';
|
||||
import { RuleRunningHandler } from './rule_running_handler';
|
||||
import { RuleResultService } from '../monitoring/rule_result_service';
|
||||
import { MaintenanceWindow } from '../application/maintenance_window/types';
|
||||
import { filterMaintenanceWindowsIds, getMaintenanceWindows } from './get_maintenance_windows';
|
||||
import { RuleTypeRunner } from './rule_type_runner';
|
||||
import { initializeAlertsClient } from '../alerts_client';
|
||||
import { createTaskRunnerLogger, withAlertingSpan, processRunResults } from './lib';
|
||||
|
@ -136,8 +134,6 @@ export class TaskRunner<
|
|||
private ruleMonitoring: RuleMonitoringService;
|
||||
private ruleRunning: RuleRunningHandler;
|
||||
private ruleResult: RuleResultService;
|
||||
private maintenanceWindows: MaintenanceWindow[] = [];
|
||||
private maintenanceWindowsWithoutScopedQueryIds: string[] = [];
|
||||
private ruleTypeRunner: RuleTypeRunner<
|
||||
Params,
|
||||
ExtractedParams,
|
||||
|
@ -299,8 +295,10 @@ export class TaskRunner<
|
|||
const ruleTypeRunnerContext = {
|
||||
alertingEventLogger: this.alertingEventLogger,
|
||||
flappingSettings,
|
||||
maintenanceWindowsService: this.context.maintenanceWindowsService,
|
||||
namespace: this.context.spaceIdToNamespace(spaceId),
|
||||
queryDelaySec: queryDelaySettings.delay,
|
||||
request: fakeRequest,
|
||||
ruleId,
|
||||
ruleLogPrefix: ruleLabel,
|
||||
ruleRunMetricsStore,
|
||||
|
@ -359,8 +357,6 @@ export class TaskRunner<
|
|||
alertsClient,
|
||||
executionId: this.executionId,
|
||||
executorServices,
|
||||
maintenanceWindows: this.maintenanceWindows,
|
||||
maintenanceWindowsWithoutScopedQueryIds: this.maintenanceWindowsWithoutScopedQueryIds,
|
||||
rule,
|
||||
ruleType: this.ruleType,
|
||||
startedAt: this.taskInstance.startedAt!,
|
||||
|
@ -526,30 +522,6 @@ export class TaskRunner<
|
|||
// Set rule monitoring data
|
||||
this.ruleMonitoring.setMonitoring(runRuleParams.rule.monitoring);
|
||||
|
||||
// Load the maintenance windows
|
||||
this.maintenanceWindows = await withAlertingSpan('alerting:load-maintenance-windows', () =>
|
||||
getMaintenanceWindows({
|
||||
context: this.context,
|
||||
fakeRequest: runRuleParams.fakeRequest,
|
||||
logger: this.logger,
|
||||
ruleTypeId: this.ruleType.id,
|
||||
ruleId,
|
||||
ruleTypeCategory: this.ruleType.category,
|
||||
})
|
||||
);
|
||||
|
||||
// Set the event log MW Id field the first time with MWs without scoped queries
|
||||
this.maintenanceWindowsWithoutScopedQueryIds = filterMaintenanceWindowsIds({
|
||||
maintenanceWindows: this.maintenanceWindows,
|
||||
withScopedQuery: false,
|
||||
});
|
||||
|
||||
if (this.maintenanceWindowsWithoutScopedQueryIds.length) {
|
||||
this.alertingEventLogger.setMaintenanceWindowIds(
|
||||
this.maintenanceWindowsWithoutScopedQueryIds
|
||||
);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await runRuleParams.rulesClient.clearExpiredSnoozes({
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
AlertInstanceContext,
|
||||
Rule,
|
||||
RuleAlertData,
|
||||
MaintenanceWindowStatus,
|
||||
DEFAULT_FLAPPING_SETTINGS,
|
||||
DEFAULT_QUERY_DELAY_SETTINGS,
|
||||
} from '../types';
|
||||
|
@ -57,7 +58,6 @@ import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_e
|
|||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
|
||||
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
|
||||
import { UntypedNormalizedRuleType } from '../rule_type_registry';
|
||||
import { alertsClientMock } from '../alerts_client/alerts_client.mock';
|
||||
|
@ -104,6 +104,8 @@ import {
|
|||
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
|
||||
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
|
||||
import { createTaskRunnerLogger } from './lib';
|
||||
import { maintenanceWindowsServiceMock } from './maintenance_windows/maintenance_windows_service.mock';
|
||||
import { getMockMaintenanceWindow } from '../data/maintenance_window/test_helpers';
|
||||
import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
|
@ -124,6 +126,7 @@ const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
|||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
const alertingEventLogger = alertingEventLoggerMock.create();
|
||||
const clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
|
||||
const ruleTypeWithAlerts: jest.Mocked<UntypedNormalizedRuleType> = {
|
||||
...ruleType,
|
||||
|
@ -181,7 +184,6 @@ describe('Task Runner', () => {
|
|||
const mockAlertsClient = alertsClientMock.create();
|
||||
const mockLegacyAlertsClient = legacyAlertsClientMock.create();
|
||||
const ruleRunMetricsStore = ruleRunMetricsStoreMock.create();
|
||||
const maintenanceWindowClient = maintenanceWindowClientMock.create();
|
||||
const connectorAdapterRegistry = new ConnectorAdapterRegistry();
|
||||
const elasticsearchAndSOAvailability$ = new Subject<boolean>();
|
||||
|
||||
|
@ -205,10 +207,10 @@ describe('Task Runner', () => {
|
|||
encryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
executionContext: executionContextServiceMock.createInternalStartContract(),
|
||||
getMaintenanceWindowClientWithRequest: jest.fn().mockReturnValue(maintenanceWindowClient),
|
||||
getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
|
||||
kibanaBaseUrl: 'https://localhost:5601',
|
||||
logger,
|
||||
maintenanceWindowsService,
|
||||
maxAlerts: 1000,
|
||||
maxEphemeralActionsPerRule: 10,
|
||||
ruleTypeRegistry,
|
||||
|
@ -236,7 +238,6 @@ describe('Task Runner', () => {
|
|||
});
|
||||
savedObjectsService.getScopedClient.mockReturnValue(services.savedObjectsClient);
|
||||
elasticsearchService.client.asScoped.mockReturnValue(services.scopedClusterClient);
|
||||
maintenanceWindowClient.getActiveMaintenanceWindows.mockResolvedValue([]);
|
||||
taskRunnerFactoryInitializerParams.getRulesClientWithRequest.mockReturnValue(rulesClient);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.getActionsClientWithRequest.mockResolvedValue(
|
||||
actionsClient
|
||||
|
@ -252,14 +253,31 @@ describe('Task Runner', () => {
|
|||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySettings: DEFAULT_QUERY_DELAY_SETTINGS,
|
||||
});
|
||||
taskRunnerFactoryInitializerParams.getMaintenanceWindowClientWithRequest.mockReturnValue(
|
||||
maintenanceWindowClient
|
||||
);
|
||||
mockedRuleTypeSavedObject.monitoring!.run.history = [];
|
||||
mockedRuleTypeSavedObject.monitoring!.run.calculated_metrics.success_ratio = 0;
|
||||
|
||||
alertingEventLogger.getStartAndDuration.mockImplementation(() => ({ start: new Date() }));
|
||||
(AlertingEventLogger as jest.Mock).mockImplementation(() => alertingEventLogger);
|
||||
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockReturnValue({
|
||||
maintenanceWindows: [
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id1',
|
||||
},
|
||||
{
|
||||
...getMockMaintenanceWindow(),
|
||||
eventStartTime: new Date().toISOString(),
|
||||
eventEndTime: new Date().toISOString(),
|
||||
status: MaintenanceWindowStatus.Running,
|
||||
id: 'test-id2',
|
||||
},
|
||||
],
|
||||
maintenanceWindowsWithoutScopedQueryIds: ['test-id1', 'test-id2'],
|
||||
});
|
||||
logger.get.mockImplementation(() => logger);
|
||||
ruleType.executor.mockResolvedValue({ state: {} });
|
||||
});
|
||||
|
@ -309,8 +327,12 @@ describe('Task Runner', () => {
|
|||
await taskRunner.run();
|
||||
|
||||
expect(mockAlertsService.createAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
logger: taskRunnerLogger,
|
||||
maintenanceWindowsService,
|
||||
request: expect.any(Object),
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
spaceId: 'default',
|
||||
namespace: 'default',
|
||||
rule: {
|
||||
alertDelay: 0,
|
||||
|
@ -588,7 +610,7 @@ describe('Task Runner', () => {
|
|||
[ALERT_FLAPPING_HISTORY]: [true],
|
||||
[ALERT_INSTANCE_ID]: '1',
|
||||
[ALERT_SEVERITY_IMPROVING]: false,
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: [],
|
||||
[ALERT_MAINTENANCE_WINDOW_IDS]: ['test-id1', 'test-id2'],
|
||||
[ALERT_RULE_CATEGORY]: 'My test rule',
|
||||
[ALERT_RULE_CONSUMER]: 'bar',
|
||||
[ALERT_RULE_EXECUTION_UUID]: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -666,8 +688,12 @@ describe('Task Runner', () => {
|
|||
{ tags: ['1', 'test'] }
|
||||
);
|
||||
expect(LegacyAlertsClientModule.LegacyAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
request: expect.any(Object),
|
||||
logger: taskRunnerLogger,
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
maintenanceWindowsService,
|
||||
spaceId: 'default',
|
||||
});
|
||||
|
||||
testCorrectAlertsClientUsed({
|
||||
|
@ -753,8 +779,12 @@ describe('Task Runner', () => {
|
|||
expect(mockAlertsService.createAlertsClient).not.toHaveBeenCalled();
|
||||
expect(logger.error).not.toHaveBeenCalled();
|
||||
expect(LegacyAlertsClientModule.LegacyAlertsClient).toHaveBeenCalledWith({
|
||||
alertingEventLogger,
|
||||
request: expect.any(Object),
|
||||
spaceId: 'default',
|
||||
logger: taskRunnerLogger,
|
||||
ruleType: ruleTypeWithAlerts,
|
||||
maintenanceWindowsService,
|
||||
});
|
||||
|
||||
testCorrectAlertsClientUsed({
|
||||
|
@ -844,12 +874,10 @@ describe('Task Runner', () => {
|
|||
lookBackWindow: 20,
|
||||
statusChangeThreshold: 4,
|
||||
},
|
||||
maintenanceWindowIds: [],
|
||||
ruleRunMetricsStore,
|
||||
});
|
||||
|
||||
expect(alertsClientToUse.logAlerts).toHaveBeenCalledWith({
|
||||
eventLogger: alertingEventLogger,
|
||||
ruleRunMetricsStore,
|
||||
shouldLogAlerts: true,
|
||||
});
|
||||
|
|
|
@ -56,7 +56,6 @@ import { EVENT_LOG_ACTIONS } from '../plugin';
|
|||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
|
||||
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
|
||||
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
|
||||
import { RULE_SAVED_OBJECT_TYPE } from '../saved_objects';
|
||||
|
@ -64,6 +63,7 @@ import { TaskRunnerContext } from './types';
|
|||
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
|
||||
import { UntypedNormalizedRuleType } from '../rule_type_registry';
|
||||
import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
|
||||
import { maintenanceWindowsServiceMock } from './maintenance_windows/maintenance_windows_service.mock';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
|
||||
|
@ -85,6 +85,7 @@ const dataViewsMock = {
|
|||
getScriptedFieldsEnabled: jest.fn().mockReturnValue(true),
|
||||
} as DataViewsServerPluginStart;
|
||||
const alertsService = alertsServiceMock.create();
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
|
||||
describe('Task Runner Cancel', () => {
|
||||
let mockedTaskInstance: ConcreteTaskInstance;
|
||||
|
@ -140,12 +141,10 @@ describe('Task Runner Cancel', () => {
|
|||
encryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
executionContext: executionContextServiceMock.createInternalStartContract(),
|
||||
getMaintenanceWindowClientWithRequest: jest
|
||||
.fn()
|
||||
.mockReturnValue(maintenanceWindowClientMock.create()),
|
||||
getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
|
||||
kibanaBaseUrl: 'https://localhost:5601',
|
||||
logger,
|
||||
maintenanceWindowsService,
|
||||
maxAlerts: 1000,
|
||||
maxEphemeralActionsPerRule: 10,
|
||||
ruleTypeRegistry,
|
||||
|
@ -187,11 +186,11 @@ describe('Task Runner Cancel', () => {
|
|||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
queryDelaySettings: DEFAULT_QUERY_DELAY_SETTINGS,
|
||||
});
|
||||
taskRunnerFactoryInitializerParams.getMaintenanceWindowClientWithRequest.mockReturnValue(
|
||||
maintenanceWindowClientMock.create()
|
||||
);
|
||||
rulesClient.getAlertFromRaw.mockReturnValue(mockedRuleTypeSavedObject as Rule);
|
||||
|
||||
maintenanceWindowsService.getMaintenanceWindows.mockReturnValue({
|
||||
maintenanceWindows: [],
|
||||
maintenanceWindowsWithoutScopedQueryIds: [],
|
||||
});
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(mockedRawRuleSO);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
|
|
|
@ -28,17 +28,18 @@ import { inMemoryMetricsMock } from '../monitoring/in_memory_metrics.mock';
|
|||
import { SharePluginStart } from '@kbn/share-plugin/server';
|
||||
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { maintenanceWindowClientMock } from '../maintenance_window_client.mock';
|
||||
import { alertsServiceMock } from '../alerts_service/alerts_service.mock';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
|
||||
import { TaskRunnerContext } from './types';
|
||||
import { backfillClientMock } from '../backfill_client/backfill_client.mock';
|
||||
import { rulesSettingsServiceMock } from '../rules_settings/rules_settings_service.mock';
|
||||
import { maintenanceWindowsServiceMock } from './maintenance_windows/maintenance_windows_service.mock';
|
||||
|
||||
const inMemoryMetrics = inMemoryMetricsMock.create();
|
||||
const backfillClient = backfillClientMock.create();
|
||||
const rulesSettingsService = rulesSettingsServiceMock.create();
|
||||
const maintenanceWindowsService = maintenanceWindowsServiceMock.create();
|
||||
const executionContext = executionContextServiceMock.createSetupContract();
|
||||
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
|
||||
const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test');
|
||||
|
@ -117,12 +118,10 @@ describe('Task Runner Factory', () => {
|
|||
encryptedSavedObjectsClient: encryptedSavedObjectsPlugin.getClient(),
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
executionContext,
|
||||
getMaintenanceWindowClientWithRequest: jest
|
||||
.fn()
|
||||
.mockReturnValue(maintenanceWindowClientMock.create()),
|
||||
getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient),
|
||||
kibanaBaseUrl: 'https://localhost:5601',
|
||||
logger: loggingSystemMock.create().get(),
|
||||
maintenanceWindowsService,
|
||||
maxAlerts: 1000,
|
||||
maxEphemeralActionsPerRule: 10,
|
||||
ruleTypeRegistry: ruleTypeRegistryMock.create(),
|
||||
|
|
|
@ -45,7 +45,6 @@ import { ActionsConfigMap } from '../lib/get_actions_config_map';
|
|||
import { NormalizedRuleType } from '../rule_type_registry';
|
||||
import {
|
||||
CombinedSummarizedAlerts,
|
||||
MaintenanceWindowClientApi,
|
||||
RawRule,
|
||||
RulesClientApi,
|
||||
RuleTypeRegistry,
|
||||
|
@ -57,6 +56,7 @@ import { BackfillClient } from '../backfill_client/backfill_client';
|
|||
import { ElasticsearchError } from '../lib';
|
||||
import { ConnectorAdapterRegistry } from '../connector_adapters/connector_adapter_registry';
|
||||
import { RulesSettingsService } from '../rules_settings';
|
||||
import { MaintenanceWindowsService } from './maintenance_windows';
|
||||
|
||||
export interface RuleTaskRunResult {
|
||||
state: RuleTaskState;
|
||||
|
@ -140,8 +140,10 @@ export type Executable<
|
|||
export interface RuleTypeRunnerContext {
|
||||
alertingEventLogger: AlertingEventLogger;
|
||||
flappingSettings?: RulesSettingsFlappingProperties;
|
||||
maintenanceWindowsService?: MaintenanceWindowsService;
|
||||
namespace?: string;
|
||||
queryDelaySec?: number;
|
||||
request: KibanaRequest;
|
||||
ruleId: string;
|
||||
ruleLogPrefix: string;
|
||||
ruleRunMetricsStore: RuleRunMetricsStore;
|
||||
|
@ -167,10 +169,10 @@ export interface TaskRunnerContext {
|
|||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
eventLogger: IEventLogger;
|
||||
executionContext: ExecutionContextStart;
|
||||
getMaintenanceWindowClientWithRequest(request: KibanaRequest): MaintenanceWindowClientApi;
|
||||
getRulesClientWithRequest(request: KibanaRequest): RulesClientApi;
|
||||
kibanaBaseUrl: string | undefined;
|
||||
logger: Logger;
|
||||
maintenanceWindowsService: MaintenanceWindowsService;
|
||||
maxAlerts: number;
|
||||
maxEphemeralActionsPerRule: number;
|
||||
ruleTypeRegistry: RuleTypeRegistry;
|
||||
|
|
|
@ -102,27 +102,28 @@ export interface RuleExecutorServices<
|
|||
ActionGroupIds extends string = never,
|
||||
AlertData extends RuleAlertData = RuleAlertData
|
||||
> {
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
uiSettingsClient: IUiSettingsClient;
|
||||
scopedClusterClient: IScopedClusterClient;
|
||||
/**
|
||||
* Only available when framework alerts are enabled and rule
|
||||
* type has registered alert context with the framework with shouldWrite set to true
|
||||
*/
|
||||
alertsClient: PublicAlertsClient<AlertData, State, Context, ActionGroupIds> | null;
|
||||
/**
|
||||
* Deprecate alertFactory and remove when all rules are onboarded to
|
||||
* the alertsClient
|
||||
* @deprecated
|
||||
*/
|
||||
alertFactory: PublicAlertFactory<State, Context, ActionGroupIds>;
|
||||
/**
|
||||
* Only available when framework alerts are enabled and rule
|
||||
* type has registered alert context with the framework with shouldWrite set to true
|
||||
*/
|
||||
alertsClient: PublicAlertsClient<AlertData, State, Context, ActionGroupIds> | null;
|
||||
shouldWriteAlerts: () => boolean;
|
||||
shouldStopExecution: () => boolean;
|
||||
ruleMonitoringService?: PublicRuleMonitoringService;
|
||||
share: SharePluginStart;
|
||||
ruleResultService?: PublicRuleResultService;
|
||||
getDataViews: () => Promise<DataViewsContract>;
|
||||
getMaintenanceWindowIds: () => Promise<string[]>;
|
||||
getSearchSourceClient: () => Promise<ISearchStartSearchSource>;
|
||||
ruleMonitoringService?: PublicRuleMonitoringService;
|
||||
ruleResultService?: PublicRuleResultService;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
scopedClusterClient: IScopedClusterClient;
|
||||
share: SharePluginStart;
|
||||
shouldStopExecution: () => boolean;
|
||||
shouldWriteAlerts: () => boolean;
|
||||
uiSettingsClient: IUiSettingsClient;
|
||||
}
|
||||
|
||||
export interface RuleExecutorOptions<
|
||||
|
@ -145,7 +146,6 @@ export interface RuleExecutorOptions<
|
|||
state: State;
|
||||
namespace?: string;
|
||||
flappingSettings: RulesSettingsFlappingProperties;
|
||||
maintenanceWindowIds?: string[];
|
||||
getTimeRange: (timeWindow?: string) => GetTimeRangeResult;
|
||||
}
|
||||
|
||||
|
|
|
@ -181,6 +181,7 @@ describe('BurnRateRuleExecutor', () => {
|
|||
shouldStopExecution: jest.fn(),
|
||||
share: {} as SharePluginStart,
|
||||
getDataViews: jest.fn().mockResolvedValue(dataViewPluginMocks.createStartContract()),
|
||||
getMaintenanceWindowIds: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -1130,6 +1130,7 @@ describe('createLifecycleExecutor', () => {
|
|||
it('updates documents with maintenance window ids for newly firing alerts', async () => {
|
||||
const logger = loggerMock.create();
|
||||
const ruleDataClientMock = createRuleDataClientMock();
|
||||
|
||||
const executor = createLifecycleExecutor(
|
||||
logger,
|
||||
ruleDataClientMock
|
||||
|
@ -1151,7 +1152,6 @@ describe('createLifecycleExecutor', () => {
|
|||
params: {},
|
||||
state: { wrapped: initialRuleState, trackedAlerts: {}, trackedAlertsRecovered: {} },
|
||||
logger,
|
||||
maintenanceWindowIds,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -1288,7 +1288,6 @@ describe('createLifecycleExecutor', () => {
|
|||
trackedAlertsRecovered: {},
|
||||
},
|
||||
logger,
|
||||
maintenanceWindowIds,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -1420,7 +1419,6 @@ describe('createLifecycleExecutor', () => {
|
|||
trackedAlertsRecovered: {},
|
||||
},
|
||||
logger,
|
||||
maintenanceWindowIds,
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -130,10 +130,9 @@ export const createLifecycleExecutor =
|
|||
>
|
||||
): Promise<{ state: WrappedLifecycleRuleState<State> }> => {
|
||||
const {
|
||||
services: { alertFactory, shouldWriteAlerts },
|
||||
services: { alertFactory, getMaintenanceWindowIds, shouldWriteAlerts },
|
||||
state: previousState,
|
||||
flappingSettings,
|
||||
maintenanceWindowIds,
|
||||
rule,
|
||||
} = options;
|
||||
|
||||
|
@ -217,6 +216,11 @@ export const createLifecycleExecutor =
|
|||
`[Rule Registry] Tracking ${allAlertIds.length} alerts (${newAlertIds.length} new, ${trackedAlertStates.length} previous)`
|
||||
);
|
||||
|
||||
// load maintenance window ids if there are new alerts
|
||||
const maintenanceWindowIds: string[] = allAlertIds.length
|
||||
? await getMaintenanceWindowIds()
|
||||
: [];
|
||||
|
||||
interface TrackedAlertData {
|
||||
indexName: string;
|
||||
fields: Partial<ParsedTechnicalFields & ParsedExperimentalFields>;
|
||||
|
|
|
@ -137,6 +137,7 @@ function createRule(shouldWriteAlerts: boolean = true) {
|
|||
savedObjectsClient: {} as any,
|
||||
scopedClusterClient: {} as any,
|
||||
search: {} as any,
|
||||
getMaintenanceWindowIds: async () => [],
|
||||
getSearchSourceClient: async () => ({} as ISearchStartSearchSource),
|
||||
shouldStopExecution: () => false,
|
||||
shouldWriteAlerts: () => shouldWriteAlerts,
|
||||
|
|
|
@ -51,7 +51,7 @@ export type BackendAlertWithSuppressionFields870<T> = Omit<
|
|||
|
||||
export const ALERT_GROUP_INDEX = `${ALERT_NAMESPACE}.group.index` as const;
|
||||
|
||||
const augmentAlerts = <T>({
|
||||
const augmentAlerts = async <T>({
|
||||
alerts,
|
||||
options,
|
||||
kibanaVersion,
|
||||
|
@ -65,6 +65,9 @@ const augmentAlerts = <T>({
|
|||
intendedTimestamp: Date | undefined;
|
||||
}) => {
|
||||
const commonRuleFields = getCommonAlertFields(options);
|
||||
const maintenanceWindowIds: string[] =
|
||||
alerts.length > 0 ? await options.services.getMaintenanceWindowIds() : [];
|
||||
|
||||
const currentDate = new Date();
|
||||
const timestampOverrideOrCurrent = currentTimeOverride ?? currentDate;
|
||||
return alerts.map((alert) => {
|
||||
|
@ -78,8 +81,8 @@ const augmentAlerts = <T>({
|
|||
? intendedTimestamp
|
||||
: timestampOverrideOrCurrent,
|
||||
[VERSION]: kibanaVersion,
|
||||
...(options?.maintenanceWindowIds?.length
|
||||
? { [ALERT_MAINTENANCE_WINDOW_IDS]: options.maintenanceWindowIds }
|
||||
...(maintenanceWindowIds.length
|
||||
? { [ALERT_MAINTENANCE_WINDOW_IDS]: maintenanceWindowIds }
|
||||
: {}),
|
||||
...commonRuleFields,
|
||||
...alert._source,
|
||||
|
@ -311,7 +314,7 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper
|
|||
intendedTimestamp = options.startedAt;
|
||||
}
|
||||
|
||||
const augmentedAlerts = augmentAlerts({
|
||||
const augmentedAlerts = await augmentAlerts({
|
||||
alerts: enrichedAlerts,
|
||||
options,
|
||||
kibanaVersion: ruleDataClient.kibanaVersion,
|
||||
|
@ -575,7 +578,7 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper
|
|||
alertsWereTruncated = true;
|
||||
}
|
||||
|
||||
const augmentedAlerts = augmentAlerts({
|
||||
const augmentedAlerts = await augmentAlerts({
|
||||
alerts: enrichedAlerts,
|
||||
options,
|
||||
kibanaVersion: ruleDataClient.kibanaVersion,
|
||||
|
|
|
@ -39,7 +39,6 @@ export const createDefaultAlertExecutorOptions = <
|
|||
startedAt = new Date(),
|
||||
updatedAt = new Date(),
|
||||
shouldWriteAlerts = true,
|
||||
maintenanceWindowIds,
|
||||
}: {
|
||||
alertId?: string;
|
||||
ruleName?: string;
|
||||
|
@ -50,7 +49,6 @@ export const createDefaultAlertExecutorOptions = <
|
|||
startedAt?: Date;
|
||||
updatedAt?: Date;
|
||||
shouldWriteAlerts?: boolean;
|
||||
maintenanceWindowIds?: string[];
|
||||
}): RuleExecutorOptions<Params, State, InstanceState, InstanceContext, ActionGroupIds> => ({
|
||||
startedAt,
|
||||
startedAtOverridden: false,
|
||||
|
@ -78,17 +76,18 @@ export const createDefaultAlertExecutorOptions = <
|
|||
params,
|
||||
spaceId: 'SPACE_ID',
|
||||
services: {
|
||||
alertsClient: null,
|
||||
alertFactory: alertsMock.createRuleExecutorServices<InstanceState, InstanceContext>()
|
||||
.alertFactory,
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
uiSettingsClient: uiSettingsServiceMock.createClient(),
|
||||
scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
|
||||
shouldWriteAlerts: () => shouldWriteAlerts,
|
||||
shouldStopExecution: () => false,
|
||||
getSearchSourceClient: async () => searchSourceCommonMock,
|
||||
share: {} as SharePluginStart,
|
||||
alertsClient: null,
|
||||
getDataViews: async () => dataViewPluginMocks.createStartContract(),
|
||||
getMaintenanceWindowIds: async () => ['test-id-1', 'test-id-2'],
|
||||
getSearchSourceClient: async () => searchSourceCommonMock,
|
||||
savedObjectsClient: savedObjectsClientMock.create(),
|
||||
scopedClusterClient: elasticsearchServiceMock.createScopedClusterClient(),
|
||||
share: {} as SharePluginStart,
|
||||
shouldStopExecution: () => false,
|
||||
shouldWriteAlerts: () => shouldWriteAlerts,
|
||||
uiSettingsClient: uiSettingsServiceMock.createClient(),
|
||||
},
|
||||
state,
|
||||
previousStartedAt: null,
|
||||
|
@ -96,7 +95,6 @@ export const createDefaultAlertExecutorOptions = <
|
|||
executionId: 'b33f65d7-6e8b-4aae-8d20-c93613deb33f',
|
||||
logger,
|
||||
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
|
||||
...(maintenanceWindowIds ? { maintenanceWindowIds } : {}),
|
||||
getTimeRange: () => {
|
||||
const date = new Date(Date.now()).toISOString();
|
||||
return { dateStart: date, dateEnd: date };
|
||||
|
|
|
@ -300,6 +300,7 @@ export const previewRulesRoute = (
|
|||
abortController,
|
||||
searchSourceClient,
|
||||
}),
|
||||
getMaintenanceWindowIds: async () => [],
|
||||
uiSettingsClient: coreContext.uiSettings.client,
|
||||
getDataViews: async () => dataViewsService,
|
||||
share,
|
||||
|
|
|
@ -105,6 +105,7 @@ export const createRuleTypeMocks = (
|
|||
alertWithPersistence: jest.fn(),
|
||||
logger: loggerMock,
|
||||
shouldWriteAlerts: () => true,
|
||||
getMaintenanceWindowIds: jest.fn().mockResolvedValue([]),
|
||||
getDataViews: jest.fn().mockResolvedValue({
|
||||
createDataViewLazy: jest.fn().mockResolvedValue({
|
||||
getFields: jest.fn().mockResolvedValue({
|
||||
|
|
|
@ -15,6 +15,8 @@ export const END_DATE = '2020-01-01T00:00:00Z';
|
|||
export const DOCUMENT_SOURCE = 'queryDataEndpointTests';
|
||||
export const DOCUMENT_REFERENCE = '-na-';
|
||||
|
||||
export const TEST_CACHE_EXPIRATION_TIME = 10000;
|
||||
|
||||
export async function createEsDocuments(
|
||||
es: Client,
|
||||
esTestIndexTool: ESTestIndexTool,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import moment from 'moment';
|
||||
import expect from '@kbn/expect';
|
||||
import { get } from 'lodash';
|
||||
import { setTimeout as setTimeoutAsync } from 'timers/promises';
|
||||
import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server';
|
||||
import { RuleNotifyWhen } from '@kbn/alerting-plugin/common';
|
||||
import { ES_TEST_INDEX_NAME, ESTestIndexTool } from '@kbn/alerting-api-integration-helpers';
|
||||
|
@ -21,6 +22,7 @@ import {
|
|||
resetRulesSettings,
|
||||
} from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
import { TEST_CACHE_EXPIRATION_TIME } from '../create_test_data';
|
||||
|
||||
const InstanceActions = new Set<string | undefined>([
|
||||
'new-instance',
|
||||
|
@ -1682,6 +1684,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
.expect(200);
|
||||
objectRemover.add(space.id, window3.id, 'rules/maintenance_window', 'alerting', true);
|
||||
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -1742,13 +1747,21 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
const actionsToCheck = [
|
||||
'new-instance',
|
||||
'active-instance',
|
||||
'recovered-instance',
|
||||
'execute',
|
||||
];
|
||||
const executeEvents = events.filter((event) => event?.event?.action === 'execute');
|
||||
|
||||
// the first execute event should not have any maintenance window ids because there were no alerts during the
|
||||
// first execution
|
||||
for (let i = 0; i < executeEvents.length; i++) {
|
||||
if (i === 0) {
|
||||
expect(executeEvents[i]?.kibana?.alert?.maintenance_window_ids).to.be(undefined);
|
||||
} else {
|
||||
const alertMaintenanceWindowIds =
|
||||
executeEvents[i]?.kibana?.alert?.maintenance_window_ids?.sort();
|
||||
expect(alertMaintenanceWindowIds).eql([window1.id, window2.id].sort());
|
||||
}
|
||||
}
|
||||
|
||||
const actionsToCheck = ['new-instance', 'active-instance', 'recovered-instance'];
|
||||
events.forEach((event) => {
|
||||
if (actionsToCheck.includes(event?.event?.action || '')) {
|
||||
const alertMaintenanceWindowIds =
|
||||
|
@ -1775,6 +1788,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
.expect(200);
|
||||
objectRemover.add(space.id, window.id, 'rules/maintenance_window', 'alerting', true);
|
||||
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
const { body: createdAction } = await supertest
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -1857,6 +1873,9 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('should generate expected events with a alertDelay', async () => {
|
||||
// wait so cache expires so maintenance window from previous test will be cleared
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
const ACTIVE_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.active';
|
||||
const NEW_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.new';
|
||||
const RECOVERED_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.recovered';
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import { setTimeout as setTimeoutAsync } from 'timers/promises';
|
||||
import { Spaces } from '../../../scenarios';
|
||||
import {
|
||||
getUrlPrefix,
|
||||
|
@ -17,6 +17,7 @@ import {
|
|||
getEventLog,
|
||||
} from '../../../../common/lib';
|
||||
import { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
import { TEST_CACHE_EXPIRATION_TIME } from '../create_test_data';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function createGetAlertSummaryTests({ getService }: FtrProviderContext) {
|
||||
|
@ -308,6 +309,9 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo
|
|||
true
|
||||
);
|
||||
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
// pattern of when the rule should fire
|
||||
const pattern = {
|
||||
alertA: [true, true, true, true],
|
||||
|
@ -384,6 +388,9 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo
|
|||
describe('legacy', function () {
|
||||
this.tags('skipFIPS');
|
||||
it('handles multi-alert status', async () => {
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
// pattern of when the alert should fire
|
||||
const pattern = {
|
||||
alertA: [true, true, true, true],
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import moment from 'moment';
|
||||
import { setTimeout as setTimeoutAsync } from 'timers/promises';
|
||||
import type { RetryService } from '@kbn/ftr-common-functional-services';
|
||||
import type { IValidatedEvent } from '@kbn/event-log-plugin/server';
|
||||
import type { Agent as SuperTestAgent } from 'supertest';
|
||||
|
@ -13,6 +14,7 @@ import expect from '@kbn/expect';
|
|||
import type { FtrProviderContext } from '../../../../common/ftr_provider_context';
|
||||
import { getUrlPrefix, getTestRuleData, ObjectRemover, getEventLog } from '../../../../common/lib';
|
||||
import { Spaces } from '../../../scenarios';
|
||||
import { TEST_CACHE_EXPIRATION_TIME } from '../create_test_data';
|
||||
|
||||
export const createRule = async ({
|
||||
actionId,
|
||||
|
@ -109,6 +111,9 @@ export const createMaintenanceWindow = async ({
|
|||
.expect(200);
|
||||
|
||||
objectRemover.add(Spaces.space1.id, window.id, 'rules/maintenance_window', 'alerting', true);
|
||||
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
return window;
|
||||
};
|
||||
|
||||
|
@ -128,12 +133,15 @@ export const finishMaintenanceWindow = async ({
|
|||
id: string;
|
||||
supertest: SuperTestAgent;
|
||||
}) => {
|
||||
return supertest
|
||||
await supertest
|
||||
.post(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/maintenance_window/${id}/_finish`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(200);
|
||||
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
};
|
||||
|
||||
export const getRuleEvents = async ({
|
||||
|
|
|
@ -48,6 +48,7 @@ import {
|
|||
ObjectRemover,
|
||||
TaskManagerDoc,
|
||||
} from '../../../../../common/lib';
|
||||
import { TEST_CACHE_EXPIRATION_TIME } from '../../create_test_data';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function createAlertsAsDataInstallResourcesTest({ getService }: FtrProviderContext) {
|
||||
|
@ -89,7 +90,7 @@ export default function createAlertsAsDataInstallResourcesTest({ getService }: F
|
|||
it(`should write alert docs during rule execution with flapping.enabled: ${enableFlapping}`, async () => {
|
||||
await setFlappingSettings(enableFlapping);
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(10000);
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
const pattern = {
|
||||
alertA: [true, true, true], // stays active across executions
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
ObjectRemover,
|
||||
TaskManagerDoc,
|
||||
} from '../../../../../common/lib';
|
||||
import { TEST_CACHE_EXPIRATION_TIME } from '../../create_test_data';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function createAlertsAsDataFlappingTest({ getService }: FtrProviderContext) {
|
||||
|
@ -59,7 +60,7 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
})
|
||||
.expect(200);
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(10000);
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
const pattern = {
|
||||
alertA: [true, false, false, true, false, true, false, true, false].concat(
|
||||
|
@ -192,7 +193,7 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
})
|
||||
.expect(200);
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(10000);
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
const pattern = {
|
||||
alertA: [true, false, false, true, false, true, false, true, false, true].concat(
|
||||
|
@ -322,7 +323,7 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
})
|
||||
.expect(200);
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(10000);
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
const pattern = {
|
||||
alertA: [true, false, true, false, false, false, false, false, false],
|
||||
|
@ -382,7 +383,7 @@ export default function createAlertsAsDataFlappingTest({ getService }: FtrProvid
|
|||
})
|
||||
.expect(200);
|
||||
// wait so cache expires
|
||||
await setTimeoutAsync(10000);
|
||||
await setTimeoutAsync(TEST_CACHE_EXPIRATION_TIME);
|
||||
|
||||
const pattern = {
|
||||
alertA: [true, false, false, true, false, true, false, true, false].concat(
|
||||
|
|
|
@ -200,6 +200,7 @@ export default function createLifecycleExecutorApiTest({ getService }: FtrProvid
|
|||
services: {
|
||||
alertFactory: getMockAlertFactory(),
|
||||
shouldWriteAlerts: sinon.stub().returns(true),
|
||||
getMaintenanceWindowIds: async () => [],
|
||||
},
|
||||
flappingSettings: {
|
||||
enabled: false,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue