kibana/x-pack/plugins/actions/server/create_execute_function.ts
Chris Roberson 1f798aac3f
[Alerting] Change execution of alerts from async to sync (#97311)
* added ability to run ephemeral tasks

* fixed typing

* added typing on plugin

* WIP

* Fix type issues

* Hook up the ephemeral task into the task runner for actions

* Tasks can now run independently of one another

* Use deferred language

* Refactor taskParams slightly

* Use Promise.all

* Remove deferred logic

* Add config options to limit the amount of tasks executing at once

* Add ephemeral task monitoring

* WIP

* Add single test so far

* Ensure we log after actions have executed

* Remove confusing * 1

* Add logic to ensure we fallback to default enqueueing if the total actions is above the config

* Add additional test

* Fix tests a bit, ensure we log the alerting:actions-execute right away and the tests should listen for alerts:execute

* Better tests

* If the queue is at capacity, attempt to execute the ephemeral task as a regular action

* Ensure we run ephemeral tasks before to avoid them getting stuck in the queue

* Do not handle the promise anymore

* Remove unnecessary code

* Properly handle errors from ephemeral task lifecycle

* moved acitons domain out of alerting and into actions plugin

* Remove some tests

* Fix TS and test issues

* Fix type issues

* Fix more type issues

* Fix more type issues

* Fix jest tests

* Fix more jest tests

* Off by default

* Fix jest tests

* Update config for this suite too

* Start of telemetry code

* Fix types and add missing files

* Fix telemetry schema

* Fix types

* Fix more types

* moved load event emission to pollingcycle and added health stats on Ephemeral tasks

* Add more telemetry data based on new health metrics for the ephemeral queue

* Fix tests and types

* Add separate request capacity for ephemeral queue

* Fix telemetry schema and add tests for usage collection

* track polled tasks by persistence and use in capacity estimation instead of executions

* fixed typing

* Bump default capacity

* added delay metric to ephemeral stats

* Fix bad merge

* Fix tests

* Fix tests

* Fix types

* Skip failing tests

* Exclude ephemeral stats from capacity estimation tests

* PR feedback

* More PR feedback

* PR feedback

* Fix merge conflict

* Try fixing CI

* Fix broken lock file from merge

* Match master

* Add this back

* PR feedback

* Change to queue and add test

* Disable ephemeral queue in tests

* Updated desc

* Comment out ephemeral-specific tests tha require the entire test suite to support ephemeral tasks

* Add clarifying comment

Co-authored-by: Gidi Meir Morris <github@gidi.io>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
2021-07-20 13:24:24 -04:00

160 lines
5.1 KiB
TypeScript

/*
* 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 { SavedObjectsClientContract } from '../../../../src/core/server';
import { RunNowResult, TaskManagerStartContract } from '../../task_manager/server';
import {
RawAction,
ActionTypeRegistryContract,
PreConfiguredAction,
ActionTaskExecutorParams,
} from './types';
import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects';
import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor';
import { isSavedObjectExecutionSource } from './lib';
import { RelatedSavedObjects } from './lib/related_saved_objects';
interface CreateExecuteFunctionOptions {
taskManager: TaskManagerStartContract;
isESOCanEncrypt: boolean;
actionTypeRegistry: ActionTypeRegistryContract;
preconfiguredActions: PreConfiguredAction[];
}
export interface ExecuteOptions extends Pick<ActionExecutorOptions, 'params' | 'source'> {
id: string;
spaceId: string;
apiKey: string | null;
relatedSavedObjects?: RelatedSavedObjects;
}
export type ExecutionEnqueuer<T> = (
unsecuredSavedObjectsClient: SavedObjectsClientContract,
options: ExecuteOptions
) => Promise<T>;
export function createExecutionEnqueuerFunction({
taskManager,
actionTypeRegistry,
isESOCanEncrypt,
preconfiguredActions,
}: CreateExecuteFunctionOptions): ExecutionEnqueuer<void> {
return async function execute(
unsecuredSavedObjectsClient: SavedObjectsClientContract,
{ id, params, spaceId, source, apiKey, relatedSavedObjects }: ExecuteOptions
) {
if (!isESOCanEncrypt) {
throw new Error(
`Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
);
}
const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id);
validateCanActionBeUsed(action);
const { actionTypeId } = action;
if (!actionTypeRegistry.isActionExecutable(id, actionTypeId, { notifyUsage: true })) {
actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);
}
const actionTaskParamsRecord = await unsecuredSavedObjectsClient.create(
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
{
actionId: id,
params,
apiKey,
relatedSavedObjects,
},
executionSourceAsSavedObjectReferences(source)
);
await taskManager.schedule({
taskType: `actions:${action.actionTypeId}`,
params: {
spaceId,
actionTaskParamsId: actionTaskParamsRecord.id,
},
state: {},
scope: ['actions'],
});
};
}
export function createEphemeralExecutionEnqueuerFunction({
taskManager,
actionTypeRegistry,
preconfiguredActions,
}: CreateExecuteFunctionOptions): ExecutionEnqueuer<RunNowResult> {
return async function execute(
unsecuredSavedObjectsClient: SavedObjectsClientContract,
{ id, params, spaceId, source, apiKey }: ExecuteOptions
): Promise<RunNowResult> {
const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id);
validateCanActionBeUsed(action);
const { actionTypeId } = action;
if (!actionTypeRegistry.isActionExecutable(id, actionTypeId, { notifyUsage: true })) {
actionTypeRegistry.ensureActionTypeEnabled(actionTypeId);
}
const taskParams: ActionTaskExecutorParams = {
spaceId,
taskParams: {
actionId: id,
// Saved Objects won't allow us to enforce unknown rather than any
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params: params as Record<string, any>,
...(apiKey ? { apiKey } : {}),
},
...executionSourceAsSavedObjectReferences(source),
};
return taskManager.ephemeralRunNow({
taskType: `actions:${action.actionTypeId}`,
params: taskParams,
state: {},
scope: ['actions'],
});
};
}
function validateCanActionBeUsed(action: PreConfiguredAction | RawAction) {
const { name, isMissingSecrets } = action;
if (isMissingSecrets) {
throw new Error(
`Unable to execute action because no secrets are defined for the "${name}" connector.`
);
}
}
function executionSourceAsSavedObjectReferences(executionSource: ActionExecutorOptions['source']) {
return isSavedObjectExecutionSource(executionSource)
? {
references: [
{
name: 'source',
...executionSource.source,
},
],
}
: {};
}
async function getAction(
unsecuredSavedObjectsClient: SavedObjectsClientContract,
preconfiguredActions: PreConfiguredAction[],
actionId: string
): Promise<PreConfiguredAction | RawAction> {
const pcAction = preconfiguredActions.find((action) => action.id === actionId);
if (pcAction) {
return pcAction;
}
const { attributes } = await unsecuredSavedObjectsClient.get<RawAction>('action', actionId);
return attributes;
}