From e7a0b8038c36c713367c90d4c660b89e4d0c0de8 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Wed, 30 Oct 2024 17:03:07 +0100 Subject: [PATCH] [Entity Analytics] [Entity Store] Run init requests sequentially to prevent resource exists error (#198268) ## Summary This PR fixes an issue where running init for both `user` and `host` entity engines in parallel would cause a race condition while enabling the risk engine, resulting in a `Resource already exists` error. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit 6a50066e00ae38a64c5365fd66b4dc32857ba1fc) --- .../components/dashboard_panels.tsx | 1 + .../entity_store/hooks/use_entity_store.ts | 16 ++++++++++++---- .../elasticsearch_assets/ingest_pipeline.ts | 2 +- .../entity_store/entity_store_data_client.ts | 17 +++++++++++------ .../task/field_retention_enrichment_task.ts | 19 +++++++++++++------ 5 files changed, 38 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx index 3b4f661e949f2..8fa89e8d45a4e 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/components/dashboard_panels.tsx @@ -66,6 +66,7 @@ const EntityStoreDashboardPanelsComponent = () => { }; setRiskEngineInitializing(true); initRiskEngine(undefined, options); + return; } if (enable.entityStore) { diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts index 2d9fa716faf1c..5b84892ffb6fb 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/entity_store/hooks/use_entity_store.ts @@ -43,7 +43,8 @@ export const useEntityStoreEnablement = () => { const { initEntityStore } = useEntityStoreRoutes(); const { refetch: initialize } = useQuery({ queryKey: [ENTITY_STORE_ENABLEMENT_INIT], - queryFn: () => Promise.all([initEntityStore('user'), initEntityStore('host')]), + queryFn: async () => + initEntityStore('user').then((usr) => initEntityStore('host').then((host) => [usr, host])), enabled: false, }); @@ -79,7 +80,9 @@ export const useInitEntityEngineMutation = (options?: UseMutationOptions<{}>) => timestamp: new Date().toISOString(), action: 'start', }); - return Promise.all([initEntityStore('user'), initEntityStore('host')]); + return initEntityStore('user').then((usr) => + initEntityStore('host').then((host) => [usr, host]) + ); }, { ...options, @@ -107,7 +110,9 @@ export const useStopEntityEngineMutation = (options?: UseMutationOptions<{}>) => timestamp: new Date().toISOString(), action: 'stop', }); - return Promise.all([stopEntityStore('user'), stopEntityStore('host')]); + return stopEntityStore('user').then((usr) => + stopEntityStore('host').then((host) => [usr, host]) + ); }, { ...options, @@ -129,7 +134,10 @@ export const useDeleteEntityEngineMutation = (options?: UseMutationOptions<{}>) const invalidateEntityEngineStatusQuery = useInvalidateEntityEngineStatusQuery(); const { deleteEntityEngine } = useEntityStoreRoutes(); return useMutation( - () => Promise.all([deleteEntityEngine('user', true), deleteEntityEngine('host', true)]), + () => + deleteEntityEngine('user', true).then((usr) => + deleteEntityEngine('host', true).then((host) => [usr, host]) + ), { ...options, mutationKey: DELETE_ENTITY_ENGINE_STATUS_KEY, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts index c2a5bff51f830..f4d2b848b726f 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts @@ -125,7 +125,7 @@ export const createPlatformPipeline = async ({ managed_by: 'entity_store', managed: true, }, - description: `Ingest pipeline for entity defiinition ${entityManagerDefinition.id}`, + description: `Ingest pipeline for entity definition ${entityManagerDefinition.id}`, processors: buildIngestPipeline({ namespace: unitedDefinition.namespace, version: unitedDefinition.version, diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index 63d065a7f9d3c..48e3b8b577d05 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -146,7 +146,7 @@ export class EntityStoreDataClient { ); } logger.info( - `In namespace ${this.options.namespace}: Initializing entity store for ${entityType}` + `[Entity Store] In namespace ${this.options.namespace}: Initializing entity store for ${entityType}` ); const descriptor = await this.engineClient.init(entityType, { @@ -154,7 +154,7 @@ export class EntityStoreDataClient { fieldHistoryLength, indexPattern, }); - logger.debug(`Initialized engine for ${entityType}`); + logger.debug(`[Entity Store] Initialized saved object for ${entityType}`); // first create the entity definition without starting it // so that the index template is created which we can add a component template to @@ -167,7 +167,9 @@ export class EntityStoreDataClient { config, pipelineDebugMode ).catch((error) => { - logger.error(`There was an error during async setup of the Entity Store: ${error.message}`); + logger.error( + `[Entity Store] There was an error during async setup of the Entity Store: ${error.message}` + ); }); return descriptor; @@ -262,7 +264,7 @@ export class EntityStoreDataClient { logger, taskManager, }); - logger.info(`Entity store initialized for ${entityType}`); + debugLog(`Entity store initialized`); const setupEndTime = moment().utc().toISOString(); const duration = moment(setupEndTime).diff(moment(setupStartTime), 'seconds'); @@ -273,7 +275,7 @@ export class EntityStoreDataClient { return updated; } catch (err) { this.options.logger.error( - `Error initializing entity store for ${entityType}: ${err.message}` + `[Entity Store] Error initializing entity store for ${entityType}: ${err.message}` ); this.options.telemetry?.reportEvent(ENTITY_ENGINE_RESOURCE_INIT_FAILURE_EVENT.eventType, { @@ -371,7 +373,9 @@ export class EntityStoreDataClient { frequency: `${config.frequency.asSeconds()}s`, }); const { entityManagerDefinition } = unitedDefinition; - logger.info(`In namespace ${namespace}: Deleting entity store for ${entityType}`); + logger.info( + `[Entity Store] In namespace ${namespace}: Deleting entity store for ${entityType}` + ); try { try { await this.entityClient.deleteEntityDefinition({ @@ -418,6 +422,7 @@ export class EntityStoreDataClient { }); } + logger.info(`[Entity Store] In namespace ${namespace}: Deleted store for ${entityType}`); return { deleted: true }; } catch (e) { logger.error(`Error deleting entity store for ${entityType}: ${e.message}`); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts index 5227473e0e51b..708b74277faae 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts @@ -36,12 +36,12 @@ import { const logFactory = (logger: Logger, taskId: string) => (message: string): void => - logger.info(`[task ${taskId}]: ${message}`); + logger.info(`[Entity Store] [task ${taskId}]: ${message}`); const debugLogFactory = (logger: Logger, taskId: string) => (message: string): void => - logger.debug(`[task ${taskId}]: ${message}`); + logger.debug(`[Entity Store] [task ${taskId}]: ${message}`); const getTaskName = (): string => TYPE; @@ -65,7 +65,9 @@ export const registerEntityStoreFieldRetentionEnrichTask = ({ taskManager: TaskManagerSetupContract | undefined; }): void => { if (!taskManager) { - logger.info('Task Manager is unavailable; skipping entity store enrich policy registration.'); + logger.info( + '[Entity Store] Task Manager is unavailable; skipping entity store enrich policy registration.' + ); return; } @@ -134,7 +136,7 @@ export const startEntityStoreFieldRetentionEnrichTask = async ({ params: { version: VERSION }, }); } catch (e) { - logger.warn(`[task ${taskId}]: error scheduling task, received ${e.message}`); + logger.warn(`[Entity Store] [task ${taskId}]: error scheduling task, received ${e.message}`); throw e; } }; @@ -150,9 +152,14 @@ export const removeEntityStoreFieldRetentionEnrichTask = async ({ }) => { try { await taskManager.remove(getTaskId(namespace)); + logger.info( + `[Entity Store] Removed entity store enrich policy task for namespace ${namespace}` + ); } catch (err) { if (!SavedObjectsErrorHelpers.isNotFoundError(err)) { - logger.error(`Failed to remove entity store enrich policy task: ${err.message}`); + logger.error( + `[Entity Store] Failed to remove entity store enrich policy task: ${err.message}` + ); throw err; } } @@ -233,7 +240,7 @@ export const runTask = async ({ state: updatedState, }; } catch (e) { - logger.error(`[task ${taskId}]: error running task, received ${e.message}`); + logger.error(`[Entity Store] [task ${taskId}]: error running task, received ${e.message}`); throw e; } };