From 337e6925a0bfd33cbc622bc0f265bd0fd2ec866f Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Fri, 6 Sep 2024 16:25:54 +0200 Subject: [PATCH] [Entity Manager] Exposing stop and start transforms actions in the EntityClient (#192186) This PR exposes two new methods (`startTransforms` and `stopTransforms`) in the EntityClient as proposed [here](https://github.com/elastic/elastic-entity-model/issues/160) This work is also required by the Entity Analytics team: https://github.com/elastic/security-team/issues/10230 In addition, it splits up `stop_and_delete_transforms` into more granular actions, one for stopping and another for deleting. The uninstall function is now responsible for appropriately calling those actions in sequence. --- .../server/lib/entities/delete_transforms.ts | 58 ++++++++++ .../lib/entities/install_entity_definition.ts | 34 +++--- ...start_transform.ts => start_transforms.ts} | 2 +- .../lib/entities/stop_and_delete_transform.ts | 102 ------------------ .../server/lib/entities/stop_transforms.ts | 65 +++++++++++ .../entities/uninstall_entity_definition.ts | 20 ++-- .../lib/entities/upgrade_entity_definition.ts | 4 +- .../server/lib/entity_client.ts | 14 ++- .../server/routes/enablement/enable.ts | 6 +- .../server/routes/entities/reset.ts | 39 ++++--- .../server/routes/entities/update.ts | 5 +- 11 files changed, 185 insertions(+), 164 deletions(-) create mode 100644 x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_transforms.ts rename x-pack/plugins/observability_solution/entity_manager/server/lib/entities/{start_transform.ts => start_transforms.ts} (97%) delete mode 100644 x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_and_delete_transform.ts create mode 100644 x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_transforms.ts diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_transforms.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_transforms.ts new file mode 100644 index 00000000000000..a66c0998c014dc --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/delete_transforms.ts @@ -0,0 +1,58 @@ +/* + * 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 { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { EntityDefinition } from '@kbn/entities-schema'; + +import { + generateHistoryTransformId, + generateHistoryBackfillTransformId, + generateLatestTransformId, +} from './helpers/generate_component_id'; +import { retryTransientEsErrors } from './helpers/retry'; +import { isBackfillEnabled } from './helpers/is_backfill_enabled'; + +export async function deleteTransforms( + esClient: ElasticsearchClient, + definition: EntityDefinition, + logger: Logger +) { + try { + const historyTransformId = generateHistoryTransformId(definition); + const latestTransformId = generateLatestTransformId(definition); + await retryTransientEsErrors( + () => + esClient.transform.deleteTransform( + { transform_id: historyTransformId, force: true }, + { ignore: [404] } + ), + { logger } + ); + if (isBackfillEnabled(definition)) { + const historyBackfillTransformId = generateHistoryBackfillTransformId(definition); + await retryTransientEsErrors( + () => + esClient.transform.deleteTransform( + { transform_id: historyBackfillTransformId, force: true }, + { ignore: [404] } + ), + { logger } + ); + } + await retryTransientEsErrors( + () => + esClient.transform.deleteTransform( + { transform_id: latestTransformId, force: true }, + { ignore: [404] } + ), + { logger } + ); + } catch (e) { + logger.error(`Cannot delete history transform [${definition.id}]: ${e}`); + throw e; + } +} diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts index 7b0b18bc08a57a..54e1a4cffcc391 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/install_entity_definition.ts @@ -32,11 +32,7 @@ import { saveEntityDefinition, updateEntityDefinition, } from './save_entity_definition'; -import { - stopAndDeleteHistoryBackfillTransform, - stopAndDeleteHistoryTransform, - stopAndDeleteLatestTransform, -} from './stop_and_delete_transform'; + import { isBackfillEnabled } from './helpers/is_backfill_enabled'; import { deleteTemplate, upsertTemplate } from '../manage_index_templates'; import { generateEntitiesLatestIndexTemplateConfig } from './templates/entities_latest_template'; @@ -45,6 +41,8 @@ import { EntityIdConflict } from './errors/entity_id_conflict_error'; import { EntityDefinitionNotFound } from './errors/entity_not_found'; import { EntityDefinitionWithState } from './types'; import { mergeEntityDefinitionUpdate } from './helpers/merge_definition_update'; +import { stopTransforms } from './stop_transforms'; +import { deleteTransforms } from './delete_transforms'; export interface InstallDefinitionParams { esClient: ElasticsearchClient; @@ -91,14 +89,7 @@ export async function installEntityDefinition({ return await install({ esClient, soClient, logger, definition: entityDefinition }); } catch (e) { logger.error(`Failed to install entity definition ${definition.id}: ${e}`); - - await Promise.all([ - stopAndDeleteHistoryTransform(esClient, definition, logger), - isBackfillEnabled(definition) - ? stopAndDeleteHistoryBackfillTransform(esClient, definition, logger) - : Promise.resolve(), - stopAndDeleteLatestTransform(esClient, definition, logger), - ]); + await stopAndDeleteTransforms(esClient, definition, logger); await Promise.all([ deleteHistoryIngestPipeline(esClient, definition, logger), @@ -253,13 +244,7 @@ export async function reinstallEntityDefinition({ }); logger.debug(`Deleting transforms for definition ${definition.id} v${definition.version}`); - await Promise.all([ - stopAndDeleteHistoryTransform(esClient, definition, logger), - isBackfillEnabled(definition) - ? stopAndDeleteHistoryBackfillTransform(esClient, definition, logger) - : Promise.resolve(), - stopAndDeleteLatestTransform(esClient, definition, logger), - ]); + await stopAndDeleteTransforms(esClient, definition, logger); return await install({ esClient, @@ -308,3 +293,12 @@ const shouldReinstallBuiltinDefinition = ( return timedOut || outdated || failed || partial; }; + +const stopAndDeleteTransforms = async ( + esClient: ElasticsearchClient, + definition: EntityDefinition, + logger: Logger +) => { + await stopTransforms(esClient, definition, logger); + await deleteTransforms(esClient, definition, logger); +}; diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/start_transform.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/start_transforms.ts similarity index 97% rename from x-pack/plugins/observability_solution/entity_manager/server/lib/entities/start_transform.ts rename to x-pack/plugins/observability_solution/entity_manager/server/lib/entities/start_transforms.ts index 46bb16ff00ae3f..ea2ec7adb5ddcf 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/start_transform.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/start_transforms.ts @@ -15,7 +15,7 @@ import { import { retryTransientEsErrors } from './helpers/retry'; import { isBackfillEnabled } from './helpers/is_backfill_enabled'; -export async function startTransform( +export async function startTransforms( esClient: ElasticsearchClient, definition: EntityDefinition, logger: Logger diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_and_delete_transform.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_and_delete_transform.ts deleted file mode 100644 index 17ffeb44affc14..00000000000000 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_and_delete_transform.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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 { ElasticsearchClient, Logger } from '@kbn/core/server'; -import { EntityDefinition } from '@kbn/entities-schema'; -import { - generateHistoryBackfillTransformId, - generateHistoryTransformId, - generateLatestTransformId, -} from './helpers/generate_component_id'; -import { retryTransientEsErrors } from './helpers/retry'; - -export async function stopAndDeleteHistoryTransform( - esClient: ElasticsearchClient, - definition: EntityDefinition, - logger: Logger -) { - try { - const historyTransformId = generateHistoryTransformId(definition); - await retryTransientEsErrors( - () => - esClient.transform.stopTransform( - { transform_id: historyTransformId, wait_for_completion: true, force: true }, - { ignore: [409, 404] } - ), - { logger } - ); - await retryTransientEsErrors( - () => - esClient.transform.deleteTransform( - { transform_id: historyTransformId, force: true }, - { ignore: [404] } - ), - { logger } - ); - } catch (e) { - logger.error(`Cannot stop or delete history transform [${definition.id}]: ${e}`); - throw e; - } -} - -export async function stopAndDeleteHistoryBackfillTransform( - esClient: ElasticsearchClient, - definition: EntityDefinition, - logger: Logger -) { - try { - const historyBackfillTransformId = generateHistoryBackfillTransformId(definition); - await retryTransientEsErrors( - () => - esClient.transform.stopTransform( - { transform_id: historyBackfillTransformId, wait_for_completion: true, force: true }, - { ignore: [409, 404] } - ), - { logger } - ); - await retryTransientEsErrors( - () => - esClient.transform.deleteTransform( - { transform_id: historyBackfillTransformId, force: true }, - { ignore: [404] } - ), - { logger } - ); - } catch (e) { - logger.error(`Cannot stop or delete history backfill transform [${definition.id}]: ${e}`); - throw e; - } -} - -export async function stopAndDeleteLatestTransform( - esClient: ElasticsearchClient, - definition: EntityDefinition, - logger: Logger -) { - try { - const latestTransformId = generateLatestTransformId(definition); - await retryTransientEsErrors( - () => - esClient.transform.stopTransform( - { transform_id: latestTransformId, wait_for_completion: true, force: true }, - { ignore: [409, 404] } - ), - { logger } - ); - await retryTransientEsErrors( - () => - esClient.transform.deleteTransform( - { transform_id: latestTransformId, force: true }, - { ignore: [404] } - ), - { logger } - ); - } catch (e) { - logger.error(`Cannot stop or delete latest transform [${definition.id}]`); - throw e; - } -} diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_transforms.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_transforms.ts new file mode 100644 index 00000000000000..98f9ad351e3771 --- /dev/null +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/stop_transforms.ts @@ -0,0 +1,65 @@ +/* + * 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 { ElasticsearchClient, Logger } from '@kbn/core/server'; +import { EntityDefinition } from '@kbn/entities-schema'; + +import { + generateHistoryTransformId, + generateHistoryBackfillTransformId, + generateLatestTransformId, +} from './helpers/generate_component_id'; +import { retryTransientEsErrors } from './helpers/retry'; + +import { isBackfillEnabled } from './helpers/is_backfill_enabled'; + +export async function stopTransforms( + esClient: ElasticsearchClient, + definition: EntityDefinition, + logger: Logger +) { + try { + const historyTransformId = generateHistoryTransformId(definition); + const latestTransformId = generateLatestTransformId(definition); + + await retryTransientEsErrors( + () => + esClient.transform.stopTransform( + { transform_id: historyTransformId, wait_for_completion: true, force: true }, + { ignore: [409, 404] } + ), + { logger } + ); + + if (isBackfillEnabled(definition)) { + const historyBackfillTransformId = generateHistoryBackfillTransformId(definition); + await retryTransientEsErrors( + () => + esClient.transform.stopTransform( + { + transform_id: historyBackfillTransformId, + wait_for_completion: true, + force: true, + }, + { ignore: [409, 404] } + ), + { logger } + ); + } + await retryTransientEsErrors( + () => + esClient.transform.stopTransform( + { transform_id: latestTransformId, wait_for_completion: true, force: true }, + { ignore: [409, 404] } + ), + { logger } + ); + } catch (e) { + logger.error(`Cannot stop entity transforms [${definition.id}]: ${e}`); + throw e; + } +} diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/uninstall_entity_definition.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/uninstall_entity_definition.ts index 64871084333c37..8bc8efa3870aa8 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/uninstall_entity_definition.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/uninstall_entity_definition.ts @@ -13,18 +13,17 @@ import { deleteEntityDefinition } from './delete_entity_definition'; import { deleteIndices } from './delete_index'; import { deleteHistoryIngestPipeline, deleteLatestIngestPipeline } from './delete_ingest_pipeline'; import { findEntityDefinitions } from './find_entity_definition'; -import { - stopAndDeleteHistoryBackfillTransform, - stopAndDeleteHistoryTransform, - stopAndDeleteLatestTransform, -} from './stop_and_delete_transform'; -import { isBackfillEnabled } from './helpers/is_backfill_enabled'; + import { generateHistoryIndexTemplateId, generateLatestIndexTemplateId, } from './helpers/generate_component_id'; import { deleteTemplate } from '../manage_index_templates'; +import { stopTransforms } from './stop_transforms'; + +import { deleteTransforms } from './delete_transforms'; + export async function uninstallEntityDefinition({ definition, esClient, @@ -38,13 +37,8 @@ export async function uninstallEntityDefinition({ logger: Logger; deleteData?: boolean; }) { - await Promise.all([ - stopAndDeleteHistoryTransform(esClient, definition, logger), - stopAndDeleteLatestTransform(esClient, definition, logger), - isBackfillEnabled(definition) - ? stopAndDeleteHistoryBackfillTransform(esClient, definition, logger) - : Promise.resolve(), - ]); + await stopTransforms(esClient, definition, logger); + await deleteTransforms(esClient, definition, logger); await Promise.all([ deleteHistoryIngestPipeline(esClient, definition, logger), diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/upgrade_entity_definition.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/upgrade_entity_definition.ts index aa94160039b047..a4d44cd45ee17c 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/upgrade_entity_definition.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entities/upgrade_entity_definition.ts @@ -7,7 +7,7 @@ import { EntityDefinition } from '@kbn/entities-schema'; import { installBuiltInEntityDefinitions } from './install_entity_definition'; -import { startTransform } from './start_transform'; +import { startTransforms } from './start_transforms'; import { EntityManagerServerSetup } from '../../types'; import { checkIfEntityDiscoveryAPIKeyIsValid, readEntityDiscoveryAPIKey } from '../auth'; import { getClientsFromAPIKey } from '../utils'; @@ -46,7 +46,7 @@ export async function upgradeBuiltInEntityDefinitions({ }); await Promise.all( - upgradedDefinitions.map((definition) => startTransform(esClient, definition, logger)) + upgradedDefinitions.map((definition) => startTransforms(esClient, definition, logger)) ); return { success: true, definitions: upgradedDefinitions }; diff --git a/x-pack/plugins/observability_solution/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/observability_solution/entity_manager/server/lib/entity_client.ts index 7fd0bb3c5ee184..0503bdf7708187 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/lib/entity_client.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/lib/entity_client.ts @@ -10,11 +10,13 @@ import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { Logger } from '@kbn/logging'; import { installEntityDefinition } from './entities/install_entity_definition'; -import { startTransform } from './entities/start_transform'; +import { startTransforms } from './entities/start_transforms'; import { findEntityDefinitions } from './entities/find_entity_definition'; import { uninstallEntityDefinition } from './entities/uninstall_entity_definition'; import { EntityDefinitionNotFound } from './entities/errors/entity_not_found'; +import { stopTransforms } from './entities/stop_transforms'; + export class EntityClient { constructor( private options: { @@ -39,7 +41,7 @@ export class EntityClient { }); if (!installOnly) { - await startTransform(this.options.esClient, definition, this.options.logger); + await startTransforms(this.options.esClient, definition, this.options.logger); } return installedDefinition; @@ -78,4 +80,12 @@ export class EntityClient { return { definitions }; } + + async startEntityDefinition(definition: EntityDefinition) { + return startTransforms(this.options.esClient, definition, this.options.logger); + } + + async stopEntityDefinition(definition: EntityDefinition) { + return stopTransforms(this.options.esClient, definition, this.options.logger); + } } diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts index a1d37de7c8df2e..9814840d20a0bd 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/enablement/enable.ts @@ -19,10 +19,12 @@ import { } from '../../lib/auth'; import { builtInDefinitions } from '../../lib/entities/built_in'; import { installBuiltInEntityDefinitions } from '../../lib/entities/install_entity_definition'; -import { startTransform } from '../../lib/entities/start_transform'; + import { EntityDiscoveryApiKeyType } from '../../saved_objects'; import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { startTransforms } from '../../lib/entities/start_transforms'; + /** * @openapi * /internal/entities/managed/enablement: @@ -125,7 +127,7 @@ export const enableEntityDiscoveryRoute = createEntityManagerServerRoute({ if (!params.query.installOnly) { await Promise.all( installedDefinitions.map((installedDefinition) => - startTransform(esClient, installedDefinition, logger) + startTransforms(esClient, installedDefinition, logger) ) ); } diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts index e6f4d18339a998..a59c38b3acf7c4 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/reset.ts @@ -7,6 +7,16 @@ import { resetEntityDefinitionParamsSchema } from '@kbn/entities-schema'; import { z } from '@kbn/zod'; + +import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; +import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; +import { readEntityDefinition } from '../../lib/entities/read_entity_definition'; + +import { + deleteHistoryIngestPipeline, + deleteLatestIngestPipeline, +} from '../../lib/entities/delete_ingest_pipeline'; +import { deleteIndices } from '../../lib/entities/delete_index'; import { createAndInstallHistoryIngestPipeline, createAndInstallLatestIngestPipeline, @@ -16,23 +26,14 @@ import { createAndInstallHistoryTransform, createAndInstallLatestTransform, } from '../../lib/entities/create_and_install_transform'; -import { deleteIndices } from '../../lib/entities/delete_index'; -import { - deleteHistoryIngestPipeline, - deleteLatestIngestPipeline, -} from '../../lib/entities/delete_ingest_pipeline'; +import { startTransforms } from '../../lib/entities/start_transforms'; import { EntityDefinitionNotFound } from '../../lib/entities/errors/entity_not_found'; -import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; -import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; + import { isBackfillEnabled } from '../../lib/entities/helpers/is_backfill_enabled'; -import { readEntityDefinition } from '../../lib/entities/read_entity_definition'; -import { startTransform } from '../../lib/entities/start_transform'; -import { - stopAndDeleteHistoryBackfillTransform, - stopAndDeleteHistoryTransform, - stopAndDeleteLatestTransform, -} from '../../lib/entities/stop_and_delete_transform'; + import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { deleteTransforms } from '../../lib/entities/delete_transforms'; +import { stopTransforms } from '../../lib/entities/stop_transforms'; export const resetEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'POST /internal/entities/definition/{id}/_reset', @@ -47,11 +48,9 @@ export const resetEntityDefinitionRoute = createEntityManagerServerRoute({ const definition = await readEntityDefinition(soClient, params.path.id, logger); // Delete the transform and ingest pipeline - await stopAndDeleteHistoryTransform(esClient, definition, logger); - if (isBackfillEnabled(definition)) { - await stopAndDeleteHistoryBackfillTransform(esClient, definition, logger); - } - await stopAndDeleteLatestTransform(esClient, definition, logger); + await stopTransforms(esClient, definition, logger); + await deleteTransforms(esClient, definition, logger); + await deleteHistoryIngestPipeline(esClient, definition, logger); await deleteLatestIngestPipeline(esClient, definition, logger); await deleteIndices(esClient, definition, logger); @@ -64,7 +63,7 @@ export const resetEntityDefinitionRoute = createEntityManagerServerRoute({ await createAndInstallHistoryBackfillTransform(esClient, definition, logger); } await createAndInstallLatestTransform(esClient, definition, logger); - await startTransform(esClient, definition, logger); + await startTransforms(esClient, definition, logger); return response.ok({ body: { acknowledged: true } }); } catch (e) { diff --git a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts index 99dee233a0e404..9cf72a9298d428 100644 --- a/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts +++ b/x-pack/plugins/observability_solution/entity_manager/server/routes/entities/update.ts @@ -13,11 +13,12 @@ import { z } from '@kbn/zod'; import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; import { findEntityDefinitionById } from '../../lib/entities/find_entity_definition'; +import { startTransforms } from '../../lib/entities/start_transforms'; import { installationInProgress, reinstallEntityDefinition, } from '../../lib/entities/install_entity_definition'; -import { startTransform } from '../../lib/entities/start_transform'; + import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; /** @@ -104,7 +105,7 @@ export const updateEntityDefinitionRoute = createEntityManagerServerRoute({ }); if (!params.query.installOnly) { - await startTransform(esClient, updatedDefinition, logger); + await startTransforms(esClient, updatedDefinition, logger); } return response.ok({ body: updatedDefinition });