diff --git a/x-pack/test/api_integration/apis/asset_manager/bootstrap_apm_synthtrace.ts b/x-pack/test/api_integration/apis/asset_manager/bootstrap_apm_synthtrace.ts new file mode 100644 index 0000000000000..6e507f2178a99 --- /dev/null +++ b/x-pack/test/api_integration/apis/asset_manager/bootstrap_apm_synthtrace.ts @@ -0,0 +1,50 @@ +/* + * 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_TEST_PASSWORD } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication'; +import { + ApmSynthtraceEsClient, + ApmSynthtraceKibanaClient, + createLogger, + LogLevel, +} from '@kbn/apm-synthtrace'; +import url from 'url'; +import { FtrProviderContext as InheritedFtrProviderContext } from '../../ftr_provider_context'; + +export async function bootstrapApmSynthtrace( + context: InheritedFtrProviderContext, + kibanaClient: ApmSynthtraceKibanaClient +) { + const es = context.getService('es'); + + const kibanaVersion = await kibanaClient.fetchLatestApmPackageVersion(); + await kibanaClient.installApmPackage(kibanaVersion); + + const esClient = new ApmSynthtraceEsClient({ + client: es, + logger: createLogger(LogLevel.info), + version: kibanaVersion, + refreshAfterIndex: true, + }); + + return esClient; +} + +export function getApmSynthtraceKibanaClient(kibanaServerUrl: string) { + const kibanaServerUrlWithAuth = url + .format({ + ...url.parse(kibanaServerUrl), + auth: `elastic:${APM_TEST_PASSWORD}`, + }) + .slice(0, -1); + + const kibanaClient = new ApmSynthtraceKibanaClient({ + target: kibanaServerUrlWithAuth, + logger: createLogger(LogLevel.debug), + }); + + return kibanaClient; +} diff --git a/x-pack/test/api_integration/apis/asset_manager/config.ts b/x-pack/test/api_integration/apis/asset_manager/config.ts index 0061b85f8c95c..43ebd7f2deff0 100644 --- a/x-pack/test/api_integration/apis/asset_manager/config.ts +++ b/x-pack/test/api_integration/apis/asset_manager/config.ts @@ -5,14 +5,39 @@ * 2.0. */ +import { format, UrlObject } from 'url'; import { FtrConfigProviderContext } from '@kbn/test'; +import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace'; -export default async function ({ readConfigFile }: FtrConfigProviderContext) { +import { bootstrapApmSynthtrace, getApmSynthtraceKibanaClient } from './bootstrap_apm_synthtrace'; +import { FtrProviderContext as InheritedFtrProviderContext } from '../../ftr_provider_context'; +import { InheritedServices } from './types'; + +interface AssetManagerConfig { + services: InheritedServices & { + synthtraceEsClient: (context: InheritedFtrProviderContext) => Promise; + }; +} + +export default async function createTestConfig({ + readConfigFile, +}: FtrConfigProviderContext): Promise { const baseIntegrationTestsConfig = await readConfigFile(require.resolve('../../config.ts')); + const services = baseIntegrationTestsConfig.get('services'); return { ...baseIntegrationTestsConfig.getAll(), testFiles: [require.resolve('.')], + services: { + ...services, + synthtraceEsClient: (context: InheritedFtrProviderContext) => { + const servers = baseIntegrationTestsConfig.get('servers'); + const kibanaServer = servers.kibana as UrlObject; + const kibanaServerUrl = format(kibanaServer); + const synthtraceKibanaClient = getApmSynthtraceKibanaClient(kibanaServerUrl); + return bootstrapApmSynthtrace(context, synthtraceKibanaClient); + }, + }, kbnTestServer: { ...baseIntegrationTestsConfig.get('kbnTestServer'), serverArgs: [ @@ -22,3 +47,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { }, }; } + +export type CreateTestConfig = Awaited>; + +export type AssetManagerServices = CreateTestConfig['services']; diff --git a/x-pack/test/api_integration/apis/asset_manager/index.ts b/x-pack/test/api_integration/apis/asset_manager/index.ts index a5c9bd0227ab4..d868220253aa1 100644 --- a/x-pack/test/api_integration/apis/asset_manager/index.ts +++ b/x-pack/test/api_integration/apis/asset_manager/index.ts @@ -13,5 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./tests/assets')); loadTestFile(require.resolve('./tests/assets_diff')); loadTestFile(require.resolve('./tests/assets_related')); + loadTestFile(require.resolve('./tests/services')); }); } diff --git a/x-pack/test/api_integration/apis/asset_manager/tests/services.ts b/x-pack/test/api_integration/apis/asset_manager/tests/services.ts new file mode 100644 index 0000000000000..684bc0a723e9e --- /dev/null +++ b/x-pack/test/api_integration/apis/asset_manager/tests/services.ts @@ -0,0 +1,107 @@ +/* + * 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 { omit } from 'lodash'; +import { apm, timerange } from '@kbn/apm-synthtrace-client'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../types'; +import { ASSETS_ENDPOINT } from '../constants'; + +const SERVICES_ASSETS_ENDPOINT = `${ASSETS_ENDPOINT}/services`; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const synthtrace = getService('synthtraceEsClient'); + + describe('asset management', () => { + beforeEach(async () => { + await synthtrace.clean(); + }); + + describe('GET /assets/services', () => { + it('should return services', async () => { + const from = new Date(Date.now() - 1000 * 60 * 2).toISOString(); + const to = new Date().toISOString(); + await synthtrace.index(generateServicesData({ from, to, count: 2 })); + + const response = await supertest + .get(SERVICES_ASSETS_ENDPOINT) + .query({ + from, + to, + }) + .expect(200); + + expect(response.body).to.have.property('services'); + expect(response.body.services.length).to.equal(2); + }); + + it('should return services running on specified host', async () => { + const from = new Date(Date.now() - 1000 * 60 * 2).toISOString(); + const to = new Date().toISOString(); + await synthtrace.index(generateServicesData({ from, to, count: 5 })); + + const response = await supertest + .get(SERVICES_ASSETS_ENDPOINT) + .query({ + from, + to, + parent: 'my-host-1', + }) + .expect(200); + + expect(response.body).to.have.property('services'); + expect(response.body.services.length).to.equal(1); + expect(omit(response.body.services[0], ['@timestamp'])).to.eql({ + 'asset.kind': 'service', + 'asset.id': 'service-1', + 'asset.ean': 'service:service-1', + 'asset.references': [], + 'asset.parents': [], + 'service.environment': 'production', + }); + }); + }); + }); +} + +function generateServicesData({ + from, + to, + count = 1, +}: { + from: string; + to: string; + count: number; +}) { + const range = timerange(from, to); + + const services = Array(count) + .fill(0) + .map((_, idx) => + apm + .service({ + name: `service-${idx}`, + environment: 'production', + agentName: 'nodejs', + }) + .instance(`my-host-${idx}`) + ); + + return range + .interval('1m') + .rate(1) + .generator((timestamp, index) => + services.map((service) => + service + .transaction({ transactionName: 'GET /foo' }) + .timestamp(timestamp) + .duration(500) + .success() + ) + ); +} diff --git a/x-pack/test/api_integration/apis/asset_manager/types.ts b/x-pack/test/api_integration/apis/asset_manager/types.ts new file mode 100644 index 0000000000000..2da488241ee47 --- /dev/null +++ b/x-pack/test/api_integration/apis/asset_manager/types.ts @@ -0,0 +1,19 @@ +/* + * 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 { GenericFtrProviderContext } from '@kbn/test'; +import { FtrProviderContext as InheritedFtrProviderContext } from '../../ftr_provider_context'; +import { AssetManagerServices } from './config'; + +export type InheritedServices = InheritedFtrProviderContext extends GenericFtrProviderContext< + infer TServices, + {} +> + ? TServices + : {}; + +export type FtrProviderContext = GenericFtrProviderContext;