Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Alert doesn't fire action if it's muted or throttled #124775

Merged
merged 4 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.alertInstanceFactory('1').scheduleActions('default');
ersin-erdal marked this conversation as resolved.
Show resolved Hide resolved
executorServices.alertInstanceFactory('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.alertInstanceFactory('1').scheduleActions('default');
ersin-erdal marked this conversation as resolved.
Show resolved Hide resolved
executorServices.alertInstanceFactory('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 @@ -475,34 +475,32 @@ export class TaskRunner<

triggeredActions = concat(triggeredActions, scheduledActionsForRecoveredAlerts);

const alertsToExecute =
notifyWhen === 'onActionGroupChange'
? Object.entries(alertsWithScheduledActions).filter(
([alertName, alert]: [string, AlertInstance<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, AlertInstance<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, AlertInstance<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