Skip to content

Commit

Permalink
[RAM][Maintenance Window] Maintenance window scoped query task manage…
Browse files Browse the repository at this point in the history
…r changes (#172252)

## Summary
Resolves: #164255

This is part 3/3 of the maintenance window scoped query PR. This change
contains only the task manager changes and has no dependency on other
PRs. To test the changes in this PR, I recommend using this branch
#172117 which has all of the
frontend changes and the changes in this PR.

This PR adds support for maintenance window scoped query in the task
manager. To do this, we need to perform a fetch on the new persisted
alerts with the scoped query as filters. We then must save these alerts
again with the update maintenance window IDs.

### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
JiaweiWu and kibanamachine authored Dec 4, 2023
1 parent 155d20b commit 75e34f6
Show file tree
Hide file tree
Showing 23 changed files with 1,602 additions and 308 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ const createAlertsClientMock = () => {
return {
initializeExecution: jest.fn(),
processAndLogAlerts: jest.fn(),
processAlerts: jest.fn(),
logAlerts: jest.fn(),
updateAlertMaintenanceWindowIds: jest.fn(),
getMaintenanceWindowScopedQueryAlerts: jest.fn(),
updateAlertsMaintenanceWindowIdByScopedQuery: jest.fn(),
getTrackedAlerts: jest.fn(),
getProcessedAlerts: jest.fn(),
getAlertsToSerialize: jest.fn(),
Expand Down
262 changes: 261 additions & 1 deletion x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/
import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks';
import type { UpdateByQueryRequest } from '@elastic/elasticsearch/lib/api/types';
import { UntypedNormalizedRuleType } from '../rule_type_registry';
import {
AlertsFilter,
Expand Down Expand Up @@ -46,7 +47,11 @@ import * as LegacyAlertsClientModule from './legacy_alerts_client';
import { LegacyAlertsClient } from './legacy_alerts_client';
import { Alert } from '../alert/alert';
import { AlertsClient, AlertsClientParams } from './alerts_client';
import { GetSummarizedAlertsParams, ProcessAndLogAlertsOpts } from './types';
import {
GetSummarizedAlertsParams,
ProcessAndLogAlertsOpts,
GetMaintenanceWindowScopedQueryAlertsParams,
} from './types';
import { legacyAlertsClientMock } from './legacy_alerts_client.mock';
import { keys, range } from 'lodash';
import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock';
Expand All @@ -58,9 +63,12 @@ import {
getExpectedQueryByTimeRange,
getParamsByExecutionUuid,
getParamsByTimeQuery,
getParamsByMaintenanceWindowScopedQuery,
getParamsByUpdateMaintenanceWindowIds,
mockAAD,
} from './alerts_client_fixtures';
import { getDataStreamAdapter } from '../alerts_service/lib/data_stream_adapter';
import { MaintenanceWindow } from '../application/maintenance_window/types';

const date = '2023-03-28T22:27:28.159Z';
const startedAtDate = '2023-03-28T13:00:00.000Z';
Expand Down Expand Up @@ -1666,6 +1674,258 @@ describe('Alerts Client', () => {
});
});

describe('getMaintenanceWindowScopedQueryAlerts', () => {
const alertWithMwId1 = {
...mockAAD,
_id: 'alert_id_1',
_source: {
...mockAAD._source,
[ALERT_UUID]: 'alert_id_1',
[ALERT_MAINTENANCE_WINDOW_IDS]: ['mw1', 'mw2'],
},
};

const alertWithMwId2 = {
...mockAAD,
_id: 'alert_id_2',
_source: {
...mockAAD._source,
[ALERT_UUID]: 'alert_id_2',
[ALERT_MAINTENANCE_WINDOW_IDS]: ['mw1'],
},
};

beforeEach(() => {
clusterClient.search.mockReturnValueOnce({
// @ts-ignore
hits: { total: { value: 2 }, hits: [alertWithMwId1, alertWithMwId2] },
aggregations: {
mw1: {
doc_count: 2,
alertId: {
hits: {
hits: [
{
_id: 'alert_id_1',
_source: { [ALERT_UUID]: 'alert_id_1' },
},
{
_id: 'alert_id_2',
_source: { [ALERT_UUID]: 'alert_id_2' },
},
],
},
},
},
mw2: {
doc_count: 1,
alertId: {
hits: {
hits: [
{
_id: 'alert_id_1',
_source: { [ALERT_UUID]: 'alert_id_1' },
},
],
},
},
},
},
});
});

test('should get the persistent lifecycle alerts affected by scoped query successfully', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
// @ts-ignore
const result = await alertsClient.getMaintenanceWindowScopedQueryAlerts(
getParamsByMaintenanceWindowScopedQuery
);

expect(result).toEqual({
mw1: ['alert_id_1', 'alert_id_2'],
mw2: ['alert_id_1'],
});
});

test('should get the persistent continual alerts affected by scoped query successfully', async () => {
const alertsClient = new AlertsClient({
...alertsClientParams,
ruleType: {
...alertsClientParams.ruleType,
autoRecoverAlerts: false,
},
});
// @ts-ignore
const result = await alertsClient.getMaintenanceWindowScopedQueryAlerts(
getParamsByMaintenanceWindowScopedQuery
);

expect(result).toEqual({
mw1: ['alert_id_1', 'alert_id_2'],
mw2: ['alert_id_1'],
});
});

test('should throw if ruleId is not specified', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
const { ruleId, ...paramsWithoutRuleId } = getParamsByMaintenanceWindowScopedQuery;

await expect(
// @ts-ignore
alertsClient.getMaintenanceWindowScopedQueryAlerts(
paramsWithoutRuleId as GetMaintenanceWindowScopedQueryAlertsParams
)
).rejects.toThrowError(
'Must specify rule ID, space ID, and executionUuid for scoped query AAD alert query.'
);
});

test('should throw if spaceId is not specified', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
const { spaceId, ...paramsWithoutRuleId } = getParamsByMaintenanceWindowScopedQuery;

await expect(
// @ts-ignore
alertsClient.getMaintenanceWindowScopedQueryAlerts(
paramsWithoutRuleId as GetMaintenanceWindowScopedQueryAlertsParams
)
).rejects.toThrowError(
'Must specify rule ID, space ID, and executionUuid for scoped query AAD alert query.'
);
});

test('should throw if executionUuid is not specified', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
const { executionUuid, ...paramsWithoutRuleId } = getParamsByMaintenanceWindowScopedQuery;

await expect(
// @ts-ignore
alertsClient.getMaintenanceWindowScopedQueryAlerts(
paramsWithoutRuleId as GetMaintenanceWindowScopedQueryAlertsParams
)
).rejects.toThrowError(
'Must specify rule ID, space ID, and executionUuid for scoped query AAD alert query.'
);
});
});

describe('updateAlertMaintenanceWindowIds', () => {
test('should update alerts with new maintenance window Ids', async () => {
const alertsClient = new AlertsClient(alertsClientParams);

const alert1 = new Alert('1', { meta: { maintenanceWindowIds: ['mw1'] } });
const alert2 = new Alert('2', { meta: { maintenanceWindowIds: ['mw1', 'mw2'] } });
const alert3 = new Alert('3', { meta: { maintenanceWindowIds: ['mw2', 'mw3'] } });

jest.spyOn(LegacyAlertsClient.prototype, 'getProcessedAlerts').mockReturnValueOnce({
'1': alert1,
'2': alert2,
'3': alert3,
});

// @ts-ignore
await alertsClient.updateAlertMaintenanceWindowIds([
alert1.getUuid(),
alert2.getUuid(),
alert3.getUuid(),
]);

const params = clusterClient.updateByQuery.mock.calls[0][0] as UpdateByQueryRequest;

expect(params.query).toEqual({
terms: {
_id: [alert1.getUuid(), alert2.getUuid(), alert3.getUuid()],
},
});

expect(params.script).toEqual({
source: expect.anything(),
lang: 'painless',
params: {
[alert1.getUuid()]: ['mw1'],
[alert2.getUuid()]: ['mw1', 'mw2'],
[alert3.getUuid()]: ['mw2', 'mw3'],
},
});
});

test('should call warn if ES errors', async () => {
clusterClient.updateByQuery.mockRejectedValueOnce('something went wrong!');
const alertsClient = new AlertsClient(alertsClientParams);

const alert1 = new Alert('1', { meta: { maintenanceWindowIds: ['mw1'] } });

jest.spyOn(LegacyAlertsClient.prototype, 'getProcessedAlerts').mockReturnValueOnce({
'1': alert1,
});

await expect(
// @ts-ignore
alertsClient.updateAlertMaintenanceWindowIds([alert1.getUuid()])
).rejects.toBe('something went wrong!');

expect(logger.warn).toHaveBeenCalledWith(
'Error updating alert maintenance window IDs: something went wrong!'
);
});
});

describe('updateAlertsMaintenanceWindowIdByScopedQuery', () => {
test('should update alerts with MW ids when provided with maintenance windows', async () => {
const alertsClient = new AlertsClient(alertsClientParams);

const alert1 = new Alert('1');
const alert2 = new Alert('2');
const alert3 = new Alert('3');
const alert4 = new Alert('4');

jest.spyOn(LegacyAlertsClient.prototype, 'getProcessedAlerts').mockReturnValueOnce({
'1': alert1,
'2': alert2,
'3': alert3,
'4': alert4,
});

jest
// @ts-ignore
.spyOn(AlertsClient.prototype, 'getMaintenanceWindowScopedQueryAlerts')
// @ts-ignore
.mockResolvedValueOnce({
mw1: [alert1.getUuid(), alert2.getUuid()],
mw2: [alert3.getUuid()],
});

const updateSpy = jest
// @ts-ignore
.spyOn(AlertsClient.prototype, 'updateAlertMaintenanceWindowIds')
// @ts-ignore
.mockResolvedValueOnce({});

const result = await alertsClient.updateAlertsMaintenanceWindowIdByScopedQuery({
...getParamsByUpdateMaintenanceWindowIds,
maintenanceWindows: [
...getParamsByUpdateMaintenanceWindowIds.maintenanceWindows,
{ id: 'mw3' } as unknown as MaintenanceWindow,
],
});

expect(alert1.getMaintenanceWindowIds()).toEqual(['mw3', 'mw1']);
expect(alert2.getMaintenanceWindowIds()).toEqual(['mw3', 'mw1']);
expect(alert3.getMaintenanceWindowIds()).toEqual(['mw3', 'mw2']);

expect(result).toEqual({
alertIds: [alert1.getUuid(), alert2.getUuid(), alert3.getUuid()],
maintenanceWindowIds: ['mw3', 'mw1', 'mw2'],
});

expect(updateSpy).toHaveBeenLastCalledWith([
alert1.getUuid(),
alert2.getUuid(),
alert3.getUuid(),
]);
});
});

describe('report()', () => {
test('should create legacy alert with id, action group', async () => {
const mockGetUuidCurrent = jest
Expand Down
Loading

0 comments on commit 75e34f6

Please sign in to comment.