Skip to content

Commit

Permalink
[8.x][Data Usage] setup integration tests (elastic#197112)
Browse files Browse the repository at this point in the history
Adds serverless api integration tests and basic functional smoke test
for data usage plugin

Both are skipped in mki until `xpack.dataUsage.enabled` is enabled by
default in serverless

- skipped in MKI because we'll need to make sure real credentials are
being used via the `xpack.dataUsage.autoops*`
- we start a mock server at localhost:9000 and set that the config
(`xpack.dataUsage.autoops.api.url=http://localhost:9000'`) along with
fake credentials for the other `xpack.dataUsage.autoops*` values. If
we're not in MKI these values will be used and the mock server will
respond to the request at `http://localhost:9000`. If we are in MKI, the
real values and credentials should be set, otherwise it will fail as
these kibana config values in the tests are not passed into the MKI
environment.

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
neptunian and kibanamachine committed Nov 6, 2024
1 parent c1b598f commit ed3d31c
Show file tree
Hide file tree
Showing 25 changed files with 392 additions and 13 deletions.
11 changes: 9 additions & 2 deletions x-pack/plugins/data_usage/common/rest_types/usage_metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ export const UsageMetricsAutoOpsResponseSchema = {
),
}),
};
export type UsageMetricsAutoOpsResponseSchemaBody = TypeOf<
export type UsageMetricsAutoOpsResponseMetricSeries = TypeOf<
typeof UsageMetricsAutoOpsResponseSchema.body
>;
>['metrics'][MetricTypes][number];

export type UsageMetricsAutoOpsResponseSchemaBody = Omit<
TypeOf<typeof UsageMetricsAutoOpsResponseSchema.body>,
'metrics'
> & {
metrics: Partial<Record<MetricTypes, UsageMetricsAutoOpsResponseMetricSeries[]>>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

import { RequestHandler } from '@kbn/core/server';
import { IndicesGetDataStreamResponse } from '@elastic/elasticsearch/lib/api/types';
import {
MetricTypes,
UsageMetricsAutoOpsResponseSchemaBody,
Expand Down Expand Up @@ -44,12 +43,22 @@ export const getUsageMetricsHandler = (
new CustomHttpRequestError('[request body.dataStreams]: no data streams selected', 400)
);
}
let dataStreamsResponse;

const { data_streams: dataStreamsResponse }: IndicesGetDataStreamResponse =
await esClient.indices.getDataStream({
try {
// Attempt to fetch data streams
const { data_streams: dataStreams } = await esClient.indices.getDataStream({
name: requestDsNames,
expand_wildcards: 'all',
});
dataStreamsResponse = dataStreams;
} catch (error) {
return errorHandler(
logger,
response,
new CustomHttpRequestError('Failed to retrieve data streams', 400)
);
}
const metrics = await dataUsageService.getMetrics({
from,
to,
Expand All @@ -69,7 +78,7 @@ export const getUsageMetricsHandler = (
};
};

function transformMetricsData(
export function transformMetricsData(
data: UsageMetricsAutoOpsResponseSchemaBody
): UsageMetricsResponseSchemaBody {
return {
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/data_usage/server/services/autoops_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { AxiosError, AxiosRequestConfig } from 'axios';
import axios from 'axios';
import { LogMeta } from '@kbn/core/server';
import {
UsageMetricsAutoOpsResponseSchema,
UsageMetricsAutoOpsResponseSchemaBody,
UsageMetricsRequestBody,
} from '../../common/rest_types';
Expand Down Expand Up @@ -134,8 +135,10 @@ export class AutoOpsAPIService {
}
);

const validatedResponse = UsageMetricsAutoOpsResponseSchema.body().validate(response.data);

logger.debug(`[AutoOps API] Successfully created an autoops agent ${response}`);
return response;
return validatedResponse;
}

private createTlsConfig(autoopsConfig: AutoOpsConfig | undefined) {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/data_usage/server/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class DataUsageService {
metricTypes,
dataStreams,
});
return response.data;
return response;
} catch (error) {
if (error instanceof ValidationError) {
throw new AutoOpsError(error.message);
Expand Down
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 { FtrProviderContext } from '../../../ftr_provider_context';

export default function ({ loadTestFile }: FtrProviderContext) {
describe('Serverless Data Usage APIs', function () {
this.tags(['esGate']);

loadTestFile(require.resolve('./tests/data_streams'));
loadTestFile(require.resolve('./tests/metrics'));
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 { createServer } from '@mswjs/http-middleware';
import { UsageMetricsAutoOpsResponseSchemaBody } from '@kbn/data-usage-plugin/common/rest_types';
import { http, HttpResponse, StrictResponse } from 'msw';
import { mockAutoOpsResponse } from './mock_data';

export const setupMockServer = () => {
const server = createServer(autoOpsHandler);
return server;
};

const autoOpsHandler = http.post(
'/',
async ({ request }): Promise<StrictResponse<UsageMetricsAutoOpsResponseSchemaBody>> => {
return HttpResponse.json(mockAutoOpsResponse);
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.
*/

export const mockAutoOpsResponse = {
metrics: {
ingest_rate: [
{
name: 'metrics-system.cpu-default',
data: [
[1726858530000, 13756849],
[1726862130000, 14657904],
],
},
{
name: 'logs-nginx.access-default',
data: [
[1726858530000, 12894623],
[1726862130000, 14436905],
],
},
],
storage_retained: [
{
name: 'metrics-system.cpu-default',
data: [
[1726858530000, 12576413],
[1726862130000, 13956423],
],
},
{
name: 'logs-nginx.access-default',
data: [
[1726858530000, 12894623],
[1726862130000, 14436905],
],
},
],
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest';
import { DataStreamsResponseBodySchemaBody } from '@kbn/data-usage-plugin/common/rest_types';
import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '@kbn/data-usage-plugin/common';
import { FtrProviderContext } from '../../../../ftr_provider_context';

export default function ({ getService }: FtrProviderContext) {
const svlDatastreamsHelpers = getService('svlDatastreamsHelpers');
const roleScopedSupertest = getService('roleScopedSupertest');
let supertestAdminWithCookieCredentials: SupertestWithRoleScope;
const testDataStreamName = 'test-data-stream';
describe(`GET ${DATA_USAGE_DATA_STREAMS_API_ROUTE}`, function () {
// due to the plugin depending on yml config (xpack.dataUsage.enabled), we cannot test in MKI until it is on by default
this.tags(['skipMKI']);
before(async () => {
await svlDatastreamsHelpers.createDataStream(testDataStreamName);
supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
'admin',
{
useCookieHeader: true,
withInternalHeaders: true,
}
);
});
after(async () => {
await svlDatastreamsHelpers.deleteDataStream(testDataStreamName);
});

it('returns created data streams', async () => {
const res = await supertestAdminWithCookieCredentials
.get(DATA_USAGE_DATA_STREAMS_API_ROUTE)
.set('elastic-api-version', '1');
const dataStreams: DataStreamsResponseBodySchemaBody = res.body;
const foundStream = dataStreams.find((stream) => stream.name === testDataStreamName);
expect(foundStream?.name).to.be(testDataStreamName);
expect(foundStream?.storageSizeBytes).to.be(0);
expect(res.statusCode).to.be(200);
});
it('returns system indices', async () => {
const res = await supertestAdminWithCookieCredentials
.get(DATA_USAGE_DATA_STREAMS_API_ROUTE)
.set('elastic-api-version', '1');
const dataStreams: DataStreamsResponseBodySchemaBody = res.body;
const systemDataStreams = dataStreams.filter((stream) => stream.name.startsWith('.'));
expect(systemDataStreams.length).to.be.greaterThan(0);
expect(res.statusCode).to.be(200);
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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 http from 'http';

import { SupertestWithRoleScope } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services/role_scoped_supertest';
import { UsageMetricsRequestBody } from '@kbn/data-usage-plugin/common/rest_types';
import { DATA_USAGE_METRICS_API_ROUTE } from '@kbn/data-usage-plugin/common';
import { transformMetricsData } from '@kbn/data-usage-plugin/server/routes/internal/usage_metrics_handler';
import { FtrProviderContext } from '../../../../ftr_provider_context';
import { setupMockServer } from '../mock_api';
import { mockAutoOpsResponse } from '../mock_data';

export default function ({ getService }: FtrProviderContext) {
const svlDatastreamsHelpers = getService('svlDatastreamsHelpers');
const roleScopedSupertest = getService('roleScopedSupertest');
let supertestAdminWithCookieCredentials: SupertestWithRoleScope;
const mockAutoopsApiService = setupMockServer();
describe('Metrics', function () {
let mockApiServer: http.Server;
// due to the plugin depending on yml config (xpack.dataUsage.enabled), we cannot test in MKI until it is on by default
this.tags(['skipMKI']);

before(async () => {
mockApiServer = mockAutoopsApiService.listen(9000);
supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
'admin',
{
useCookieHeader: true,
withInternalHeaders: true,
}
);
});

after(() => {
mockApiServer.close();
});
describe(`POST ${DATA_USAGE_METRICS_API_ROUTE}`, () => {
const testDataStreamName = 'test-data-stream';
before(async () => await svlDatastreamsHelpers.createDataStream(testDataStreamName));
after(async () => await svlDatastreamsHelpers.deleteDataStream(testDataStreamName));
it('returns 400 with non-existent data streams', async () => {
const requestBody: UsageMetricsRequestBody = {
from: 'now-24h/h',
to: 'now',
metricTypes: ['ingest_rate', 'storage_retained'],
dataStreams: ['invalid-data-stream'],
};
const res = await supertestAdminWithCookieCredentials
.post(DATA_USAGE_METRICS_API_ROUTE)
.set('elastic-api-version', '1')
.send(requestBody);
expect(res.statusCode).to.be(400);
expect(res.body.message).to.be('Failed to retrieve data streams');
});

it('returns 400 when requesting no data streams', async () => {
const requestBody = {
from: 'now-24h/h',
to: 'now',
metricTypes: ['ingest_rate'],
dataStreams: [],
};
const res = await supertestAdminWithCookieCredentials
.post(DATA_USAGE_METRICS_API_ROUTE)
.set('elastic-api-version', '1')
.send(requestBody);
expect(res.statusCode).to.be(400);
expect(res.body.message).to.be('[request body.dataStreams]: no data streams selected');
});

it('returns 400 when requesting an invalid metric type', async () => {
const requestBody = {
from: 'now-24h/h',
to: 'now',
metricTypes: [testDataStreamName],
dataStreams: ['datastream'],
};
const res = await supertestAdminWithCookieCredentials
.post(DATA_USAGE_METRICS_API_ROUTE)
.set('elastic-api-version', '1')
.send(requestBody);
expect(res.statusCode).to.be(400);
expect(res.body.message).to.be(
'[request body.metricTypes]: must be one of ingest_rate, storage_retained, search_vcu, ingest_vcu, ml_vcu, index_latency, index_rate, search_latency, search_rate'
);
});

it('returns 200 with valid request', async () => {
const requestBody: UsageMetricsRequestBody = {
from: 'now-24h/h',
to: 'now',
metricTypes: ['ingest_rate', 'storage_retained'],
dataStreams: [testDataStreamName],
};
const res = await supertestAdminWithCookieCredentials
.post(DATA_USAGE_METRICS_API_ROUTE)
.set('elastic-api-version', '1')
.send(requestBody);
expect(res.statusCode).to.be(200);
expect(res.body).to.eql(transformMetricsData(mockAutoOpsResponse));
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('../../common/console'),
require.resolve('../../common/saved_objects_management'),
require.resolve('../../common/telemetry'),
require.resolve('../../common/data_usage'),
],
junit: {
reportName: 'Serverless Observability API Integration Tests - Common Group 1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
import { createTestConfig } from '../../config.base';
import { services as apmServices } from './apm_api_integration/common/services';
import { services as datasetQualityServices } from './dataset_quality_api_integration/common/services';
Expand All @@ -26,5 +26,12 @@ export default createTestConfig({
'--xpack.uptime.service.manifestUrl=mockDevUrl',
// useful for testing (also enabled in MKI QA)
'--coreApp.allowDynamicConfigOverrides=true',
'--xpack.dataUsage.enabled=true',
// dataUsage.autoops* config is set in kibana controller
'--xpack.dataUsage.autoops.enabled=true',
'--xpack.dataUsage.autoops.api.url=http://localhost:9000',
`--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`,
`--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`,
`--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`,
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('../../common/console'),
require.resolve('../../common/saved_objects_management'),
require.resolve('../../common/telemetry'),
require.resolve('../../common/data_usage'),
],
junit: {
reportName: 'Serverless Search API Integration Tests - Common Group 1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
import { createTestConfig } from '../../config.base';

export default createTestConfig({
Expand All @@ -21,5 +22,12 @@ export default createTestConfig({
kbnServerArgs: [
// useful for testing (also enabled in MKI QA)
'--coreApp.allowDynamicConfigOverrides=true',
'--xpack.dataUsage.enabled=true',
// dataUsage.autoops* config is set in kibana controller
'--xpack.dataUsage.autoops.enabled=true',
'--xpack.dataUsage.autoops.api.url=http://localhost:9000',
`--xpack.dataUsage.autoops.api.tls.certificate=${KBN_CERT_PATH}`,
`--xpack.dataUsage.autoops.api.tls.key=${KBN_KEY_PATH}`,
`--xpack.dataUsage.autoops.api.tls.ca=${CA_CERT_PATH}`,
],
});
Loading

0 comments on commit ed3d31c

Please sign in to comment.