Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Synthetics UI] update sync service endpoints #155054

Merged
merged 19 commits into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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