Skip to content

Commit

Permalink
[Synthetics UI] update sync service endpoints (#155054)
Browse files Browse the repository at this point in the history
Co-authored-by: Alejandro Fernández Gómez <alejandro.fernandez@elastic.co>
  • Loading branch information
shahzad31 and Alejandro Fernández Gómez authored Apr 18, 2023
1 parent 75c9781 commit b6d4090
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
setAddingNewPrivateLocation,
getAgentPoliciesAction,
selectAgentPolicies,
cleanMonitorListState,
} from '../../state';
import { MONITOR_ADD_ROUTE } from '../../../../../common/constants/ui';
import { PrivateLocation } from '../../../../../common/runtime_types';
Expand All @@ -46,6 +47,9 @@ export const GettingStartedPage = () => {
if (canReadAgentPolicies) {
dispatch(getAgentPoliciesAction.get());
}
return () => {
dispatch(cleanMonitorListState());
};
}, [canReadAgentPolicies, dispatch]);

useBreadcrumbs([{ text: MONITORING_OVERVIEW_LABEL }]); // No extra breadcrumbs on overview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ describe('callAPI', () => {
1,
{
isEdit: undefined,
runOnce: undefined,
monitors: testMonitors.filter((monitor: any) =>
monitor.locations.some((loc: any) => loc.id === 'us_central')
),
Expand All @@ -192,7 +191,6 @@ describe('callAPI', () => {
2,
{
isEdit: undefined,
runOnce: undefined,
monitors: testMonitors.filter((monitor: any) =>
monitor.locations.some((loc: any) => loc.id === 'us_central_qa')
),
Expand All @@ -207,7 +205,6 @@ describe('callAPI', () => {
3,
{
isEdit: undefined,
runOnce: undefined,
monitors: testMonitors.filter((monitor: any) =>
monitor.locations.some((loc: any) => loc.id === 'us_central_staging')
),
Expand Down Expand Up @@ -284,15 +281,15 @@ describe('callAPI', () => {
});
expect(logger.debug).toHaveBeenNthCalledWith(
2,
'Successfully called service location https://service.dev with method POST with 4 monitors '
'Successfully called service location https://service.devundefined with method POST with 4 monitors'
);
expect(logger.debug).toHaveBeenNthCalledWith(
4,
'Successfully called service location https://qa.service.elstc.co with method POST with 4 monitors '
'Successfully called service location https://qa.service.elstc.coundefined with method POST with 4 monitors'
);
expect(logger.debug).toHaveBeenNthCalledWith(
6,
'Successfully called service location https://qa.service.stg.co with method POST with 1 monitors '
'Successfully called service location https://qa.service.stg.coundefined with method POST with 1 monitors'
);
});

Expand Down Expand Up @@ -320,7 +317,6 @@ describe('callAPI', () => {
coreStart: mockCoreStart,
} as UptimeServerSetup
);

apiClient.locations = testLocations;

const output = { hosts: ['https://localhost:9200'], api_key: '12345' };
Expand Down Expand Up @@ -354,6 +350,110 @@ describe('callAPI', () => {
url: 'https://service.dev/monitors',
});
});

it('Calls the `/run` endpoint when calling `runOnce`', async () => {
const axiosSpy = (axios as jest.MockedFunction<typeof axios>).mockResolvedValue({
status: 200,
statusText: 'ok',
headers: {},
config: {},
data: { allowed: true, signupUrl: 'http://localhost:666/example' },
});

const apiClient = new ServiceAPIClient(
logger,
{
manifestUrl: 'http://localhost:8080/api/manifest',
tls: { certificate: 'test-certificate', key: 'test-key' } as any,
},
{ isDev: true, stackVersion: '8.7.0' } as UptimeServerSetup
);

apiClient.locations = testLocations;

const output = { hosts: ['https://localhost:9200'], api_key: '12345' };

await apiClient.runOnce({
monitors: testMonitors,
output,
licenseLevel: 'trial',
});

expect(axiosSpy).toHaveBeenNthCalledWith(1, {
data: {
monitors: request1,
is_edit: undefined,
output,
stack_version: '8.7.0',
license_level: 'trial',
},
headers: {
'x-kibana-version': '8.7.0',
},
httpsAgent: expect.objectContaining({
options: {
rejectUnauthorized: true,
path: null,
cert: 'test-certificate',
key: 'test-key',
},
}),
method: 'POST',
url: 'https://service.dev/run',
});
});

it('Calls the `/monitors/sync` endpoint when calling `syncMonitors`', async () => {
const axiosSpy = (axios as jest.MockedFunction<typeof axios>).mockResolvedValue({
status: 200,
statusText: 'ok',
headers: {},
config: {},
data: { allowed: true, signupUrl: 'http://localhost:666/example' },
});

const apiClient = new ServiceAPIClient(
logger,
{
manifestUrl: 'http://localhost:8080/api/manifest',
tls: { certificate: 'test-certificate', key: 'test-key' } as any,
},
{ isDev: true, stackVersion: '8.7.0' } as UptimeServerSetup
);

apiClient.locations = testLocations;

const output = { hosts: ['https://localhost:9200'], api_key: '12345' };

await apiClient.syncMonitors({
monitors: testMonitors,
output,
licenseLevel: 'trial',
});

expect(axiosSpy).toHaveBeenNthCalledWith(1, {
data: {
monitors: request1,
is_edit: undefined,
output,
stack_version: '8.7.0',
license_level: 'trial',
},
headers: {
'x-kibana-version': '8.7.0',
},
httpsAgent: expect.objectContaining({
options: {
rejectUnauthorized: true,
path: null,
cert: 'test-certificate',
key: 'test-key',
},
}),
method: 'PUT',
url: 'https://service.dev/monitors/sync',
});
});
});

const testLocations: PublicLocations = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface ServiceData {
hosts: string[];
api_key: string;
};
runOnce?: boolean;
endpoint?: 'monitors' | 'runOnce' | 'sync';
isEdit?: boolean;
licenseLevel: string;
}
Expand Down Expand Up @@ -81,7 +81,7 @@ export class ServiceAPIClient {
}

async post(data: ServiceData) {
return this.callAPI('PUT', data);
return this.callAPI('POST', data);
}

async put(data: ServiceData) {
Expand All @@ -93,7 +93,11 @@ export class ServiceAPIClient {
}

async runOnce(data: ServiceData) {
return this.callAPI('POST', { ...data, runOnce: true });
return this.callAPI('POST', { ...data, endpoint: 'runOnce' });
}

async syncMonitors(data: ServiceData) {
return this.callAPI('PUT', { ...data, endpoint: 'sync' });
}

addVersionHeader(req: AxiosRequestConfig) {
Expand All @@ -112,7 +116,7 @@ export class ServiceAPIClient {
const url = this.locations[Math.floor(Math.random() * this.locations.length)].url;

/* url is required for service locations, but omitted for private locations.
/* this.locations is only service locations */
/* this.locations is only service locations */
const httpsAgent = this.getHttpsAgent(url);

if (httpsAgent) {
Expand All @@ -138,7 +142,7 @@ export class ServiceAPIClient {

async callAPI(
method: 'POST' | 'PUT' | 'DELETE',
{ monitors: allMonitors, output, runOnce, isEdit, licenseLevel }: ServiceData
{ monitors: allMonitors, output, endpoint, isEdit, licenseLevel }: ServiceData
) {
if (this.username === TEST_SERVICE_USERNAME) {
// we don't want to call service while local integration tests are running
Expand All @@ -154,25 +158,24 @@ export class ServiceAPIClient {
locations?.find((loc) => loc.id === id && loc.isServiceManaged)
);
if (locMonitors.length > 0) {
const promise = this.callServiceEndpoint(
{ monitors: locMonitors, isEdit, endpoint, output, licenseLevel },
method,
url
);
promises.push(
rxjsFrom(
this.callServiceEndpoint(
{ monitors: locMonitors, isEdit, runOnce, output, licenseLevel },
method,
url
)
).pipe(
rxjsFrom(promise).pipe(
tap((result) => {
this.logger.debug(result.data);
this.logger.debug(
`Successfully called service location ${url} with method ${method} with ${locMonitors.length} monitors `
`Successfully called service location ${url}${result.request?.path} with method ${method} with ${locMonitors.length} monitors`
);
}),
catchError((err: AxiosError<{ reason: string; status: number }>) => {
pushErrors.push({ locationId: id, error: err.response?.data! });
const reason = err.response?.data?.reason ?? '';

err.message = `Failed to call service location ${url} with method ${method} with ${locMonitors.length} monitors: ${err.message}, ${reason}`;
err.message = `Failed to call service location ${url}${err.request?.path} with method ${method} with ${locMonitors.length} monitors: ${err.message}, ${reason}`;
this.logger.error(err);
sendErrorTelemetryEvents(this.logger, this.server.telemetry, {
reason: err.response?.data?.reason,
Expand All @@ -197,32 +200,43 @@ export class ServiceAPIClient {
}

async callServiceEndpoint(
{ monitors, output, runOnce, isEdit, licenseLevel }: ServiceData,
{ monitors, output, endpoint = 'monitors', isEdit, licenseLevel }: ServiceData,
method: 'POST' | 'PUT' | 'DELETE',
url: string
baseUrl: string
) {
// don't need to pass locations to heartbeat
const monitorsStreams = monitors.map(({ locations, ...rest }) =>
convertToDataStreamFormat(rest)
);

let url = baseUrl;
switch (endpoint) {
case 'monitors':
url += '/monitors';
break;
case 'runOnce':
url += '/run';
break;
case 'sync':
url += '/monitors/sync';
break;
}

const authHeader = this.authorization ? { Authorization: this.authorization } : undefined;

return axios(
this.addVersionHeader({
method,
url: url + (runOnce ? '/run' : '/monitors'),
url,
data: {
monitors: monitorsStreams,
output,
stack_version: this.stackVersion,
is_edit: isEdit,
license_level: licenseLevel,
},
headers: this.authorization
? {
Authorization: this.authorization,
}
: undefined,
httpsAgent: this.getHttpsAgent(url),
headers: authHeader,
httpsAgent: this.getHttpsAgent(baseUrl),
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ export class SyntheticsService {

this.logger.debug(`${monitors.length} monitors will be pushed to synthetics service.`);

service.syncErrors = await this.apiClient.put({
service.syncErrors = await this.apiClient.syncMonitors({
monitors,
output,
licenseLevel: license.type,
Expand Down

0 comments on commit b6d4090

Please sign in to comment.