Skip to content

Commit

Permalink
[Synthetics] Refactor delete route !! (#195387)
Browse files Browse the repository at this point in the history
## Summary

Fixes #193790 !!

Refactor delete route !!

Make sure to send delete response in bulk to synthetics service !!

(cherry picked from commit 72888f6)
  • Loading branch information
shahzad31 committed Oct 30, 2024
1 parent b7c8696 commit 591f5c3
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 235 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { SavedObject } from '@kbn/core-saved-objects-common/src/server_types';
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { isValidNamespace } from '@kbn/fleet-plugin/common';
import { i18n } from '@kbn/i18n';
import { DeleteMonitorAPI } from '../services/delete_monitor_api';
import { parseMonitorLocations } from './utils';
import { MonitorValidationError } from '../monitor_validation';
import { getSavedObjectKqlFilter } from '../../common';
import { deleteMonitor } from '../delete_monitor';
import { monitorAttributes, syntheticsMonitorType } from '../../../../common/types/saved_objects';
import { PrivateLocationAttributes } from '../../../runtime_types/private_locations';
import { ConfigKey } from '../../../../common/constants/monitor_management';
Expand Down Expand Up @@ -339,9 +339,9 @@ export class AddEditMonitorAPI {
if (encryptedMonitor) {
await savedObjectsClient.delete(syntheticsMonitorType, newMonitorId);

await deleteMonitor({
routeContext: this.routeContext,
monitorId: newMonitorId,
const deleteMonitorAPI = new DeleteMonitorAPI(this.routeContext);
await deleteMonitorAPI.execute({
monitorIds: [newMonitorId],
});
}
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { schema } from '@kbn/config-schema';
import { i18n } from '@kbn/i18n';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { validateSpaceId } from './services/validate_space_id';
import { RouteContext, SyntheticsRestApiRouteFactory } from '../types';
import { ProjectMonitor } from '../../../common/runtime_types';

Expand Down Expand Up @@ -46,9 +47,7 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsRestApiRouteFactory = (
}

try {
const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? {
id: DEFAULT_SPACE_ID,
};
const spaceId = await validateSpaceId(routeContext);

const permissionError = await validatePermissions(routeContext, monitors);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { SavedObjectsBulkResponse } from '@kbn/core-saved-objects-api-server';
import { v4 as uuidV4 } from 'uuid';
import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
import { SavedObjectError } from '@kbn/core-saved-objects-common';
import { deleteMonitorBulk } from './delete_monitor_bulk';
import { SyntheticsServerSetup } from '../../../types';
import { RouteContext } from '../../types';
import { formatTelemetryEvent, sendTelemetryEvents } from '../../telemetry/monitor_upgrade_sender';
import { deleteMonitor } from '../delete_monitor';
import { formatSecrets } from '../../../synthetics_service/utils';
import { syntheticsMonitorType } from '../../../../common/types/saved_objects';
import {
Expand All @@ -24,6 +24,7 @@ import {
SyntheticsMonitor,
type SyntheticsPrivateLocations,
} from '../../../../common/runtime_types';
import { DeleteMonitorAPI } from '../services/delete_monitor_api';

export const createNewSavedObjectMonitorBulk = async ({
soClient,
Expand Down Expand Up @@ -146,15 +147,10 @@ const rollBackNewMonitorBulk = async (
) => {
const { server } = routeContext;
try {
await pMap(
monitorsToCreate,
async (monitor) =>
deleteMonitor({
routeContext,
monitorId: monitor.id,
}),
{ concurrency: 100, stopOnError: false }
);
const deleteMonitorAPI = new DeleteMonitorAPI(routeContext);
await deleteMonitorAPI.execute({
monitorIds: monitorsToCreate.map(({ id }) => id),
});
} catch (e) {
// ignore errors here
server.logger.error(e);
Expand Down Expand Up @@ -194,11 +190,9 @@ export const deleteMonitorIfCreated = async ({
newMonitorId
);
if (encryptedMonitor) {
await savedObjectsClient.delete(syntheticsMonitorType, newMonitorId);

await deleteMonitor({
await deleteMonitorBulk({
monitors: [encryptedMonitor],
routeContext,
monitorId: newMonitorId,
});
}
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { SavedObjectsClientContract, KibanaRequest } from '@kbn/core/server';
import { SavedObject } from '@kbn/core-saved-objects-server';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { SyntheticsServerSetup } from '../../../types';
import {
formatTelemetryDeleteEvent,
sendTelemetryEvents,
Expand All @@ -19,29 +16,20 @@ import {
EncryptedSyntheticsMonitorAttributes,
SyntheticsMonitorWithId,
} from '../../../../common/runtime_types';
import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_monitor/synthetics_monitor_client';
import { syntheticsMonitorType } from '../../../../common/types/saved_objects';
import { RouteContext } from '../../types';

export const deleteMonitorBulk = async ({
savedObjectsClient,
server,
monitors,
syntheticsMonitorClient,
request,
routeContext,
}: {
savedObjectsClient: SavedObjectsClientContract;
server: SyntheticsServerSetup;
monitors: Array<SavedObject<SyntheticsMonitor | EncryptedSyntheticsMonitorAttributes>>;
syntheticsMonitorClient: SyntheticsMonitorClient;
request: KibanaRequest;
routeContext: RouteContext;
}) => {
const { savedObjectsClient, server, spaceId, syntheticsMonitorClient } = routeContext;
const { logger, telemetry, stackVersion } = server;

try {
const { id: spaceId } = (await server.spaces?.spacesService.getActiveSpace(request)) ?? {
id: DEFAULT_SPACE_ID,
};

const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors(
monitors.map((normalizedMonitor) => ({
...normalizedMonitor.attributes,
Expand All @@ -55,7 +43,7 @@ export const deleteMonitorBulk = async ({
monitors.map((monitor) => ({ type: syntheticsMonitorType, id: monitor.id }))
);

const [errors] = await Promise.all([deleteSyncPromise, deletePromises]);
const [errors, result] = await Promise.all([deleteSyncPromise, deletePromises]);

monitors.forEach((monitor) => {
sendTelemetryEvents(
Expand All @@ -71,7 +59,7 @@ export const deleteMonitorBulk = async ({
);
});

return errors;
return { errors, result };
} catch (e) {
throw e;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,10 @@
* 2.0.
*/
import { schema } from '@kbn/config-schema';
import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from '@kbn/core/server';
import pMap from 'p-map';
import { validatePermissions } from './edit_monitor';
import { SyntheticsServerSetup } from '../../types';
import { RouteContext, SyntheticsRestApiRouteFactory } from '../types';
import { syntheticsMonitorType } from '../../../common/types/saved_objects';
import {
ConfigKey,
DeleteParamsResponse,
EncryptedSyntheticsMonitorAttributes,
MonitorFields,
SyntheticsMonitorWithId,
SyntheticsMonitorWithSecretsAttributes,
} from '../../../common/runtime_types';
import { DeleteMonitorAPI } from './services/delete_monitor_api';
import { SyntheticsRestApiRouteFactory } from '../types';
import { DeleteParamsResponse } from '../../../common/runtime_types';
import { SYNTHETICS_API_URLS } from '../../../common/constants';
import {
formatTelemetryDeleteEvent,
sendErrorTelemetryEvents,
sendTelemetryEvents,
} from '../telemetry/monitor_upgrade_sender';
import { formatSecrets, normalizeSecrets } from '../../synthetics_service/utils/secrets';

export const deleteSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory<
DeleteParamsResponse[],
Expand Down Expand Up @@ -62,186 +45,28 @@ export const deleteSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory<
});
}

const result: Array<{ id: string; deleted: boolean; error?: string }> = [];
const idsToDelete = [...(ids ?? []), ...(queryId ? [queryId] : [])];
if (idsToDelete.length === 0) {
return response.badRequest({
body: { message: 'id must be provided via param or body.' },
});
}

await pMap(idsToDelete, async (id) => {
try {
const { errors, res } = await deleteMonitor({
routeContext,
monitorId: id,
});
if (res) {
return res;
}

if (errors && errors.length > 0) {
return response.ok({
body: { message: 'error pushing monitor to the service', attributes: { errors } },
});
}
const deleteMonitorAPI = new DeleteMonitorAPI(routeContext);
try {
const { errors } = await deleteMonitorAPI.execute({
monitorIds: idsToDelete,
});

result.push({ id, deleted: true });
} catch (getErr) {
if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) {
result.push({ id, deleted: false, error: `Monitor id ${id} not found!` });
} else {
throw getErr;
}
if (errors && errors.length > 0) {
return response.ok({
body: { message: 'error pushing monitor to the service', attributes: { errors } },
});
}
});
} catch (getErr) {
throw getErr;
}

return result;
return deleteMonitorAPI.result;
},
});

export const deleteMonitor = async ({
routeContext,
monitorId,
}: {
routeContext: RouteContext;
monitorId: string;
}) => {
const { response, spaceId, savedObjectsClient, server, syntheticsMonitorClient } = routeContext;
const { logger, telemetry, stackVersion } = server;

const { monitor, monitorWithSecret } = await getMonitorToDelete(
monitorId,
savedObjectsClient,
server,
spaceId
);

const err = await validatePermissions(routeContext, monitor.attributes.locations);
if (err) {
return {
res: response.forbidden({
body: {
message: err,
},
}),
};
}

let deletePromise;

try {
deletePromise = savedObjectsClient.delete(syntheticsMonitorType, monitorId);

const deleteSyncPromise = syntheticsMonitorClient.deleteMonitors(
[
{
...monitor.attributes,
id: (monitor.attributes as MonitorFields)[ConfigKey.MONITOR_QUERY_ID],
},
/* Type cast encrypted saved objects to decrypted saved objects for delete flow only.
* Deletion does not require all monitor fields */
] as SyntheticsMonitorWithId[],
savedObjectsClient,
spaceId
);

const [errors] = await Promise.all([deleteSyncPromise, deletePromise]).catch((e) => {
server.logger.error(e);
throw e;
});

sendTelemetryEvents(
logger,
telemetry,
formatTelemetryDeleteEvent(
monitor,
stackVersion,
new Date().toISOString(),
Boolean((monitor.attributes as MonitorFields)[ConfigKey.SOURCE_INLINE]),
errors
)
);

return { errors };
} catch (e) {
if (deletePromise) {
await deletePromise;
}
server.logger.error(
`Unable to delete Synthetics monitor ${monitor.attributes[ConfigKey.NAME]}`
);

if (monitorWithSecret) {
await restoreDeletedMonitor({
monitorId,
normalizedMonitor: formatSecrets({
...monitorWithSecret.attributes,
}),
savedObjectsClient,
});
}
throw e;
}
};

const getMonitorToDelete = async (
monitorId: string,
soClient: SavedObjectsClientContract,
server: SyntheticsServerSetup,
spaceId: string
) => {
const encryptedSOClient = server.encryptedSavedObjects.getClient();

try {
const monitor =
await encryptedSOClient.getDecryptedAsInternalUser<SyntheticsMonitorWithSecretsAttributes>(
syntheticsMonitorType,
monitorId,
{
namespace: spaceId,
}
);
return { monitor: normalizeSecrets(monitor), monitorWithSecret: normalizeSecrets(monitor) };
} catch (e) {
server.logger.error(`Failed to decrypt monitor to delete ${monitorId}${e}`);
sendErrorTelemetryEvents(server.logger, server.telemetry, {
reason: `Failed to decrypt monitor to delete ${monitorId}`,
message: e?.message,
type: 'deletionError',
code: e?.code,
status: e.status,
stackVersion: server.stackVersion,
});
}

const monitor = await soClient.get<EncryptedSyntheticsMonitorAttributes>(
syntheticsMonitorType,
monitorId
);
return { monitor, withSecrets: false };
};

const restoreDeletedMonitor = async ({
monitorId,
savedObjectsClient,
normalizedMonitor,
}: {
monitorId: string;
normalizedMonitor: SyntheticsMonitorWithSecretsAttributes;
savedObjectsClient: SavedObjectsClientContract;
}) => {
try {
await savedObjectsClient.get<EncryptedSyntheticsMonitorAttributes>(
syntheticsMonitorType,
monitorId
);
} catch (e) {
if (SavedObjectsErrorHelpers.isNotFoundError(e)) {
await savedObjectsClient.create(syntheticsMonitorType, normalizedMonitor, {
id: monitorId,
overwrite: true,
});
}
}
};
Loading

0 comments on commit 591f5c3

Please sign in to comment.