Skip to content

Commit

Permalink
[Synthetics] Handle private locations simultaneous edits !! (elastic#…
Browse files Browse the repository at this point in the history
…195874)

## Summary

Fixes elastic#190801 !!

Handle private locations simultaneous edits !! 

Registered a new saved object to handle private locations properly.
Instead of using a singleton, now each private location will be
represented by it's own saved object.

### Existing private locations
When we are doing any write operation, we migrate them to new kind of
saved object and remove the legacy saved object type.


### Testing

- Create multiple private locations on main 
- Switch to branch and create few more locations
- Make sure all private locations are editable, deleteable and have been
migrated to new saved object types in the course

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
shahzad31 and kibanamachine authored Nov 7, 2024
1 parent c1e430b commit 96c9b5b
Show file tree
Hide file tree
Showing 42 changed files with 576 additions and 327 deletions.
1 change: 1 addition & 0 deletions packages/kbn-check-mappings-update-cli/current_fields.json
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,7 @@
"urls"
],
"synthetics-param": [],
"synthetics-private-location": [],
"synthetics-privates-locations": [],
"tag": [
"color",
Expand Down
4 changes: 4 additions & 0 deletions packages/kbn-check-mappings-update-cli/current_mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3552,6 +3552,10 @@
"dynamic": false,
"properties": {}
},
"synthetics-private-location": {
"dynamic": false,
"properties": {}
},
"synthetics-privates-locations": {
"dynamic": false,
"properties": {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const STANDARD_LIST_TYPES = [
'synthetics-monitor',
'uptime-dynamic-settings',
'synthetics-privates-locations',
'synthetics-private-location',

'osquery-saved-query',
'osquery-pack',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"synthetics-dynamic-settings": "4b40a93eb3e222619bf4e7fe34a9b9e7ab91a0a7",
"synthetics-monitor": "5ceb25b6249bd26902c9b34273c71c3dce06dbea",
"synthetics-param": "3ebb744e5571de678b1312d5c418c8188002cf5e",
"synthetics-private-location": "8cecc9e4f39637d2f8244eb7985c0690ceab24be",
"synthetics-privates-locations": "f53d799d5c9bc8454aaa32c6abc99a899b025d5c",
"tag": "e2544392fe6563e215bb677abc8b01c2601ef2dc",
"task": "3c89a7c918d5b896a5f8800f06e9114ad7e7aea3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ const previouslyRegisteredTypes = [
'synthetics-monitor',
'synthetics-param',
'synthetics-privates-locations',
'synthetics-private-location',
'tag',
'task',
'telemetry',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
* 2.0.
*/

export const privateLocationsSavedObjectId = 'synthetics-privates-locations-singleton';
export const privateLocationsSavedObjectName = 'synthetics-privates-locations';
export const legacyPrivateLocationsSavedObjectId = 'synthetics-privates-locations-singleton';
export const legacyPrivateLocationsSavedObjectName = 'synthetics-privates-locations';

export const privateLocationSavedObjectName = 'synthetics-private-location';
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@

import { journey, step, before, after, expect } from '@elastic/synthetics';
import { waitForLoadingToFinish } from '@kbn/ux-plugin/e2e/journeys/utils';
import { SyntheticsServices } from './services/synthetics_services';
import { byTestId } from '../../helpers/utils';
import {
addTestMonitor,
cleanPrivateLocations,
cleanTestMonitors,
getPrivateLocations,
} from './services/add_monitor';
import { addTestMonitor, cleanPrivateLocations, cleanTestMonitors } from './services/add_monitor';
import { syntheticsAppPageProvider } from '../page_objects/synthetics_app';

journey(`PrivateLocationsSettings`, async ({ page, params }) => {
const syntheticsApp = syntheticsAppPageProvider({ page, kibanaUrl: params.kibanaUrl, params });
const services = new SyntheticsServices(params);

page.setDefaultTimeout(2 * 30000);

Expand Down Expand Up @@ -78,16 +75,14 @@ journey(`PrivateLocationsSettings`, async ({ page, params }) => {
await page.click('text=Private Locations');
await page.click('h1:has-text("Settings")');

const privateLocations = await getPrivateLocations(params);
const privateLocations = await services.getPrivateLocations();

const locations = privateLocations.attributes.locations;
expect(privateLocations.length).toBe(1);

expect(locations.length).toBe(1);

locationId = locations[0].id;
locationId = privateLocations[0].id;

await addTestMonitor(params.kibanaUrl, 'test-monitor', {
locations: [locations[0]],
locations: [privateLocations[0]],
type: 'browser',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@

import axios from 'axios';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import {
privateLocationsSavedObjectId,
privateLocationsSavedObjectName,
} from '@kbn/synthetics-plugin/common/saved_objects/private_locations';
import { legacyPrivateLocationsSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations';

export const enableMonitorManagedViaApi = async (kibanaUrl: string) => {
try {
Expand Down Expand Up @@ -46,21 +43,6 @@ export const addTestMonitor = async (
}
};

export const getPrivateLocations = async (params: Record<string, any>) => {
const getService = params.getService;
const server = getService('kibanaServer');

try {
return await server.savedObjects.get({
id: privateLocationsSavedObjectId,
type: privateLocationsSavedObjectName,
});
} catch (e) {
// eslint-disable-next-line no-console
console.log(e);
}
};

export const cleanTestMonitors = async (params: Record<string, any>) => {
const getService = params.getService;
const server = getService('kibanaServer');
Expand All @@ -79,7 +61,11 @@ export const cleanPrivateLocations = async (params: Record<string, any>) => {

try {
await server.savedObjects.clean({
types: [privateLocationsSavedObjectName, 'ingest-agent-policies', 'ingest-package-policies'],
types: [
legacyPrivateLocationsSavedObjectName,
'ingest-agent-policies',
'ingest-package-policies',
],
});
} catch (e) {
// eslint-disable-next-line no-console
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import type { Client } from '@elastic/elasticsearch';
import { KbnClient } from '@kbn/test';
import pMap from 'p-map';
import { makeDownSummary, makeUpSummary } from '@kbn/observability-synthetics-test-data';
import { SyntheticsMonitor } from '@kbn/synthetics-plugin/common/runtime_types';
import {
SyntheticsMonitor,
SyntheticsPrivateLocations,
} from '@kbn/synthetics-plugin/common/runtime_types';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import { journeyStart, journeySummary, step1, step2 } from './data/browser_docs';

Expand Down Expand Up @@ -251,4 +254,12 @@ export class SyntheticsServices {
});
return connector.data;
}

async getPrivateLocations(): Promise<SyntheticsPrivateLocations> {
const response = await this.requester.request({
path: SYNTHETICS_API_URLS.PRIVATE_LOCATIONS,
method: 'GET',
});
return response.data as SyntheticsPrivateLocations;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import {
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { syntheticsMonitorType, syntheticsParamType } from '../common/types/saved_objects';
import { SYNTHETICS_RULE_TYPES } from '../common/constants/synthetics_alerts';
import { privateLocationsSavedObjectName } from '../common/saved_objects/private_locations';
import {
legacyPrivateLocationsSavedObjectName,
privateLocationSavedObjectName,
} from '../common/saved_objects/private_locations';
import { PLUGIN } from '../common/constants/plugin';
import {
syntheticsSettingsObjectType,
Expand Down Expand Up @@ -71,7 +74,8 @@ export const syntheticsFeature = {
syntheticsSettingsObjectType,
syntheticsMonitorType,
syntheticsApiKeyObjectType,
privateLocationsSavedObjectName,
privateLocationSavedObjectName,
legacyPrivateLocationsSavedObjectName,
syntheticsParamType,
// uptime settings object is also registered here since feature is shared between synthetics and uptime
uptimeSettingsObjectType,
Expand Down Expand Up @@ -102,7 +106,7 @@ export const syntheticsFeature = {
syntheticsSettingsObjectType,
syntheticsMonitorType,
syntheticsApiKeyObjectType,
privateLocationsSavedObjectName,
legacyPrivateLocationsSavedObjectName,
// uptime settings object is also registered here since feature is shared between synthetics and uptime
uptimeSettingsObjectType,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ describe('syncEditedMonitor', () => {
bulkUpdate: jest.fn(),
get: jest.fn(),
update: jest.fn(),
createPointInTimeFinder: jest.fn().mockImplementation(({ perPage, type: soType }) => ({
close: jest.fn(async () => {}),
find: jest.fn().mockReturnValue({
async *[Symbol.asyncIterator]() {
yield {
saved_objects: [],
};
},
}),
})),
},
logger,
config: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@
*/

import { schema, TypeOf } from '@kbn/config-schema';
import { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { getPrivateLocationsAndAgentPolicies } from './get_private_locations';
import {
privateLocationsSavedObjectId,
privateLocationsSavedObjectName,
} from '../../../../common/saved_objects/private_locations';
import { privateLocationSavedObjectName } from '../../../../common/saved_objects/private_locations';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations';
import { PrivateLocationAttributes } from '../../../runtime_types/private_locations';
import { toClientContract, toSavedObjectContract } from './helpers';
import { PrivateLocation } from '../../../../common/runtime_types';

Expand All @@ -40,7 +38,11 @@ export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory<PrivateLocat
body: PrivateLocationSchema,
},
},
handler: async ({ response, request, savedObjectsClient, syntheticsMonitorClient }) => {
handler: async (routeContext) => {
await migrateLegacyPrivateLocations(routeContext);

const { response, request, savedObjectsClient, syntheticsMonitorClient } = routeContext;

const location = request.body as PrivateLocationObject;

const { locations, agentPolicies } = await getPrivateLocationsAndAgentPolicies(
Expand All @@ -65,7 +67,6 @@ export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory<PrivateLocat
});
}

const existingLocations = locations.filter((loc) => loc.id !== location.agentPolicyId);
const formattedLocation = toSavedObjectContract({
...location,
id: location.agentPolicyId,
Expand All @@ -80,17 +81,17 @@ export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory<PrivateLocat
});
}

const result = await savedObjectsClient.create<SyntheticsPrivateLocationsAttributes>(
privateLocationsSavedObjectName,
{ locations: [...existingLocations, formattedLocation] },
const soClient = routeContext.server.coreStart.savedObjects.createInternalRepository();

const result = await soClient.create<PrivateLocationAttributes>(
privateLocationSavedObjectName,
formattedLocation,
{
id: privateLocationsSavedObjectId,
overwrite: true,
id: location.agentPolicyId,
initialNamespaces: ['*'],
}
);

const allLocations = toClientContract(result.attributes, agentPolicies);

return allLocations.find((loc) => loc.id === location.agentPolicyId)!;
return toClientContract(result.attributes, agentPolicies);
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@

import { schema } from '@kbn/config-schema';
import { isEmpty } from 'lodash';
import { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations';
import { getMonitorsByLocation } from './get_location_monitors';
import { getPrivateLocationsAndAgentPolicies } from './get_private_locations';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
import {
privateLocationsSavedObjectId,
privateLocationsSavedObjectName,
} from '../../../../common/saved_objects/private_locations';
import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations';
import { privateLocationSavedObjectName } from '../../../../common/saved_objects/private_locations';

export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory<undefined> = () => ({
method: 'DELETE',
Expand All @@ -28,12 +25,16 @@ export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory<undefined
}),
},
},
handler: async ({ response, savedObjectsClient, syntheticsMonitorClient, request, server }) => {
handler: async (routeContext) => {
await migrateLegacyPrivateLocations(routeContext);

const { savedObjectsClient, syntheticsMonitorClient, request, response, server } = routeContext;
const { locationId } = request.params as { locationId: string };

const { locations } = await getPrivateLocationsAndAgentPolicies(
savedObjectsClient,
syntheticsMonitorClient
syntheticsMonitorClient,
true
);

if (!locations.find((loc) => loc.id === locationId)) {
Expand All @@ -55,17 +56,8 @@ export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory<undefined
});
}

const remainingLocations = locations.filter((loc) => loc.id !== locationId);

await savedObjectsClient.create<SyntheticsPrivateLocationsAttributes>(
privateLocationsSavedObjectName,
{ locations: remainingLocations },
{
id: privateLocationsSavedObjectId,
overwrite: true,
}
);

return;
await savedObjectsClient.delete(privateLocationSavedObjectName, locationId, {
force: true,
});
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { schema } from '@kbn/config-schema';
import { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations';
import { AgentPolicyInfo } from '../../../../common/types';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { PrivateLocation, SyntheticsPrivateLocations } from '../../../../common/runtime_types';
import { SYNTHETICS_API_URLS } from '../../../../common/constants';
import { getPrivateLocations } from '../../../synthetics_service/get_private_locations';
import type { SyntheticsPrivateLocationsAttributes } from '../../../runtime_types/private_locations';
import { SyntheticsMonitorClient } from '../../../synthetics_service/synthetics_monitor/synthetics_monitor_client';
import { toClientContract } from './helpers';
import { allLocationsToClientContract } from './helpers';

export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory<
SyntheticsPrivateLocations | PrivateLocation
Expand All @@ -29,14 +30,17 @@ export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory<
}),
},
},
handler: async ({ savedObjectsClient, syntheticsMonitorClient, request, response }) => {
handler: async (routeContext) => {
await migrateLegacyPrivateLocations(routeContext);

const { savedObjectsClient, syntheticsMonitorClient, request, response } = routeContext;
const { id } = request.params as { id?: string };

const { locations, agentPolicies } = await getPrivateLocationsAndAgentPolicies(
savedObjectsClient,
syntheticsMonitorClient
);
const list = toClientContract({ locations }, agentPolicies);
const list = allLocationsToClientContract({ locations }, agentPolicies);
if (!id) return list;
const location = list.find((loc) => loc.id === id || loc.label === id);
if (!location) {
Expand All @@ -53,7 +57,7 @@ export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory<
export const getPrivateLocationsAndAgentPolicies = async (
savedObjectsClient: SavedObjectsClientContract,
syntheticsMonitorClient: SyntheticsMonitorClient,
excludeAgentPolicies: boolean = false
excludeAgentPolicies = false
): Promise<SyntheticsPrivateLocationsAttributes & { agentPolicies: AgentPolicyInfo[] }> => {
try {
const [privateLocations, agentPolicies] = await Promise.all([
Expand Down
Loading

0 comments on commit 96c9b5b

Please sign in to comment.