Skip to content

Commit

Permalink
[8.x] [APM] Migrate `/data_view` to deployment agnostic test (
Browse files Browse the repository at this point in the history
elastic#199296) (elastic#199753)

# Backport

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

<!--- 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-12T10:41:34Z","message":"[APM]
Migrate `/data_view` to deployment agnostic test (elastic#199296)\n\n##
Summary\n\nCloses https://github.com/elastic/kibana/issues/198965\nPart
of https://github.com/elastic/kibana/issues/193245\n\nThis PR contains
the changes to migrate `data_view` test folder to\nDeployment-agnostic
testing strategy.\n\n### How to test\n\n- Serverless\n\n```\nnode
scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
--grep=\"APM\"\n```\n\nIt's recommended to be run
against\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\n\n-
Stateful\n```\nnode scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
--grep=\"APM\"\n```\n\n## Checks\n\n- [x] (OPTIONAL, only if a test has
been unskipped) Run flaky test suite\n- [x] local run for serverless\n-
[x] local run for stateful\n- [x] MKI run for
serverless","sha":"803738fa0c8fea3cdfc4760c7b3dd667637de724","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"],"title":"[APM]
Migrate `/data_view` to deployment agnostic
test","number":199296,"url":"https://github.com/elastic/kibana/pull/199296","mergeCommit":{"message":"[APM]
Migrate `/data_view` to deployment agnostic test (elastic#199296)\n\n##
Summary\n\nCloses https://github.com/elastic/kibana/issues/198965\nPart
of https://github.com/elastic/kibana/issues/193245\n\nThis PR contains
the changes to migrate `data_view` test folder to\nDeployment-agnostic
testing strategy.\n\n### How to test\n\n- Serverless\n\n```\nnode
scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
--grep=\"APM\"\n```\n\nIt's recommended to be run
against\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\n\n-
Stateful\n```\nnode scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
--grep=\"APM\"\n```\n\n## Checks\n\n- [x] (OPTIONAL, only if a test has
been unskipped) Run flaky test suite\n- [x] local run for serverless\n-
[x] local run for stateful\n- [x] MKI run for
serverless","sha":"803738fa0c8fea3cdfc4760c7b3dd667637de724"}},"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/199296","number":199296,"mergeCommit":{"message":"[APM]
Migrate `/data_view` to deployment agnostic test (elastic#199296)\n\n##
Summary\n\nCloses https://github.com/elastic/kibana/issues/198965\nPart
of https://github.com/elastic/kibana/issues/193245\n\nThis PR contains
the changes to migrate `data_view` test folder to\nDeployment-agnostic
testing strategy.\n\n### How to test\n\n- Serverless\n\n```\nnode
scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/serverless/oblt.serverless.config.ts
--grep=\"APM\"\n```\n\nIt's recommended to be run
against\n[MKI](https://github.com/crespocarlos/kibana/blob/main/x-pack/test_serverless/README.md#run-tests-on-mki)\n\n-
Stateful\n```\nnode scripts/functional_tests_server --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts\nnode
scripts/functional_test_runner --config
x-pack/test/api_integration/deployment_agnostic/configs/stateful/oblt.stateful.config.ts
--grep=\"APM\"\n```\n\n## Checks\n\n- [x] (OPTIONAL, only if a test has
been unskipped) Run flaky test suite\n- [x] local run for serverless\n-
[x] local run for stateful\n- [x] MKI run for
serverless","sha":"803738fa0c8fea3cdfc4760c7b3dd667637de724"}}]}]
BACKPORT-->

Co-authored-by: Sergi Romeu <[email protected]>
  • Loading branch information
kibanamachine and rmyz authored Nov 12, 2024
1 parent ee0934b commit 8fe516a
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 268 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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('data_view', () => {
loadTestFile(require.resolve('./static.spec.ts'));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/*
* 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 { apm, timerange } from '@kbn/apm-synthtrace-client';
import type { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import expect from '@kbn/expect';
import { DataView } from '@kbn/data-views-plugin/common';
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import request from 'superagent';
import { getStaticDataViewId } from '@kbn/apm-data-view';
import { SupertestWithRoleScope } from '../../../../services/role_scoped_supertest';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import {
SupertestReturnType,
ApmApiError,
} from '../../../../../../apm_api_integration/common/apm_api_supertest';

export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const apmApiClient = getService('apmApi');
const roleScopedSupertest = getService('roleScopedSupertest');
const synthtraceService = getService('synthtrace');
const logger = getService('log');
const dataViewPattern =
'traces-apm*,apm-*,traces-*.otel-*,logs-apm*,apm-*,logs-*.otel-*,metrics-apm*,apm-*,metrics-*.otel-*';

function createDataViewWithWriteUser({ spaceId }: { spaceId: string }) {
return apmApiClient.writeUser({
endpoint: 'POST /internal/apm/data_view/static',
spaceId,
});
}

function createDataViewWithReadUser({ spaceId }: { spaceId: string }) {
return apmApiClient.readUser({
endpoint: 'POST /internal/apm/data_view/static',
spaceId,
});
}

describe('Data view static', () => {
let supertestEditorWithApiKey: SupertestWithRoleScope;
let supertestEditorWithCookieCredentials: SupertestWithRoleScope;

before(async () => {
supertestEditorWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('editor', {
withInternalHeaders: true,
});

supertestEditorWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
'editor',
{
useCookieHeader: true,
withInternalHeaders: true,
}
);
});

after(async () => {
await supertestEditorWithApiKey.destroy();
});

function deleteDataView(spaceId: string) {
return supertestEditorWithApiKey.delete(
`/s/${spaceId}/api/saved_objects/index-pattern/${getStaticDataViewId(spaceId)}?force=true`
);
}

function getDataView({ spaceId }: { spaceId: string }) {
const spacePrefix = spaceId !== 'default' ? `/s/${spaceId}` : '';
return supertestEditorWithApiKey.get(
`${spacePrefix}/api/saved_objects/index-pattern/${getStaticDataViewId(spaceId)}`
);
}

function getDataViewSuggestions(field: string) {
return supertestEditorWithCookieCredentials
.post(`/internal/kibana/suggestions/values/${dataViewPattern}`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.send({ query: '', field, method: 'terms_agg' });
}

describe('no mappings exist', () => {
let response: SupertestReturnType<'POST /internal/apm/data_view/static'>;
before(async () => {
response = await createDataViewWithWriteUser({ spaceId: 'default' });
});

it('does not create data view', async () => {
expect(response.status).to.be(200);
expect(response.body).to.eql({
created: false,
reason: 'No APM data',
});
});

it('cannot fetch data view', async () => {
const res = await getDataView({ spaceId: 'default' });
expect(res.status).to.be(404);
expect(res.body.message).to.eql(
'Saved object [index-pattern/apm_static_data_view_id_default] not found'
);
});
});

describe('when mappings and APM data exists', () => {
let synthtrace: ApmSynthtraceEsClient;

before(async () => {
synthtrace = await synthtraceService.createApmSynthtraceEsClient();
await generateApmData(synthtrace);
});

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

afterEach(async () => {
try {
await Promise.all([deleteDataView('default'), deleteDataView('foo')]);
} catch (e) {
logger.error(`Could not delete data views ${e.message}`);
}
});

describe('when creating data view with write user', () => {
let response: SupertestReturnType<'POST /internal/apm/data_view/static'>;

before(async () => {
response = await createDataViewWithWriteUser({ spaceId: 'default' });
});

it('successfully creates the apm data view', async () => {
expect(response.status).to.be(200);

const dataView = (response.body as { dataView: DataView }).dataView;

expect(dataView.id).to.be('apm_static_data_view_id_default');
expect(dataView.name).to.be('APM');
expect(dataView.title).to.be(
'traces-apm*,apm-*,traces-*.otel-*,logs-apm*,apm-*,logs-*.otel-*,metrics-apm*,apm-*,metrics-*.otel-*'
);
});
});

describe('when fetching the data view', () => {
let dataViewResponse: request.Response;

before(async () => {
await createDataViewWithWriteUser({ spaceId: 'default' });
dataViewResponse = await getDataView({ spaceId: 'default' });
});

it('return 200', () => {
expect(dataViewResponse.status).to.be(200);
});

it('has correct id', () => {
expect(dataViewResponse.body.id).to.be('apm_static_data_view_id_default');
});

it('has correct title', () => {
expect(dataViewResponse.body.attributes.title).to.be(dataViewPattern);
});

it('has correct attributes', () => {
expect(dataViewResponse.body.attributes.fieldFormatMap).to.be(
JSON.stringify({
'trace.id': {
id: 'url',
params: {
urlTemplate: 'apm/link-to/trace/{{value}}',
labelTemplate: '{{value}}',
},
},
'transaction.id': {
id: 'url',
params: {
urlTemplate: 'apm/link-to/transaction/{{value}}',
labelTemplate: '{{value}}',
},
},
'transaction.duration.us': {
id: 'duration',
params: {
inputFormat: 'microseconds',
outputFormat: 'asMilliseconds',
showSuffix: true,
useShortSuffix: true,
outputPrecision: 2,
includeSpaceWithSuffix: true,
},
},
})
);
});

// this test ensures that the default APM Data View doesn't interfere with suggestions returned in the kuery bar (this has been a problem in the past)
it('can get suggestions for `trace.id`', async () => {
const suggestions = await getDataViewSuggestions('trace.id');
expect(suggestions.body.length).to.be(10);
});
});

describe('when creating data view via read user', () => {
it('throws an error', async () => {
try {
await createDataViewWithReadUser({ spaceId: 'default' });
} catch (e) {
const err = e as ApmApiError;
const responseBody = err.res.body;
expect(err.res.status).to.eql(403);
expect(responseBody.statusCode).to.eql(403);
expect(responseBody.error).to.eql('Forbidden');
expect(responseBody.message).to.eql('Unable to create index-pattern');
}
});
});

describe('when creating data view twice', () => {
it('returns 200 response with reason, if data view already exists', async () => {
await createDataViewWithWriteUser({ spaceId: 'default' });
const res = await createDataViewWithWriteUser({ spaceId: 'default' });

expect(res.status).to.be(200);
expect(res.body).to.eql({
created: false,
reason: 'Dataview already exists in the active space and does not need to be updated',
});
});
});

describe('when creating data view in "default" space', () => {
it('can be retrieved from the "default" space', async () => {
await createDataViewWithWriteUser({ spaceId: 'default' });
const res = await getDataView({ spaceId: 'default' });
expect(res.body.id).to.eql('apm_static_data_view_id_default');
expect(res.body.namespaces).to.eql(['default']);
});

it('cannot be retrieved from the "foo" space', async () => {
await createDataViewWithWriteUser({ spaceId: 'default' });
const res = await getDataView({ spaceId: 'foo' });
expect(res.body.statusCode).to.be(404);
});
});

describe('when creating data view in "foo" space', () => {
it('can be retrieved from the "foo" space', async () => {
await createDataViewWithWriteUser({ spaceId: 'foo' });
const res = await getDataView({ spaceId: 'foo' });
expect(res.body.id).to.eql('apm_static_data_view_id_foo');
expect(res.body.namespaces).to.eql(['foo']);
});

it('cannot be retrieved from the "default" space', async () => {
await createDataViewWithWriteUser({ spaceId: 'foo' });
const res = await getDataView({ spaceId: 'default' });
expect(res.body.statusCode).to.be(404);
});
});
});
});
}

function generateApmData(synthtrace: ApmSynthtraceEsClient) {
const range = timerange(
new Date('2021-10-01T00:00:00.000Z').getTime(),
new Date('2021-10-01T00:01:00.000Z').getTime()
);

const instance = apm
.service({ name: 'my-service', environment: 'production', agentName: 'go' })
.instance('my-instance');

return synthtrace.index([
range
.interval('1s')
.rate(1)
.generator((timestamp) =>
instance
.transaction({ transactionName: 'GET /api' })
.timestamp(timestamp)
.duration(30)
.success()
),
]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default function apmApiIntegrationTests({
loadTestFile(require.resolve('./agent_explorer'));
loadTestFile(require.resolve('./custom_dashboards'));
loadTestFile(require.resolve('./dependencies'));
loadTestFile(require.resolve('./data_view'));
loadTestFile(require.resolve('./correlations'));
loadTestFile(require.resolve('./entities'));
loadTestFile(require.resolve('./cold_start'));
Expand Down
Loading

0 comments on commit 8fe516a

Please sign in to comment.