[Alerting] Refactor task_runner. Move Interface and test fixtures to … (#125418)

* [Alerting] Refactor task_runner. Move Interface and test fixtures to their own file
This commit is contained in:
Ersin Erdal 2022-02-25 00:36:12 +01:00 committed by GitHub
parent bf89f3d0d3
commit ef62706a59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 1216 additions and 3946 deletions

View file

@ -8,6 +8,7 @@
import * as t from 'io-ts';
import { rawAlertInstance } from './alert_instance';
import { DateFromString } from './date_from_string';
import { IntervalSchedule, RuleMonitoring } from './alert';
const actionSchema = t.partial({
group: t.string,
@ -44,3 +45,9 @@ export const ruleParamsSchema = t.intersection([
}),
]);
export type RuleTaskParams = t.TypeOf<typeof ruleParamsSchema>;
export interface RuleExecutionRunResult {
state: RuleExecutionState;
monitoring: RuleMonitoring | undefined;
schedule: IntervalSchedule | undefined;
}

View file

@ -0,0 +1,378 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { isNil } from 'lodash';
import { Alert, AlertTypeParams, RecoveredActionGroup } from '../../common';
import { getDefaultRuleMonitoring } from './task_runner';
import { UntypedNormalizedRuleType } from '../rule_type_registry';
import { TaskStatus } from '../../../task_manager/server';
import { EVENT_LOG_ACTIONS } from '../plugin';
interface GeneratorParams {
[key: string]: string | number | boolean | undefined | object[] | boolean[] | object;
}
export const RULE_NAME = 'rule-name';
export const RULE_ID = '1';
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 MOCK_DURATION = 86400000000000;
export const SAVED_OBJECT = {
id: '1',
type: 'alert',
attributes: {
apiKey: Buffer.from('123:abc').toString('base64'),
enabled: true,
},
references: [],
};
export const RULE_ACTIONS = [
{
actionTypeId: 'action',
group: 'default',
id: '1',
params: {
foo: true,
},
},
{
actionTypeId: 'action',
group: 'recovered',
id: '2',
params: {
isResolved: true,
},
},
];
export const SAVED_OBJECT_UPDATE_PARAMS = [
'alert',
'1',
{
monitoring: {
execution: {
calculated_metrics: {
success_ratio: 1,
},
history: [
{
success: true,
timestamp: 0,
},
],
},
},
executionStatus: {
error: null,
lastDuration: 0,
lastExecutionDate: '1970-01-01T00:00:00.000Z',
status: 'ok',
},
},
{ refresh: false, namespace: undefined },
];
export const GENERIC_ERROR_MESSAGE = 'GENERIC ERROR MESSAGE';
export const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
id: RULE_TYPE_ID,
name: 'My test rule',
actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
isExportable: true,
recoveryActionGroup: RecoveredActionGroup,
executor: jest.fn(),
producer: 'alerts',
};
export const mockRunNowResponse = {
id: 1,
} as jest.ResolvedValue<unknown>;
export const mockDate = new Date('2019-02-12T21:01:22.479Z');
export const mockedRuleTypeSavedObject: Alert<AlertTypeParams> = {
id: '1',
consumer: 'bar',
createdAt: mockDate,
updatedAt: mockDate,
throttle: null,
muteAll: false,
notifyWhen: 'onActiveAlert',
enabled: true,
alertTypeId: ruleType.id,
apiKey: '',
apiKeyOwner: 'elastic',
schedule: { interval: '10s' },
name: RULE_NAME,
tags: ['rule-', '-tags'],
createdBy: 'rule-creator',
updatedBy: 'rule-updater',
mutedInstanceIds: [],
params: {
bar: true,
},
actions: [
{
group: 'default',
id: '1',
actionTypeId: 'action',
params: {
foo: true,
},
},
{
group: RecoveredActionGroup.id,
id: '2',
actionTypeId: 'action',
params: {
isResolved: true,
},
},
],
executionStatus: {
status: 'unknown',
lastExecutionDate: new Date('2020-08-20T19:23:38Z'),
},
monitoring: getDefaultRuleMonitoring(),
};
export const mockTaskInstance = () => ({
id: '',
attempts: 0,
status: TaskStatus.Running,
version: '123',
runAt: new Date(),
schedule: { interval: '10s' },
scheduledAt: new Date(),
startedAt: new Date(),
retryAt: new Date(Date.now() + 5 * 60 * 1000),
state: {},
taskType: 'alerting:test',
params: {
alertId: RULE_ID,
},
ownerId: null,
});
export const generateAlertSO = (id: string) => ({
id,
rel: 'primary',
type: 'alert',
type_id: RULE_TYPE_ID,
});
export const generateActionSO = (id: string) => ({
id,
namespace: undefined,
type: 'action',
type_id: 'action',
});
export const generateEventLog = ({
action,
task,
duration,
start,
end,
outcome,
reason,
instanceId,
actionSubgroup,
actionGroupId,
status,
numberOfTriggeredActions,
savedObjects = [generateAlertSO('1')],
}: GeneratorParams = {}) => ({
...(status === 'error' && {
error: {
message: generateErrorMessage(String(reason)),
},
}),
event: {
action,
...(!isNil(duration) && { duration }),
...(start && { start }),
...(end && { end }),
...(outcome && { outcome }),
...(reason && { reason }),
category: ['alerts'],
kind: 'alert',
},
kibana: {
alert: {
rule: {
execution: {
uuid: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
...(!isNil(numberOfTriggeredActions) && {
metrics: {
number_of_triggered_actions: numberOfTriggeredActions,
number_of_searches: 3,
es_search_duration_ms: 33,
total_search_duration_ms: 23423,
},
}),
},
},
},
...((actionSubgroup || actionGroupId || instanceId || status) && {
alerting: {
...(actionSubgroup && { action_subgroup: actionSubgroup }),
...(actionGroupId && { action_group_id: actionGroupId }),
...(instanceId && { instance_id: instanceId }),
...(status && { status }),
},
}),
saved_objects: savedObjects,
...(task && {
task: {
schedule_delay: 0,
scheduled: '1970-01-01T00:00:00.000Z',
},
}),
},
message: generateMessage({ action, instanceId, actionGroupId, actionSubgroup, reason, status }),
rule: {
category: 'test',
id: '1',
license: 'basic',
...(hasRuleName({ action, status }) && { name: RULE_NAME }),
ruleset: 'alerts',
},
});
const generateMessage = ({
action,
instanceId,
actionGroupId,
actionSubgroup,
reason,
status,
}: GeneratorParams) => {
if (action === EVENT_LOG_ACTIONS.executeStart) {
return `rule execution start: "${mockTaskInstance().params.alertId}"`;
}
if (action === EVENT_LOG_ACTIONS.newInstance) {
return `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}' created new alert: '${instanceId}'`;
}
if (action === EVENT_LOG_ACTIONS.activeInstance) {
if (actionSubgroup) {
return `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}' active alert: '${instanceId}' in actionGroup(subgroup): 'default(${actionSubgroup})'`;
}
return `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}' active alert: '${instanceId}' in actionGroup: '${actionGroupId}'`;
}
if (action === EVENT_LOG_ACTIONS.recoveredInstance) {
return `${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}' alert '${instanceId}' has recovered`;
}
if (action === EVENT_LOG_ACTIONS.executeAction) {
if (actionSubgroup) {
return `alert: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}' instanceId: '${instanceId}' scheduled actionGroup(subgroup): 'default(${actionSubgroup})' action: action:${instanceId}`;
}
return `alert: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}' instanceId: '${instanceId}' scheduled actionGroup: '${actionGroupId}' action: action:${instanceId}`;
}
if (action === EVENT_LOG_ACTIONS.execute) {
if (status === 'error' && reason === 'execute') {
return `rule execution failure: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`;
}
if (status === 'error') {
return `${RULE_TYPE_ID}:${RULE_ID}: execution failed`;
}
if (actionGroupId === 'recovered') {
return `rule-name' instanceId: '${instanceId}' scheduled actionGroup: '${actionGroupId}' action: action:${instanceId}`;
}
return `rule executed: ${RULE_TYPE_ID}:${RULE_ID}: '${RULE_NAME}'`;
}
};
const generateErrorMessage = (reason: string) => {
if (reason === 'disabled') {
return 'Rule failed to execute because rule ran after it was disabled.';
}
return GENERIC_ERROR_MESSAGE;
};
export const generateRunnerResult = ({
successRatio = 1,
history = Array(false),
state = false,
interval = '10s',
}: GeneratorParams = {}) => {
return {
monitoring: {
execution: {
calculated_metrics: {
success_ratio: successRatio,
},
// @ts-ignore
history: history.map((success) => ({ success, timestamp: 0 })),
},
},
schedule: {
interval,
},
state: {
...(state && { alertInstances: {} }),
...(state && { alertTypeState: undefined }),
...(state && { previousStartedAt: new Date('1970-01-01T00:00:00.000Z') }),
},
};
};
export const generateEnqueueFunctionInput = () => ({
apiKey: 'MTIzOmFiYw==',
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
id: '1',
params: {
foo: true,
},
relatedSavedObjects: [
{
id: '1',
namespace: undefined,
type: 'alert',
typeId: RULE_TYPE_ID,
},
],
source: {
source: {
id: '1',
type: 'alert',
},
type: 'SAVED_OBJECT',
},
spaceId: undefined,
});
export const generateAlertInstance = ({ id, duration, start }: GeneratorParams = { id: 1 }) => ({
[String(id)]: {
meta: {
lastScheduledActions: {
date: new Date(DATE_1970),
group: 'default',
subgroup: undefined,
},
},
state: {
bar: false,
duration,
start,
},
},
});
const hasRuleName = ({ action, status }: GeneratorParams) => {
return action !== 'execute-start' && status !== 'error';
};

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import apm from 'elastic-apm-node';
import { Dictionary, pickBy, mapValues, without, cloneDeep, concat, set, omit } from 'lodash';
import { pickBy, mapValues, without, cloneDeep, concat, set, omit } from 'lodash';
import type { Request } from '@hapi/hapi';
import { UsageCounter } from 'src/plugins/usage_collection/server';
import uuid from 'uuid';
@ -38,16 +38,16 @@ import {
RawRuleExecutionStatus,
AlertAction,
RuleExecutionState,
RuleExecutionRunResult,
} from '../types';
import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type';
import { getExecutionSuccessRatio, getExecutionDurationPercentiles } from '../lib/monitoring';
import { taskInstanceToAlertTaskInstance } from './alert_task_instance';
import { EVENT_LOG_ACTIONS } from '../plugin';
import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
import { IEvent, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
import { isAlertSavedObjectNotFoundError, isEsUnavailableError } from '../lib/is_alerting_error';
import { partiallyUpdateAlert } from '../saved_objects';
import {
ActionGroup,
AlertTypeParams,
AlertTypeState,
AlertInstanceState,
@ -65,6 +65,14 @@ import {
import { createAbortableEsClientFactory } from '../lib/create_abortable_es_client_factory';
import { createWrappedScopedClusterClientFactory } from '../lib';
import { getRecoveredAlerts } from '../lib';
import {
GenerateNewAndRecoveredAlertEventsParams,
LogActiveAndRecoveredAlertsParams,
RuleTaskInstance,
RuleTaskRunResult,
ScheduleActionsForRecoveredAlertsParams,
TrackAlertDurationsParams,
} from './types';
const FALLBACK_RETRY_INTERVAL = '5m';
const CONNECTIVITY_RETRY_INTERVAL = '5m';
@ -81,22 +89,6 @@ export const getDefaultRuleMonitoring = (): RuleMonitoring => ({
},
});
interface RuleExecutionRunResult {
state: RuleExecutionState;
monitoring: RuleMonitoring | undefined;
schedule: IntervalSchedule | undefined;
}
interface RuleTaskRunResult {
state: RuleTaskState;
monitoring: RuleMonitoring | undefined;
schedule: IntervalSchedule | undefined;
}
interface RuleTaskInstance extends ConcreteTaskInstance {
state: RuleTaskState;
}
export class TaskRunner<
Params extends AlertTypeParams,
ExtractedParams extends AlertTypeParams,
@ -940,15 +932,6 @@ export class TaskRunner<
}
}
interface TrackAlertDurationsParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
> {
originalAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
currentAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
recoveredAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
}
function trackAlertDurations<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
@ -995,34 +978,6 @@ function trackAlertDurations<
}
}
interface GenerateNewAndRecoveredAlertEventsParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
> {
eventLogger: IEventLogger;
executionId: string;
originalAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
currentAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
recoveredAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
ruleId: string;
ruleLabel: string;
namespace: string | undefined;
ruleType: NormalizedRuleType<
AlertTypeParams,
AlertTypeParams,
AlertTypeState,
{
[x: string]: unknown;
},
{
[x: string]: unknown;
},
string,
string
>;
rule: SanitizedAlert<AlertTypeParams>;
}
function generateNewAndRecoveredAlertEvents<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
@ -1144,19 +1099,6 @@ function generateNewAndRecoveredAlertEvents<
}
}
interface ScheduleActionsForRecoveredAlertsParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext,
RecoveryActionGroupId extends string
> {
logger: Logger;
recoveryActionGroup: ActionGroup<RecoveryActionGroupId>;
recoveredAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext, RecoveryActionGroupId>>;
executionHandler: ExecutionHandler<RecoveryActionGroupId | RecoveryActionGroupId>;
mutedAlertIdsSet: Set<string>;
ruleLabel: string;
}
async function scheduleActionsForRecoveredAlerts<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext,
@ -1200,19 +1142,6 @@ async function scheduleActionsForRecoveredAlerts<
return triggeredActions;
}
interface LogActiveAndRecoveredAlertsParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
> {
logger: Logger;
activeAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext, ActionGroupIds>>;
recoveredAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext, RecoveryActionGroupId>>;
ruleLabel: string;
canSetRecoveryContext: boolean;
}
function logActiveAndRecoveredAlerts<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext,

View file

@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Dictionary } from 'lodash';
import { Logger } from 'kibana/server';
import {
ActionGroup,
AlertInstanceContext,
AlertInstanceState,
AlertTypeParams,
AlertTypeState,
IntervalSchedule,
RuleExecutionState,
RuleMonitoring,
RuleTaskState,
SanitizedAlert,
} from '../../common';
import { ConcreteTaskInstance } from '../../../task_manager/server';
import { Alert as CreatedAlert } from '../alert';
import { IEventLogger } from '../../../event_log/server';
import { NormalizedRuleType } from '../rule_type_registry';
import { ExecutionHandler } from './create_execution_handler';
export interface RuleTaskRunResultWithActions {
state: RuleExecutionState;
monitoring: RuleMonitoring | undefined;
schedule: IntervalSchedule | undefined;
}
export interface RuleTaskRunResult {
state: RuleTaskState;
monitoring: RuleMonitoring | undefined;
schedule: IntervalSchedule | undefined;
}
export interface RuleTaskInstance extends ConcreteTaskInstance {
state: RuleTaskState;
}
export interface TrackAlertDurationsParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
> {
originalAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
currentAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
recoveredAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
}
export interface GenerateNewAndRecoveredAlertEventsParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext
> {
eventLogger: IEventLogger;
executionId: string;
originalAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
currentAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
recoveredAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext>>;
ruleId: string;
ruleLabel: string;
namespace: string | undefined;
ruleType: NormalizedRuleType<
AlertTypeParams,
AlertTypeParams,
AlertTypeState,
{
[x: string]: unknown;
},
{
[x: string]: unknown;
},
string,
string
>;
rule: SanitizedAlert<AlertTypeParams>;
}
export interface ScheduleActionsForRecoveredAlertsParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext,
RecoveryActionGroupId extends string
> {
logger: Logger;
recoveryActionGroup: ActionGroup<RecoveryActionGroupId>;
recoveredAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext, RecoveryActionGroupId>>;
executionHandler: ExecutionHandler<RecoveryActionGroupId | RecoveryActionGroupId>;
mutedAlertIdsSet: Set<string>;
ruleLabel: string;
}
export interface LogActiveAndRecoveredAlertsParams<
InstanceState extends AlertInstanceState,
InstanceContext extends AlertInstanceContext,
ActionGroupIds extends string,
RecoveryActionGroupId extends string
> {
logger: Logger;
activeAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext, ActionGroupIds>>;
recoveredAlerts: Dictionary<CreatedAlert<InstanceState, InstanceContext, RecoveryActionGroupId>>;
ruleLabel: string;
canSetRecoveryContext: boolean;
}