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

[Alerting] Allow rule types to specify custom timeout values #113487

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions docs/settings/alert-action-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,10 @@ For example, `20m`, `24h`, `7d`, `1w`. Default: `60s`.

`xpack.alerting.maxEphemeralActionsPerAlert`::
Sets the number of actions that will be executed ephemerally. To use this, enable ephemeral tasks in task manager first with <<task-manager-settings,`xpack.task_manager.ephemeral_tasks.enabled`>>

`xpack.alerting.defaultRuleTaskTimeout`::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add this to the docker allowlist and also open a cloud PR for this setting?

Specifies the default timeout for the all rule types tasks. The time is formatted as:
+
`<count>[ms,s,m,h,d,w,M,Y]`
+
For example, `20m`, `24h`, `7d`, `1w`. Default: `60s`.
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ kibana_vars=(
xpack.alerting.healthCheck.interval
xpack.alerting.invalidateApiKeysTask.interval
xpack.alerting.invalidateApiKeysTask.removalDelay
xpack.alerting.defaultRuleTaskTimeout
xpack.alerts.healthCheck.interval
xpack.alerts.invalidateApiKeysTask.interval
xpack.alerts.invalidateApiKeysTask.removalDelay
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/alerting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ The following table describes the properties of the `options` object.
|executor|This is where the code for the rule type lives. This is a function to be called when executing a rule on an interval basis. For full details, see the executor section below.|Function|
|producer|The id of the application producing this rule type.|string|
|minimumLicenseRequired|The value of a minimum license. Most of the rules are licensed as "basic".|string|
|ruleTaskTimeout|The length of time a rule can run before being cancelled due to timeout. By default, this value is "5m".|string|
|useSavedObjectReferences.extractReferences|(Optional) When developing a rule type, you can choose to implement hooks for extracting saved object references from rule parameters. This hook will be invoked when a rule is created or updated. Implementing this hook is optional, but if an extract hook is implemented, an inject hook must also be implemented.|Function
|useSavedObjectReferences.injectReferences|(Optional) When developing a rule type, you can choose to implement hooks for injecting saved object references into rule parameters. This hook will be invoked when a rule is retrieved (get or find). Implementing this hook is optional, but if an inject hook is implemented, an extract hook must also be implemented.|Function
|isExportable|Whether the rule type is exportable from the Saved Objects Management UI.|boolean|
Expand Down Expand Up @@ -344,6 +345,7 @@ const myRuleType: AlertType<
};
},
producer: 'alerting',
ruleTaskTimeout: '10m',
useSavedObjectReferences: {
extractReferences: (params: Params): RuleParamsAndRefs<ExtractedParams> => {
const { testSavedObjectId, ...otherParams } = params;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/server/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('config validation', () => {
const config: Record<string, unknown> = {};
expect(configSchema.validate(config)).toMatchInlineSnapshot(`
Object {
"defaultRuleTaskTimeout": "5m",
"healthCheck": Object {
"interval": "60m",
},
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const configSchema = schema.object({
maxEphemeralActionsPerAlert: schema.number({
defaultValue: DEFAULT_MAX_EPHEMERAL_ACTIONS_PER_ALERT,
}),
defaultRuleTaskTimeout: schema.string({ validate: validateDurationSchema, defaultValue: '5m' }),
});

export type AlertsConfig = TypeOf<typeof configSchema>;
8 changes: 8 additions & 0 deletions x-pack/plugins/alerting/server/health/get_state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '20m',
}),
pollInterval
).subscribe();
Expand Down Expand Up @@ -106,6 +107,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '20m',
}),
pollInterval,
retryDelay
Expand Down Expand Up @@ -151,6 +153,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '20m',
})
).toPromise();

Expand Down Expand Up @@ -182,6 +185,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '20m',
})
).toPromise();

Expand Down Expand Up @@ -213,6 +217,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '20m',
})
).toPromise();

Expand Down Expand Up @@ -241,6 +246,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '20m',
}),
retryDelay
).subscribe((status) => {
Expand Down Expand Up @@ -272,6 +278,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '20m',
}),
retryDelay
).subscribe((status) => {
Expand Down Expand Up @@ -309,6 +316,7 @@ describe('getHealthServiceStatusWithRetryAndErrorHandling', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '20m',
})
).toPromise();

Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/alerting/server/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('Alerting Plugin', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 10,
defaultRuleTaskTimeout: '5m',
});
plugin = new AlertingPlugin(context);

Expand Down Expand Up @@ -71,6 +72,7 @@ describe('Alerting Plugin', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 10,
defaultRuleTaskTimeout: '5m',
});
plugin = new AlertingPlugin(context);

Expand Down Expand Up @@ -142,6 +144,15 @@ describe('Alerting Plugin', () => {
minimumLicenseRequired: 'basic',
});
});

it('should apply default config value for ruleTaskTimeout', async () => {
const ruleType = {
...sampleAlertType,
minimumLicenseRequired: 'basic',
} as AlertType<never, never, never, never, never, 'default', never>;
await setup.registerType(ruleType);
expect(ruleType.ruleTaskTimeout).toBe('5m');
});
});
});

Expand All @@ -157,6 +168,7 @@ describe('Alerting Plugin', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 10,
defaultRuleTaskTimeout: '5m',
});
const plugin = new AlertingPlugin(context);

Expand Down Expand Up @@ -197,6 +209,7 @@ describe('Alerting Plugin', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 10,
defaultRuleTaskTimeout: '5m',
});
const plugin = new AlertingPlugin(context);

Expand Down Expand Up @@ -251,6 +264,7 @@ describe('Alerting Plugin', () => {
removalDelay: '1h',
},
maxEphemeralActionsPerAlert: 100,
defaultRuleTaskTimeout: '5m',
});
const plugin = new AlertingPlugin(context);

Expand Down
10 changes: 9 additions & 1 deletion x-pack/plugins/alerting/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export class AlertingPlugin {
encryptedSavedObjects: plugins.encryptedSavedObjects,
});

const alertingConfig = this.config;
return {
registerType<
Params extends AlertTypeParams = AlertTypeParams,
Expand All @@ -308,7 +309,14 @@ export class AlertingPlugin {
if (!(alertType.minimumLicenseRequired in LICENSE_TYPE)) {
throw new Error(`"${alertType.minimumLicenseRequired}" is not a valid license type`);
}
ruleTypeRegistry.register(alertType);
if (!alertType.ruleTaskTimeout) {
YulNaumenko marked this conversation as resolved.
Show resolved Hide resolved
alertingConfig.then((config) => {
alertType.ruleTaskTimeout = config.defaultRuleTaskTimeout;
ruleTypeRegistry.register(alertType);
});
} else {
ruleTypeRegistry.register(alertType);
}
},
};
}
Expand Down
50 changes: 50 additions & 0 deletions x-pack/plugins/alerting/server/rule_type_registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,32 @@ describe('register()', () => {
);
});

test('throws if AlertType ruleTaskTimeout is not a valid duration', () => {
const alertType: AlertType<never, never, never, never, never, 'default'> = {
id: 123 as unknown as string,
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
ruleTaskTimeout: '23 milisec',
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);

expect(() => registry.register(alertType)).toThrowError(
new Error(
`Rule type \"123\" has invalid timeout: string is not a valid duration: 23 milisec.`
)
);
});

test('throws if RuleType action groups contains reserved group id', () => {
const alertType: AlertType<never, never, never, never, never, 'default' | 'NotReserved'> = {
id: 'test',
Expand Down Expand Up @@ -181,6 +207,28 @@ describe('register()', () => {
`);
});

test('allows an AlertType to specify a custom rule task timeout', () => {
const alertType: AlertType<never, never, never, never, never, 'default', 'backToAwesome'> = {
id: 'test',
name: 'Test',
actionGroups: [
{
id: 'default',
name: 'Default',
},
],
defaultActionGroupId: 'default',
ruleTaskTimeout: '13m',
executor: jest.fn(),
producer: 'alerts',
minimumLicenseRequired: 'basic',
isExportable: true,
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register(alertType);
expect(registry.get('test').ruleTaskTimeout).toBe('13m');
});

test('throws if the custom recovery group is contained in the AlertType action groups', () => {
const alertType: AlertType<
never,
Expand Down Expand Up @@ -237,6 +285,7 @@ describe('register()', () => {
isExportable: true,
executor: jest.fn(),
producer: 'alerts',
ruleTaskTimeout: '20m',
};
const registry = new RuleTypeRegistry(ruleTypeRegistryParams);
registry.register(alertType);
Expand All @@ -246,6 +295,7 @@ describe('register()', () => {
Object {
"alerting:test": Object {
"createTaskRunner": [Function],
"timeout": "20m",
"title": "Test",
},
},
Expand Down
17 changes: 17 additions & 0 deletions x-pack/plugins/alerting/server/rule_type_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
getBuiltinActionGroups,
RecoveredActionGroupId,
ActionGroup,
validateDurationSchema,
} from '../common';
import { ILicenseState } from './lib/license_state';
import { getAlertTypeFeatureUsageName } from './lib/get_alert_type_feature_usage_name';
Expand Down Expand Up @@ -170,6 +171,21 @@ export class RuleTypeRegistry {
})
);
}
// validate ruleTypeTimeout here
if (alertType.ruleTaskTimeout) {
const invalidTimeout = validateDurationSchema(alertType.ruleTaskTimeout);
if (invalidTimeout) {
throw new Error(
i18n.translate('xpack.alerting.ruleTypeRegistry.register.invalidTimeoutAlertTypeError', {
defaultMessage: 'Rule type "{id}" has invalid timeout: {errorMessage}.',
values: {
id: alertType.id,
errorMessage: invalidTimeout,
},
})
);
}
}
alertType.actionVariables = normalizedActionVariables(alertType.actionVariables);

const normalizedAlertType = augmentActionGroupsWithReserved<
Expand All @@ -190,6 +206,7 @@ export class RuleTypeRegistry {
this.taskManager.registerTaskDefinitions({
[`alerting:${alertType.id}`]: {
title: alertType.name,
timeout: alertType.ruleTaskTimeout,
createTaskRunner: (context: RunContext) =>
this.taskRunnerFactory.create<
Params,
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/alerting/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ export interface AlertType<
injectReferences: (params: ExtractedParams, references: SavedObjectReference[]) => Params;
};
isExportable: boolean;
ruleTaskTimeout?: string;
}

export type UntypedAlertType = AlertType<
AlertTypeParams,
AlertTypeState,
Expand Down