From 166d4e951e3ed021dcf8623d3bbba8278429ebad Mon Sep 17 00:00:00 2001 From: Sergi Romeu Date: Mon, 11 Nov 2024 18:23:46 +0100 Subject: [PATCH] [APM] Migrate `/entities` to deployment agnostic test (#199579) ## Summary Closes https://github.com/elastic/kibana/issues/198968 Part of https://github.com/elastic/kibana/issues/193245 This PR contains the changes to migrate `entities` test folder to Deployment-agnostic testing strategy. ### How to test - Serverless ``` node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts --grep="APM" ``` It's recommended to be run against [MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki) - Stateful ``` node scripts/functional_tests_server --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts node scripts/functional_test_runner --config x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts --grep="APM" ``` ## Checks - [ ] (OPTIONAL, only if a test has been unskipped) Run flaky test suite - [x] local run for serverless - [x] local run for stateful - [x] MKI run for serverless --- .../apis/observability/apm/entities/index.ts | 15 ++ ...service_logs_error_rate_timeseries.spec.ts | 47 ++--- .../service_logs_rate_timeseries.spec.ts | 180 ++++++++++++++++++ .../apis/observability/apm/index.ts | 1 + .../logs/service_logs_rate_timeseries.spec.ts | 173 ----------------- 5 files changed, 220 insertions(+), 196 deletions(-) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts rename x-pack/test/{apm_api_integration/tests/entities/logs => api_integration/deployment_agnostic/apis/observability/apm/entities}/service_logs_error_rate_timeseries.spec.ts (85%) create mode 100644 x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_rate_timeseries.spec.ts delete mode 100644 x-pack/test/apm_api_integration/tests/entities/logs/service_logs_rate_timeseries.spec.ts diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts new file mode 100644 index 0000000000000..d7a36e3e447b7 --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/index.ts @@ -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('entities', () => { + loadTestFile(require.resolve('./service_logs_error_rate_timeseries.spec.ts')); + loadTestFile(require.resolve('./service_logs_rate_timeseries.spec.ts')); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_error_rate_timeseries.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_error_rate_timeseries.spec.ts similarity index 85% rename from x-pack/test/apm_api_integration/tests/entities/logs/service_logs_error_rate_timeseries.spec.ts rename to x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_error_rate_timeseries.spec.ts index 282039b8957c9..f6e167db0318e 100644 --- a/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_error_rate_timeseries.spec.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_error_rate_timeseries.spec.ts @@ -9,12 +9,12 @@ import { log, timerange } from '@kbn/apm-synthtrace-client'; import { first, last } from 'lodash'; import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { LogsSynthtraceEsClient } from '@kbn/apm-synthtrace'; +import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context'; -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const logSynthtrace = getService('logSynthtraceEsClient'); +export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) { + const apmApiClient = getService('apmApi'); + const synthtrace = getService('synthtrace'); const serviceName = 'synth-go'; const start = new Date('2024-01-01T00:00:00.000Z').getTime(); @@ -45,25 +45,26 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); return response; } + describe('logs error rate timeseries', () => { + describe('when data is not loaded', () => { + it('handles empty state', async () => { + const response = await getLogsErrorRateTimeseries(); + expect(response.status).to.be(200); + expect(response.body.currentPeriod).to.empty(); + }); + }); - registry.when( - 'Logs error rate timeseries when data is not loaded', - { config: 'basic', archives: [] }, - () => { - describe('Logs error rate api', () => { - it('handles the empty state', async () => { - const response = await getLogsErrorRateTimeseries(); - expect(response.status).to.be(200); - expect(response.body.currentPeriod).to.empty(); - }); + describe('when data loaded', () => { + let logSynthtrace: LogsSynthtraceEsClient; + + before(async () => { + logSynthtrace = await synthtrace.createLogsSynthtraceEsClient(); + }); + + after(async () => { + await logSynthtrace.clean(); }); - } - ); - registry.when( - 'Logs error rate timeseries when data loaded', - { config: 'basic', archives: [] }, - () => { describe('Logs without log level field', () => { before(async () => { return logSynthtrace.index([ @@ -170,6 +171,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); }); - } - ); + }); + }); } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_rate_timeseries.spec.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_rate_timeseries.spec.ts new file mode 100644 index 0000000000000..fb10925b9906d --- /dev/null +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/entities/service_logs_rate_timeseries.spec.ts @@ -0,0 +1,180 @@ +/* + * 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 { log, timerange } from '@kbn/apm-synthtrace-client'; +import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; +import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; +import { first, last } from 'lodash'; +import { LogsSynthtraceEsClient } 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 serviceName = 'synth-go'; + const start = new Date('2024-01-01T00:00:00.000Z').getTime(); + const end = new Date('2024-01-01T00:15:00.000Z').getTime() - 1; + + const hostName = 'synth-host'; + + async function getLogsRateTimeseries( + overrides?: RecursivePartial< + APIClientRequestParamsOf<'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries'>['params'] + > + ) { + const response = await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries', + params: { + path: { + serviceName: 'synth-go', + ...overrides?.path, + }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + kuery: '', + ...overrides?.query, + }, + }, + }); + return response; + } + describe('logs rate timeseries', () => { + describe('when data is not loaded', () => { + it('handles empty state', async () => { + const response = await getLogsRateTimeseries(); + expect(response.status).to.be(200); + expect(response.body.currentPeriod).to.empty(); + }); + }); + + describe('when data loaded', () => { + let logSynthtrace: LogsSynthtraceEsClient; + + before(async () => { + logSynthtrace = await synthtrace.createLogsSynthtraceEsClient(); + }); + + after(async () => { + await logSynthtrace.clean(); + }); + + describe('Logs without log level field', () => { + before(async () => { + return logSynthtrace.index([ + timerange(start, end) + .interval('1m') + .rate(1) + .generator((timestamp) => + log.create().message('This is a log message').timestamp(timestamp).defaults({ + 'log.file.path': '/my-service.log', + 'service.name': serviceName, + 'host.name': hostName, + }) + ), + ]); + }); + after(async () => { + await logSynthtrace.clean(); + }); + + it('returns {} if log level is not available ', async () => { + const response = await getLogsRateTimeseries(); + expect(response.status).to.be(200); + }); + }); + + describe('Logs with log.level=error', () => { + before(async () => { + return logSynthtrace.index([ + timerange(start, end) + .interval('1m') + .rate(1) + .generator((timestamp) => + log + .create() + .message('This is a log message') + .logLevel('error') + .timestamp(timestamp) + .defaults({ + 'log.file.path': '/my-service.log', + 'service.name': serviceName, + 'host.name': hostName, + 'service.environment': 'test', + }) + ), + timerange(start, end) + .interval('2m') + .rate(1) + .generator((timestamp) => + log + .create() + .message('This is an error log message') + .logLevel('error') + .timestamp(timestamp) + .defaults({ + 'log.file.path': '/my-service.log', + 'service.name': 'my-service', + 'host.name': hostName, + 'service.environment': 'production', + }) + ), + timerange(start, end) + .interval('5m') + .rate(1) + .generator((timestamp) => + log + .create() + .message('This is an info message') + .logLevel('info') + .timestamp(timestamp) + .defaults({ + 'log.file.path': '/my-service.log', + 'service.name': 'my-service', + 'host.name': hostName, + 'service.environment': 'production', + }) + ), + ]); + }); + after(async () => { + await logSynthtrace.clean(); + }); + + it('returns log rate timeseries', async () => { + const response = await getLogsRateTimeseries(); + expect(response.status).to.be(200); + expect( + response.body.currentPeriod[serviceName].every(({ y }) => y === 0.06666666666666667) + ).to.be(true); + }); + + it('handles environment filter', async () => { + const response = await getLogsRateTimeseries({ query: { environment: 'foo' } }); + expect(response.status).to.be(200); + expect(response.body.currentPeriod).to.empty(); + }); + + describe('when my-service is selected', () => { + it('returns some data', async () => { + const response = await getLogsRateTimeseries({ + path: { serviceName: 'my-service' }, + }); + + expect(response.status).to.be(200); + expect(first(response.body.currentPeriod?.['my-service'])?.y).to.be( + 0.18181818181818182 + ); + expect(last(response.body.currentPeriod?.['my-service'])?.y).to.be(0.09090909090909091); + }); + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts index 3e490a621d3f9..f8c0352984473 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/apm/index.ts @@ -15,6 +15,7 @@ export default function apmApiIntegrationTests({ loadTestFile(require.resolve('./mobile')); loadTestFile(require.resolve('./custom_dashboards')); loadTestFile(require.resolve('./dependencies')); + loadTestFile(require.resolve('./entities')); loadTestFile(require.resolve('./cold_start')); }); } diff --git a/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_rate_timeseries.spec.ts b/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_rate_timeseries.spec.ts deleted file mode 100644 index d4717b25bba93..0000000000000 --- a/x-pack/test/apm_api_integration/tests/entities/logs/service_logs_rate_timeseries.spec.ts +++ /dev/null @@ -1,173 +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 expect from '@kbn/expect'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import { RecursivePartial } from '@kbn/apm-plugin/typings/common'; -import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api'; -import { first, last } from 'lodash'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const registry = getService('registry'); - const apmApiClient = getService('apmApiClient'); - const logSynthtrace = getService('logSynthtraceEsClient'); - - const serviceName = 'synth-go'; - const start = new Date('2024-01-01T00:00:00.000Z').getTime(); - const end = new Date('2024-01-01T00:15:00.000Z').getTime() - 1; - - const hostName = 'synth-host'; - - async function getLogsRateTimeseries( - overrides?: RecursivePartial< - APIClientRequestParamsOf<'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries'>['params'] - > - ) { - const response = await apmApiClient.readUser({ - endpoint: 'GET /internal/apm/entities/services/{serviceName}/logs_rate_timeseries', - params: { - path: { - serviceName: 'synth-go', - ...overrides?.path, - }, - query: { - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - environment: 'ENVIRONMENT_ALL', - kuery: '', - ...overrides?.query, - }, - }, - }); - return response; - } - - registry.when( - 'Logs rate timeseries when data is not loaded', - { config: 'basic', archives: [] }, - () => { - describe('Logs rate api', () => { - it('handles the empty state', async () => { - const response = await getLogsRateTimeseries(); - expect(response.status).to.be(200); - expect(response.body.currentPeriod).to.empty(); - }); - }); - } - ); - - registry.when('Logs rate timeseries when data loaded', { config: 'basic', archives: [] }, () => { - describe('Logs without log level field', () => { - before(async () => { - return logSynthtrace.index([ - timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => - log.create().message('This is a log message').timestamp(timestamp).defaults({ - 'log.file.path': '/my-service.log', - 'service.name': serviceName, - 'host.name': hostName, - }) - ), - ]); - }); - after(async () => { - await logSynthtrace.clean(); - }); - - it('returns {} if log level is not available ', async () => { - const response = await getLogsRateTimeseries(); - expect(response.status).to.be(200); - }); - }); - - describe('Logs with log.level=error', () => { - before(async () => { - return logSynthtrace.index([ - timerange(start, end) - .interval('1m') - .rate(1) - .generator((timestamp) => - log - .create() - .message('This is a log message') - .logLevel('error') - .timestamp(timestamp) - .defaults({ - 'log.file.path': '/my-service.log', - 'service.name': serviceName, - 'host.name': hostName, - 'service.environment': 'test', - }) - ), - timerange(start, end) - .interval('2m') - .rate(1) - .generator((timestamp) => - log - .create() - .message('This is an error log message') - .logLevel('error') - .timestamp(timestamp) - .defaults({ - 'log.file.path': '/my-service.log', - 'service.name': 'my-service', - 'host.name': hostName, - 'service.environment': 'production', - }) - ), - timerange(start, end) - .interval('5m') - .rate(1) - .generator((timestamp) => - log - .create() - .message('This is an info message') - .logLevel('info') - .timestamp(timestamp) - .defaults({ - 'log.file.path': '/my-service.log', - 'service.name': 'my-service', - 'host.name': hostName, - 'service.environment': 'production', - }) - ), - ]); - }); - after(async () => { - await logSynthtrace.clean(); - }); - - it('returns log rate timeseries', async () => { - const response = await getLogsRateTimeseries(); - expect(response.status).to.be(200); - expect( - response.body.currentPeriod[serviceName].every(({ y }) => y === 0.06666666666666667) - ).to.be(true); - }); - - it('handles environment filter', async () => { - const response = await getLogsRateTimeseries({ query: { environment: 'foo' } }); - expect(response.status).to.be(200); - expect(response.body.currentPeriod).to.empty(); - }); - - describe('when my-service is selected', () => { - it('returns some data', async () => { - const response = await getLogsRateTimeseries({ - path: { serviceName: 'my-service' }, - }); - - expect(response.status).to.be(200); - expect(first(response.body.currentPeriod?.['my-service'])?.y).to.be(0.18181818181818182); - expect(last(response.body.currentPeriod?.['my-service'])?.y).to.be(0.09090909090909091); - }); - }); - }); - }); -}