[8.x] [Synthetics] Handle private locations simultaneous edits !! (#195874) (#199387)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Synthetics] Handle private locations simultaneous edits !!
(#195874)](https://github.com/elastic/kibana/pull/195874)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"shahzad31comp@gmail.com"},"sourceCommit":{"committedDate":"2024-11-07T21:31:09Z","message":"[Synthetics]
Handle private locations simultaneous edits !! (#195874)\n\n##
Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/190801
!!\r\n\r\nHandle private locations simultaneous edits !!
\r\n\r\nRegistered a new saved object to handle private locations
properly.\r\nInstead of using a singleton, now each private location
will be\r\nrepresented by it's own saved object.\r\n\r\n### Existing
private locations\r\nWhen we are doing any write operation, we migrate
them to new kind of\r\nsaved object and remove the legacy saved object
type.\r\n\r\n\r\n### Testing\r\n\r\n- Create multiple private locations
on main \r\n- Switch to branch and create few more locations\r\n- Make
sure all private locations are editable, deleteable and have
been\r\nmigrated to new saved object types in the
course\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"96c9b5b5d0a82c6e4b0a9b86fc1ab4dd9fa4d707","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-management"],"title":"[Synthetics]
Handle private locations simultaneous edits
!!","number":195874,"url":"https://github.com/elastic/kibana/pull/195874","mergeCommit":{"message":"[Synthetics]
Handle private locations simultaneous edits !! (#195874)\n\n##
Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/190801
!!\r\n\r\nHandle private locations simultaneous edits !!
\r\n\r\nRegistered a new saved object to handle private locations
properly.\r\nInstead of using a singleton, now each private location
will be\r\nrepresented by it's own saved object.\r\n\r\n### Existing
private locations\r\nWhen we are doing any write operation, we migrate
them to new kind of\r\nsaved object and remove the legacy saved object
type.\r\n\r\n\r\n### Testing\r\n\r\n- Create multiple private locations
on main \r\n- Switch to branch and create few more locations\r\n- Make
sure all private locations are editable, deleteable and have
been\r\nmigrated to new saved object types in the
course\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"96c9b5b5d0a82c6e4b0a9b86fc1ab4dd9fa4d707"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195874","number":195874,"mergeCommit":{"message":"[Synthetics]
Handle private locations simultaneous edits !! (#195874)\n\n##
Summary\r\n\r\nFixes https://github.com/elastic/kibana/issues/190801
!!\r\n\r\nHandle private locations simultaneous edits !!
\r\n\r\nRegistered a new saved object to handle private locations
properly.\r\nInstead of using a singleton, now each private location
will be\r\nrepresented by it's own saved object.\r\n\r\n### Existing
private locations\r\nWhen we are doing any write operation, we migrate
them to new kind of\r\nsaved object and remove the legacy saved object
type.\r\n\r\n\r\n### Testing\r\n\r\n- Create multiple private locations
on main \r\n- Switch to branch and create few more locations\r\n- Make
sure all private locations are editable, deleteable and have
been\r\nmigrated to new saved object types in the
course\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"96c9b5b5d0a82c6e4b0a9b86fc1ab4dd9fa4d707"}}]}]
BACKPORT-->

Co-authored-by: Shahzad <shahzad31comp@gmail.com>
This commit is contained in:
Kibana Machine 2024-11-08 10:15:15 +11:00 committed by GitHub
parent de09e3af76
commit afb795de1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 576 additions and 327 deletions

View file

@ -1072,6 +1072,7 @@
"urls"
],
"synthetics-param": [],
"synthetics-private-location": [],
"synthetics-privates-locations": [],
"tag": [
"color",

View file

@ -3549,6 +3549,10 @@
"dynamic": false,
"properties": {}
},
"synthetics-private-location": {
"dynamic": false,
"properties": {}
},
"synthetics-privates-locations": {
"dynamic": false,
"properties": {}

View file

@ -109,6 +109,7 @@ const STANDARD_LIST_TYPES = [
'synthetics-monitor',
'uptime-dynamic-settings',
'synthetics-privates-locations',
'synthetics-private-location',
'osquery-saved-query',
'osquery-pack',

View file

@ -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",

View file

@ -139,6 +139,7 @@ const previouslyRegisteredTypes = [
'synthetics-monitor',
'synthetics-param',
'synthetics-privates-locations',
'synthetics-private-location',
'tag',
'task',
'telemetry',

View file

@ -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';

View file

@ -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);
@ -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',
});
});

View file

@ -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 {
@ -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');
@ -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

View file

@ -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';
@ -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;
}
}

View file

@ -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,
@ -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,
@ -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,
],

View file

@ -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: {

View file

@ -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';
@ -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(
@ -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,
@ -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);
},
});

View file

@ -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',
@ -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)) {
@ -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,
});
},
});

View file

@ -7,6 +7,7 @@
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';
@ -14,7 +15,7 @@ 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
@ -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) {
@ -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([

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { toClientContract } from './helpers';
import { allLocationsToClientContract } from './helpers';
const testLocations = {
locations: [
@ -56,7 +56,7 @@ const testLocations2 = {
describe('toClientContract', () => {
it('formats SO attributes to client contract with falsy geo location', () => {
// @ts-ignore fixtures are purposely wrong types for testing
expect(toClientContract(testLocations)).toEqual([
expect(allLocationsToClientContract(testLocations)).toEqual([
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb',
geo: {
@ -86,7 +86,7 @@ describe('toClientContract', () => {
it('formats SO attributes to client contract with truthy geo location', () => {
// @ts-ignore fixtures are purposely wrong types for testing
expect(toClientContract(testLocations2)).toEqual([
expect(allLocationsToClientContract(testLocations2)).toEqual([
{
agentPolicyId: 'e3134290-0f73-11ee-ba15-159f4f728deb',
geo: {

View file

@ -13,6 +13,22 @@ import type {
import { PrivateLocation } from '../../../../common/runtime_types';
export const toClientContract = (
location: PrivateLocationAttributes,
agentPolicies?: AgentPolicyInfo[]
): PrivateLocation => {
const agPolicy = agentPolicies?.find((policy) => policy.id === location.agentPolicyId);
return {
label: location.label,
id: location.id,
agentPolicyId: location.agentPolicyId,
isServiceManaged: false,
isInvalid: !Boolean(agPolicy),
tags: location.tags,
geo: location.geo,
};
};
export const allLocationsToClientContract = (
attributes: SyntheticsPrivateLocationsAttributes,
agentPolicies?: AgentPolicyInfo[]
): SyntheticsPrivateLocations => {

View file

@ -0,0 +1,119 @@
/*
* 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 { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations';
import { SyntheticsServerSetup } from '../../../types';
import { coreMock, savedObjectsClientMock } from '@kbn/core/server/mocks';
import { loggerMock } from '@kbn/logging-mocks';
import {
type ISavedObjectsRepository,
SavedObjectsClientContract,
} from '@kbn/core-saved-objects-api-server';
describe('migrateLegacyPrivateLocations', () => {
let serverMock: SyntheticsServerSetup;
let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
let repositoryMock: ISavedObjectsRepository;
beforeEach(() => {
const coreStartMock = coreMock.createStart();
serverMock = {
coreStart: coreStartMock,
logger: loggerMock.create(),
} as any;
savedObjectsClient = savedObjectsClientMock.create();
repositoryMock = coreMock.createStart().savedObjects.createInternalRepository();
coreStartMock.savedObjects.createInternalRepository.mockReturnValue(repositoryMock);
});
it('should get the legacy private locations', async () => {
savedObjectsClient.get.mockResolvedValueOnce({
attributes: { locations: [{ id: '1', label: 'Location 1' }] },
} as any);
savedObjectsClient.find.mockResolvedValueOnce({ total: 1 } as any);
await migrateLegacyPrivateLocations({
server: serverMock,
savedObjectsClient,
} as any);
expect(savedObjectsClient.get).toHaveBeenCalledWith(
'synthetics-privates-locations',
'synthetics-privates-locations-singleton'
);
});
it('should log and return if an error occurs while getting legacy private locations', async () => {
const error = new Error('Get error');
savedObjectsClient.get.mockRejectedValueOnce(error);
await migrateLegacyPrivateLocations({
server: serverMock,
savedObjectsClient,
} as any);
expect(serverMock.logger.error).toHaveBeenCalledWith(
`Error getting legacy private locations: ${error}`
);
expect(repositoryMock.bulkCreate).not.toHaveBeenCalled();
});
it('should return if there are no legacy locations', async () => {
savedObjectsClient.get.mockResolvedValueOnce({
attributes: { locations: [] },
} as any);
await migrateLegacyPrivateLocations({
server: serverMock,
savedObjectsClient: savedObjectsClientMock,
} as any);
expect(repositoryMock.bulkCreate).not.toHaveBeenCalled();
});
it('should bulk create new private locations if there are legacy locations', async () => {
const legacyLocations = [{ id: '1', label: 'Location 1' }];
savedObjectsClient.get.mockResolvedValueOnce({
attributes: { locations: legacyLocations },
} as any);
savedObjectsClient.find.mockResolvedValueOnce({ total: 1 } as any);
await migrateLegacyPrivateLocations({
server: serverMock,
savedObjectsClient,
} as any);
expect(repositoryMock.bulkCreate).toHaveBeenCalledWith(
legacyLocations.map((location) => ({
id: location.id,
attributes: location,
type: 'synthetics-private-location',
initialNamespaces: ['*'],
})),
{ overwrite: true }
);
});
it('should delete legacy private locations if bulk create count matches', async () => {
const legacyLocations = [{ id: '1', label: 'Location 1' }];
savedObjectsClient.get.mockResolvedValueOnce({
attributes: { locations: legacyLocations },
} as any);
savedObjectsClient.find.mockResolvedValueOnce({ total: 1 } as any);
await migrateLegacyPrivateLocations({
server: serverMock,
savedObjectsClient,
} as any);
expect(savedObjectsClient.delete).toHaveBeenCalledWith(
'synthetics-privates-locations',
'synthetics-privates-locations-singleton',
{}
);
});
});

View file

@ -0,0 +1,70 @@
/*
* 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 { SavedObject } from '@kbn/core-saved-objects-server';
import {
type PrivateLocationAttributes,
SyntheticsPrivateLocationsAttributes,
} from '../../../runtime_types/private_locations';
import {
legacyPrivateLocationsSavedObjectId,
legacyPrivateLocationsSavedObjectName,
privateLocationSavedObjectName,
} from '../../../../common/saved_objects/private_locations';
import { RouteContext } from '../../types';
export const migrateLegacyPrivateLocations = async ({
server,
savedObjectsClient,
}: RouteContext) => {
try {
let obj: SavedObject<SyntheticsPrivateLocationsAttributes> | undefined;
try {
obj = await savedObjectsClient.get<SyntheticsPrivateLocationsAttributes>(
legacyPrivateLocationsSavedObjectName,
legacyPrivateLocationsSavedObjectId
);
} catch (e) {
server.logger.error(`Error getting legacy private locations: ${e}`);
return;
}
const legacyLocations = obj?.attributes.locations ?? [];
if (legacyLocations.length === 0) {
return;
}
const soClient = server.coreStart.savedObjects.createInternalRepository();
await soClient.bulkCreate<PrivateLocationAttributes>(
legacyLocations.map((location) => ({
id: location.id,
attributes: location,
type: privateLocationSavedObjectName,
initialNamespaces: ['*'],
})),
{
overwrite: true,
}
);
const { total } = await savedObjectsClient.find<PrivateLocationAttributes>({
type: privateLocationSavedObjectName,
fields: [],
perPage: 0,
});
if (total === legacyLocations.length) {
await savedObjectsClient.delete(
legacyPrivateLocationsSavedObjectName,
legacyPrivateLocationsSavedObjectId,
{}
);
}
} catch (e) {
server.logger.error(`Error migrating legacy private locations: ${e}`);
}
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { toClientContract } from '../settings/private_locations/helpers';
import { allLocationsToClientContract } from '../settings/private_locations/helpers';
import { getPrivateLocationsAndAgentPolicies } from '../settings/private_locations/get_private_locations';
import { SyntheticsRestApiRouteFactory } from '../types';
import { getAllLocations } from '../../synthetics_service/get_all_locations';
@ -45,7 +45,7 @@ export const getServiceLocationsRoute: SyntheticsRestApiRouteFactory = () => ({
const { locations: privateLocations, agentPolicies } =
await getPrivateLocationsAndAgentPolicies(savedObjectsClient, syntheticsMonitorClient);
const result = toClientContract({ locations: privateLocations }, agentPolicies);
const result = allLocationsToClientContract({ locations: privateLocations }, agentPolicies);
return {
locations: result,
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { transformGeoProperty } from './model_version_1';
import { privateLocationsSavedObjectName } from '../../../../common/saved_objects/private_locations';
import { legacyPrivateLocationsSavedObjectName } from '../../../../common/saved_objects/private_locations';
describe('model version 1 migration', () => {
const testLocation = {
@ -19,7 +19,7 @@ describe('model version 1 migration', () => {
concurrentMonitors: 1,
};
const testObject = {
type: privateLocationsSavedObjectName,
type: legacyPrivateLocationsSavedObjectName,
id: 'synthetics-privates-locations-singleton',
attributes: {
locations: [testLocation],

View file

@ -7,11 +7,33 @@
import { SavedObjectsType } from '@kbn/core/server';
import { modelVersion1 } from './migrations/private_locations/model_version_1';
import { privateLocationsSavedObjectName } from '../../common/saved_objects/private_locations';
export const privateLocationsSavedObjectId = 'synthetics-privates-locations-singleton';
import {
legacyPrivateLocationsSavedObjectName,
privateLocationSavedObjectName,
} from '../../common/saved_objects/private_locations';
export const PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE: SavedObjectsType = {
name: privateLocationsSavedObjectName,
export const PRIVATE_LOCATION_SAVED_OBJECT_TYPE: SavedObjectsType = {
name: privateLocationSavedObjectName,
hidden: false,
namespaceType: 'multiple',
mappings: {
dynamic: false,
properties: {
/* Leaving these commented to make it clear that these fields exist, even though we don't want them indexed.
When adding new fields please add them here. If they need to be searchable put them in the uncommented
part of properties.
*/
},
},
management: {
importableAndExportable: true,
},
};
export const legacyPrivateLocationsSavedObjectId = 'synthetics-privates-locations-singleton';
export const LEGACY_PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE: SavedObjectsType = {
name: legacyPrivateLocationsSavedObjectName,
hidden: false,
namespaceType: 'agnostic',
mappings: {

View file

@ -24,7 +24,10 @@ import {
SYNTHETICS_SECRET_ENCRYPTED_TYPE,
syntheticsParamSavedObjectType,
} from './synthetics_param';
import { PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE } from './private_locations';
import {
LEGACY_PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE,
PRIVATE_LOCATION_SAVED_OBJECT_TYPE,
} from './private_locations';
import { DYNAMIC_SETTINGS_DEFAULT_ATTRIBUTES } from '../constants/settings';
import { DynamicSettingsAttributes } from '../runtime_types/settings';
import {
@ -37,7 +40,8 @@ export const registerSyntheticsSavedObjects = (
savedObjectsService: SavedObjectsServiceSetup,
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup
) => {
savedObjectsService.registerType(PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE);
savedObjectsService.registerType(LEGACY_PRIVATE_LOCATIONS_SAVED_OBJECT_TYPE);
savedObjectsService.registerType(PRIVATE_LOCATION_SAVED_OBJECT_TYPE);
savedObjectsService.registerType(getSyntheticsMonitorSavedObjectType(encryptedSavedObjects));
savedObjectsService.registerType(syntheticsServiceApiKey);

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { SavedObjectsClientContract } from '@kbn/core/server';
import { toClientContract } from '../routes/settings/private_locations/helpers';
import { allLocationsToClientContract } from '../routes/settings/private_locations/helpers';
import { getPrivateLocationsAndAgentPolicies } from '../routes/settings/private_locations/get_private_locations';
import { SyntheticsServerSetup } from '../types';
import { getServiceLocations } from './get_service_locations';
@ -34,7 +34,10 @@ export async function getAllLocations({
),
getServicePublicLocations(server, syntheticsMonitorClient),
]);
const pvtLocations = toClientContract({ locations: privateLocations }, agentPolicies);
const pvtLocations = allLocationsToClientContract(
{ locations: privateLocations },
agentPolicies
);
return {
publicLocations,
privateLocations: pvtLocations,

View file

@ -5,22 +5,42 @@
* 2.0.
*/
import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from '@kbn/core/server';
import {
privateLocationsSavedObjectId,
privateLocationsSavedObjectName,
SavedObject,
SavedObjectsClientContract,
SavedObjectsErrorHelpers,
} from '@kbn/core/server';
import { uniqBy } from 'lodash';
import {
legacyPrivateLocationsSavedObjectId,
legacyPrivateLocationsSavedObjectName,
privateLocationSavedObjectName,
} from '../../common/saved_objects/private_locations';
import type { SyntheticsPrivateLocationsAttributes } from '../runtime_types/private_locations';
import {
PrivateLocationAttributes,
SyntheticsPrivateLocationsAttributes,
} from '../runtime_types/private_locations';
export const getPrivateLocations = async (
client: SavedObjectsClientContract
): Promise<SyntheticsPrivateLocationsAttributes['locations']> => {
try {
const obj = await client.get<SyntheticsPrivateLocationsAttributes>(
privateLocationsSavedObjectName,
privateLocationsSavedObjectId
);
return obj?.attributes.locations ?? [];
const finder = client.createPointInTimeFinder<PrivateLocationAttributes>({
type: privateLocationSavedObjectName,
perPage: 1000,
});
const results: Array<SavedObject<PrivateLocationAttributes>> = [];
for await (const response of finder.find()) {
results.push(...response.saved_objects);
}
finder.close().catch((e) => {});
const legacyLocations = await getLegacyPrivateLocations(client);
return uniqBy([...results.map((r) => r.attributes), ...legacyLocations], 'id');
} catch (getErr) {
if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) {
return [];
@ -28,3 +48,15 @@ export const getPrivateLocations = async (
throw getErr;
}
};
const getLegacyPrivateLocations = async (client: SavedObjectsClientContract) => {
try {
const obj = await client.get<SyntheticsPrivateLocationsAttributes>(
legacyPrivateLocationsSavedObjectName,
legacyPrivateLocationsSavedObjectId
);
return obj?.attributes.locations ?? [];
} catch (getErr) {
return [];
}
};

View file

@ -1,83 +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 { v4 as uuidv4 } from 'uuid';
import { privateLocationsSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations';
import { privateLocationsSavedObjectId } from '@kbn/synthetics-plugin/server/saved_objects/private_locations';
import { SyntheticsPrivateLocations } from '@kbn/synthetics-plugin/common/runtime_types';
import { Agent as SuperTestAgent } from 'supertest';
import { FtrProviderContext } from '../../common/ftr_provider_context';
export const INSTALLED_VERSION = '1.1.1';
export class PrivateLocationTestService {
private supertest: SuperTestAgent;
private readonly getService: FtrProviderContext['getService'];
constructor(getService: FtrProviderContext['getService']) {
this.supertest = getService('supertest');
this.getService = getService;
}
async installSyntheticsPackage() {
await this.supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200);
const response = await this.supertest
.get(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`)
.set('kbn-xsrf', 'true')
.expect(200);
if (response.body.item.status !== 'installed') {
await this.supertest
.post(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`)
.set('kbn-xsrf', 'true')
.send({ force: true })
.expect(200);
}
}
async addTestPrivateLocation() {
const apiResponse = await this.addFleetPolicy(uuidv4());
const testPolicyId = apiResponse.body.item.id;
return (await this.setTestLocations([testPolicyId]))[0];
}
async addFleetPolicy(name: string) {
return this.supertest
.post('/api/fleet/agent_policies?sys_monitoring=true')
.set('kbn-xsrf', 'true')
.send({
name,
description: '',
namespace: 'default',
monitoring_enabled: [],
})
.expect(200);
}
async setTestLocations(testFleetPolicyIds: string[]) {
const server = this.getService('kibanaServer');
const locations: SyntheticsPrivateLocations = testFleetPolicyIds.map((id, index) => ({
label: 'Test private location ' + index,
agentPolicyId: id,
id,
geo: {
lat: 0,
lon: 0,
},
isServiceManaged: false,
}));
await server.savedObjects.create({
type: privateLocationsSavedObjectName,
id: privateLocationsSavedObjectId,
attributes: {
locations,
},
overwrite: true,
});
return locations;
}
}

View file

@ -16,9 +16,9 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { Agent as SuperTestAgent } from 'supertest';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import expect from '@kbn/expect';
import { PrivateLocationTestService } from '../../../api_integration/apis/synthetics/services/private_location_test_service';
import { waitForAlertInIndex } from '../helpers/alerting_wait_for_helpers';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { PrivateLocationTestService } from './private_location_test_service';
import { createIndexConnector, createRule } from '../helpers/alerting_api_helper';
export const SYNTHETICS_ALERT_ACTION_INDEX = 'alert-action-synthetics';
@ -172,11 +172,6 @@ export class SyntheticsRuleHelper {
return result.body as EncryptedSyntheticsSavedMonitor;
}
async addPrivateLocation() {
await this.locService.installSyntheticsPackage();
return this.locService.addTestPrivateLocation();
}
async waitForStatusAlert({
ruleId,
filters,

View file

@ -37,6 +37,7 @@ export default function ({ getService }: FtrProviderContext) {
const supertestWithoutAuth = getService('supertestWithoutAuth');
let testFleetPolicyID: string;
let pvtLoc: PrivateLocation;
const testPolicyName = 'Fleet test server policy' + Date.now();
let _httpMonitorJson: HTTPFields;
@ -68,29 +69,15 @@ export default function ({ getService }: FtrProviderContext) {
httpMonitorJson = _httpMonitorJson;
});
it('adds a test fleet policy', async () => {
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testFleetPolicyID = apiResponse.body.item.id;
});
it('add a test private location', async () => {
await testPrivateLocations.setTestLocations([testFleetPolicyID]);
pvtLoc = await testPrivateLocations.addPrivateLocation();
testFleetPolicyID = pvtLoc.id;
const apiResponse = await supertestAPI.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS);
const testResponse: Array<PrivateLocation | ServiceLocation> = [
...getDevLocation('mockDevUrl'),
{
id: testFleetPolicyID,
isServiceManaged: false,
isInvalid: false,
label: 'Test private location 0',
geo: {
lat: 0,
lon: 0,
},
agentPolicyId: testFleetPolicyID,
},
{ ...pvtLoc, isInvalid: false },
];
expect(apiResponse.body.locations).eql(testResponse);
@ -137,16 +124,7 @@ export default function ({ getService }: FtrProviderContext) {
it('adds a monitor in private location', async () => {
const newMonitor = httpMonitorJson;
newMonitor.locations.push({
id: testFleetPolicyID,
agentPolicyId: testFleetPolicyID,
label: 'Test private location 0',
isServiceManaged: false,
geo: {
lat: 0,
lon: 0,
},
});
newMonitor.locations.push(pvtLoc);
const { body, rawBody } = await addMonitorAPI(newMonitor);
@ -182,19 +160,13 @@ export default function ({ getService }: FtrProviderContext) {
const resPolicy = await testPrivateLocations.addFleetPolicy(testPolicyName + 1);
testFleetPolicyID2 = resPolicy.body.item.id;
await testPrivateLocations.setTestLocations([testFleetPolicyID, testFleetPolicyID2]);
httpMonitorJson.locations.push({
id: testFleetPolicyID2,
agentPolicyId: testFleetPolicyID2,
label: 'Test private location ' + 1,
isServiceManaged: false,
geo: {
lat: 0,
lon: 0,
},
const pvtLoc2 = await testPrivateLocations.addPrivateLocation({
policyId: testFleetPolicyID2,
label: 'Test private location 1',
});
httpMonitorJson.locations.push(pvtLoc2);
const apiResponse = await supertestAPI
.put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId)
.set('kbn-xsrf', 'true')
@ -308,54 +280,23 @@ export default function ({ getService }: FtrProviderContext) {
});
it('handles spaces', async () => {
const username = 'admin';
const password = `${username}-password`;
const roleName = 'uptime-role';
const SPACE_ID = `test-space-${uuidv4()}`;
const SPACE_NAME = `test-space-name ${uuidv4()}`;
const { username, password, SPACE_ID, roleName } = await monitorTestService.addsNewSpace();
let monitorId = '';
const monitor = {
...httpMonitorJson,
name: `Test monitor ${uuidv4()}`,
[ConfigKey.NAMESPACE]: 'default',
locations: [
{
id: testFleetPolicyID,
agentPolicyId: testFleetPolicyID,
label: 'Test private location 0',
isServiceManaged: false,
geo: {
lat: 0,
lon: 0,
},
},
],
locations: [pvtLoc],
};
try {
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
await security.role.create(roleName, {
kibana: [
{
feature: {
uptime: ['all'],
actions: ['all'],
},
spaces: ['*'],
},
],
});
await security.user.create(username, {
password,
roles: [roleName],
full_name: 'a kibana user',
});
const apiResponse = await supertestWithoutAuth
.post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.send(monitor)
.expect(200);
.send(monitor);
expect(apiResponse.status).eql(200, JSON.stringify(apiResponse.body));
const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body;
expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]);

View file

@ -46,8 +46,6 @@ export default function ({ getService }: FtrProviderContext) {
let icmpProjectMonitors: ProjectMonitorsRequest;
let testPolicyId = '';
const testPolicyName = 'Fleet test server policy' + Date.now();
const setUniqueIds = (request: ProjectMonitorsRequest) => {
return {
...request,
@ -87,9 +85,8 @@ export default function ({ getService }: FtrProviderContext) {
.expect(200);
await testPrivateLocations.installSyntheticsPackage();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
await testPrivateLocations.setTestLocations([testPolicyId]);
const loc = await testPrivateLocations.addPrivateLocation();
testPolicyId = loc.id;
await supertest
.post(SYNTHETICS_API_URLS.PARAMS)
.set('kbn-xsrf', 'true')

View file

@ -24,9 +24,6 @@ export default function ({ getService }: FtrProviderContext) {
let projectMonitors: ProjectMonitorsRequest;
const monitorTestService = new SyntheticsMonitorTestService(getService);
let testPolicyId = '';
const testPolicyName = 'Fleet test server policy' + Date.now();
const testPrivateLocations = new PrivateLocationTestService(getService);
const setUniqueIds = (request: ProjectMonitorsRequest) => {
@ -42,10 +39,7 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.expect(200);
await testPrivateLocations.installSyntheticsPackage();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
await testPrivateLocations.setTestLocations([testPolicyId]);
await testPrivateLocations.addPrivateLocation();
});
after(async () => {

View file

@ -59,10 +59,8 @@ export default function ({ getService }: FtrProviderContext) {
await testPrivateLocations.installSyntheticsPackage();
const testPolicyName = 'Fleet test server policy' + Date.now();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
await testPrivateLocations.setTestLocations([testPolicyId]);
const loc = await testPrivateLocations.addPrivateLocation();
testPolicyId = loc.id;
});
beforeEach(() => {

View file

@ -37,11 +37,10 @@ export default function ({ getService }: FtrProviderContext) {
};
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await testPrivateLocations.installSyntheticsPackage();
const testPolicyName = 'Fleet test server policy' + Date.now();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
await testPrivateLocations.setTestLocations([testPolicyId]);
const loc = await testPrivateLocations.addPrivateLocation();
testPolicyId = loc.id;
});
beforeEach(() => {

View file

@ -79,10 +79,8 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.expect(200);
const testPolicyName = 'Fleet test server policy' + Date.now();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
await testPrivateLocations.setTestLocations([testPolicyId]);
const loc = await testPrivateLocations.addPrivateLocation();
testPolicyId = loc.id;
});
after(async () => {

View file

@ -240,7 +240,7 @@ export default function ({ getService }: FtrProviderContext) {
it('can add private location to existing monitor', async () => {
await testPrivateLocations.installSyntheticsPackage();
pvtLoc = await testPrivateLocations.addTestPrivateLocation();
pvtLoc = await testPrivateLocations.addPrivateLocation();
expect(pvtLoc).not.empty();

View file

@ -22,13 +22,13 @@ export default function ({ getService }: FtrProviderContext) {
this.tags('skipCloud');
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
let projectMonitors: LegacyProjectMonitorsRequest;
let httpProjectMonitors: LegacyProjectMonitorsRequest;
let tcpProjectMonitors: LegacyProjectMonitorsRequest;
let icmpProjectMonitors: LegacyProjectMonitorsRequest;
let testPolicyId = '';
const testPrivateLocations = new PrivateLocationTestService(getService);
const setUniqueIds = (request: LegacyProjectMonitorsRequest) => {
@ -39,12 +39,10 @@ export default function ({ getService }: FtrProviderContext) {
};
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await testPrivateLocations.installSyntheticsPackage();
const testPolicyName = 'Fleet test server policy' + Date.now();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testPolicyId = apiResponse.body.item.id;
await testPrivateLocations.setTestLocations([testPolicyId]);
await testPrivateLocations.addPrivateLocation();
});
beforeEach(() => {

View file

@ -35,5 +35,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./inspect_monitor'));
loadTestFile(require.resolve('./test_now_monitor'));
loadTestFile(require.resolve('./suggestions'));
loadTestFile(require.resolve('./private_location_apis'));
});
}

View file

@ -171,7 +171,7 @@ export default function ({ getService }: FtrProviderContext) {
});
it('inspect http monitor in private location', async () => {
const location = await testPrivateLocations.addTestPrivateLocation();
const location = await testPrivateLocations.addPrivateLocation();
const apiResponse = await monitorTestService.inspectMonitor({
..._monitors[0],
locations: [

View file

@ -0,0 +1,64 @@
/*
* 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 expect from '@kbn/expect';
import {
legacyPrivateLocationsSavedObjectId,
legacyPrivateLocationsSavedObjectName,
privateLocationSavedObjectName,
} from '@kbn/synthetics-plugin/common/saved_objects/private_locations';
import { FtrProviderContext } from '../../ftr_provider_context';
import { PrivateLocationTestService } from './services/private_location_test_service';
export default function ({ getService }: FtrProviderContext) {
describe('PrivateLocationAPI', function () {
this.tags('skipCloud');
const kServer = getService('kibanaServer');
const testPrivateLocations = new PrivateLocationTestService(getService);
before(async () => {
await testPrivateLocations.installSyntheticsPackage();
await kServer.savedObjects.clean({
types: [legacyPrivateLocationsSavedObjectName, privateLocationSavedObjectName],
});
});
it('adds a test legacy private location', async () => {
const locs = await testPrivateLocations.addLegacyPrivateLocations();
expect(locs.length).to.be(2);
});
it('adds a test private location', async () => {
await testPrivateLocations.addPrivateLocation();
});
it('list all locations', async () => {
const locs = await testPrivateLocations.fetchAll();
expect(locs.body.length).to.be(3);
});
it('migrates to new saved objet type', async () => {
const newData = await kServer.savedObjects.find({
type: privateLocationSavedObjectName,
});
expect(newData.saved_objects.length).to.be(3);
try {
await kServer.savedObjects.get({
type: legacyPrivateLocationsSavedObjectName,
id: legacyPrivateLocationsSavedObjectId,
});
} catch (e) {
expect(e.response.status).to.be(404);
}
});
});
}

View file

@ -4,11 +4,14 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { v4 as uuidv4 } from 'uuid';
import { privateLocationsSavedObjectName } from '@kbn/synthetics-plugin/common/saved_objects/private_locations';
import { privateLocationsSavedObjectId } from '@kbn/synthetics-plugin/server/saved_objects/private_locations';
import { SyntheticsPrivateLocations } from '@kbn/synthetics-plugin/common/runtime_types';
import expect from '@kbn/expect';
import { PrivateLocation } from '@kbn/synthetics-plugin/common/runtime_types';
import { KibanaSupertestProvider } from '@kbn/ftr-common-functional-services';
import { SYNTHETICS_API_URLS } from '@kbn/synthetics-plugin/common/constants';
import {
legacyPrivateLocationsSavedObjectId,
legacyPrivateLocationsSavedObjectName,
} from '@kbn/synthetics-plugin/common/saved_objects/private_locations';
import { FtrProviderContext } from '../../../ftr_provider_context';
export const INSTALLED_VERSION = '1.1.1';
@ -37,18 +40,12 @@ export class PrivateLocationTestService {
}
}
async addTestPrivateLocation() {
const apiResponse = await this.addFleetPolicy(uuidv4());
const testPolicyId = apiResponse.body.item.id;
return (await this.setTestLocations([testPolicyId]))[0];
}
async addFleetPolicy(name: string) {
async addFleetPolicy(name?: string) {
return this.supertest
.post('/api/fleet/agent_policies?sys_monitoring=true')
.set('kbn-xsrf', 'true')
.send({
name,
name: name ?? 'Fleet test server policy' + Date.now(),
description: '',
namespace: 'default',
monitoring_enabled: [],
@ -56,28 +53,72 @@ export class PrivateLocationTestService {
.expect(200);
}
async setTestLocations(testFleetPolicyIds: string[]) {
const server = this.getService('kibanaServer');
async addPrivateLocation({ policyId, label }: { policyId?: string; label?: string } = {}) {
let agentPolicyId = policyId;
const locations: SyntheticsPrivateLocations = testFleetPolicyIds.map((id, index) => ({
label: 'Test private location ' + index,
agentPolicyId: id,
id,
if (!agentPolicyId) {
const apiResponse = await this.addFleetPolicy();
agentPolicyId = apiResponse.body.item.id;
}
const location: Omit<PrivateLocation, 'id'> = {
label: label ?? 'Test private location 0',
agentPolicyId: agentPolicyId!,
geo: {
lat: 0,
lon: 0,
},
isServiceManaged: false,
}));
};
const response = await this.supertest
.post(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS)
.set('kbn-xsrf', 'true')
.send(location);
expect(response.status).to.be(200);
const { isInvalid, ...loc } = response.body;
return loc;
}
async addLegacyPrivateLocations() {
const server = this.getService('kibanaServer');
const fleetPolicy = await this.addFleetPolicy();
const fleetPolicy2 = await this.addFleetPolicy();
const locs = [
{
id: fleetPolicy.body.item.id,
agentPolicyId: fleetPolicy.body.item.id,
name: 'Test private location 1',
lat: 0,
lon: 0,
},
{
id: fleetPolicy2.body.item.id,
agentPolicyId: fleetPolicy2.body.item.id,
name: 'Test private location 2',
lat: 0,
lon: 0,
},
];
await server.savedObjects.create({
type: privateLocationsSavedObjectName,
id: privateLocationsSavedObjectId,
type: legacyPrivateLocationsSavedObjectName,
id: legacyPrivateLocationsSavedObjectId,
attributes: {
locations,
locations: locs,
},
overwrite: true,
});
return locations;
return locs;
}
async fetchAll() {
return this.supertest
.get(SYNTHETICS_API_URLS.PRIVATE_LOCATIONS)
.set('kbn-xsrf', 'true')
.expect(200);
}
}

View file

@ -202,7 +202,7 @@ export class SyntheticsMonitorTestService {
full_name: 'a kibana user',
});
return { username, password, SPACE_ID };
return { username, password, SPACE_ID, roleName };
}
async deleteMonitor(monitorId?: string | string[], statusCode = 200, spaceId?: string) {

View file

@ -60,15 +60,9 @@ export default function ({ getService }: FtrProviderContext) {
httpMonitorJson = _httpMonitorJson;
});
const testPolicyName = 'Fleet test server policy' + Date.now();
it('adds a test fleet policy', async () => {
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);
testFleetPolicyID = apiResponse.body.item.id;
});
it('add a test private location', async () => {
await testPrivateLocations.setTestLocations([testFleetPolicyID]);
const loc = await testPrivateLocations.addPrivateLocation();
testFleetPolicyID = loc.id;
const apiResponse = await supertestAPI.get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS);

View file

@ -48,6 +48,15 @@ export default function ({ getService }: FtrProviderContext) {
);
};
before(async () => {
// clean up all api keys
const { body } = await esSupertest.get(`/_security/api_key`).query({ with_limited_by: true });
const apiKeys = body.api_keys || [];
for (const apiKey of apiKeys) {
await esSupertest.delete(`/_security/api_key`).send({ ids: [apiKey.id] });
}
});
describe('[PUT] /internal/uptime/service/enablement', () => {
beforeEach(async () => {
const apiKeys = await getApiKeys();

View file

@ -8728,6 +8728,18 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:uptime-synthetics-api-key/delete",
"saved_object:uptime-synthetics-api-key/bulk_delete",
"saved_object:uptime-synthetics-api-key/share_to_space",
"saved_object:synthetics-private-location/bulk_get",
"saved_object:synthetics-private-location/get",
"saved_object:synthetics-private-location/find",
"saved_object:synthetics-private-location/open_point_in_time",
"saved_object:synthetics-private-location/close_point_in_time",
"saved_object:synthetics-private-location/create",
"saved_object:synthetics-private-location/bulk_create",
"saved_object:synthetics-private-location/update",
"saved_object:synthetics-private-location/bulk_update",
"saved_object:synthetics-private-location/delete",
"saved_object:synthetics-private-location/bulk_delete",
"saved_object:synthetics-private-location/share_to_space",
"saved_object:synthetics-privates-locations/bulk_get",
"saved_object:synthetics-privates-locations/get",
"saved_object:synthetics-privates-locations/find",
@ -9417,6 +9429,18 @@ export default function ({ getService }: FtrProviderContext) {
"saved_object:uptime-synthetics-api-key/delete",
"saved_object:uptime-synthetics-api-key/bulk_delete",
"saved_object:uptime-synthetics-api-key/share_to_space",
"saved_object:synthetics-private-location/bulk_get",
"saved_object:synthetics-private-location/get",
"saved_object:synthetics-private-location/find",
"saved_object:synthetics-private-location/open_point_in_time",
"saved_object:synthetics-private-location/close_point_in_time",
"saved_object:synthetics-private-location/create",
"saved_object:synthetics-private-location/bulk_create",
"saved_object:synthetics-private-location/update",
"saved_object:synthetics-private-location/bulk_update",
"saved_object:synthetics-private-location/delete",
"saved_object:synthetics-private-location/bulk_delete",
"saved_object:synthetics-private-location/share_to_space",
"saved_object:synthetics-privates-locations/bulk_get",
"saved_object:synthetics-privates-locations/get",
"saved_object:synthetics-privates-locations/find",