Skip to content

Commit

Permalink
[8.x] [APM] Migrate `/service_maps` to deployment agnostic …
Browse files Browse the repository at this point in the history
…test (#199984) (#200541)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[APM] Migrate `/service_maps` to deployment agnostic test
(#199984)](#199984)

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

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

<!--BACKPORT [{"author":{"name":"Sergi
Romeu","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-18T11:32:26Z","message":"[APM]
Migrate `/service_maps` to deployment agnostic test (#199984)\n\n##
Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/198983\r\nPart of
https://github.com/elastic/kibana/issues/193245\r\n\r\nThis PR contains
the changes to migrate `service_maps` test folder
to\r\nDeployment-agnostic testing strategy.\r\n\r\n### How to
test\r\n\r\n- Serverless\r\n\r\n```\r\nnode
scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\r\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
--grep=\"APM\"\r\n```\r\n\r\nIt's recommended to be run
against\r\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\r\n\r\n-
Stateful\r\n```\r\nnode scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\r\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
--grep=\"APM\"\r\n```\r\n\r\n## Checks\r\n\r\n- [ ] (OPTIONAL, only if a
test has been unskipped) Run flaky test suite\r\n- [x] local run for
serverless\r\n- [x] local run for stateful\r\n- [x] MKI run for
serverless\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"53d05233a5484f7071167392e762ee4dd4dcda9c","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","apm","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-infra_services"],"title":"[APM]
Migrate `/service_maps` to deployment agnostic
test","number":199984,"url":"https://github.com/elastic/kibana/pull/199984","mergeCommit":{"message":"[APM]
Migrate `/service_maps` to deployment agnostic test (#199984)\n\n##
Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/198983\r\nPart of
https://github.com/elastic/kibana/issues/193245\r\n\r\nThis PR contains
the changes to migrate `service_maps` test folder
to\r\nDeployment-agnostic testing strategy.\r\n\r\n### How to
test\r\n\r\n- Serverless\r\n\r\n```\r\nnode
scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\r\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
--grep=\"APM\"\r\n```\r\n\r\nIt's recommended to be run
against\r\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\r\n\r\n-
Stateful\r\n```\r\nnode scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\r\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
--grep=\"APM\"\r\n```\r\n\r\n## Checks\r\n\r\n- [ ] (OPTIONAL, only if a
test has been unskipped) Run flaky test suite\r\n- [x] local run for
serverless\r\n- [x] local run for stateful\r\n- [x] MKI run for
serverless\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"53d05233a5484f7071167392e762ee4dd4dcda9c"}},"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/199984","number":199984,"mergeCommit":{"message":"[APM]
Migrate `/service_maps` to deployment agnostic test (#199984)\n\n##
Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/198983\r\nPart of
https://github.com/elastic/kibana/issues/193245\r\n\r\nThis PR contains
the changes to migrate `service_maps` test folder
to\r\nDeployment-agnostic testing strategy.\r\n\r\n### How to
test\r\n\r\n- Serverless\r\n\r\n```\r\nnode
scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\r\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
--grep=\"APM\"\r\n```\r\n\r\nIt's recommended to be run
against\r\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\r\n\r\n-
Stateful\r\n```\r\nnode scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\r\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
--grep=\"APM\"\r\n```\r\n\r\n## Checks\r\n\r\n- [ ] (OPTIONAL, only if a
test has been unskipped) Run flaky test suite\r\n- [x] local run for
serverless\r\n- [x] local run for stateful\r\n- [x] MKI run for
serverless\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<[email protected]>","sha":"53d05233a5484f7071167392e762ee4dd4dcda9c"}}]}]
BACKPORT-->

Co-authored-by: Sergi Romeu <[email protected]>
  • Loading branch information
kibanamachine and rmyz authored Nov 18, 2024
1 parent 900a2de commit 27f20ee
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 298 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function apmApiIntegrationTests({
loadTestFile(require.resolve('./observability_overview'));
loadTestFile(require.resolve('./latency'));
loadTestFile(require.resolve('./infrastructure'));
loadTestFile(require.resolve('./service_maps'));
loadTestFile(require.resolve('./inspect'));
loadTestFile(require.resolve('./service_groups'));
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';

export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
describe('service_maps', () => {
loadTestFile(require.resolve('./service_maps.spec.ts'));
loadTestFile(require.resolve('./service_maps_kuery_filter.spec.ts'));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* 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 type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import expect from 'expect';
import { serviceMap, timerange } from '@kbn/apm-synthtrace-client';
import { Readable } from 'node:stream';
import type { SupertestReturnType } from '../../../../../../apm_api_integration/common/apm_api_supertest';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';

type DependencyResponse = SupertestReturnType<'GET /internal/apm/service-map/dependency'>;
type ServiceNodeResponse =
SupertestReturnType<'GET /internal/apm/service-map/service/{serviceName}'>;

export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const synthtrace = getService('synthtrace');

const start = new Date('2024-06-01T00:00:00.000Z').getTime();
const end = new Date('2024-06-01T00:01:00.000Z').getTime();

describe('APM Service maps', () => {
describe('without data', () => {
it('returns an empty list', async () => {
const response = await apmApiClient.readUser({
endpoint: `GET /internal/apm/service-map`,
params: {
query: {
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
environment: 'ENVIRONMENT_ALL',
},
},
});

expect(response.status).toBe(200);
expect(response.body.elements.length).toBe(0);
});

describe('/internal/apm/service-map/service/{serviceName} without data', () => {
let response: ServiceNodeResponse;
before(async () => {
response = await apmApiClient.readUser({
endpoint: `GET /internal/apm/service-map/service/{serviceName}`,
params: {
path: { serviceName: 'opbeans-node' },
query: {
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
environment: 'ENVIRONMENT_ALL',
},
},
});
});

it('retuns status code 200', () => {
expect(response.status).toBe(200);
});

it('returns an object with nulls', async () => {
[
response.body.currentPeriod?.failedTransactionsRate?.value,
response.body.currentPeriod?.memoryUsage?.value,
response.body.currentPeriod?.cpuUsage?.value,
response.body.currentPeriod?.transactionStats?.latency?.value,
response.body.currentPeriod?.transactionStats?.throughput?.value,
].forEach((value) => {
expect(value).toEqual(null);
});
});
});

describe('/internal/apm/service-map/dependency', () => {
let response: DependencyResponse;
before(async () => {
response = await apmApiClient.readUser({
endpoint: `GET /internal/apm/service-map/dependency`,
params: {
query: {
dependencyName: 'postgres',
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
environment: 'ENVIRONMENT_ALL',
},
},
});
});

it('retuns status code 200', () => {
expect(response.status).toBe(200);
});

it('returns undefined values', () => {
expect(response.body.currentPeriod).toEqual({ transactionStats: {} });
});
});
});

describe('with synthtrace data', () => {
let synthtraceEsClient: ApmSynthtraceEsClient;

before(async () => {
synthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();

const events = timerange(start, end)
.interval('10s')
.rate(3)
.generator(
serviceMap({
services: [
{ 'frontend-rum': 'rum-js' },
{ 'frontend-node': 'nodejs' },
{ advertService: 'java' },
],
definePaths([rum, node, adv]) {
return [
[
[rum, 'fetchAd'],
[node, 'GET /nodejs/adTag'],
[adv, 'APIRestController#getAd'],
['elasticsearch', 'GET ad-*/_search'],
],
];
},
})
);

return synthtraceEsClient.index(Readable.from(Array.from(events)));
});

after(async () => {
await synthtraceEsClient.clean();
});

it('returns service map elements', async () => {
const response = await apmApiClient.readUser({
endpoint: 'GET /internal/apm/service-map',
params: {
query: {
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
environment: 'ENVIRONMENT_ALL',
},
},
});

expect(response.status).toBe(200);
expect(response.body.elements.length).toBeGreaterThan(0);
});
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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 { timerange, serviceMap } from '@kbn/apm-synthtrace-client';
import {
APIClientRequestParamsOf,
APIReturnType,
} from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';

export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const synthtrace = getService('synthtrace');

const start = new Date('2023-01-01T00:00:00.000Z').getTime();
const end = new Date('2023-01-01T00:15:00.000Z').getTime() - 1;

async function callApi(
overrides?: RecursivePartial<
APIClientRequestParamsOf<'GET /internal/apm/service-map'>['params']
>
) {
return await apmApiClient.readUser({
endpoint: 'GET /internal/apm/service-map',
params: {
query: {
start: new Date(start).toISOString(),
end: new Date(end).toISOString(),
environment: 'ENVIRONMENT_ALL',
kuery: '',
...overrides?.query,
},
},
});
}

describe('service map kuery filter', () => {
let apmSynthtraceEsClient: ApmSynthtraceEsClient;

before(async () => {
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();

const events = timerange(start, end)
.interval('15m')
.rate(1)
.generator(
serviceMap({
services: [
{ 'synthbeans-go': 'go' },
{ 'synthbeans-java': 'java' },
{ 'synthbeans-node': 'nodejs' },
],
definePaths([go, java, node]) {
return [
[go, java],
[java, go, 'redis'],
[node, 'redis'],
{
path: [node, java, go, 'elasticsearch'],
transaction: (t) => t.defaults({ 'labels.name': 'node-java-go-es' }),
},
[go, node, java],
];
},
})
);
await apmSynthtraceEsClient.index(events);
});

after(() => apmSynthtraceEsClient.clean());

it('returns full service map when no kuery is defined', async () => {
const { status, body } = await callApi();

expect(status).to.be(200);

const { nodes, edges } = partitionElements(body.elements);

expect(getIds(nodes)).to.eql([
'>elasticsearch',
'>redis',
'synthbeans-go',
'synthbeans-java',
'synthbeans-node',
]);
expect(getIds(edges)).to.eql([
'synthbeans-go~>elasticsearch',
'synthbeans-go~>redis',
'synthbeans-go~synthbeans-java',
'synthbeans-go~synthbeans-node',
'synthbeans-java~synthbeans-go',
'synthbeans-node~>redis',
'synthbeans-node~synthbeans-java',
]);
});

it('returns only service nodes and connections filtered by given kuery', async () => {
const { status, body } = await callApi({
query: { kuery: `labels.name: "node-java-go-es"` },
});

expect(status).to.be(200);

const { nodes, edges } = partitionElements(body.elements);

expect(getIds(nodes)).to.eql([
'>elasticsearch',
'synthbeans-go',
'synthbeans-java',
'synthbeans-node',
]);
expect(getIds(edges)).to.eql([
'synthbeans-go~>elasticsearch',
'synthbeans-java~synthbeans-go',
'synthbeans-node~synthbeans-java',
]);
});
});
}

type ConnectionElements = APIReturnType<'GET /internal/apm/service-map'>['elements'];

function partitionElements(elements: ConnectionElements) {
const edges = elements.filter(({ data }) => 'source' in data && 'target' in data);
const nodes = elements.filter((element) => !edges.includes(element));
return { edges, nodes };
}

function getIds(elements: ConnectionElements) {
return elements.map(({ data }) => data.id).sort();
}
Loading

0 comments on commit 27f20ee

Please sign in to comment.