Skip to content

Commit

Permalink
[Synthetics] Improve project monitors creation performance (#140525)
Browse files Browse the repository at this point in the history
  • Loading branch information
shahzad31 authored Sep 19, 2022
1 parent f290977 commit 5cec30a
Show file tree
Hide file tree
Showing 17 changed files with 1,195 additions and 199 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import {
SavedObjectsErrorHelpers,
} from '@kbn/core/server';
import { isValidNamespace } from '@kbn/fleet-plugin/common';
import { getSyntheticsPrivateLocations } from '../../legacy_uptime/lib/saved_objects/private_locations';
import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client';
import {
ConfigKey,
MonitorFields,
SyntheticsMonitor,
EncryptedSyntheticsMonitor,
PrivateLocation,
} from '../../../common/runtime_types';
import { formatKibanaNamespace } from '../../../common/formatters';
import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types';
Expand Down Expand Up @@ -54,6 +56,8 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
// usually id is auto generated, but this is useful for testing
const { id } = request.query;

const spaceId = server.spaces.spacesService.getSpaceId(request);

const monitor: SyntheticsMonitor = request.body as SyntheticsMonitor;
const monitorType = monitor[ConfigKey.MONITOR_TYPE];
const monitorWithDefaults = {
Expand All @@ -68,6 +72,10 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
return response.badRequest({ body: { message, attributes: { details, ...payload } } });
}

const privateLocations: PrivateLocation[] = await getSyntheticsPrivateLocations(
savedObjectsClient
);

try {
const { errors, newMonitor } = await syncNewMonitor({
normalizedMonitor: monitorWithDefaults,
Expand All @@ -77,6 +85,8 @@ export const addSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => ({
savedObjectsClient,
request,
id,
privateLocations,
spaceId,
});

if (errors && errors.length > 0) {
Expand Down Expand Up @@ -136,6 +146,8 @@ export const syncNewMonitor = async ({
savedObjectsClient,
request,
normalizedMonitor,
privateLocations,
spaceId,
}: {
id?: string;
monitor: SyntheticsMonitor;
Expand All @@ -144,6 +156,8 @@ export const syncNewMonitor = async ({
syntheticsMonitorClient: SyntheticsMonitorClient;
savedObjectsClient: SavedObjectsClientContract;
request: KibanaRequest;
privateLocations: PrivateLocation[];
spaceId: string;
}) => {
const newMonitorId = id ?? uuidV4();
const { preserve_namespace: preserveNamespace } = request.query as Record<
Expand All @@ -166,14 +180,15 @@ export const syncNewMonitor = async ({
savedObjectsClient,
});

const syncErrorsPromise = syntheticsMonitorClient.addMonitor(
monitorWithNamespace as MonitorFields,
newMonitorId,
const syncErrorsPromise = syntheticsMonitorClient.addMonitors(
[{ monitor: monitorWithNamespace as MonitorFields, id: newMonitorId }],
request,
savedObjectsClient
savedObjectsClient,
privateLocations,
spaceId
);

const [monitorSavedObjectN, syncErrors] = await Promise.all([
const [monitorSavedObjectN, { syncErrors }] = await Promise.all([
newMonitorPromise,
syncErrorsPromise,
]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* 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, KibanaRequest, SavedObject } from '@kbn/core/server';
import pMap from 'p-map';
import { SavedObjectsBulkResponse } from '@kbn/core-saved-objects-api-server';
import { v4 as uuidV4 } from 'uuid';
import { formatTelemetryEvent, sendTelemetryEvents } from '../../telemetry/monitor_upgrade_sender';
import { deleteMonitor } from '../delete_monitor';
import { UptimeServerSetup } from '../../../legacy_uptime/lib/adapters';
import { formatSecrets } from '../../../synthetics_service/utils';
import { syntheticsMonitorType } from '../../../../common/types/saved_objects';
import {
ConfigKey,
EncryptedSyntheticsMonitor,
MonitorFields,
PrivateLocation,
ServiceLocationErrors,
SyntheticsMonitor,
} from '../../../../common/runtime_types';
import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_monitor/synthetics_monitor_client';

export const createNewSavedObjectMonitorBulk = async ({
soClient,
monitorsToCreate,
}: {
soClient: SavedObjectsClientContract;
monitorsToCreate: Array<{ id: string; monitor: MonitorFields }>;
}) => {
const newMonitors = monitorsToCreate.map(({ id, monitor }) => ({
id,
type: syntheticsMonitorType,
attributes: formatSecrets({
...monitor,
revision: 1,
}),
}));

return await soClient.bulkCreate<EncryptedSyntheticsMonitor>(newMonitors);
};

export const syncNewMonitorBulk = async ({
normalizedMonitors,
server,
syntheticsMonitorClient,
soClient,
request,
privateLocations,
spaceId,
}: {
normalizedMonitors: SyntheticsMonitor[];
server: UptimeServerSetup;
syntheticsMonitorClient: SyntheticsMonitorClient;
soClient: SavedObjectsClientContract;
request: KibanaRequest;
privateLocations: PrivateLocation[];
spaceId: string;
}) => {
let newMonitors: SavedObjectsBulkResponse<EncryptedSyntheticsMonitor> | null = null;

const monitorsToCreate = normalizedMonitors.map((monitor) => ({
id: uuidV4(),
monitor: monitor as MonitorFields,
}));

try {
const [createdMonitors, { syncErrors }] = await Promise.all([
createNewSavedObjectMonitorBulk({
monitorsToCreate,
soClient,
}),
syntheticsMonitorClient.addMonitors(
monitorsToCreate,
request,
soClient,
privateLocations,
spaceId
),
]);

newMonitors = createdMonitors;

sendNewMonitorTelemetry(server, newMonitors.saved_objects, syncErrors);

return { errors: syncErrors, newMonitors: newMonitors.saved_objects };
} catch (e) {
await rollBackNewMonitorBulk(
monitorsToCreate,
server,
soClient,
syntheticsMonitorClient,
request
);

throw e;
}
};

const rollBackNewMonitorBulk = async (
monitorsToCreate: Array<{ id: string; monitor: MonitorFields }>,
server: UptimeServerSetup,
soClient: SavedObjectsClientContract,
syntheticsMonitorClient: SyntheticsMonitorClient,
request: KibanaRequest
) => {
try {
await pMap(
monitorsToCreate,
async (monitor) =>
deleteMonitor({
server,
request,
savedObjectsClient: soClient,
monitorId: monitor.id,
syntheticsMonitorClient,
}),
{ concurrency: 100 }
);
} catch (e) {
// ignore errors here
server.logger.error(e);
}
};

const sendNewMonitorTelemetry = (
server: UptimeServerSetup,
monitors: Array<SavedObject<EncryptedSyntheticsMonitor>>,
errors?: ServiceLocationErrors | null
) => {
for (const monitor of monitors) {
sendTelemetryEvents(
server.logger,
server.telemetry,
formatTelemetryEvent({
errors,
monitor,
isInlineScript: Boolean((monitor.attributes as MonitorFields)[ConfigKey.SOURCE_INLINE]),
kibanaVersion: server.kibanaVersion,
})
);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export const deleteMonitor = async ({
request: KibanaRequest;
}) => {
const { logger, telemetry, kibanaVersion, encryptedSavedObjects } = server;
const spaceId = server.spaces.spacesService.getSpaceId(request);

const encryptedSavedObjectsClient = encryptedSavedObjects.getClient();
let normalizedMonitor;
try {
Expand Down Expand Up @@ -115,7 +117,8 @@ export const deleteMonitor = async ({
monitorId,
},
request,
savedObjectsClient
savedObjectsClient,
spaceId
);
const deletePromise = savedObjectsClient.delete(syntheticsMonitorType, monitorId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ describe('syncEditedMonitor', () => {
.fn()
.mockReturnValue({ integrations: { writeIntegrationPolicies: true } }),
},
packagePolicyService: {
get: jest.fn().mockReturnValue({}),
buildPackagePolicyFromPackage: jest.fn().mockReturnValue({}),
},
},
} as unknown as UptimeServerSetup;

Expand Down Expand Up @@ -96,6 +100,7 @@ describe('syncEditedMonitor', () => {
request: {} as unknown as KibanaRequest,
savedObjectsClient:
serverMock.authSavedObjectsClient as unknown as SavedObjectsClientContract,
spaceId: 'test-space',
});

expect(syntheticsService.editConfig).toHaveBeenCalledWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => (
const monitor = request.body as SyntheticsMonitor;
const { monitorId } = request.params;

const spaceId = server.spaces.spacesService.getSpaceId(request);

try {
const previousMonitor: SavedObject<EncryptedSyntheticsMonitor> = await savedObjectsClient.get(
syntheticsMonitorType,
Expand Down Expand Up @@ -101,6 +103,7 @@ export const editSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () => (
request,
normalizedMonitor: editedMonitor,
monitorWithRevision: formattedMonitor,
spaceId,
});

// Return service sync errors in OK response
Expand Down Expand Up @@ -131,6 +134,7 @@ export const syncEditedMonitor = async ({
syntheticsMonitorClient,
savedObjectsClient,
request,
spaceId,
}: {
normalizedMonitor: SyntheticsMonitor;
monitorWithRevision: SyntheticsMonitorWithSecrets;
Expand All @@ -140,6 +144,7 @@ export const syncEditedMonitor = async ({
syntheticsMonitorClient: SyntheticsMonitorClient;
savedObjectsClient: SavedObjectsClientContract;
request: KibanaRequest;
spaceId: string;
}) => {
try {
const editedSOPromise = savedObjectsClient.update<MonitorFields>(
Expand All @@ -152,7 +157,8 @@ export const syncEditedMonitor = async ({
normalizedMonitor as MonitorFields,
previousMonitor.id,
request,
savedObjectsClient
savedObjectsClient,
spaceId
);

const [editedMonitorSavedObject, errors] = await Promise.all([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,25 @@ export async function getAllLocations(
try {
const [privateLocations, { locations: publicLocations, throttling }] = await Promise.all([
getPrivateLocations(syntheticsMonitorClient, savedObjectsClient),
getServiceLocations(server),
getServicePublicLocations(server, syntheticsMonitorClient),
]);
return { publicLocations, privateLocations, throttling };
} catch (e) {
server.logger.error(e);
return { publicLocations: [], privateLocations: [] };
}
}

const getServicePublicLocations = async (
server: UptimeServerSetup,
syntheticsMonitorClient: SyntheticsMonitorClient
) => {
if (syntheticsMonitorClient.syntheticsService.locations.length === 0) {
return await getServiceLocations(server);
}

return {
locations: syntheticsMonitorClient.syntheticsService.locations,
throttling: syntheticsMonitorClient.syntheticsService.throttling,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Locations,
LocationStatus,
ProjectBrowserMonitor,
PrivateLocation,
} from '../../../common/runtime_types';
import { DEFAULT_FIELDS } from '../../../common/constants/monitor_defaults';
import { normalizeProjectMonitors } from './browser';
Expand Down Expand Up @@ -42,11 +43,12 @@ describe('browser normalizers', () => {
status: LocationStatus.GA,
},
];
const privateLocations: Locations = [
const privateLocations: PrivateLocation[] = [
{
id: 'germany',
label: 'Germany',
isServiceManaged: false,
concurrentMonitors: 1,
agentPolicyId: 'germany',
},
];
const monitors: ProjectBrowserMonitor[] = [
Expand Down Expand Up @@ -234,11 +236,7 @@ describe('browser normalizers', () => {
url: 'test-url',
status: 'ga',
},
{
id: 'germany',
isServiceManaged: false,
label: 'Germany',
},
privateLocations[0],
],
name: 'test-name-3',
params: JSON.stringify(params),
Expand Down
Loading

0 comments on commit 5cec30a

Please sign in to comment.