Skip to content

Commit

Permalink
[8.x] [ObsUX][APM] Migration of Service Overview tests to deployment …
Browse files Browse the repository at this point in the history
…agnostic approach (#200226) (#200916)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ObsUX][APM] Migration of Service Overview tests to deployment
agnostic approach
(#200226)](#200226)

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

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

<!--BACKPORT [{"author":{"name":"Gonçalo Rica Pais da
Silva","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-20T13:17:24Z","message":"[ObsUX][APM]
Migration of Service Overview tests to deployment agnostic approach
(#200226)\n\n## Summary\r\n\r\nPart of #193245\r\nCloses
#198986\r\n\r\nThis PR moves all compatible/supported test cases for
Service Overview.\r\nUnsupported cases are kept in the old test section
to run on stateful\r\nfor now.\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 API tests\"\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\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 API tests\"\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"185fa2a7dc086a0b30925ea4447f9ec3de1c8651","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","backport:prev-minor","ci:project-deploy-observability","Team:obs-ux-infra_services","v8.17.0"],"title":"[ObsUX][APM]
Migration of Service Overview tests to deployment agnostic
approach","number":200226,"url":"https://github.com/elastic/kibana/pull/200226","mergeCommit":{"message":"[ObsUX][APM]
Migration of Service Overview tests to deployment agnostic approach
(#200226)\n\n## Summary\r\n\r\nPart of #193245\r\nCloses
#198986\r\n\r\nThis PR moves all compatible/supported test cases for
Service Overview.\r\nUnsupported cases are kept in the old test section
to run on stateful\r\nfor now.\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 API tests\"\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\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 API tests\"\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"185fa2a7dc086a0b30925ea4447f9ec3de1c8651"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/200226","number":200226,"mergeCommit":{"message":"[ObsUX][APM]
Migration of Service Overview tests to deployment agnostic approach
(#200226)\n\n## Summary\r\n\r\nPart of #193245\r\nCloses
#198986\r\n\r\nThis PR moves all compatible/supported test cases for
Service Overview.\r\nUnsupported cases are kept in the old test section
to run on stateful\r\nfor now.\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 API tests\"\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\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 API tests\"\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by:
kibanamachine
<[email protected]>\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"185fa2a7dc086a0b30925ea4447f9ec3de1c8651"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Gonçalo Rica Pais da Silva <[email protected]>
  • Loading branch information
kibanamachine and Bluefinger authored Nov 20, 2024
1 parent 096a3cd commit ea3ff91
Show file tree
Hide file tree
Showing 14 changed files with 1,306 additions and 1,200 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ export default function apmApiIntegrationTests({
loadTestFile(require.resolve('./span_links'));
loadTestFile(require.resolve('./suggestions'));
loadTestFile(require.resolve('./throughput'));
loadTestFile(require.resolve('./service_overview'));
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { v4 as uuidv4 } from 'uuid';

export function createServiceDependencyDocs({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
/*
* 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 { last, pick } from 'lodash';
import type { ValuesType } from 'utility-types';
import { type Node, NodeType } from '@kbn/apm-plugin/common/connections';
import {
ENVIRONMENT_ALL,
ENVIRONMENT_NOT_DEFINED,
} from '@kbn/apm-plugin/common/environment_filter_values';
import type { APIReturnType } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
import { roundNumber } from '../../utils/common';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../../ftr_provider_context';
import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils';

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

const { start, end } = {
start: '2021-08-03T06:50:15.910Z',
end: '2021-08-03T07:20:15.910Z',
};

function getName(node: Node) {
return node.type === NodeType.service ? node.serviceName : node.dependencyName;
}

describe('Service Overview', () => {
describe('Dependencies', () => {
describe('when data is not loaded', () => {
it('handles the empty state', async () => {
const response = await apmApiClient.readUser({
endpoint: `GET /internal/apm/services/{serviceName}/dependencies`,
params: {
path: { serviceName: 'opbeans-java' },
query: {
start,
end,
numBuckets: 20,
environment: ENVIRONMENT_ALL.value,
},
},
});

expect(response.status).to.be(200);
expect(response.body.serviceDependencies).to.eql([]);
});
});

describe('when specific data is loaded', () => {
let response: {
status: number;
body: APIReturnType<'GET /internal/apm/services/{serviceName}/dependencies'>;
};

const indices = {
metric: 'apm-dependencies-metric',
transaction: 'apm-dependencies-transaction',
span: 'apm-dependencies-span',
};

const startTime = new Date(start).getTime();
const endTime = new Date(end).getTime();

after(async () => {
const allIndices = Object.values(indices).join(',');
const indexExists = await es.indices.exists({ index: allIndices });
if (indexExists) {
await es.indices.delete({
index: allIndices,
});
}
});

before(async () => {
await es.indices.create({
index: indices.metric,
body: {
mappings: apmDependenciesMapping,
},
});

await es.indices.create({
index: indices.transaction,
body: {
mappings: apmDependenciesMapping,
},
});

await es.indices.create({
index: indices.span,
body: {
mappings: apmDependenciesMapping,
},
});

const docs = [
...createServiceDependencyDocs({
service: {
name: 'opbeans-java',
environment: 'production',
},
agentName: 'java',
span: {
type: 'external',
subtype: 'http',
},
resource: 'opbeans-node:3000',
outcome: 'success',
responseTime: {
count: 2,
sum: 10,
},
time: startTime,
to: {
service: {
name: 'opbeans-node',
},
agentName: 'nodejs',
},
}),
...createServiceDependencyDocs({
service: {
name: 'opbeans-java',
environment: 'production',
},
agentName: 'java',
span: {
type: 'external',
subtype: 'http',
},
resource: 'opbeans-node:3000',
outcome: 'failure',
responseTime: {
count: 1,
sum: 10,
},
time: startTime,
}),
...createServiceDependencyDocs({
service: {
name: 'opbeans-java',
environment: 'production',
},
agentName: 'java',
span: {
type: 'external',
subtype: 'http',
},
resource: 'postgres',
outcome: 'success',
responseTime: {
count: 1,
sum: 3,
},
time: startTime,
}),
...createServiceDependencyDocs({
service: {
name: 'opbeans-java',
environment: 'production',
},
agentName: 'java',
span: {
type: 'external',
subtype: 'http',
},
resource: 'opbeans-node-via-proxy',
outcome: 'success',
responseTime: {
count: 1,
sum: 1,
},
time: endTime - 1,
to: {
service: {
name: 'opbeans-node',
},
agentName: 'nodejs',
},
}),
];

const bulkActions = docs.reduce(
(prev, doc) => {
return [...prev, { index: { _index: indices[doc.processor.event] } }, doc];
},
[] as Array<
| {
index: {
_index: string;
};
}
| ValuesType<typeof docs>
>
);

await es.bulk({
body: bulkActions,
refresh: 'wait_for',
});

response = await apmApiClient.readUser({
endpoint: `GET /internal/apm/services/{serviceName}/dependencies`,
params: {
path: { serviceName: 'opbeans-java' },
query: {
start,
end,
numBuckets: 20,
environment: ENVIRONMENT_ALL.value,
},
},
});
});

it('returns a 200', () => {
expect(response.status).to.be(200);
});

it('returns two dependencies', () => {
expect(response.body.serviceDependencies.length).to.be(2);
});

it('returns opbeans-node as a dependency', () => {
const opbeansNode = response.body.serviceDependencies.find(
(item) => getName(item.location) === 'opbeans-node'
);

expect(opbeansNode !== undefined).to.be(true);

const values = {
latency: roundNumber(opbeansNode?.currentStats.latency.value),
throughput: roundNumber(opbeansNode?.currentStats.throughput.value),
errorRate: roundNumber(opbeansNode?.currentStats.errorRate.value),
impact: opbeansNode?.currentStats.impact,
...pick(opbeansNode?.location, 'serviceName', 'type', 'agentName', 'environment'),
};

const count = 4;
const sum = 21;
const errors = 1;

expect(values).to.eql({
agentName: 'nodejs',
environment: ENVIRONMENT_NOT_DEFINED.value,
serviceName: 'opbeans-node',
type: 'service',
errorRate: roundNumber(errors / count),
latency: roundNumber(sum / count),
throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)),
impact: 100,
});

const firstValue = roundNumber(opbeansNode?.currentStats.latency.timeseries[0].y);
const lastValue = roundNumber(last(opbeansNode?.currentStats.latency.timeseries)?.y);

expect(firstValue).to.be(roundNumber(20 / 3));
expect(lastValue).to.be(1);
});

it('returns postgres as an external dependency', () => {
const postgres = response.body.serviceDependencies.find(
(item) => getName(item.location) === 'postgres'
);

expect(postgres !== undefined).to.be(true);

const values = {
latency: roundNumber(postgres?.currentStats.latency.value),
throughput: roundNumber(postgres?.currentStats.throughput.value),
errorRate: roundNumber(postgres?.currentStats.errorRate.value),
impact: postgres?.currentStats.impact,
...pick(postgres?.location, 'spanType', 'spanSubtype', 'dependencyName', 'type'),
};

const count = 1;
const sum = 3;
const errors = 0;

expect(values).to.eql({
spanType: 'external',
spanSubtype: 'http',
dependencyName: 'postgres',
type: 'dependency',
errorRate: roundNumber(errors / count),
latency: roundNumber(sum / count),
throughput: roundNumber(count / ((endTime - startTime) / 1000 / 60)),
impact: 0,
});
});
});

// UNSUPPORTED TEST CASES - when data is loaded
// TODO: These tests should be migrated to use synthtrace: https://github.com/elastic/kibana/issues/200743
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 { take } from 'lodash';
import { LatencyAggregationType } from '@kbn/apm-plugin/common/latency_aggregation_types';
import type { ApmApiClient } from '../custom_dashboards/api_helper';

export async function getServiceNodeIds({
apmApiClient,
start,
end,
serviceName = 'opbeans-java',
count = 1,
}: {
apmApiClient: Awaited<ApmApiClient>;
start: string;
end: string;
serviceName?: string;
count?: number;
}) {
const { body } = await apmApiClient.readUser({
endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`,
params: {
path: { serviceName },
query: {
latencyAggregationType: LatencyAggregationType.avg,
start,
end,
transactionType: 'request',
environment: 'ENVIRONMENT_ALL',
kuery: '',
sortField: 'throughput',
sortDirection: 'desc',
},
},
});

return take(body.currentPeriod.map((item) => item.serviceNodeName).sort(), count);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* 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 { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';

export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext) {
describe('service_overview', () => {
loadTestFile(require.resolve('./instance_details.spec.ts'));
loadTestFile(require.resolve('./instances_detailed_statistics.spec.ts'));
loadTestFile(require.resolve('./instances_main_statistics.spec.ts'));
loadTestFile(require.resolve('./dependencies/index.spec.ts'));
});
}
Loading

0 comments on commit ea3ff91

Please sign in to comment.