Skip to content

Commit

Permalink
Alert doesn't fire action if it's muted or throttled (elastic#124775)
Browse files Browse the repository at this point in the history
* Alert doesn't fire action if it's muted or throttled
  • Loading branch information
ersin-erdal authored Feb 9, 2022
1 parent f85e59d commit e4dd6e7
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 28 deletions.
124 changes: 123 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 @@ -41,6 +41,7 @@ import { omit } from 'lodash';
import { UntypedNormalizedRuleType } from '../rule_type_registry';
import { ruleTypeRegistryMock } from '../rule_type_registry.mock';
import { ExecuteOptions } from '../../../actions/server/create_execute_function';
import moment from 'moment';

jest.mock('uuid', () => ({
v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
Expand Down Expand Up @@ -978,7 +979,128 @@ describe('Task Runner', () => {
}
);

test('actionsPlugin.execute is not called when notifyWhen=onActionGroupChange and alert alert state does not change', async () => {
test.each(ephemeralTestParams)(
'skips firing actions for active alert if alert is throttled %s',
async (nameExtension, customTaskRunnerFactoryInitializerParams, enqueueFunction) => {
(
customTaskRunnerFactoryInitializerParams as TaskRunnerFactoryInitializerParamsType
).actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
customTaskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(
true
);
actionsClient.ephemeralEnqueuedExecution.mockResolvedValue(mockRunNowResponse);
ruleType.executor.mockImplementation(
async ({
services: executorServices,
}: AlertExecutorOptions<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
string
>) => {
executorServices.alertFactory.create('1').scheduleActions('default');
executorServices.alertFactory.create('2').scheduleActions('default');
}
);
const taskRunner = new TaskRunner(
ruleType,
{
...mockedTaskInstance,
state: {
...mockedTaskInstance.state,
alertInstances: {
'2': {
meta: {
lastScheduledActions: { date: moment().toISOString(), group: 'default' },
},
state: {
bar: false,
start: '1969-12-31T00:00:00.000Z',
duration: 86400000000000,
},
},
},
},
},
taskRunnerFactoryInitializerParams
);
rulesClient.get.mockResolvedValue({
...mockedRuleTypeSavedObject,
throttle: '1d',
});
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({
id: '1',
type: 'alert',
attributes: {
apiKey: Buffer.from('123:abc').toString('base64'),
enabled: true,
},
references: [],
});
await taskRunner.run();
// expect(enqueueFunction).toHaveBeenCalledTimes(1);

const logger = customTaskRunnerFactoryInitializerParams.logger;
// expect(logger.debug).toHaveBeenCalledTimes(5);
expect(logger.debug).nthCalledWith(
3,
`skipping scheduling of actions for '2' in rule test:1: 'rule-name': rule is throttled`
);
}
);

test.each(ephemeralTestParams)(
'skips firing actions for active alert when alert is muted even if notifyWhen === onActionGroupChange %s',
async (nameExtension, customTaskRunnerFactoryInitializerParams, enqueueFunction) => {
customTaskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(
true
);
ruleType.executor.mockImplementation(
async ({
services: executorServices,
}: AlertExecutorOptions<
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
AlertInstanceContext,
string
>) => {
executorServices.alertFactory.create('1').scheduleActions('default');
executorServices.alertFactory.create('2').scheduleActions('default');
}
);
const taskRunner = new TaskRunner(
ruleType,
mockedTaskInstance,
customTaskRunnerFactoryInitializerParams
);
rulesClient.get.mockResolvedValue({
...mockedRuleTypeSavedObject,
mutedInstanceIds: ['2'],
notifyWhen: 'onActionGroupChange',
});
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({
id: '1',
type: 'alert',
attributes: {
apiKey: Buffer.from('123:abc').toString('base64'),
enabled: true,
},
references: [],
});
await taskRunner.run();
expect(enqueueFunction).toHaveBeenCalledTimes(1);
const logger = customTaskRunnerFactoryInitializerParams.logger;
expect(logger.debug).toHaveBeenCalledTimes(5);
expect(logger.debug).nthCalledWith(
3,
`skipping scheduling of actions for '2' in rule test:1: 'rule-name': rule is muted`
);
}
);

test('actionsPlugin.execute is not called when notifyWhen=onActionGroupChange and alert state does not change', async () => {
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
ruleType.executor.mockImplementation(
Expand Down
52 changes: 25 additions & 27 deletions x-pack/plugins/alerting/server/task_runner/task_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,34 +477,32 @@ export class TaskRunner<

triggeredActions = concat(triggeredActions, scheduledActionsForRecoveredAlerts);

const alertsToExecute =
notifyWhen === 'onActionGroupChange'
? Object.entries(alertsWithScheduledActions).filter(
([alertName, alert]: [string, CreatedAlert<InstanceState, InstanceContext>]) => {
const shouldExecuteAction = alert.scheduledActionGroupOrSubgroupHasChanged();
if (!shouldExecuteAction) {
this.logger.debug(
`skipping scheduling of actions for '${alertName}' in rule ${ruleLabel}: alert is active but action group has not changed`
);
}
return shouldExecuteAction;
}
)
: Object.entries(alertsWithScheduledActions).filter(
([alertName, alert]: [string, CreatedAlert<InstanceState, InstanceContext>]) => {
const throttled = alert.isThrottled(throttle);
const muted = mutedAlertIdsSet.has(alertName);
const shouldExecuteAction = !throttled && !muted;
if (!shouldExecuteAction) {
this.logger.debug(
`skipping scheduling of actions for '${alertName}' in rule ${ruleLabel}: rule is ${
muted ? 'muted' : 'throttled'
}`
);
}
return shouldExecuteAction;
}
const alertsToExecute = Object.entries(alertsWithScheduledActions).filter(
([alertName, alert]: [string, CreatedAlert<InstanceState, InstanceContext>]) => {
const throttled = alert.isThrottled(throttle);
const muted = mutedAlertIdsSet.has(alertName);
let shouldExecuteAction = true;

if (throttled || muted) {
shouldExecuteAction = false;
this.logger.debug(
`skipping scheduling of actions for '${alertName}' in rule ${ruleLabel}: rule is ${
muted ? 'muted' : 'throttled'
}`
);
} else if (
notifyWhen === 'onActionGroupChange' &&
!alert.scheduledActionGroupOrSubgroupHasChanged()
) {
shouldExecuteAction = false;
this.logger.debug(
`skipping scheduling of actions for '${alertName}' in rule ${ruleLabel}: alert is active but action group has not changed`
);
}

return shouldExecuteAction;
}
);

const allTriggeredActions = await Promise.all(
alertsToExecute.map(
Expand Down

0 comments on commit e4dd6e7

Please sign in to comment.