From 265b687f06958c4c0da7bb160ab76c642a9f46be Mon Sep 17 00:00:00 2001 From: Panagiota Mitsopoulou Date: Fri, 10 May 2024 22:30:47 +0200 Subject: [PATCH] move alertingApi and sloApi services in the new observability_solution_api_integration folder --- .../config/ess/config.base.ts | 5 +- .../config/serverless/config.base.ts | 1 + .../ftr_provider_context.d.ts | 2 +- .../services/alerting_api.ts | 162 +++++++++++++++ .../services/slo_api.ts | 185 ++++++++++++++++++ .../test_suites/slo/burn_rate_rule.ts | 9 +- .../test_suites/slo/configs/ess.config.ts | 6 + 7 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 x-pack/test/observability_solution_api_integration/services/alerting_api.ts create mode 100644 x-pack/test/observability_solution_api_integration/services/slo_api.ts diff --git a/x-pack/test/observability_solution_api_integration/config/ess/config.base.ts b/x-pack/test/observability_solution_api_integration/config/ess/config.base.ts index 79b8bf126597886..e19b33241ea322e 100644 --- a/x-pack/test/observability_solution_api_integration/config/ess/config.base.ts +++ b/x-pack/test/observability_solution_api_integration/config/ess/config.base.ts @@ -16,7 +16,10 @@ export function createTestConfig(options) { return { ...xPackApiIntegrationTestsConfig.getAll(), testFiles: options.testFiles, - services, + services: { + ...services, + ...options.services, + }, junit: { reportName: 'X-Pack Οbservability Solution API Integration Tests', }, diff --git a/x-pack/test/observability_solution_api_integration/config/serverless/config.base.ts b/x-pack/test/observability_solution_api_integration/config/serverless/config.base.ts index d332fc3f7ae1847..f31dbdd6417091c 100644 --- a/x-pack/test/observability_solution_api_integration/config/serverless/config.base.ts +++ b/x-pack/test/observability_solution_api_integration/config/serverless/config.base.ts @@ -23,6 +23,7 @@ export function createTestConfig(options: CreateTestConfigOptions) { ...svlSharedConfig.getAll(), services: { ...services, + ...options.services, }, kbnTestServer: { ...svlSharedConfig.get('kbnTestServer'), diff --git a/x-pack/test/observability_solution_api_integration/ftr_provider_context.d.ts b/x-pack/test/observability_solution_api_integration/ftr_provider_context.d.ts index 3a44ea9db01fecc..757fb635c667d7f 100644 --- a/x-pack/test/observability_solution_api_integration/ftr_provider_context.d.ts +++ b/x-pack/test/observability_solution_api_integration/ftr_provider_context.d.ts @@ -6,6 +6,6 @@ */ import { GenericFtrProviderContext } from '@kbn/test'; -import { services } from '../../test_serverless/api_integration/services'; +import { services } from '../api_integration/services'; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/observability_solution_api_integration/services/alerting_api.ts b/x-pack/test/observability_solution_api_integration/services/alerting_api.ts new file mode 100644 index 000000000000000..b9545275a5281a9 --- /dev/null +++ b/x-pack/test/observability_solution_api_integration/services/alerting_api.ts @@ -0,0 +1,162 @@ +/* + * 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 { + AggregationsAggregate, + SearchResponse, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics'; +import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { SloBurnRateRuleParams } from './slo_api'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export function AlertingApiProvider({ getService }: FtrProviderContext) { + const retry = getService('retry'); + const supertest = getService('supertest'); + const es = getService('es'); + const requestTimeout = 30 * 1000; + const retryTimeout = 120 * 1000; + const logger = getService('log'); + + return { + async waitForRuleStatus({ + ruleId, + expectedStatus, + }: { + ruleId: string; + expectedStatus: string; + }) { + if (!ruleId) { + throw new Error(`'ruleId' is undefined`); + } + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertest + .get(`/api/alerting/rule/${ruleId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .timeout(requestTimeout); + const { execution_status: executionStatus } = response.body || {}; + const { status } = executionStatus || {}; + if (status !== expectedStatus) { + throw new Error(`waitForStatus(${expectedStatus}): got ${status}`); + } + return executionStatus?.status; + }); + }, + + async waitForDocumentInIndex({ + indexName, + docCountTarget = 1, + }: { + indexName: string; + docCountTarget?: number; + }): Promise>> { + return await retry.tryForTime(retryTimeout, async () => { + const response = await es.search({ + index: indexName, + rest_total_hits_as_int: true, + }); + logger.debug(`Found ${response.hits.total} docs, looking for atleast ${docCountTarget}.`); + if (!response.hits.total || response.hits.total < docCountTarget) { + throw new Error('No hits found'); + } + return response; + }); + }, + + async waitForAlertInIndex({ + indexName, + ruleId, + }: { + indexName: string; + ruleId: string; + }): Promise>> { + if (!ruleId) { + throw new Error(`'ruleId' is undefined`); + } + return await retry.tryForTime(retryTimeout, async () => { + const response = await es.search({ + index: indexName, + body: { + query: { + term: { + 'kibana.alert.rule.uuid': ruleId, + }, + }, + }, + }); + if (response.hits.hits.length === 0) { + throw new Error('No hits found'); + } + return response; + }); + }, + + async createIndexConnector({ name, indexName }: { name: string; indexName: string }) { + const { body } = await supertest + .post(`/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .send({ + name, + config: { + index: indexName, + refresh: true, + }, + connector_type_id: '.index', + }); + return body.id as string; + }, + + async createRule({ + name, + ruleTypeId, + params, + actions = [], + tags = [], + schedule, + consumer, + }: { + ruleTypeId: string; + name: string; + params: MetricThresholdParams | ThresholdParams | SloBurnRateRuleParams; + actions?: any[]; + tags?: any[]; + schedule?: { interval: string }; + consumer: string; + }) { + const { body } = await supertest + .post(`/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + // .set('x-elastic-internal-origin', 'foo') + .send({ + params, + consumer, + schedule: schedule || { + interval: '5m', + }, + tags, + name, + rule_type_id: ruleTypeId, + actions, + }); + return body; + }, + + async findRule(ruleId: string) { + if (!ruleId) { + throw new Error(`'ruleId' is undefined`); + } + const response = await supertest + .get('/api/alerting/rules/_find') + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + return response.body.data.find((obj: any) => obj.id === ruleId); + }, + }; +} diff --git a/x-pack/test/observability_solution_api_integration/services/slo_api.ts b/x-pack/test/observability_solution_api_integration/services/slo_api.ts new file mode 100644 index 000000000000000..5a5c9eb5c5ff76d --- /dev/null +++ b/x-pack/test/observability_solution_api_integration/services/slo_api.ts @@ -0,0 +1,185 @@ +/* + * 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'; + +type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M'; + +interface Duration { + value: number; + unit: DurationUnit; +} + +interface WindowSchema { + id: string; + burnRateThreshold: number; + maxBurnRateThreshold: number; + longWindow: Duration; + shortWindow: Duration; + actionGroup: string; +} + +interface Dependency { + ruleId: string; + actionGroupsToSuppressOn: string[]; +} + +export interface SloBurnRateRuleParams { + sloId: string; + windows: WindowSchema[]; + dependencies?: Dependency[]; +} + +interface SloParams { + id?: string; + name: string; + description: string; + indicator: { + type: 'sli.kql.custom'; + params: { + index: string; + good: string; + total: string; + timestampField: string; + }; + }; + timeWindow: { + duration: string; + type: string; + }; + budgetingMethod: string; + objective: { + target: number; + }; + groupBy: string; +} + +export function SloApiProvider({ getService }: FtrProviderContext) { + const es = getService('es'); + const supertest = getService('supertest'); + const retry = getService('retry'); + const requestTimeout = 30 * 1000; + const retryTimeout = 180 * 1000; + + return { + async create(slo: SloParams) { + const { body } = await supertest + .post(`/api/observability/slos`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .send(slo); + + return body; + }, + + async delete(sloId: string) { + const response = await supertest + .delete(`/api/observability/slos/${sloId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo'); + return response; + }, + + async waitForSloToBeDeleted(sloId: string) { + if (!sloId) { + throw new Error(`sloId is undefined`); + } + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertest + .delete(`/api/observability/slos/${sloId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .timeout(requestTimeout); + if (!response.ok) { + throw new Error(`slodId [${sloId}] was not deleted`); + } + return response; + }); + }, + + async waitForSloCreated({ sloId }: { sloId: string }) { + if (!sloId) { + throw new Error(`'sloId is undefined`); + } + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertest + .get(`/api/observability/slos/${sloId}`) + .set('kbn-xsrf', 'foo') + .set('x-elastic-internal-origin', 'foo') + .timeout(requestTimeout); + if (response.body.id === undefined) { + throw new Error(`No slo with id ${sloId} found`); + } + return response.body; + }); + }, + + async waitForSloSummaryTempIndexToExist(index: string) { + if (!index) { + throw new Error(`index is undefined`); + } + + return await retry.tryForTime(retryTimeout, async () => { + const indexExists = await es.indices.exists({ index, allow_no_indices: false }); + if (!indexExists) { + throw new Error(`index ${index} should exist`); + } + return indexExists; + }); + }, + + async getSloData({ sloId, indexName }: { sloId: string; indexName: string }) { + const response = await es.search({ + index: indexName, + body: { + query: { + bool: { + filter: [{ term: { 'slo.id': sloId } }], + }, + }, + }, + }); + return response; + }, + async waitForSloData({ sloId, indexName }: { sloId: string; indexName: string }) { + return await retry.tryForTime(retryTimeout, async () => { + const response = await es.search({ + index: indexName, + body: { + query: { + bool: { + filter: [{ term: { 'slo.id': sloId } }], + }, + }, + }, + }); + if (response.hits.hits.length === 0) { + throw new Error(`No hits found at index [${indexName}] for slo [${sloId}] `); + } + return response; + }); + }, + async deleteAllSLOs() { + const response = await supertest + .get(`/api/observability/slos/_definitions`) + .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') + .send() + .expect(200); + await Promise.all( + response.body.results.map(({ id }: { id: string }) => { + return supertest + .delete(`/api/observability/slos/${id}`) + .set('kbn-xsrf', 'true') + .set('x-elastic-internal-origin', 'foo') + .send() + .expect(204); + }) + ); + }, + }; +} diff --git a/x-pack/test/observability_solution_api_integration/test_suites/slo/burn_rate_rule.ts b/x-pack/test/observability_solution_api_integration/test_suites/slo/burn_rate_rule.ts index 452070cb80f9d9e..6aaeed5609bf211 100644 --- a/x-pack/test/observability_solution_api_integration/test_suites/slo/burn_rate_rule.ts +++ b/x-pack/test/observability_solution_api_integration/test_suites/slo/burn_rate_rule.ts @@ -17,6 +17,9 @@ export default function ({ getService }: FtrProviderContext) { const alertingApi = getService('alertingApi'); const dataViewApi = getService('dataViewApi'); const sloApi = getService('sloApi'); + const config = getService('config'); + const isServerless = config.get('serverless'); + const expectedConsumer = isServerless ? 'observability' : 'slo'; describe('@serverless @ess Burn rate rule', () => { const RULE_TYPE_ID = 'slo.rules.burnRate'; @@ -114,7 +117,7 @@ export default function ({ getService }: FtrProviderContext) { const dependencyRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'observability', + consumer: expectedConsumer, name: 'SLO Burn Rate rule - Dependency', ruleTypeId: RULE_TYPE_ID, schedule: { @@ -186,7 +189,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'observability', + consumer: expectedConsumer, name: 'SLO Burn Rate rule', ruleTypeId: RULE_TYPE_ID, schedule: { @@ -288,7 +291,7 @@ export default function ({ getService }: FtrProviderContext) { it('should find the created rule with correct information about the consumer', async () => { const match = await alertingApi.findRule(ruleId); expect(match).not.to.be(undefined); - expect(match.consumer).to.be('observability'); + expect(match.consumer).to.be(expectedConsumer); }); }); }); diff --git a/x-pack/test/observability_solution_api_integration/test_suites/slo/configs/ess.config.ts b/x-pack/test/observability_solution_api_integration/test_suites/slo/configs/ess.config.ts index 011c536f5e1bf9d..f97aad18dfba3b3 100644 --- a/x-pack/test/observability_solution_api_integration/test_suites/slo/configs/ess.config.ts +++ b/x-pack/test/observability_solution_api_integration/test_suites/slo/configs/ess.config.ts @@ -6,8 +6,14 @@ */ import { createTestConfig } from '../../../config/ess/config.base'; +import { AlertingApiProvider } from '../../../services/alerting_api'; +import { SloApiProvider } from '../../../services/slo_api'; export default createTestConfig({ + services: { + alertingApi: AlertingApiProvider, + sloApi: SloApiProvider, + }, testFiles: [require.resolve('..')], junit: { reportName: 'SLO - Burn rate Integration Tests - ESS Env',