diff --git a/x-pack/solutions/observability/plugins/synthetics/common/constants/synthetics/rest_api.ts b/x-pack/solutions/observability/plugins/synthetics/common/constants/synthetics/rest_api.ts index 2596069714f8d..b4e10b3f58e5c 100644 --- a/x-pack/solutions/observability/plugins/synthetics/common/constants/synthetics/rest_api.ts +++ b/x-pack/solutions/observability/plugins/synthetics/common/constants/synthetics/rest_api.ts @@ -31,7 +31,6 @@ export enum SYNTHETICS_API_URLS { INDEX_SIZE = `/internal/synthetics/index_size`, AGENT_POLICIES = `/internal/synthetics/agent_policies`, PRIVATE_LOCATIONS_MONITORS = `/internal/synthetics/private_locations/monitors`, - SYNC_GLOBAL_PARAMS = `/internal/synthetics/sync_global_params`, ENABLE_DEFAULT_ALERTING = `/internal/synthetics/enable_default_alerting`, GET_ACTIONS_CONNECTORS = `/internal/synthetics/get_actions_connectors`, GET_CONNECTOR_TYPES = `/internal/synthetics/get_connector_types`, diff --git a/x-pack/solutions/observability/plugins/synthetics/server/plugin.ts b/x-pack/solutions/observability/plugins/synthetics/server/plugin.ts index d327c36b3fe90..5783a44b1f0fb 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/plugin.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/plugin.ts @@ -30,6 +30,7 @@ import { SyntheticsService } from './synthetics_service/synthetics_service'; import { syntheticsServiceApiKey } from './saved_objects/service_api_key'; import { SYNTHETICS_RULE_TYPES_ALERT_CONTEXT } from '../common/constants/synthetics_alerts'; import { syntheticsRuleTypeFieldMap } from './alert_rules/common'; +import { SyncPrivateLocationMonitorsTask } from './tasks/sync_private_locations_monitors_task'; export class Plugin implements PluginType { private savedObjectsClient?: SavedObjectsClientContract; @@ -38,6 +39,7 @@ export class Plugin implements PluginType { private syntheticsService?: SyntheticsService; private syntheticsMonitorClient?: SyntheticsMonitorClient; private readonly telemetryEventsSender: TelemetryEventsSender; + private syncPrivateLocationMonitorsTask?: SyncPrivateLocationMonitorsTask; constructor(private readonly initContext: PluginInitializerContext) { this.logger = initContext.logger.get(); @@ -89,6 +91,12 @@ export class Plugin implements PluginType { registerSyntheticsSavedObjects(core.savedObjects, plugins.encryptedSavedObjects); + this.syncPrivateLocationMonitorsTask = new SyncPrivateLocationMonitorsTask( + this.server, + plugins.taskManager, + this.syntheticsMonitorClient + ); + return {}; } @@ -107,6 +115,9 @@ export class Plugin implements PluginType { this.server.spaces = pluginsStart.spaces; this.server.isElasticsearchServerless = coreStart.elasticsearch.getCapabilities().serverless; } + this.syncPrivateLocationMonitorsTask?.start().catch(() => { + this.logger.error('Failed to start sync private location monitors task'); + }); this.syntheticsService?.start(pluginsStart.taskManager); diff --git a/x-pack/solutions/observability/plugins/synthetics/server/routes/index.ts b/x-pack/solutions/observability/plugins/synthetics/server/routes/index.ts index f6657a33b5a12..19b45f7b662dd 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/routes/index.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/routes/index.ts @@ -29,7 +29,6 @@ import { createJourneyScreenshotBlocksRoute } from './pings/journey_screenshot_b import { createLastSuccessfulCheckRoute } from './pings/last_successful_check'; import { createJourneyFailedStepsRoute, createJourneyRoute } from './pings/journeys'; import { updateDefaultAlertingRoute } from './default_alerts/update_default_alert'; -import { syncParamsSyntheticsParamsRoute } from './settings/sync_global_params'; import { getIndexSizesRoute } from './settings/settings'; import { getAPIKeySyntheticsRoute } from './monitor_cruds/get_api_key'; import { getServiceLocationsRoute } from './synthetics_service/get_service_locations'; @@ -80,7 +79,6 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ getHasIntegrationMonitorsRoute, createGetCurrentStatusRoute, getIndexSizesRoute, - syncParamsSyntheticsParamsRoute, enableDefaultAlertingRoute, getDefaultAlertingRoute, updateDefaultAlertingRoute, diff --git a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/add_param.ts b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/add_param.ts index 314938d32a561..217d0eaf88a97 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/add_param.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/add_param.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import { ALL_SPACES_ID } from '@kbn/security-plugin/common/constants'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { SavedObject, SavedObjectsBulkCreateObject } from '@kbn/core-saved-objects-api-server'; -import { syncSpaceGlobalParams } from '../../../synthetics_service/sync_global_params'; +import { runSynPrivateLocationMonitorsTaskSoon } from '../../../tasks/sync_private_locations_monitors_task'; import { SyntheticsRestApiRouteFactory } from '../../types'; import { SyntheticsParamRequest, @@ -42,7 +42,7 @@ export const addSyntheticsParamsRoute: SyntheticsRestApiRouteFactory< body: schema.oneOf([ParamsObjectSchema, schema.arrayOf(ParamsObjectSchema)]), }, }, - handler: async ({ request, response, server, savedObjectsClient, syntheticsMonitorClient }) => { + handler: async ({ request, response, server, savedObjectsClient }) => { try { const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? { id: DEFAULT_SPACE_ID, @@ -57,12 +57,8 @@ export const addSyntheticsParamsRoute: SyntheticsRestApiRouteFactory< savedObjectsData ); - void syncSpaceGlobalParams({ - spaceId, - logger: server.logger, - encryptedSavedObjects: server.encryptedSavedObjects, - savedObjects: server.coreStart.savedObjects, - syntheticsMonitorClient, + await runSynPrivateLocationMonitorsTaskSoon({ + server, }); if (savedObjectsData.length > 1) { diff --git a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/delete_param.ts b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/delete_param.ts index 5b8d9f7cd842a..2f32346155e82 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/delete_param.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/delete_param.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; -import { syncSpaceGlobalParams } from '../../../synthetics_service/sync_global_params'; +import { runSynPrivateLocationMonitorsTaskSoon } from '../../../tasks/sync_private_locations_monitors_task'; import { SyntheticsRestApiRouteFactory } from '../../types'; import { syntheticsParamType } from '../../../../common/types/saved_objects'; import { SYNTHETICS_API_URLS } from '../../../../common/constants'; @@ -36,14 +36,7 @@ export const deleteSyntheticsParamsRoute: SyntheticsRestApiRouteFactory< }), }, }, - handler: async ({ - savedObjectsClient, - request, - response, - server, - spaceId, - syntheticsMonitorClient, - }) => { + handler: async ({ savedObjectsClient, request, response, server }) => { const { ids } = request.body ?? {}; const { id: paramId } = request.params ?? {}; @@ -69,13 +62,8 @@ export const deleteSyntheticsParamsRoute: SyntheticsRestApiRouteFactory< idsToDelete.map((id) => ({ type: syntheticsParamType, id })), { force: true } ); - - void syncSpaceGlobalParams({ - spaceId, - logger: server.logger, - encryptedSavedObjects: server.encryptedSavedObjects, - savedObjects: server.coreStart.savedObjects, - syntheticsMonitorClient, + await runSynPrivateLocationMonitorsTaskSoon({ + server, }); return result.statuses.map(({ id, success }) => ({ id, deleted: success })); diff --git a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/delete_params_bulk.ts b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/delete_params_bulk.ts index 6d010db07e66e..c0ead7403eebf 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/delete_params_bulk.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/delete_params_bulk.ts @@ -6,7 +6,7 @@ */ import { schema } from '@kbn/config-schema'; -import { syncSpaceGlobalParams } from '../../../synthetics_service/sync_global_params'; +import { runSynPrivateLocationMonitorsTaskSoon } from '../../../tasks/sync_private_locations_monitors_task'; import { SyntheticsRestApiRouteFactory } from '../../types'; import { syntheticsParamType } from '../../../../common/types/saved_objects'; import { SYNTHETICS_API_URLS } from '../../../../common/constants'; @@ -28,7 +28,7 @@ export const deleteSyntheticsParamsBulkRoute: SyntheticsRestApiRouteFactory< }), }, }, - handler: async ({ savedObjectsClient, request, server, spaceId, syntheticsMonitorClient }) => { + handler: async ({ savedObjectsClient, request, server, spaceId }) => { const { ids } = request.body; const result = await savedObjectsClient.bulkDelete( @@ -36,12 +36,8 @@ export const deleteSyntheticsParamsBulkRoute: SyntheticsRestApiRouteFactory< { force: true } ); - void syncSpaceGlobalParams({ - spaceId, - logger: server.logger, - encryptedSavedObjects: server.encryptedSavedObjects, - savedObjects: server.coreStart.savedObjects, - syntheticsMonitorClient, + await runSynPrivateLocationMonitorsTaskSoon({ + server, }); return result.statuses.map(({ id, success }) => ({ id, deleted: success })); diff --git a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/edit_param.ts b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/edit_param.ts index bdec48fb4fadd..b1cf731308ac0 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/edit_param.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/params/edit_param.ts @@ -8,7 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { SavedObject, SavedObjectsErrorHelpers } from '@kbn/core/server'; import { isEmpty } from 'lodash'; -import { syncSpaceGlobalParams } from '../../../synthetics_service/sync_global_params'; +import { runSynPrivateLocationMonitorsTaskSoon } from '../../../tasks/sync_private_locations_monitors_task'; import { validateRouteSpaceName } from '../../common'; import { SyntheticsRestApiRouteFactory } from '../../types'; import { SyntheticsParamRequest, SyntheticsParams } from '../../../../common/runtime_types'; @@ -48,8 +48,7 @@ export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory< }, }, handler: async (routeContext) => { - const { savedObjectsClient, request, response, spaceId, server, syntheticsMonitorClient } = - routeContext; + const { savedObjectsClient, request, response, spaceId, server } = routeContext; const { invalidResponse } = await validateRouteSpaceName(routeContext); if (invalidResponse) return invalidResponse; @@ -85,12 +84,8 @@ export const editSyntheticsParamsRoute: SyntheticsRestApiRouteFactory< newParam )) as SavedObject; - void syncSpaceGlobalParams({ - spaceId, - logger: server.logger, - encryptedSavedObjects: server.encryptedSavedObjects, - savedObjects: server.coreStart.savedObjects, - syntheticsMonitorClient, + await runSynPrivateLocationMonitorsTaskSoon({ + server, }); return { id: responseId, key, tags, description, namespaces, value }; diff --git a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/sync_global_params.ts b/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/sync_global_params.ts deleted file mode 100644 index 05e6a2860b2cc..0000000000000 --- a/x-pack/solutions/observability/plugins/synthetics/server/routes/settings/sync_global_params.ts +++ /dev/null @@ -1,36 +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 { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; -import { SyntheticsRestApiRouteFactory } from '../types'; -import { getPrivateLocations } from '../../synthetics_service/get_private_locations'; -import { SYNTHETICS_API_URLS } from '../../../common/constants'; - -export const syncParamsSyntheticsParamsRoute: SyntheticsRestApiRouteFactory = () => ({ - method: 'GET', - path: SYNTHETICS_API_URLS.SYNC_GLOBAL_PARAMS, - validate: {}, - handler: async ({ - savedObjectsClient, - syntheticsMonitorClient, - request, - server, - }): Promise => { - const spaceId = server.spaces?.spacesService.getSpaceId(request) ?? DEFAULT_SPACE_ID; - - const allPrivateLocations = await getPrivateLocations(savedObjectsClient); - - await syntheticsMonitorClient.syncGlobalParams({ - spaceId, - allPrivateLocations, - soClient: savedObjectsClient, - encryptedSavedObjects: server.encryptedSavedObjects, - }); - - return { success: true }; - }, -}); diff --git a/x-pack/solutions/observability/plugins/synthetics/server/synthetics_service/sync_global_params.ts b/x-pack/solutions/observability/plugins/synthetics/server/synthetics_service/sync_global_params.ts deleted file mode 100644 index d90ca0eec524a..0000000000000 --- a/x-pack/solutions/observability/plugins/synthetics/server/synthetics_service/sync_global_params.ts +++ /dev/null @@ -1,44 +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 { Logger } from '@kbn/logging'; -import { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-plugin/server/plugin'; -import { SavedObjectsServiceStart } from '@kbn/core/server'; -import pRetry from 'p-retry'; -import { getPrivateLocations } from './get_private_locations'; -import { SyntheticsMonitorClient } from './synthetics_monitor/synthetics_monitor_client'; - -export const syncSpaceGlobalParams = async ({ - spaceId, - savedObjects, - logger, - syntheticsMonitorClient, - encryptedSavedObjects, -}: { - spaceId: string; - savedObjects: SavedObjectsServiceStart; - logger: Logger; - syntheticsMonitorClient: SyntheticsMonitorClient; - encryptedSavedObjects: EncryptedSavedObjectsPluginStart; -}) => { - try { - await pRetry(async () => { - logger.debug(`Syncing global parameters of space with id ${spaceId}`); - const savedObjectsClient = savedObjects.createInternalRepository(); - const allPrivateLocations = await getPrivateLocations(savedObjectsClient); - await syntheticsMonitorClient.syncGlobalParams({ - spaceId, - allPrivateLocations, - soClient: savedObjectsClient, - encryptedSavedObjects, - }); - logger.debug(`Sync of global parameters for space with id ${spaceId} succeeded`); - }); - } catch (error) { - logger.error(`Sync of global parameters for space with id ${spaceId} failed: ${error.message}`); - } -}; diff --git a/x-pack/solutions/observability/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts b/x-pack/solutions/observability/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts index c7f2ffc73daf1..9b09c19f31061 100644 --- a/x-pack/solutions/observability/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts +++ b/x-pack/solutions/observability/plugins/synthetics/server/synthetics_service/synthetics_monitor/synthetics_monitor_client.ts @@ -4,11 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { SavedObject, SavedObjectsClientContract, SavedObjectsFindResult } from '@kbn/core/server'; -import { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-plugin/server'; -import { MonitorConfigRepository } from '../../services/monitor_config_repository'; +import { SavedObject, SavedObjectsClientContract } from '@kbn/core/server'; import { SyntheticsServerSetup } from '../../types'; -import { syntheticsMonitorType } from '../../../common/types/saved_objects'; import { normalizeSecrets } from '../utils'; import { PrivateConfig, @@ -271,131 +268,6 @@ export class SyntheticsMonitorClient { return { privateLocations, publicLocations }; } - async syncGlobalParams({ - spaceId, - allPrivateLocations, - encryptedSavedObjects, - soClient, - }: { - spaceId: string; - soClient: SavedObjectsClientContract; - allPrivateLocations: PrivateLocationAttributes[]; - encryptedSavedObjects: EncryptedSavedObjectsPluginStart; - }) { - const privateConfigs: Array<{ config: HeartbeatConfig; globalParams: Record }> = - []; - const publicConfigs: ConfigData[] = []; - - const { allConfigs: monitors, paramsBySpace } = await this.getAllMonitorConfigs({ - encryptedSavedObjects, - soClient, - spaceId, - }); - - for (const monitor of monitors) { - const { publicLocations, privateLocations } = this.parseLocations(monitor); - if (publicLocations.length > 0) { - publicConfigs.push({ spaceId, monitor, configId: monitor.config_id, params: {} }); - } - - if (privateLocations.length > 0) { - privateConfigs.push({ config: monitor, globalParams: paramsBySpace[monitor.namespace] }); - } - } - if (privateConfigs.length > 0) { - await this.privateLocationAPI.editMonitors(privateConfigs, allPrivateLocations, spaceId); - } - - if (publicConfigs.length > 0) { - return await this.syntheticsService.editConfig(publicConfigs, false); - } - } - - async getAllMonitorConfigs({ - spaceId, - soClient, - encryptedSavedObjects, - }: { - spaceId: string; - soClient: SavedObjectsClientContract; - encryptedSavedObjects: EncryptedSavedObjectsPluginStart; - }) { - const paramsBySpacePromise = this.syntheticsService.getSyntheticsParams({ spaceId }); - const monitorConfigRepository = new MonitorConfigRepository( - soClient, - encryptedSavedObjects.getClient() - ); - - const monitorsPromise = monitorConfigRepository.findDecryptedMonitors({ spaceId }); - - const [paramsBySpace, monitors] = await Promise.all([paramsBySpacePromise, monitorsPromise]); - - return { - allConfigs: this.mixParamsWithMonitors(spaceId, monitors, paramsBySpace), - paramsBySpace, - }; - } - - async getAllMonitors({ - spaceId, - encryptedSavedObjects, - }: { - spaceId: string; - encryptedSavedObjects: EncryptedSavedObjectsPluginStart; - }) { - const encryptedClient = encryptedSavedObjects.getClient(); - - const monitors: Array> = []; - - const finder = - await encryptedClient.createPointInTimeFinderDecryptedAsInternalUser( - { - type: syntheticsMonitorType, - perPage: 1000, - namespaces: [spaceId], - } - ); - - for await (const response of finder.find()) { - response.saved_objects.forEach((monitor) => { - monitors.push(monitor); - }); - } - - finder.close().catch(() => {}); - - return monitors; - } - - mixParamsWithMonitors( - spaceId: string, - monitors: Array>, - paramsBySpace: Record> - ) { - const heartbeatConfigs: HeartbeatConfig[] = []; - - for (const monitor of monitors) { - const normalizedMonitor = normalizeSecrets(monitor).attributes as MonitorFields; - const { str: paramsString } = mixParamsWithGlobalParams( - paramsBySpace[spaceId], - normalizedMonitor - ); - - heartbeatConfigs.push( - formatHeartbeatRequest( - { - spaceId, - monitor: normalizedMonitor, - configId: monitor.id, - }, - paramsString - ) - ); - } - - return heartbeatConfigs; - } - async formatConfigWithParams( monitorObj: { monitor: MonitorFields; id: string }, spaceId: string, diff --git a/x-pack/solutions/observability/plugins/synthetics/server/tasks/sync_private_locations_monitors_task.ts b/x-pack/solutions/observability/plugins/synthetics/server/tasks/sync_private_locations_monitors_task.ts new file mode 100644 index 0000000000000..47632ee76ace3 --- /dev/null +++ b/x-pack/solutions/observability/plugins/synthetics/server/tasks/sync_private_locations_monitors_task.ts @@ -0,0 +1,298 @@ +/* + * 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 { TaskManagerSetupContract } from '@kbn/task-manager-plugin/server/plugin'; +import { + SavedObjectsClientContract, + SavedObjectsFindResult, +} from '@kbn/core-saved-objects-api-server'; +import { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-plugin/server'; +import { ALL_SPACES_ID } from '@kbn/spaces-plugin/common/constants'; +import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server'; +import moment from 'moment'; +import { syntheticsParamType } from '../../common/types/saved_objects'; +import { normalizeSecrets } from '../synthetics_service/utils'; +import type { PrivateLocationAttributes } from '../runtime_types/private_locations'; +import { + HeartbeatConfig, + MonitorFields, + SyntheticsMonitorWithSecretsAttributes, +} from '../../common/runtime_types'; +import { MonitorConfigRepository } from '../services/monitor_config_repository'; +import { SyntheticsMonitorClient } from '../synthetics_service/synthetics_monitor/synthetics_monitor_client'; +import { getPrivateLocations } from '../synthetics_service/get_private_locations'; +import { SyntheticsServerSetup } from '../types'; +import { + formatHeartbeatRequest, + mixParamsWithGlobalParams, +} from '../synthetics_service/formatters/public_formatters/format_configs'; + +const TASK_TYPE = 'Synthetics:Sync-Private-Location-Monitors'; +const TASK_ID = `${TASK_TYPE}-single-instance`; + +interface TaskState extends Record { + lastStartedAt: string; + lastTotalParams: number; +} + +type CustomTaskInstance = Omit & { state: Partial }; + +export class SyncPrivateLocationMonitorsTask { + constructor( + public serverSetup: SyntheticsServerSetup, + public taskManager: TaskManagerSetupContract, + public syntheticsMonitorClient: SyntheticsMonitorClient + ) { + taskManager.registerTaskDefinitions({ + [TASK_TYPE]: { + title: 'Synthetics Sync Global Params Task', + description: + 'This task is executed so that we can sync private location monitors for example when global params are updated', + timeout: '3m', + maxAttempts: 3, + createTaskRunner: ({ taskInstance }) => { + return { + run: async () => { + return this.runTask({ taskInstance }); + }, + }; + }, + }, + }); + } + + public async runTask({ + taskInstance, + }: { + taskInstance: CustomTaskInstance; + }): Promise<{ state: TaskState; error?: Error }> { + const { + coreStart: { savedObjects }, + encryptedSavedObjects, + logger, + } = this.serverSetup; + const lastStartedAt = + taskInstance.state.lastStartedAt || moment().subtract(10, 'minute').toISOString(); + const startedAt = taskInstance.startedAt || new Date(); + let lastTotalParams = taskInstance.state.lastTotalParams || 0; + try { + logger.debug( + `Syncing private location monitors, last total params ${lastTotalParams}, last run ${lastStartedAt}` + ); + const soClient = savedObjects.createInternalRepository(); + const allPrivateLocations = await getPrivateLocations(soClient); + const { updatedParams, totalParams } = await this.hasAnyParamChanged(soClient, lastStartedAt); + if (updatedParams > 0 || totalParams !== lastTotalParams) { + lastTotalParams = totalParams; + logger.debug( + `Syncing private location monitors because params changed, updated params ${updatedParams}, total params ${totalParams}` + ); + + if (allPrivateLocations.length > 0) { + await this.syncGlobalParams({ + allPrivateLocations, + soClient, + encryptedSavedObjects, + }); + } + logger.debug(`Sync of private location monitors succeeded`); + } else { + lastTotalParams = totalParams; + logger.debug( + `No params changed since last run ${lastStartedAt}, skipping sync of private location monitors` + ); + } + } catch (error) { + logger.error(`Sync of private location monitors failed: ${error.message}`); + return { + error, + state: { + lastStartedAt: startedAt.toISOString(), + lastTotalParams, + }, + }; + } + return { + state: { + lastStartedAt: startedAt.toISOString(), + lastTotalParams, + }, + }; + } + + start = async () => { + const { + logger, + pluginsStart: { taskManager }, + } = this.serverSetup; + logger.debug(`Scheduling private location task`); + await taskManager.ensureScheduled({ + id: TASK_ID, + state: {}, + schedule: { + interval: '10m', + }, + taskType: TASK_TYPE, + params: {}, + }); + await taskManager.runSoon(TASK_ID); + logger.debug(`Sync private location monitors task scheduled successfully`); + }; + + async syncGlobalParams({ + allPrivateLocations, + encryptedSavedObjects, + soClient, + }: { + soClient: SavedObjectsClientContract; + allPrivateLocations: PrivateLocationAttributes[]; + encryptedSavedObjects: EncryptedSavedObjectsPluginStart; + }) { + const { privateLocationAPI } = this.syntheticsMonitorClient; + const privateConfigs: Array<{ config: HeartbeatConfig; globalParams: Record }> = + []; + + const { configsBySpaces, paramsBySpace, spaceIds } = await this.getAllMonitorConfigs({ + encryptedSavedObjects, + soClient, + }); + + for (const spaceId of spaceIds) { + const monitors = configsBySpaces[spaceId]; + if (!monitors) { + continue; + } + for (const monitor of monitors) { + const { privateLocations } = this.parseLocations(monitor); + + if (privateLocations.length > 0) { + privateConfigs.push({ config: monitor, globalParams: paramsBySpace[monitor.namespace] }); + } + } + if (privateConfigs.length > 0) { + await privateLocationAPI.editMonitors(privateConfigs, allPrivateLocations, spaceId); + } + } + } + + async getAllMonitorConfigs({ + soClient, + encryptedSavedObjects, + }: { + soClient: SavedObjectsClientContract; + encryptedSavedObjects: EncryptedSavedObjectsPluginStart; + }) { + const { syntheticsService } = this.syntheticsMonitorClient; + const paramsBySpacePromise = syntheticsService.getSyntheticsParams({ spaceId: ALL_SPACES_ID }); + const monitorConfigRepository = new MonitorConfigRepository( + soClient, + encryptedSavedObjects.getClient() + ); + + const monitorsPromise = monitorConfigRepository.findDecryptedMonitors({ + spaceId: ALL_SPACES_ID, + }); + + const [paramsBySpace, monitors] = await Promise.all([paramsBySpacePromise, monitorsPromise]); + + return { + ...this.mixParamsWithMonitors(monitors, paramsBySpace), + paramsBySpace, + }; + } + + parseLocations(config: HeartbeatConfig) { + const { locations } = config; + + const privateLocations = locations.filter((loc) => !loc.isServiceManaged); + const publicLocations = locations.filter((loc) => loc.isServiceManaged); + + return { privateLocations, publicLocations }; + } + + mixParamsWithMonitors( + monitors: Array>, + paramsBySpace: Record> + ) { + const configsBySpaces: Record = {}; + const spaceIds = new Set(); + + for (const monitor of monitors) { + const spaceId = monitor.namespaces?.[0]; + if (!spaceId) { + continue; + } + spaceIds.add(spaceId); + const normalizedMonitor = normalizeSecrets(monitor).attributes as MonitorFields; + const { str: paramsString } = mixParamsWithGlobalParams( + paramsBySpace[spaceId], + normalizedMonitor + ); + + if (!configsBySpaces[spaceId]) { + configsBySpaces[spaceId] = []; + } + + configsBySpaces[spaceId].push( + formatHeartbeatRequest( + { + spaceId, + monitor: normalizedMonitor, + configId: monitor.id, + }, + paramsString + ) + ); + } + + return { configsBySpaces, spaceIds }; + } + + async hasAnyParamChanged(soClient: SavedObjectsClientContract, lastStartedAt: string) { + const { logger } = this.serverSetup; + const [editedParams, totalParams] = await Promise.all([ + soClient.find({ + type: syntheticsParamType, + perPage: 0, + namespaces: [ALL_SPACES_ID], + filter: `synthetics-param.updated_at > "${lastStartedAt}"`, + fields: [], + }), + soClient.find({ + type: syntheticsParamType, + perPage: 0, + namespaces: [ALL_SPACES_ID], + fields: [], + }), + ]); + logger.debug(`Found ${editedParams.total} params and ${totalParams.total} total params`); + return { + updatedParams: editedParams.total, + totalParams: totalParams.total, + }; + } +} + +export const runSynPrivateLocationMonitorsTaskSoon = async ({ + server, +}: { + server: SyntheticsServerSetup; +}) => { + const { + logger, + pluginsStart: { taskManager }, + } = server; + try { + logger.debug(`Scheduling Synthetics sync private location monitors task soon`); + await taskManager.runSoon(TASK_ID); + logger.debug(`Synthetics sync private location task scheduled successfully`); + } catch (error) { + logger.error( + `Error scheduling Synthetics sync private location monitors task: ${error.message}` + ); + } +}; diff --git a/x-pack/solutions/observability/test/api_integration/apis/synthetics/sync_global_params.ts b/x-pack/solutions/observability/test/api_integration/apis/synthetics/sync_global_params.ts index 4c9ce95d06b57..fa23d85c3f64f 100644 --- a/x-pack/solutions/observability/test/api_integration/apis/synthetics/sync_global_params.ts +++ b/x-pack/solutions/observability/test/api_integration/apis/synthetics/sync_global_params.ts @@ -188,15 +188,6 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('sync global params', async () => { - const apiResponse = await supertestAPI - .get(SYNTHETICS_API_URLS.SYNC_GLOBAL_PARAMS) - .set('kbn-xsrf', 'true') - .send({ key: 'test', value: 'test' }); - - expect(apiResponse.status).eql(200); - }); - it('added params to for previously added integration', async () => { const apiResponse = await supertestAPI.get( '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' @@ -302,11 +293,6 @@ export default function ({ getService }: FtrProviderContext) { expect(getResponseAfterDelete.body.length).eql(0); - await supertestAPI - .get(SYNTHETICS_API_URLS.SYNC_GLOBAL_PARAMS) - .set('kbn-xsrf', 'true') - .expect(200); - const apiResponse = await supertestAPI.get( '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' ); diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/sync_global_params.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/sync_global_params.ts index 425eb0c704b50..94a5e859b2360 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/sync_global_params.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/sync_global_params.ts @@ -203,16 +203,6 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { }); }); - it('sync global params', async () => { - const apiResponse = await supertestAPI - .get(SYNTHETICS_API_URLS.SYNC_GLOBAL_PARAMS) - .set(editorUser.apiKeyHeader) - .set(samlAuth.getInternalRequestHeader()) - .send({ key: 'test', value: 'test' }); - - expect(apiResponse.status).eql(200); - }); - it('added params to for previously added integration', async () => { const apiResponse = await supertestWithAuth.get( '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' @@ -323,12 +313,6 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(getResponseAfterDelete.body.length).eql(0); - await supertestAPI - .get(SYNTHETICS_API_URLS.SYNC_GLOBAL_PARAMS) - .set(editorUser.apiKeyHeader) - .set(samlAuth.getInternalRequestHeader()) - .expect(200); - const apiResponse = await supertestWithAuth.get( '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' ); diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index 30148b4343040..d2d43b3ee88fe 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -57,6 +57,7 @@ export default function ({ getService }: FtrProviderContext) { 'ProductDocBase:UninstallAll', 'SLO:ORPHAN_SUMMARIES-CLEANUP-TASK', 'Synthetics:Clean-Up-Package-Policies', + 'Synthetics:Sync-Private-Location-Monitors', 'UPTIME:SyntheticsService:Sync-Saved-Monitor-Objects', 'actions:.bedrock', 'actions:.cases',