Skip to content

Commit

Permalink
Merge branch 'main' into add-session-leader-table
Browse files Browse the repository at this point in the history
  • Loading branch information
opauloh authored Mar 25, 2022
2 parents a4effb0 + 32ac1a5 commit 23be142
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 17 deletions.
4 changes: 4 additions & 0 deletions x-pack/plugins/alerting/server/rules_client/rules_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2116,12 +2116,15 @@ export class RulesClient {
executionStatus,
schedule,
actions,
snoozeEndTime,
...partialRawRule
}: Partial<RawRule>,
references: SavedObjectReference[] | undefined,
includeLegacyId: boolean = false,
excludeFromPublicApi: boolean = false
): PartialRule<Params> | PartialRuleWithLegacyId<Params> {
const snoozeEndTimeDate = snoozeEndTime != null ? new Date(snoozeEndTime) : snoozeEndTime;
const includeSnoozeEndTime = snoozeEndTimeDate !== undefined && !excludeFromPublicApi;
const rule = {
id,
notifyWhen,
Expand All @@ -2131,6 +2134,7 @@ export class RulesClient {
schedule: schedule as IntervalSchedule,
actions: actions ? this.injectReferencesIntoActions(id, actions, references || []) : [],
params: this.injectReferencesIntoParams(id, ruleType, params, references || []) as Params,
...(includeSnoozeEndTime ? { snoozeEndTime: snoozeEndTimeDate } : {}),
...(updatedAt ? { updatedAt: new Date(updatedAt) } : {}),
...(createdAt ? { createdAt: new Date(createdAt) } : {}),
...(scheduledTaskId ? { scheduledTaskId } : {}),
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/server/task_runner/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const RULE_TYPE_ID = 'test';
export const DATE_1969 = '1969-12-31T00:00:00.000Z';
export const DATE_1970 = '1970-01-01T00:00:00.000Z';
export const DATE_1970_5_MIN = '1969-12-31T23:55:00.000Z';
export const DATE_9999 = '9999-12-31T12:34:56.789Z';
export const MOCK_DURATION = 86400000000000;

export const SAVED_OBJECT = {
Expand Down
66 changes: 65 additions & 1 deletion x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
DATE_1969,
DATE_1970,
DATE_1970_5_MIN,
DATE_9999,
} from './fixtures';
import { EVENT_LOG_ACTIONS } from '../plugin';
import { IN_MEMORY_METRICS } from '../monitoring';
Expand Down Expand Up @@ -414,7 +415,7 @@ describe('Task Runner', () => {
);
expect(logger.debug).nthCalledWith(
3,
`no scheduling of actions for rule test:1: '${RULE_NAME}': rule is muted.`
`no scheduling of actions for rule test:1: '${RULE_NAME}': rule is snoozed.`
);
expect(logger.debug).nthCalledWith(
4,
Expand Down Expand Up @@ -468,6 +469,69 @@ describe('Task Runner', () => {
expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled();
});

type SnoozeTestParams = [
muteAll: boolean,
snoozeEndTime: string | undefined | null,
shouldBeSnoozed: boolean
];

const snoozeTestParams: SnoozeTestParams[] = [
[false, null, false],
[false, undefined, false],
[false, DATE_1970, false],
[false, DATE_9999, true],
[true, null, true],
[true, undefined, true],
[true, DATE_1970, true],
[true, DATE_9999, true],
];

test.each(snoozeTestParams)(
'snoozing works as expected with muteAll: %s; snoozeEndTime: %s',
async (muteAll, snoozeEndTime, shouldBeSnoozed) => {
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
ruleType.executor.mockImplementation(
async ({
services: executorServices,
}: AlertExecutorOptions<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
string
>) => {
executorServices.alertFactory.create('1').scheduleActions('default');
}
);
const taskRunner = new TaskRunner(
ruleType,
mockedTaskInstance,
taskRunnerFactoryInitializerParams,
inMemoryMetrics
);
rulesClient.get.mockResolvedValue({
...mockedRuleTypeSavedObject,
muteAll,
snoozeEndTime: snoozeEndTime != null ? new Date(snoozeEndTime) : snoozeEndTime,
});
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(SAVED_OBJECT);
await taskRunner.run();

const expectedExecutions = shouldBeSnoozed ? 0 : 1;
expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(expectedExecutions);
expect(actionsClient.ephemeralEnqueuedExecution).toHaveBeenCalledTimes(0);

const logger = taskRunnerFactoryInitializerParams.logger;
const expectedMessage = `no scheduling of actions for rule test:1: '${RULE_NAME}': rule is snoozed.`;
if (expectedExecutions) {
expect(logger.debug).not.toHaveBeenCalledWith(expectedMessage);
} else {
expect(logger.debug).toHaveBeenCalledWith(expectedMessage);
}
}
);

test.each(ephemeralTestParams)(
'skips firing actions for active alert if alert is muted %s',
async (nameExtension, customTaskRunnerFactoryInitializerParams, enqueueFunction) => {
Expand Down
20 changes: 16 additions & 4 deletions x-pack/plugins/alerting/server/task_runner/task_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ export class TaskRunner<
}
}

private isRuleSnoozed(rule: SanitizedAlert<Params>): boolean {
if (rule.muteAll) {
return true;
}

if (rule.snoozeEndTime == null) {
return false;
}

return Date.now() < rule.snoozeEndTime.getTime();
}

private shouldLogAndScheduleActionsForAlerts() {
// if execution hasn't been cancelled, return true
if (!this.cancelled) {
Expand Down Expand Up @@ -312,7 +324,6 @@ export class TaskRunner<
schedule,
throttle,
notifyWhen,
muteAll,
mutedInstanceIds,
name,
tags,
Expand Down Expand Up @@ -484,7 +495,8 @@ export class TaskRunner<
triggeredActionsStatus: ActionsCompletion.COMPLETE,
};

if (!muteAll && this.shouldLogAndScheduleActionsForAlerts()) {
const ruleIsSnoozed = this.isRuleSnoozed(rule);
if (!ruleIsSnoozed && this.shouldLogAndScheduleActionsForAlerts()) {
const mutedAlertIdsSet = new Set(mutedInstanceIds);

const alertsWithExecutableActions = Object.entries(alertsWithScheduledActions).filter(
Expand Down Expand Up @@ -535,8 +547,8 @@ export class TaskRunner<
alertExecutionStore,
});
} else {
if (muteAll) {
this.logger.debug(`no scheduling of actions for rule ${ruleLabel}: rule is muted.`);
if (ruleIsSnoozed) {
this.logger.debug(`no scheduling of actions for rule ${ruleLabel}: rule is snoozed.`);
}
if (!this.shouldLogAndScheduleActionsForAlerts()) {
this.logger.debug(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC
loadTestFile(require.resolve('./notify_when'));
loadTestFile(require.resolve('./ephemeral'));
loadTestFile(require.resolve('./event_log_alerts'));
loadTestFile(require.resolve('./snooze'));
loadTestFile(require.resolve('./scheduled_task_id'));
// Do not place test files here, due to https://github.com/elastic/kibana/issues/123059

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
getUrlPrefix,
getTestRuleData,
ObjectRemover,
getEventLog,
} from '../../../common/lib';

const FUTURE_SNOOZE_TIME = '9999-12-31T06:00:00.000Z';
Expand All @@ -22,6 +23,8 @@ const FUTURE_SNOOZE_TIME = '9999-12-31T06:00:00.000Z';
export default function createSnoozeRuleTests({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const log = getService('log');
const retry = getService('retry');

describe('snooze', () => {
const objectRemover = new ObjectRemover(supertest);
Expand All @@ -32,7 +35,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext

it('should handle snooze rule request appropriately', async () => {
const { body: createdAction } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}}/api/actions/connector`)
.post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`)
.set('kbn-xsrf', 'foo')
.send({
name: 'MY action',
Expand All @@ -41,8 +44,9 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext
secrets: {},
})
.expect(200);
objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions');

const { body: createdAlert } = await supertest
const { body: createdRule } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
Expand All @@ -58,16 +62,16 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext
})
)
.expect(200);
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting');

const response = await alertUtils
.getSnoozeRequest(createdAlert.id)
.getSnoozeRequest(createdRule.id)
.send({ snooze_end_time: FUTURE_SNOOZE_TIME });

expect(response.statusCode).to.eql(204);
expect(response.body).to.eql('');
const { body: updatedAlert } = await supertestWithoutAuth
.get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}`)
.get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`)
.set('kbn-xsrf', 'foo')
.expect(200);
expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME);
Expand All @@ -77,13 +81,13 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext
supertest,
spaceId: Spaces.space1.id,
type: 'alert',
id: createdAlert.id,
id: createdRule.id,
});
});

it('should handle snooze rule request appropriately when snoozeEndTime is -1', async () => {
const { body: createdAction } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}}/api/actions/connector`)
.post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`)
.set('kbn-xsrf', 'foo')
.send({
name: 'MY action',
Expand All @@ -92,8 +96,9 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext
secrets: {},
})
.expect(200);
objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions');

const { body: createdAlert } = await supertest
const { body: createdRule } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
Expand All @@ -109,16 +114,16 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext
})
)
.expect(200);
objectRemover.add(Spaces.space1.id, createdAlert.id, 'rule', 'alerting');
objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting');

const response = await alertUtils
.getSnoozeRequest(createdAlert.id)
.getSnoozeRequest(createdRule.id)
.send({ snooze_end_time: -1 });

expect(response.statusCode).to.eql(204);
expect(response.body).to.eql('');
const { body: updatedAlert } = await supertestWithoutAuth
.get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdAlert.id}`)
.get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`)
.set('kbn-xsrf', 'foo')
.expect(200);
expect(updatedAlert.snooze_end_time).to.eql(null);
Expand All @@ -128,8 +133,111 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext
supertest,
spaceId: Spaces.space1.id,
type: 'alert',
id: createdAlert.id,
id: createdRule.id,
});
});

it('should not trigger actions when snoozed', async () => {
const { body: createdAction, status: connStatus } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`)
.set('kbn-xsrf', 'foo')
.send({
name: 'MY action',
connector_type_id: 'test.noop',
config: {},
secrets: {},
});
expect(connStatus).to.be(200);
objectRemover.add(Spaces.space1.id, createdAction.id, 'action', 'actions');

log.info('creating rule');
const { body: createdRule, status: ruleStatus } = await supertest
.post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(
getTestRuleData({
name: 'should not trigger actions when snoozed',
rule_type_id: 'test.patternFiring',
schedule: { interval: '1s' },
throttle: null,
notify_when: 'onActiveAlert',
params: {
pattern: { instance: arrayOfTrues(100) },
},
actions: [
{
id: createdAction.id,
group: 'default',
params: {},
},
],
})
);
expect(ruleStatus).to.be(200);
objectRemover.add(Spaces.space1.id, createdRule.id, 'rule', 'alerting');

// wait for an action to be triggered
log.info('wait for rule to trigger an action');
await getRuleEvents(createdRule.id);

log.info('start snoozing');
const snoozeSeconds = 10;
const snoozeEndDate = new Date(Date.now() + 1000 * snoozeSeconds);
await alertUtils
.getSnoozeRequest(createdRule.id)
.send({ snooze_end_time: snoozeEndDate.toISOString() });

// could be an action execution while calling snooze, so set snooze start
// to a value that we know it will be in effect (after this call)
const snoozeStartDate = new Date();

// wait for 4 triggered actions - in case some fired before snooze went into effect
log.info('wait for snoozing to end');
const ruleEvents = await getRuleEvents(createdRule.id, 4);
const snoozeStart = snoozeStartDate.valueOf();
const snoozeEnd = snoozeStartDate.valueOf();
let actionsBefore = 0;
let actionsDuring = 0;
let actionsAfter = 0;

for (const event of ruleEvents) {
const timestamp = event?.['@timestamp'];
if (!timestamp) continue;

const time = new Date(timestamp).valueOf();
if (time < snoozeStart) {
actionsBefore++;
} else if (time > snoozeEnd) {
actionsAfter++;
} else {
actionsDuring++;
}
}

expect(actionsBefore).to.be.greaterThan(0, 'no actions triggered before snooze');
expect(actionsAfter).to.be.greaterThan(0, 'no actions triggered after snooze');
expect(actionsDuring).to.be(0);
});
});

async function getRuleEvents(id: string, minActions: number = 1) {
return await retry.try(async () => {
return await getEventLog({
getService,
spaceId: Spaces.space1.id,
type: 'alert',
id,
provider: 'alerting',
actions: new Map([['execute-action', { gte: minActions }]]),
});
});
}
}

function arrayOfTrues(length: number) {
const result = [];
for (let i = 0; i < length; i++) {
result.push(true);
}
return result;
}

0 comments on commit 23be142

Please sign in to comment.