diff --git a/packages/kbn-ftr-common-functional-services/index.ts b/packages/kbn-ftr-common-functional-services/index.ts index e156949f0daf9..506566216c721 100644 --- a/packages/kbn-ftr-common-functional-services/index.ts +++ b/packages/kbn-ftr-common-functional-services/index.ts @@ -14,7 +14,7 @@ import { KibanaServerProvider } from './services/kibana_server'; export { KibanaServerProvider } from './services/kibana_server'; export type KibanaServer = ProvidedType; -export { RetryService } from './services/retry'; +export { RetryService, type TryWithRetriesOptions } from './services/retry'; import { EsArchiverProvider } from './services/es_archiver'; export type EsArchiver = ProvidedType; diff --git a/packages/kbn-ftr-common-functional-services/services/retry/index.ts b/packages/kbn-ftr-common-functional-services/services/retry/index.ts index 6f42e0368364d..f96e413da2680 100644 --- a/packages/kbn-ftr-common-functional-services/services/retry/index.ts +++ b/packages/kbn-ftr-common-functional-services/services/retry/index.ts @@ -7,4 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { RetryService } from './retry'; +export { RetryService, type TryWithRetriesOptions } from './retry'; diff --git a/packages/kbn-ftr-common-functional-services/services/retry/retry.ts b/packages/kbn-ftr-common-functional-services/services/retry/retry.ts index 9ddd13ea583a7..614f57064512c 100644 --- a/packages/kbn-ftr-common-functional-services/services/retry/retry.ts +++ b/packages/kbn-ftr-common-functional-services/services/retry/retry.ts @@ -11,7 +11,7 @@ import { FtrService } from '../ftr_provider_context'; import { retryForSuccess } from './retry_for_success'; import { retryForTruthy } from './retry_for_truthy'; -interface TryWithRetriesOptions { +export interface TryWithRetriesOptions { retryCount: number; retryDelay?: number; timeout?: number; diff --git a/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts b/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts index 5401eb21286d1..921efacd88fcc 100644 --- a/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts +++ b/packages/kbn-ftr-common-functional-services/services/retry/retry_for_success.ts @@ -92,7 +92,7 @@ export async function retryForSuccess(log: ToolingLog, options: Options) { if (lastError && onFailureBlock) { const before = await runAttempt(onFailureBlock); if ('error' in before) { - log.debug(`--- onRetryBlock error: ${before.error.message}`); + log.debug(`--- onRetryBlock error: ${before.error.message} - Attempt #: ${attemptCounter}`); } } @@ -104,9 +104,13 @@ export async function retryForSuccess(log: ToolingLog, options: Options) { if ('error' in attempt) { if (lastError && lastError.message === attempt.error.message) { - log.debug(`--- ${methodName} failed again with the same message...`); + log.debug( + `--- ${methodName} failed again with the same message... - Attempt #: ${attemptCounter}` + ); } else { - log.debug(`--- ${methodName} error: ${attempt.error.message}`); + log.debug( + `--- ${methodName} error: ${attempt.error.message} - Attempt #: ${attemptCounter}` + ); } lastError = attempt.error; diff --git a/x-pack/test_serverless/api_integration/services/alerting_api.ts b/x-pack/test_serverless/api_integration/services/alerting_api.ts deleted file mode 100644 index 6000e9d8bdc88..0000000000000 --- a/x-pack/test_serverless/api_integration/services/alerting_api.ts +++ /dev/null @@ -1,176 +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 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 { RoleCredentials } from '../../shared/services'; -import { SloBurnRateRuleParams } from './slo_api'; -import { FtrProviderContext } from '../ftr_provider_context'; - -export function AlertingApiProvider({ getService }: FtrProviderContext) { - const retry = getService('retry'); - const es = getService('es'); - const requestTimeout = 30 * 1000; - const retryTimeout = 120 * 1000; - const logger = getService('log'); - const svlCommonApi = getService('svlCommonApi'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); - - return { - async waitForRuleStatus({ - roleAuthc, - ruleId, - expectedStatus, - }: { - roleAuthc: RoleCredentials; - ruleId: string; - expectedStatus: string; - }) { - if (!ruleId) { - throw new Error(`'ruleId' is undefined`); - } - return await retry.tryForTime(retryTimeout, async () => { - const response = await supertestWithoutAuth - .get(`/api/alerting/rule/${ruleId}`) - .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .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 as number) < 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({ - roleAuthc, - name, - indexName, - }: { - roleAuthc: RoleCredentials; - name: string; - indexName: string; - }) { - const { body } = await supertestWithoutAuth - .post(`/api/actions/connector`) - .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .send({ - name, - config: { - index: indexName, - refresh: true, - }, - connector_type_id: '.index', - }); - return body.id as string; - }, - - async createRule({ - roleAuthc, - name, - ruleTypeId, - params, - actions = [], - tags = [], - schedule, - consumer, - }: { - roleAuthc: RoleCredentials; - ruleTypeId: string; - name: string; - params: MetricThresholdParams | ThresholdParams | SloBurnRateRuleParams; - actions?: any[]; - tags?: any[]; - schedule?: { interval: string }; - consumer: string; - }) { - const { body } = await supertestWithoutAuth - .post(`/api/alerting/rule`) - .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader) - .send({ - params, - consumer, - schedule: schedule || { - interval: '5m', - }, - tags, - name, - rule_type_id: ruleTypeId, - actions, - }); - return body; - }, - - async findRule(roleAuthc: RoleCredentials, ruleId: string) { - if (!ruleId) { - throw new Error(`'ruleId' is undefined`); - } - const response = await supertestWithoutAuth - .get('/api/alerting/rules/_find') - .set(svlCommonApi.getInternalRequestHeader()) - .set(roleAuthc.apiKeyHeader); - return response.body.data.find((obj: any) => obj.id === ruleId); - }, - }; -} diff --git a/x-pack/test_serverless/api_integration/services/index.ts b/x-pack/test_serverless/api_integration/services/index.ts index 347fc1f68b0ca..22ce9b3bb4794 100644 --- a/x-pack/test_serverless/api_integration/services/index.ts +++ b/x-pack/test_serverless/api_integration/services/index.ts @@ -9,7 +9,6 @@ import { GenericFtrProviderContext } from '@kbn/test'; import { services as deploymentAgnosticSharedServices } from '../../shared/services/deployment_agnostic_services'; import { services as svlSharedServices } from '../../shared/services'; -import { AlertingApiProvider } from './alerting_api'; import { SamlToolsProvider } from './saml_tools'; import { SvlCasesServiceProvider } from './svl_cases'; import { SloApiProvider } from './slo_api'; @@ -35,7 +34,6 @@ export const services = { // serverless FTR services ...svlSharedServices, - alertingApi: AlertingApiProvider, samlTools: SamlToolsProvider, svlCases: SvlCasesServiceProvider, sloApi: SloApiProvider, diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts index 93dd4e5565db5..cf727fd9fd1bc 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/alert_documents.ts @@ -42,22 +42,18 @@ import { ALERT_PREVIOUS_ACTION_GROUP, } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { createEsQueryRule } from './helpers/alerting_api_helper'; -import { waitForAlertInIndex, waitForNumRuleRuns } from './helpers/alerting_wait_for_helpers'; import { ObjectRemover } from '../../../../shared/lib'; -import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import { RoleCredentials } from '../../../../shared/services'; const OPEN_OR_ACTIVE = new Set(['open', 'active']); export default function ({ getService }: FtrProviderContext) { - const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); let roleAdmin: RoleCredentials; - let internalReqHeader: InternalRequestHeader; const supertest = getService('supertest'); const esClient = getService('es'); const objectRemover = new ObjectRemover(supertest); + const alertingApi = getService('alertingApi'); describe('Alert documents', function () { // Timeout of 360000ms exceeded @@ -68,7 +64,6 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { roleAdmin = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); }); afterEach(async () => { @@ -80,10 +75,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should generate an alert document for an active alert', async () => { - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -103,17 +96,15 @@ export default function ({ getService }: FtrProviderContext) { // get the first alert document written const testStart1 = new Date(); - await waitForNumRuleRuns({ - supertestWithoutAuth, + await alertingApi.helpers.waitForNumRuleRuns({ roleAuthc: roleAdmin, - internalReqHeader, numOfRuns: 1, ruleId, esClient, testStart: testStart1, }); - const alResp1 = await waitForAlertInIndex({ + const alResp1 = await alertingApi.helpers.waitForAlertInIndex({ esClient, filter: testStart1, indexName: ALERT_INDEX, @@ -206,10 +197,8 @@ export default function ({ getService }: FtrProviderContext) { }); it('should update an alert document for an ongoing alert', async () => { - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -229,17 +218,15 @@ export default function ({ getService }: FtrProviderContext) { // get the first alert document written const testStart1 = new Date(); - await waitForNumRuleRuns({ - supertestWithoutAuth, + await alertingApi.helpers.waitForNumRuleRuns({ roleAuthc: roleAdmin, - internalReqHeader, numOfRuns: 1, ruleId, esClient, testStart: testStart1, }); - const alResp1 = await waitForAlertInIndex({ + const alResp1 = await alertingApi.helpers.waitForAlertInIndex({ esClient, filter: testStart1, indexName: ALERT_INDEX, @@ -249,17 +236,15 @@ export default function ({ getService }: FtrProviderContext) { // wait for another run, get the updated alert document const testStart2 = new Date(); - await waitForNumRuleRuns({ - supertestWithoutAuth, + await alertingApi.helpers.waitForNumRuleRuns({ roleAuthc: roleAdmin, - internalReqHeader, numOfRuns: 1, ruleId, esClient, testStart: testStart2, }); - const alResp2 = await waitForAlertInIndex({ + const alResp2 = await alertingApi.helpers.waitForAlertInIndex({ esClient, filter: testStart2, indexName: ALERT_INDEX, diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts deleted file mode 100644 index f7a909c688d0e..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_api_helper.ts +++ /dev/null @@ -1,478 +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 moment from 'moment'; -import { v4 as uuidv4 } from 'uuid'; -import type { Agent as SuperTestAgent } from 'supertest'; -import { - InternalRequestHeader, - RoleCredentials, - SupertestWithoutAuthProviderType, -} from '../../../../../shared/services'; - -interface CreateEsQueryRuleParams { - size: number; - thresholdComparator: string; - threshold: number[]; - timeWindowSize?: number; - timeWindowUnit?: string; - esQuery?: string; - timeField?: string; - searchConfiguration?: unknown; - indexName?: string; - excludeHitsFromPreviousRun?: boolean; - aggType?: string; - aggField?: string; - groupBy?: string; - termField?: string; - termSize?: number; - index?: string[]; -} - -export async function createIndexConnector({ - supertestWithoutAuth, - roleAuthc, - internalReqHeader, - name, - indexName, -}: { - supertestWithoutAuth: SupertestWithoutAuthProviderType; - roleAuthc: RoleCredentials; - internalReqHeader: InternalRequestHeader; - name: string; - indexName: string; -}) { - const { body } = await supertestWithoutAuth - .post(`/api/actions/connector`) - .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader) - .send({ - name, - config: { - index: indexName, - refresh: true, - }, - connector_type_id: '.index', - }) - .expect(200); - return body; -} - -export async function createSlackConnector({ - supertestWithoutAuth, - roleAuthc, - internalReqHeader, - name, -}: { - supertestWithoutAuth: SupertestWithoutAuthProviderType; - roleAuthc: RoleCredentials; - internalReqHeader: InternalRequestHeader; - name: string; -}) { - const { body } = await supertestWithoutAuth - .post(`/api/actions/connector`) - .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader) - .send({ - name, - config: {}, - secrets: { - webhookUrl: 'http://test', - }, - connector_type_id: '.slack', - }) - .expect(200); - return body; -} - -export async function createEsQueryRule({ - supertestWithoutAuth, - roleAuthc, - internalReqHeader, - name, - ruleTypeId, - params, - actions = [], - tags = [], - schedule, - consumer, - notifyWhen, - enabled = true, -}: { - supertestWithoutAuth: SupertestWithoutAuthProviderType; - roleAuthc: RoleCredentials; - internalReqHeader: InternalRequestHeader; - ruleTypeId: string; - name: string; - params: CreateEsQueryRuleParams; - consumer: string; - actions?: any[]; - tags?: any[]; - schedule?: { interval: string }; - notifyWhen?: string; - enabled?: boolean; -}) { - const { body } = await supertestWithoutAuth - .post(`/api/alerting/rule`) - .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader) - .send({ - enabled, - params, - consumer, - schedule: schedule || { - interval: '1h', - }, - tags, - name, - rule_type_id: ruleTypeId, - actions, - ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), - }) - .expect(200); - return body; -} - -export const generateUniqueKey = () => uuidv4().replace(/-/g, ''); - -export async function createAnomalyRule({ - supertest, - name = generateUniqueKey(), - actions = [], - tags = ['foo', 'bar'], - schedule, - consumer = 'alerts', - notifyWhen, - enabled = true, - ruleTypeId = 'apm.anomaly', - params, -}: { - supertest: SuperTestAgent; - name?: string; - consumer?: string; - actions?: any[]; - tags?: any[]; - schedule?: { interval: string }; - notifyWhen?: string; - enabled?: boolean; - ruleTypeId?: string; - params?: any; -}) { - const { body } = await supertest - .post(`/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .send({ - enabled, - params: params || { - anomalySeverityType: 'critical', - anomalyDetectorTypes: ['txLatency'], - environment: 'ENVIRONMENT_ALL', - windowSize: 30, - windowUnit: 'm', - }, - consumer, - schedule: schedule || { - interval: '1m', - }, - tags, - name, - rule_type_id: ruleTypeId, - actions, - ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), - }) - .expect(200); - return body; -} - -export async function createLatencyThresholdRule({ - supertest, - name = generateUniqueKey(), - actions = [], - tags = ['foo', 'bar'], - schedule, - consumer = 'apm', - notifyWhen, - enabled = true, - ruleTypeId = 'apm.transaction_duration', - params, -}: { - supertest: SuperTestAgent; - name?: string; - consumer?: string; - actions?: any[]; - tags?: any[]; - schedule?: { interval: string }; - notifyWhen?: string; - enabled?: boolean; - ruleTypeId?: string; - params?: any; -}) { - const { body } = await supertest - .post(`/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .send({ - enabled, - params: params || { - aggregationType: 'avg', - environment: 'ENVIRONMENT_ALL', - threshold: 1500, - windowSize: 5, - windowUnit: 'm', - }, - consumer, - schedule: schedule || { - interval: '1m', - }, - tags, - name, - rule_type_id: ruleTypeId, - actions, - ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), - }); - return body; -} - -export async function createInventoryRule({ - supertest, - name = generateUniqueKey(), - actions = [], - tags = ['foo', 'bar'], - schedule, - consumer = 'alerts', - notifyWhen, - enabled = true, - ruleTypeId = 'metrics.alert.inventory.threshold', - params, -}: { - supertest: SuperTestAgent; - name?: string; - consumer?: string; - actions?: any[]; - tags?: any[]; - schedule?: { interval: string }; - notifyWhen?: string; - enabled?: boolean; - ruleTypeId?: string; - params?: any; -}) { - const { body } = await supertest - .post(`/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .send({ - enabled, - params: params || { - nodeType: 'host', - criteria: [ - { - metric: 'cpu', - comparator: '>', - threshold: [5], - timeSize: 1, - timeUnit: 'm', - customMetric: { - type: 'custom', - id: 'alert-custom-metric', - field: '', - aggregation: 'avg', - }, - }, - ], - sourceId: 'default', - }, - consumer, - schedule: schedule || { - interval: '1m', - }, - tags, - name, - rule_type_id: ruleTypeId, - actions, - ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), - }) - .expect(200); - return body; -} - -export async function disableRule({ - supertest, - ruleId, -}: { - supertest: SuperTestAgent; - ruleId: string; -}) { - const { body } = await supertest - .post(`/api/alerting/rule/${ruleId}/_disable`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .expect(204); - return body; -} - -export async function updateEsQueryRule({ - supertest, - ruleId, - updates, -}: { - supertest: SuperTestAgent; - ruleId: string; - updates: any; -}) { - const { body: r } = await supertest - .get(`/api/alerting/rule/${ruleId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .expect(200); - const body = await supertest - .put(`/api/alerting/rule/${ruleId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .send({ - ...{ - name: r.name, - schedule: r.schedule, - throttle: r.throttle, - tags: r.tags, - params: r.params, - notify_when: r.notifyWhen, - actions: r.actions.map((action: any) => ({ - group: action.group, - params: action.params, - id: action.id, - frequency: action.frequency, - })), - }, - ...updates, - }) - .expect(200); - return body; -} - -export async function runRule({ - supertestWithoutAuth, - roleAuthc, - internalReqHeader, - ruleId, -}: { - supertestWithoutAuth: SupertestWithoutAuthProviderType; - roleAuthc: RoleCredentials; - internalReqHeader: InternalRequestHeader; - ruleId: string; -}) { - const response = await supertestWithoutAuth - .post(`/internal/alerting/rule/${ruleId}/_run_soon`) - .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader) - .expect(204); - return response; -} - -export async function muteRule({ - supertest, - ruleId, -}: { - supertest: SuperTestAgent; - ruleId: string; -}) { - const { body } = await supertest - .post(`/api/alerting/rule/${ruleId}/_mute_all`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .expect(204); - return body; -} - -export async function enableRule({ - supertest, - ruleId, -}: { - supertest: SuperTestAgent; - ruleId: string; -}) { - const { body } = await supertest - .post(`/api/alerting/rule/${ruleId}/_enable`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .expect(204); - return body; -} - -export async function muteAlert({ - supertest, - ruleId, - alertId, -}: { - supertest: SuperTestAgent; - ruleId: string; - alertId: string; -}) { - const { body } = await supertest - .post(`/api/alerting/rule/${ruleId}/alert/${alertId}/_mute`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .expect(204); - return body; -} - -export async function unmuteRule({ - supertest, - ruleId, -}: { - supertest: SuperTestAgent; - ruleId: string; -}) { - const { body } = await supertest - .post(`/api/alerting/rule/${ruleId}/_unmute_all`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .expect(204); - return body; -} - -export async function snoozeRule({ - supertest, - ruleId, -}: { - supertest: SuperTestAgent; - ruleId: string; -}) { - const { body } = await supertest - .post(`/internal/alerting/rule/${ruleId}/_snooze`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo') - .send({ - snooze_schedule: { - duration: 100000000, - rRule: { - count: 1, - dtstart: moment().format(), - tzid: 'UTC', - }, - }, - }) - .expect(204); - return body; -} - -export async function findRule({ - supertest, - ruleId, -}: { - supertest: SuperTestAgent; - ruleId: string; -}) { - if (!ruleId) { - throw new Error(`'ruleId' is undefined`); - } - const response = await supertest - .get(`/api/alerting/rule/${ruleId}`) - .set('kbn-xsrf', 'foo') - .set('x-elastic-internal-origin', 'foo'); - return response.body || {}; -} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts deleted file mode 100644 index c7f2ac357e4a2..0000000000000 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/helpers/alerting_wait_for_helpers.ts +++ /dev/null @@ -1,402 +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 pRetry from 'p-retry'; -import type { Client } from '@elastic/elasticsearch'; -import type { - AggregationsAggregate, - SearchResponse, -} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { runRule } from './alerting_api_helper'; -import type { SupertestWithoutAuthProviderType } from '../../../../../shared/services'; -import { RoleCredentials } from '../../../../../shared/services'; -import { InternalRequestHeader } from '../../../../../shared/services'; - -export async function waitForDocumentInIndex({ - esClient, - indexName, - ruleId, - num = 1, - sort = 'desc', -}: { - esClient: Client; - indexName: string; - ruleId: string; - num?: number; - sort?: 'asc' | 'desc'; -}): Promise { - return await pRetry( - async () => { - const response = await esClient.search({ - index: indexName, - sort: `date:${sort}`, - body: { - query: { - bool: { - must: [ - { - term: { - 'ruleId.keyword': ruleId, - }, - }, - ], - }, - }, - }, - }); - if (response.hits.hits.length < num) { - throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`); - } - return response; - }, - { retries: 10 } - ); -} - -export async function getDocumentsInIndex({ - esClient, - indexName, - ruleId, -}: { - esClient: Client; - indexName: string; - ruleId: string; -}): Promise { - return await esClient.search({ - index: indexName, - body: { - query: { - bool: { - must: [ - { - term: { - 'ruleId.keyword': ruleId, - }, - }, - ], - }, - }, - }, - }); -} - -export async function createIndex({ - esClient, - indexName, -}: { - esClient: Client; - indexName: string; -}) { - return await esClient.indices.create( - { - index: indexName, - body: {}, - }, - { meta: true } - ); -} - -export async function waitForAlertInIndex({ - esClient, - filter, - indexName, - ruleId, - num = 1, -}: { - esClient: Client; - filter: Date; - indexName: string; - ruleId: string; - num: number; -}): Promise>> { - return await pRetry( - async () => { - const response = await esClient.search({ - index: indexName, - body: { - query: { - bool: { - must: [ - { - term: { - 'kibana.alert.rule.uuid': ruleId, - }, - }, - { - range: { - '@timestamp': { - gte: filter.getTime().toString(), - }, - }, - }, - ], - }, - }, - }, - }); - if (response.hits.hits.length < num) { - throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`); - } - return response; - }, - { retries: 10 } - ); -} - -export async function waitForAllTasksIdle({ - esClient, - filter, -}: { - esClient: Client; - filter: Date; -}): Promise { - return await pRetry( - async () => { - const response = await esClient.search({ - index: '.kibana_task_manager', - body: { - query: { - bool: { - must: [ - { - terms: { - 'task.scope': ['actions', 'alerting'], - }, - }, - { - range: { - 'task.scheduledAt': { - gte: filter.getTime().toString(), - }, - }, - }, - ], - must_not: [ - { - term: { - 'task.status': 'idle', - }, - }, - ], - }, - }, - }, - }); - if (response.hits.hits.length !== 0) { - throw new Error(`Expected 0 hits but received ${response.hits.hits.length}`); - } - return response; - }, - { retries: 10 } - ); -} - -export async function waitForAllTasks({ - esClient, - filter, - taskType, - attempts, -}: { - esClient: Client; - filter: Date; - taskType: string; - attempts: number; -}): Promise { - return await pRetry( - async () => { - const response = await esClient.search({ - index: '.kibana_task_manager', - body: { - query: { - bool: { - must: [ - { - term: { - 'task.status': 'idle', - }, - }, - { - term: { - 'task.attempts': attempts, - }, - }, - { - terms: { - 'task.scope': ['actions', 'alerting'], - }, - }, - { - term: { - 'task.taskType': taskType, - }, - }, - { - range: { - 'task.scheduledAt': { - gte: filter.getTime().toString(), - }, - }, - }, - ], - }, - }, - }, - }); - if (response.hits.hits.length === 0) { - throw new Error('No hits found'); - } - return response; - }, - { retries: 10 } - ); -} - -export async function waitForDisabled({ - esClient, - ruleId, - filter, -}: { - esClient: Client; - ruleId: string; - filter: Date; -}): Promise { - return await pRetry( - async () => { - const response = await esClient.search({ - index: '.kibana_task_manager', - body: { - query: { - bool: { - must: [ - { - term: { - 'task.id': `task:${ruleId}`, - }, - }, - { - terms: { - 'task.scope': ['actions', 'alerting'], - }, - }, - { - range: { - 'task.scheduledAt': { - gte: filter.getTime().toString(), - }, - }, - }, - { - term: { - 'task.enabled': true, - }, - }, - ], - }, - }, - }, - }); - if (response.hits.hits.length !== 0) { - throw new Error(`Expected 0 hits but received ${response.hits.hits.length}`); - } - return response; - }, - { retries: 10 } - ); -} - -export async function waitForExecutionEventLog({ - esClient, - filter, - ruleId, - num = 1, -}: { - esClient: Client; - filter: Date; - ruleId: string; - num?: number; -}): Promise { - return await pRetry( - async () => { - const response = await esClient.search({ - index: '.kibana-event-log*', - body: { - query: { - bool: { - filter: [ - { - term: { - 'rule.id': { - value: ruleId, - }, - }, - }, - { - term: { - 'event.provider': { - value: 'alerting', - }, - }, - }, - { - term: { - 'event.action': 'execute', - }, - }, - { - range: { - '@timestamp': { - gte: filter.getTime().toString(), - }, - }, - }, - ], - }, - }, - }, - }); - if (response.hits.hits.length < num) { - throw new Error('No hits found'); - } - return response; - }, - { retries: 10 } - ); -} - -export async function waitForNumRuleRuns({ - supertestWithoutAuth, - roleAuthc, - internalReqHeader, - numOfRuns, - ruleId, - esClient, - testStart, -}: { - supertestWithoutAuth: SupertestWithoutAuthProviderType; - roleAuthc: RoleCredentials; - internalReqHeader: InternalRequestHeader; - numOfRuns: number; - ruleId: string; - esClient: Client; - testStart: Date; -}) { - for (let i = 0; i < numOfRuns; i++) { - await pRetry( - async () => { - await runRule({ supertestWithoutAuth, roleAuthc, internalReqHeader, ruleId }); - await waitForExecutionEventLog({ - esClient, - filter: testStart, - ruleId, - num: i + 1, - }); - await waitForAllTasksIdle({ esClient, filter: testStart }); - }, - { retries: 10 } - ); - } -} diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts index 37b78a5e1b36f..593c10f371f09 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/rules.ts @@ -9,28 +9,6 @@ import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server'; import expect from '@kbn/expect'; import { omit } from 'lodash'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - createIndexConnector, - createEsQueryRule, - disableRule, - updateEsQueryRule, - runRule, - muteRule, - enableRule, - muteAlert, - unmuteRule, - createSlackConnector, -} from './helpers/alerting_api_helper'; -import { - createIndex, - getDocumentsInIndex, - waitForAllTasks, - waitForAllTasksIdle, - waitForDisabled, - waitForDocumentInIndex, - waitForExecutionEventLog, - waitForNumRuleRuns, -} from './helpers/alerting_wait_for_helpers'; import type { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; export default function ({ getService }: FtrProviderContext) { @@ -39,7 +17,8 @@ export default function ({ getService }: FtrProviderContext) { const esDeleteAllIndices = getService('esDeleteAllIndices'); const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); + const alertingApi = getService('alertingApi'); + let roleAdmin: RoleCredentials; let internalReqHeader: InternalRequestHeader; @@ -73,19 +52,15 @@ export default function ({ getService }: FtrProviderContext) { it('should schedule task, run rule and schedule actions when appropriate', async () => { const testStart = new Date(); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -130,10 +105,14 @@ export default function ({ getService }: FtrProviderContext) { ruleId = createdRule.id; // Wait for the action to index a document before disabling the alert and waiting for tasks to finish - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, + retryOptions: { + retryCount: 12, + retryDelay: 2000, + }, }); expect(resp.hits.hits.length).to.be(1); @@ -151,7 +130,7 @@ export default function ({ getService }: FtrProviderContext) { tags: '', }); - const eventLogResp = await waitForExecutionEventLog({ + const eventLogResp = await alertingApi.helpers.waiting.waitForExecutionEventLog({ esClient, filter: testStart, ruleId, @@ -171,19 +150,15 @@ export default function ({ getService }: FtrProviderContext) { it('should pass updated rule params to executor', async () => { const testStart = new Date(); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -228,10 +203,11 @@ export default function ({ getService }: FtrProviderContext) { ruleId = createdRule.id; // Wait for the action to index a document before disabling the alert and waiting for tasks to finish - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, + retryOptions: { retryDelay: 800, retryCount: 10 }, }); expect(resp.hits.hits.length).to.be(1); @@ -249,13 +225,13 @@ export default function ({ getService }: FtrProviderContext) { tags: '', }); - await waitForAllTasksIdle({ + await alertingApi.helpers.waiting.waitForAllTasksIdle({ esClient, filter: testStart, }); - await updateEsQueryRule({ - supertest, + await alertingApi.helpers.updateEsQueryRule({ + roleAuthc: roleAdmin, ruleId, updates: { name: 'def', @@ -263,15 +239,13 @@ export default function ({ getService }: FtrProviderContext) { }, }); - await runRule({ - supertestWithoutAuth, + await alertingApi.helpers.runRule({ roleAuthc: roleAdmin, - internalReqHeader, ruleId, }); // make sure alert info passed to executor is correct - const resp2 = await waitForDocumentInIndex({ + const resp2 = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, @@ -298,18 +272,14 @@ export default function ({ getService }: FtrProviderContext) { const testStart = new Date(); // Should fail - const createdConnector = await createSlackConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createSlackConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Slack Connector: Alerting API test', }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -341,7 +311,7 @@ export default function ({ getService }: FtrProviderContext) { ruleId = createdRule.id; // Should retry when the the action fails - const resp = await waitForAllTasks({ + const resp = await alertingApi.helpers.waiting.waitForAllTasks({ esClient, filter: testStart, taskType: 'actions:.slack', @@ -353,19 +323,15 @@ export default function ({ getService }: FtrProviderContext) { it('should throttle alerts when appropriate', async () => { const testStart = new Date(); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -407,29 +373,27 @@ export default function ({ getService }: FtrProviderContext) { ruleId = createdRule.id; // Wait until alerts ran at least 3 times before disabling the alert and waiting for tasks to finish - await waitForNumRuleRuns({ - supertestWithoutAuth, + await alertingApi.helpers.waitForNumRuleRuns({ roleAuthc: roleAdmin, - internalReqHeader, numOfRuns: 3, ruleId, esClient, testStart, }); - await disableRule({ - supertest, + await alertingApi.helpers.disableRule({ + roleAuthc: roleAdmin, ruleId, }); - await waitForDisabled({ + await alertingApi.helpers.waiting.waitForDisabled({ esClient, ruleId, filter: testStart, }); // Ensure actions only executed once - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, @@ -440,19 +404,15 @@ export default function ({ getService }: FtrProviderContext) { it('should throttle alerts with throttled action when appropriate', async () => { const testStart = new Date(); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -498,29 +458,27 @@ export default function ({ getService }: FtrProviderContext) { ruleId = createdRule.id; // Wait until alerts ran at least 3 times before disabling the alert and waiting for tasks to finish - await waitForNumRuleRuns({ - supertestWithoutAuth, + await alertingApi.helpers.waitForNumRuleRuns({ roleAuthc: roleAdmin, - internalReqHeader, numOfRuns: 3, ruleId, esClient, testStart, }); - await disableRule({ - supertest, + await alertingApi.helpers.disableRule({ + roleAuthc: roleAdmin, ruleId, }); - await waitForDisabled({ + await alertingApi.helpers.waiting.waitForDisabled({ esClient, ruleId, filter: testStart, }); // Ensure actions only executed once - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, @@ -531,19 +489,15 @@ export default function ({ getService }: FtrProviderContext) { it('should reset throttle window when not firing and should not throttle when changing groups', async () => { const testStart = new Date(); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -614,21 +568,21 @@ export default function ({ getService }: FtrProviderContext) { ruleId = createdRule.id; // Wait for the action to index a document - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waiting.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, }); expect(resp.hits.hits.length).to.be(1); - await waitForAllTasksIdle({ + await alertingApi.helpers.waiting.waitForAllTasksIdle({ esClient, filter: testStart, }); // Update the rule to recover - await updateEsQueryRule({ - supertest, + await alertingApi.helpers.updateEsQueryRule({ + roleAuthc: roleAdmin, ruleId, updates: { name: 'never fire', @@ -645,34 +599,36 @@ export default function ({ getService }: FtrProviderContext) { }, }); - await runRule({ - supertestWithoutAuth, + await alertingApi.helpers.runRule({ roleAuthc: roleAdmin, - internalReqHeader, ruleId, }); - const eventLogResp = await waitForExecutionEventLog({ + const eventLogResp = await alertingApi.helpers.waiting.waitForExecutionEventLog({ esClient, filter: testStart, ruleId, num: 2, + retryOptions: { + retryCount: 12, + retryDelay: 2000, + }, }); expect(eventLogResp.hits.hits.length).to.be(2); - await disableRule({ - supertest, + await alertingApi.helpers.disableRule({ + roleAuthc: roleAdmin, ruleId, }); - await waitForDisabled({ + await alertingApi.helpers.waiting.waitForDisabled({ esClient, ruleId, filter: testStart, }); // Ensure only 2 actions are executed - const resp2 = await waitForDocumentInIndex({ + const resp2 = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, @@ -683,21 +639,17 @@ export default function ({ getService }: FtrProviderContext) { it(`shouldn't schedule actions when alert is muted`, async () => { const testStart = new Date(); - await createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); + await alertingApi.helpers.waiting.createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, enabled: false, consumer: 'alerts', name: 'always fire', @@ -742,41 +694,39 @@ export default function ({ getService }: FtrProviderContext) { }); ruleId = createdRule.id; - await muteRule({ - supertest, + await alertingApi.helpers.muteRule({ + roleAuthc: roleAdmin, ruleId, }); - await enableRule({ - supertest, + await alertingApi.helpers.enableRule({ + roleAuthc: roleAdmin, ruleId, }); // Wait until alerts schedule actions twice to ensure actions had a chance to skip // execution once before disabling the alert and waiting for tasks to finish - await waitForNumRuleRuns({ - supertestWithoutAuth, + await alertingApi.helpers.waitForNumRuleRuns({ roleAuthc: roleAdmin, - internalReqHeader, numOfRuns: 2, ruleId, esClient, testStart, }); - await disableRule({ - supertest, + await alertingApi.helpers.disableRule({ + roleAuthc: roleAdmin, ruleId, }); - await waitForDisabled({ + await alertingApi.helpers.waiting.waitForDisabled({ esClient, ruleId, filter: testStart, }); // Should not have executed any action - const resp2 = await getDocumentsInIndex({ + const resp2 = await alertingApi.helpers.waiting.getDocumentsInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, @@ -786,21 +736,17 @@ export default function ({ getService }: FtrProviderContext) { it(`shouldn't schedule actions when alert instance is muted`, async () => { const testStart = new Date(); - await createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); + await alertingApi.helpers.waiting.createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, enabled: false, consumer: 'alerts', name: 'always fire', @@ -845,42 +791,40 @@ export default function ({ getService }: FtrProviderContext) { }); ruleId = createdRule.id; - await muteAlert({ - supertest, + await alertingApi.helpers.muteAlert({ + roleAuthc: roleAdmin, ruleId, alertId: 'query matched', }); - await enableRule({ - supertest, + await alertingApi.helpers.enableRule({ + roleAuthc: roleAdmin, ruleId, }); // Wait until alerts schedule actions twice to ensure actions had a chance to skip // execution once before disabling the alert and waiting for tasks to finish - await waitForNumRuleRuns({ - supertestWithoutAuth, + await alertingApi.helpers.waitForNumRuleRuns({ roleAuthc: roleAdmin, - internalReqHeader, numOfRuns: 2, ruleId, esClient, testStart, }); - await disableRule({ - supertest, + await alertingApi.helpers.disableRule({ + roleAuthc: roleAdmin, ruleId, }); - await waitForDisabled({ + await alertingApi.helpers.waiting.waitForDisabled({ esClient, ruleId, filter: testStart, }); // Should not have executed any action - const resp2 = await getDocumentsInIndex({ + const resp2 = await alertingApi.helpers.waiting.getDocumentsInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, @@ -889,19 +833,15 @@ export default function ({ getService }: FtrProviderContext) { }); it(`should unmute all instances when unmuting an alert`, async () => { - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, enabled: false, consumer: 'alerts', name: 'always fire', @@ -946,29 +886,29 @@ export default function ({ getService }: FtrProviderContext) { }); ruleId = createdRule.id; - await muteAlert({ - supertest, + await alertingApi.helpers.muteAlert({ + roleAuthc: roleAdmin, ruleId, alertId: 'query matched', }); - await muteRule({ - supertest, + await alertingApi.helpers.muteRule({ + roleAuthc: roleAdmin, ruleId, }); - await unmuteRule({ - supertest, + await alertingApi.helpers.unmuteRule({ + roleAuthc: roleAdmin, ruleId, }); - await enableRule({ - supertest, + await alertingApi.helpers.enableRule({ + roleAuthc: roleAdmin, ruleId, }); // Should have one document indexed by the action - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, diff --git a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts index 995a7ee197610..2726af585e28f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/alerting/summary_actions.ts @@ -29,25 +29,15 @@ import { } from '@kbn/rule-data-utils'; import { omit, padStart } from 'lodash'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { createIndexConnector, createEsQueryRule } from './helpers/alerting_api_helper'; -import { - createIndex, - getDocumentsInIndex, - waitForAlertInIndex, - waitForDocumentInIndex, -} from './helpers/alerting_wait_for_helpers'; -import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import { RoleCredentials } from '../../../../shared/services'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esClient = getService('es'); const esDeleteAllIndices = getService('esDeleteAllIndices'); - - const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); + const alertingApi = getService('alertingApi'); let roleAdmin: RoleCredentials; - let internalReqHeader: InternalRequestHeader; describe('Summary actions', function () { const RULE_TYPE_ID = '.es-query'; @@ -75,7 +65,6 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { roleAdmin = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); }); afterEach(async () => { @@ -98,19 +87,15 @@ export default function ({ getService }: FtrProviderContext) { it('should schedule actions for summary of alerts per rule run', async () => { const testStart = new Date(); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -158,19 +143,27 @@ export default function ({ getService }: FtrProviderContext) { }); ruleId = createdRule.id; - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, + retryOptions: { + retryCount: 20, + retryDelay: 15_000, + }, }); expect(resp.hits.hits.length).to.be(1); - const resp2 = await waitForAlertInIndex({ + const resp2 = await alertingApi.helpers.waitForAlertInIndex({ esClient, filter: testStart, indexName: ALERT_INDEX, ruleId, num: 1, + retryOptions: { + retryCount: 20, + retryDelay: 15_000, + }, }); expect(resp2.hits.hits.length).to.be(1); @@ -228,19 +221,15 @@ export default function ({ getService }: FtrProviderContext) { it('should filter alerts by kql', async () => { const testStart = new Date(); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -288,19 +277,27 @@ export default function ({ getService }: FtrProviderContext) { }); ruleId = createdRule.id; - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, + retryOptions: { + retryCount: 20, + retryDelay: 15_000, + }, }); expect(resp.hits.hits.length).to.be(1); - const resp2 = await waitForAlertInIndex({ + const resp2 = await alertingApi.helpers.waitForAlertInIndex({ esClient, filter: testStart, indexName: ALERT_INDEX, ruleId, num: 1, + retryOptions: { + retryCount: 20, + retryDelay: 15_000, + }, }); expect(resp2.hits.hits.length).to.be(1); @@ -365,21 +362,17 @@ export default function ({ getService }: FtrProviderContext) { const start = `${hour}:${minutes}`; const end = `${hour}:${minutes}`; - await createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); + await alertingApi.helpers.waiting.createIndex({ esClient, indexName: ALERT_ACTION_INDEX }); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -433,7 +426,7 @@ export default function ({ getService }: FtrProviderContext) { ruleId = createdRule.id; // Should not have executed any action - const resp = await getDocumentsInIndex({ + const resp = await alertingApi.helpers.waiting.getDocumentsInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, @@ -443,19 +436,15 @@ export default function ({ getService }: FtrProviderContext) { it('should schedule actions for summary of alerts on a custom interval', async () => { const testStart = new Date(); - const createdConnector = await createIndexConnector({ - supertestWithoutAuth, + const createdConnector = await alertingApi.helpers.createIndexConnector({ roleAuthc: roleAdmin, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: ALERT_ACTION_INDEX, }); connectorId = createdConnector.id; - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc: roleAdmin, - internalReqHeader, consumer: 'alerts', name: 'always fire', ruleTypeId: RULE_TYPE_ID, @@ -501,20 +490,28 @@ export default function ({ getService }: FtrProviderContext) { }); ruleId = createdRule.id; - const resp = await waitForDocumentInIndex({ + const resp = await alertingApi.helpers.waitForDocumentInIndex({ esClient, indexName: ALERT_ACTION_INDEX, ruleId, num: 2, sort: 'asc', + retryOptions: { + retryCount: 20, + retryDelay: 10_000, + }, }); - const resp2 = await waitForAlertInIndex({ + const resp2 = await alertingApi.helpers.waitForAlertInIndex({ esClient, filter: testStart, indexName: ALERT_INDEX, ruleId, num: 1, + retryOptions: { + retryCount: 20, + retryDelay: 15_000, + }, }); expect(resp2.hits.hits.length).to.be(1); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts b/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts index 39edd9ba01eb9..8d627413ecbc0 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule/es_query_rule.ts @@ -12,7 +12,6 @@ */ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { createEsQueryRule } from '../../common/alerting/helpers/alerting_api_helper'; import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; export default function ({ getService }: FtrProviderContext) { @@ -22,7 +21,6 @@ export default function ({ getService }: FtrProviderContext) { const alertingApi = getService('alertingApi'); const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); let roleAuthc: RoleCredentials; let internalReqHeader: InternalRequestHeader; @@ -58,10 +56,8 @@ export default function ({ getService }: FtrProviderContext) { indexName: ALERT_ACTION_INDEX, }); - const createdRule = await createEsQueryRule({ - supertestWithoutAuth, + const createdRule = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, consumer: 'observability', name: 'always fire', ruleTypeId: RULE_TYPE_ID, diff --git a/x-pack/test_serverless/functional/services/index.ts b/x-pack/test_serverless/functional/services/index.ts index c63a16b4402f1..770cdbb88c97a 100644 --- a/x-pack/test_serverless/functional/services/index.ts +++ b/x-pack/test_serverless/functional/services/index.ts @@ -7,7 +7,6 @@ import { services as deploymentAgnosticFunctionalServices } from './deployment_agnostic_services'; import { services as svlSharedServices } from '../../shared/services'; - import { SvlCommonNavigationServiceProvider } from './svl_common_navigation'; import { SvlObltNavigationServiceProvider } from './svl_oblt_navigation'; import { SvlSearchNavigationServiceProvider } from './svl_search_navigation'; @@ -17,6 +16,7 @@ import { SvlCasesServiceProvider } from '../../api_integration/services/svl_case import { MachineLearningProvider } from './ml'; import { LogsSynthtraceProvider } from './log'; import { UISettingsServiceProvider } from './ui_settings'; +import { services as SvlApiIntegrationSvcs } from '../../api_integration/services'; export const services = { // deployment agnostic FTR services @@ -34,4 +34,5 @@ export const services = { uiSettings: UISettingsServiceProvider, // log services svlLogsSynthtraceClient: LogsSynthtraceProvider, + alertingApi: SvlApiIntegrationSvcs.alertingApi, }; diff --git a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts index c2b878511a4dd..6a0d515afd232 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/rules/rules_list.ts @@ -7,17 +7,7 @@ import { expect } from 'expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - createAnomalyRule as createRule, - disableRule, - enableRule, - runRule, - createIndexConnector, - snoozeRule, - createLatencyThresholdRule, - createEsQueryRule, -} from '../../../../api_integration/test_suites/common/alerting/helpers/alerting_api_helper'; -import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import { RoleCredentials } from '../../../../shared/services'; export default ({ getPageObject, getService }: FtrProviderContext) => { const svlCommonPage = getPageObject('svlCommonPage'); @@ -31,11 +21,9 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const retry = getService('retry'); const toasts = getService('toasts'); const log = getService('log'); - const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); + const alertingApi = getService('alertingApi'); let roleAuthc: RoleCredentials; - let internalReqHeader: InternalRequestHeader; async function refreshRulesList() { const existsClearFilter = await testSubjects.exists('rules-list-clear-filter'); @@ -57,10 +45,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { numAttempts: number; }) { for (let i = 0; i < numAttempts; i++) { - await runRule({ supertestWithoutAuth, roleAuthc, internalReqHeader, ruleId }); + await alertingApi.helpers.runRule({ roleAuthc, ruleId }); await new Promise((resolve) => setTimeout(resolve, intervalMilliseconds)); - await disableRule({ supertest, ruleId }); + await alertingApi.helpers.disableRule({ + roleAuthc, + ruleId, + }); await new Promise((resolve) => setTimeout(resolve, intervalMilliseconds)); await refreshRulesList(); @@ -68,7 +59,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const rulesStatuses = result.map((item: { status: string }) => item.status); if (rulesStatuses.includes('Failed')) return; - await enableRule({ supertest, ruleId }); + await alertingApi.helpers.enableRule({ roleAuthc, ruleId }); } } @@ -84,7 +75,6 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); await svlCommonPage.loginWithPrivilegedRole(); await svlObltNavigation.navigateToLandingPage(); await svlCommonNavigation.sidenav.clickLink({ text: 'Alerts' }); @@ -107,10 +97,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should create an ES Query Rule and display it when consumer is observability', async () => { - const esQuery = await createEsQueryRule({ - supertestWithoutAuth, + const esQuery = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, name: 'ES Query', consumer: 'observability', ruleTypeId: '.es-query', @@ -134,10 +122,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should create an ES Query rule but not display it when consumer is stackAlerts', async () => { - const esQuery = await createEsQueryRule({ - supertestWithoutAuth, + const esQuery = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, name: 'ES Query', consumer: 'stackAlerts', ruleTypeId: '.es-query', @@ -159,7 +145,10 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should create and display an APM latency rule', async () => { - const apmLatency = await createLatencyThresholdRule({ supertest, name: 'Apm latency' }); + const apmLatency = await alertingApi.helpers.createLatencyThresholdRule({ + roleAuthc, + name: 'Apm latency', + }); ruleIdList = [apmLatency.id]; await refreshRulesList(); @@ -169,16 +158,16 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should display rules in alphabetical order', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'b', }); - const rule2 = await createRule({ - supertest, + const rule2 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'c', }); - const rule3 = await createRule({ - supertest, + const rule3 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'a', }); @@ -194,8 +183,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should search for rule', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id]; @@ -215,13 +204,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should update rule list on the search clear button click', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'a', }); - const rule2 = await createRule({ - supertest, + const rule2 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'b', tags: [], }); @@ -266,8 +255,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should search for tags', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'a', tags: ['tag', 'tagtag', 'taggity tag'], }); @@ -289,8 +278,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should display an empty list when search did not return any rules', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id]; @@ -301,8 +290,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should disable single rule', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id]; @@ -329,14 +318,17 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should re-enable single rule', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'a', }); ruleIdList = [rule1.id]; - await disableRule({ supertest, ruleId: rule1.id }); + await alertingApi.helpers.disableRule({ + roleAuthc, + ruleId: rule1.id, + }); await refreshRulesList(); await svlTriggersActionsUI.searchRules(rule1.name); @@ -360,13 +352,13 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should delete single rule', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'a', }); - const rule2 = await createRule({ - supertest, + const rule2 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'b', }); @@ -392,8 +384,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should disable all selection', async () => { - const createdRule1 = await createRule({ - supertest, + const createdRule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [createdRule1.id]; @@ -422,13 +414,16 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should enable all selection', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id]; - await disableRule({ supertest, ruleId: rule1.id }); + await alertingApi.helpers.disableRule({ + roleAuthc, + ruleId: rule1.id, + }); await refreshRulesList(); await svlTriggersActionsUI.searchRules(rule1.name); @@ -445,8 +440,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should render percentile column and cells correctly', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id]; @@ -481,8 +476,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should delete all selection', async () => { - const createdRule1 = await createRule({ - supertest, + const createdRule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [createdRule1.id]; @@ -508,12 +503,12 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it.skip('should filter rules by the status', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); - const failedRule = await createRule({ - supertest, + const failedRule = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id, failedRule.id]; @@ -558,8 +553,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it.skip('should display total rules by status and error banner only when exists rules with status error', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); await refreshRulesList(); @@ -582,8 +577,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { ); expect(alertsErrorBannerWhenNoErrors).toHaveLength(0); - const failedRule = await createRule({ - supertest, + const failedRule = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id, failedRule.id]; @@ -617,8 +612,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it.skip('Expand error in rules table when there is rule with an error associated', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, name: 'a', }); @@ -639,8 +634,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { let expandRulesErrorLink = await find.allByCssSelector('[data-test-subj="expandRulesError"]'); expect(expandRulesErrorLink).toHaveLength(0); - const failedRule = await createRule({ - supertest, + const failedRule = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id, failedRule.id]; @@ -666,12 +661,12 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should filter rules by the rule type', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); - const rule2 = await createLatencyThresholdRule({ - supertest, + const rule2 = await alertingApi.helpers.createLatencyThresholdRule({ + roleAuthc, }); ruleIdList = [rule1.id, rule2.id]; @@ -730,36 +725,36 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }; // Enabled alert - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); - const disabledRule = await createRule({ - supertest, + const disabledRule = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); - await disableRule({ - supertest, + await alertingApi.helpers.disableRule({ + roleAuthc, ruleId: disabledRule.id, }); - const snoozedRule = await createRule({ - supertest, + const snoozedRule = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); - await snoozeRule({ - supertest, + await alertingApi.helpers.snoozeRule({ + roleAuthc, ruleId: snoozedRule.id, }); - const snoozedAndDisabledRule = await createRule({ - supertest, + const snoozedAndDisabledRule = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); - await snoozeRule({ - supertest, + await alertingApi.helpers.snoozeRule({ + roleAuthc, ruleId: snoozedAndDisabledRule.id, }); - await disableRule({ - supertest, + await alertingApi.helpers.disableRule({ + roleAuthc, ruleId: snoozedAndDisabledRule.id, }); @@ -801,28 +796,28 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should filter rules by the tag', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, tags: ['a'], }); - const rule2 = await createRule({ - supertest, + const rule2 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, tags: ['b'], }); - const rule3 = await createRule({ - supertest, + const rule3 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, tags: ['a', 'b'], }); - const rule4 = await createRule({ - supertest, + const rule4 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, tags: ['b', 'c'], }); - const rule5 = await createRule({ - supertest, + const rule5 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, tags: ['c'], }); @@ -864,17 +859,15 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should not prevent rules with action execution capabilities from being edited', async () => { - const action = await createIndexConnector({ - supertestWithoutAuth, + const action = await alertingApi.helpers.createIndexConnector({ roleAuthc, - internalReqHeader, name: 'Index Connector: Alerting API test', indexName: '.alerts-observability.apm.alerts-default', }); expect(action).not.toBe(undefined); - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, actions: [ { group: 'threshold_met', @@ -902,8 +895,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should allow rules to be snoozed using the right side dropdown', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id]; @@ -922,8 +915,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should allow rules to be snoozed indefinitely using the right side dropdown', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id]; @@ -942,14 +935,14 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('should allow snoozed rules to be unsnoozed using the right side dropdown', async () => { - const rule1 = await createRule({ - supertest, + const rule1 = await alertingApi.helpers.createAnomalyRule({ + roleAuthc, }); ruleIdList = [rule1.id]; - await snoozeRule({ - supertest, + await alertingApi.helpers.snoozeRule({ + roleAuthc, ruleId: rule1.id, }); diff --git a/x-pack/test_serverless/functional/test_suites/search/rules/rule_details.ts b/x-pack/test_serverless/functional/test_suites/search/rules/rule_details.ts index 40d57101693bc..00363f21299de 100644 --- a/x-pack/test_serverless/functional/test_suites/search/rules/rule_details.ts +++ b/x-pack/test_serverless/functional/test_suites/search/rules/rule_details.ts @@ -7,13 +7,8 @@ import { expect } from 'expect'; import { v4 as uuidv4 } from 'uuid'; -import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; +import { RoleCredentials } from '../../../../shared/services'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { - createEsQueryRule as createRule, - createSlackConnector, - createIndexConnector, -} from '../../../../api_integration/test_suites/common/alerting/helpers/alerting_api_helper'; export enum RuleNotifyWhen { CHANGE = 'onActionGroupChange', @@ -34,6 +29,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const toasts = getService('toasts'); const comboBox = getService('comboBox'); const config = getService('config'); + const alertingApi = getService('alertingApi'); const openFirstRule = async (ruleName: string) => { await svlTriggersActionsUI.searchRules(ruleName); @@ -66,15 +62,11 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { let ruleIdList: string[]; let connectorIdList: string[]; - const svlCommonApi = getService('svlCommonApi'); const svlUserManager = getService('svlUserManager'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); let roleAuthc: RoleCredentials; - let internalReqHeader: InternalRequestHeader; before(async () => { roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin'); - internalReqHeader = svlCommonApi.getInternalRequestHeader(); await svlCommonPage.loginAsViewer(); }); @@ -88,10 +80,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const RULE_TYPE_ID = '.es-query'; before(async () => { - const rule = await createRule({ - supertestWithoutAuth, + const rule = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, consumer: 'alerts', name: ruleName, ruleTypeId: RULE_TYPE_ID, @@ -261,10 +251,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const RULE_TYPE_ID = '.es-query'; before(async () => { - const rule = await createRule({ - supertestWithoutAuth, + const rule = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, consumer: 'alerts', name: ruleName, ruleTypeId: RULE_TYPE_ID, @@ -369,26 +357,20 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { it('should show and update deleted connectors when there are existing connectors of the same type', async () => { const testRunUuid = uuidv4(); - const connector1 = await createSlackConnector({ - supertestWithoutAuth, + const connector1 = await alertingApi.helpers.createSlackConnector({ roleAuthc, - internalReqHeader, name: `slack-${testRunUuid}-${0}`, }); - const connector2 = await createSlackConnector({ - supertestWithoutAuth, + const connector2 = await alertingApi.helpers.createSlackConnector({ roleAuthc, - internalReqHeader, name: `slack-${testRunUuid}-${1}`, }); connectorIdList = [connector2.id]; - const rule = await createRule({ - supertestWithoutAuth, + const rule = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, consumer: 'alerts', name: testRunUuid, ruleTypeId: RULE_TYPE_ID, @@ -450,18 +432,14 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { it('should show and update deleted connectors when there are no existing connectors of the same type', async () => { const testRunUuid = uuidv4(); - const connector = await createIndexConnector({ - supertestWithoutAuth, + const connector = await alertingApi.helpers.createIndexConnector({ roleAuthc, - internalReqHeader, name: `index-${testRunUuid}-${2}`, indexName: ALERT_ACTION_INDEX, }); - const rule = await createRule({ - supertestWithoutAuth, + const rule = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, consumer: 'alerts', name: testRunUuid, ruleTypeId: RULE_TYPE_ID, @@ -576,26 +554,20 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { const testRunUuid = uuidv4(); const RULE_TYPE_ID = '.es-query'; - const connector1 = await createSlackConnector({ - supertestWithoutAuth, + const connector1 = await alertingApi.helpers.createSlackConnector({ roleAuthc, - internalReqHeader, name: `slack-${testRunUuid}-${0}`, }); - const connector2 = await createSlackConnector({ - supertestWithoutAuth, + const connector2 = await alertingApi.helpers.createSlackConnector({ roleAuthc, - internalReqHeader, name: `slack-${testRunUuid}-${1}`, }); connectorIdList = [connector1.id, connector2.id]; - const rule = await createRule({ - supertestWithoutAuth, + const rule = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, consumer: 'alerts', name: `test-rule-${testRunUuid}`, ruleTypeId: RULE_TYPE_ID, @@ -670,10 +642,8 @@ export default ({ getPageObject, getService }: FtrProviderContext) => { }); it('renders a disabled rule details view in app button', async () => { - const rule = await createRule({ - supertestWithoutAuth, + const rule = await alertingApi.helpers.createEsQueryRule({ roleAuthc, - internalReqHeader, consumer: 'alerts', name: ruleName, ruleTypeId: RULE_TYPE_ID, diff --git a/x-pack/test_serverless/shared/services/alerting_api.ts b/x-pack/test_serverless/shared/services/alerting_api.ts new file mode 100644 index 0000000000000..afed22fbe2c9a --- /dev/null +++ b/x-pack/test_serverless/shared/services/alerting_api.ts @@ -0,0 +1,1032 @@ +/* + * 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 moment from 'moment'; +import type { + AggregationsAggregate, + SearchResponse, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { Client } from '@elastic/elasticsearch'; +import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics'; +import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; +import { v4 as uuidv4 } from 'uuid'; +import type { TryWithRetriesOptions } from '@kbn/ftr-common-functional-services'; +import { RoleCredentials } from '.'; +import type { SloBurnRateRuleParams } from '../../api_integration/services/slo_api'; +import { FtrProviderContext } from '../../functional/ftr_provider_context'; + +interface CreateEsQueryRuleParams { + size: number; + thresholdComparator: string; + threshold: number[]; + timeWindowSize?: number; + timeWindowUnit?: string; + esQuery?: string; + timeField?: string; + searchConfiguration?: unknown; + indexName?: string; + excludeHitsFromPreviousRun?: boolean; + aggType?: string; + aggField?: string; + groupBy?: string; + termField?: string; + termSize?: number; + index?: string[]; +} +const RETRY_COUNT = 10; +const RETRY_DELAY = 1000; + +export function AlertingApiProvider({ getService }: FtrProviderContext) { + const retry = getService('retry'); + const es = getService('es'); + const requestTimeout = 30 * 1000; + const retryTimeout = 120 * 1000; + const logger = getService('log'); + const svlCommonApi = getService('svlCommonApi'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + const generateUniqueKey = () => uuidv4().replace(/-/g, ''); + + const helpers = { + async waitForAlertInIndex({ + esClient, + filter, + indexName, + ruleId, + num = 1, + retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY }, + }: { + esClient: Client; + filter: Date; + indexName: string; + ruleId: string; + num: number; + retryOptions?: TryWithRetriesOptions; + }): Promise>> { + return await retry.tryWithRetries( + `Alerting API - waitForAlertInIndex, retryOptions: ${JSON.stringify(retryOptions)}`, + async () => { + const response = await esClient.search({ + index: indexName, + body: { + query: { + bool: { + must: [ + { + term: { + 'kibana.alert.rule.uuid': ruleId, + }, + }, + { + range: { + '@timestamp': { + gte: filter.getTime().toString(), + }, + }, + }, + ], + }, + }, + }, + }); + if (response.hits.hits.length < num) + throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`); + + return response; + }, + retryOptions + ); + }, + + async waitForDocumentInIndex({ + esClient, + indexName, + ruleId, + num = 1, + sort = 'desc', + retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY }, + }: { + esClient: Client; + indexName: string; + ruleId: string; + num?: number; + sort?: 'asc' | 'desc'; + retryOptions?: TryWithRetriesOptions; + }): Promise { + return await retry.tryWithRetries( + `Alerting API - waitForDocumentInIndex, retryOptions: ${JSON.stringify(retryOptions)}`, + async () => { + const response = await esClient.search({ + index: indexName, + sort: `date:${sort}`, + body: { + query: { + bool: { + must: [ + { + term: { + 'ruleId.keyword': ruleId, + }, + }, + ], + }, + }, + }, + }); + if (response.hits.hits.length < num) { + throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`); + } + return response; + }, + retryOptions + ); + }, + + async createIndexConnector({ + roleAuthc, + name, + indexName, + }: { + roleAuthc: RoleCredentials; + name: string; + indexName: string; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/actions/connector`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + name, + config: { + index: indexName, + refresh: true, + }, + connector_type_id: '.index', + }) + .expect(200); + return body; + }, + + async createSlackConnector({ roleAuthc, name }: { roleAuthc: RoleCredentials; name: string }) { + const { body } = await supertestWithoutAuth + .post(`/api/actions/connector`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + name, + config: {}, + secrets: { + webhookUrl: 'http://test', + }, + connector_type_id: '.slack', + }) + .expect(200); + return body; + }, + + async createEsQueryRule({ + roleAuthc, + name, + ruleTypeId, + params, + actions = [], + tags = [], + schedule, + consumer, + notifyWhen, + enabled = true, + }: { + roleAuthc: RoleCredentials; + ruleTypeId: string; + name: string; + params: CreateEsQueryRuleParams; + consumer: string; + actions?: any[]; + tags?: any[]; + schedule?: { interval: string }; + notifyWhen?: string; + enabled?: boolean; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + enabled, + params, + consumer, + schedule: schedule || { + interval: '1h', + }, + tags, + name, + rule_type_id: ruleTypeId, + actions, + ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), + }) + .expect(200); + return body; + }, + + async createAnomalyRule({ + roleAuthc, + name = generateUniqueKey(), + actions = [], + tags = ['foo', 'bar'], + schedule, + consumer = 'alerts', + notifyWhen, + enabled = true, + ruleTypeId = 'apm.anomaly', + params, + }: { + roleAuthc: RoleCredentials; + name?: string; + consumer?: string; + actions?: any[]; + tags?: any[]; + schedule?: { interval: string }; + notifyWhen?: string; + enabled?: boolean; + ruleTypeId?: string; + params?: any; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + enabled, + params: params || { + anomalySeverityType: 'critical', + anomalyDetectorTypes: ['txLatency'], + environment: 'ENVIRONMENT_ALL', + windowSize: 30, + windowUnit: 'm', + }, + consumer, + schedule: schedule || { + interval: '1m', + }, + tags, + name, + rule_type_id: ruleTypeId, + actions, + ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), + }) + .expect(200); + return body; + }, + + async createLatencyThresholdRule({ + roleAuthc, + name = generateUniqueKey(), + actions = [], + tags = ['foo', 'bar'], + schedule, + consumer = 'apm', + notifyWhen, + enabled = true, + ruleTypeId = 'apm.transaction_duration', + params, + }: { + roleAuthc: RoleCredentials; + name?: string; + consumer?: string; + actions?: any[]; + tags?: any[]; + schedule?: { interval: string }; + notifyWhen?: string; + enabled?: boolean; + ruleTypeId?: string; + params?: any; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + enabled, + params: params || { + aggregationType: 'avg', + environment: 'ENVIRONMENT_ALL', + threshold: 1500, + windowSize: 5, + windowUnit: 'm', + }, + consumer, + schedule: schedule || { + interval: '1m', + }, + tags, + name, + rule_type_id: ruleTypeId, + actions, + ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), + }); + return body; + }, + + async createInventoryRule({ + roleAuthc, + name = generateUniqueKey(), + actions = [], + tags = ['foo', 'bar'], + schedule, + consumer = 'alerts', + notifyWhen, + enabled = true, + ruleTypeId = 'metrics.alert.inventory.threshold', + params, + }: { + roleAuthc: RoleCredentials; + name?: string; + consumer?: string; + actions?: any[]; + tags?: any[]; + schedule?: { interval: string }; + notifyWhen?: string; + enabled?: boolean; + ruleTypeId?: string; + params?: any; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + enabled, + params: params || { + nodeType: 'host', + criteria: [ + { + metric: 'cpu', + comparator: '>', + threshold: [5], + timeSize: 1, + timeUnit: 'm', + customMetric: { + type: 'custom', + id: 'alert-custom-metric', + field: '', + aggregation: 'avg', + }, + }, + ], + sourceId: 'default', + }, + consumer, + schedule: schedule || { + interval: '1m', + }, + tags, + name, + rule_type_id: ruleTypeId, + actions, + ...(notifyWhen ? { notify_when: notifyWhen, throttle: '5m' } : {}), + }) + .expect(200); + return body; + }, + + async disableRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule/${ruleId}/_disable`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(204); + return body; + }, + + async updateEsQueryRule({ + roleAuthc, + ruleId, + updates, + }: { + roleAuthc: RoleCredentials; + ruleId: string; + updates: any; + }) { + const { body: r } = await supertestWithoutAuth + .get(`/api/alerting/rule/${ruleId}`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(200); + const body = await supertestWithoutAuth + .put(`/api/alerting/rule/${ruleId}`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + ...{ + name: r.name, + schedule: r.schedule, + throttle: r.throttle, + tags: r.tags, + params: r.params, + notify_when: r.notifyWhen, + actions: r.actions.map((action: any) => ({ + group: action.group, + params: action.params, + id: action.id, + frequency: action.frequency, + })), + }, + ...updates, + }) + .expect(200); + return body; + }, + + async runRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) { + const response = await supertestWithoutAuth + .post(`/internal/alerting/rule/${ruleId}/_run_soon`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(204); + return response; + }, + + async waitForNumRuleRuns({ + roleAuthc, + numOfRuns, + ruleId, + esClient, + testStart, + retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY }, + }: { + roleAuthc: RoleCredentials; + numOfRuns: number; + ruleId: string; + esClient: Client; + testStart: Date; + retryOptions?: TryWithRetriesOptions; + }) { + for (let i = 0; i < numOfRuns; i++) { + await retry.tryWithRetries( + `Alerting API - waitForNumRuleRuns, retryOptions: ${JSON.stringify(retryOptions)}`, + async () => { + await this.runRule({ roleAuthc, ruleId }); + await this.waiting.waitForExecutionEventLog({ + esClient, + filter: testStart, + ruleId, + num: i + 1, + }); + await this.waiting.waitForAllTasksIdle({ esClient, filter: testStart }); + }, + retryOptions + ); + } + }, + + async muteRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule/${ruleId}/_mute_all`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(204); + return body; + }, + + async enableRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule/${ruleId}/_enable`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(204); + return body; + }, + + async muteAlert({ + roleAuthc, + ruleId, + alertId, + }: { + roleAuthc: RoleCredentials; + ruleId: string; + alertId: string; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule/${ruleId}/alert/${alertId}/_mute`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(204); + return body; + }, + + async unmuteRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule/${ruleId}/_unmute_all`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .expect(204); + return body; + }, + + async snoozeRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) { + const { body } = await supertestWithoutAuth + .post(`/internal/alerting/rule/${ruleId}/_snooze`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + snooze_schedule: { + duration: 100000000, + rRule: { + count: 1, + dtstart: moment().format(), + tzid: 'UTC', + }, + }, + }) + .expect(204); + return body; + }, + + async findRule({ roleAuthc, ruleId }: { roleAuthc: RoleCredentials; ruleId: string }) { + if (!ruleId) { + throw new Error(`'ruleId' is undefined`); + } + const response = await supertestWithoutAuth + .get(`/api/alerting/rule/${ruleId}`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader); + return response.body || {}; + }, + + waiting: { + async waitForDocumentInIndex({ + esClient, + indexName, + ruleId, + num = 1, + sort = 'desc', + retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY }, + }: { + esClient: Client; + indexName: string; + ruleId: string; + num?: number; + sort?: 'asc' | 'desc'; + retryOptions?: TryWithRetriesOptions; + }): Promise { + return await retry.tryWithRetries( + `Alerting API - waiting.waitForDocumentInIndex, retryOptions: ${JSON.stringify( + retryOptions + )}`, + async () => { + const response = await esClient.search({ + index: indexName, + sort: `date:${sort}`, + body: { + query: { + bool: { + must: [ + { + term: { + 'ruleId.keyword': ruleId, + }, + }, + ], + }, + }, + }, + }); + if (response.hits.hits.length < num) { + throw new Error(`Only found ${response.hits.hits.length} / ${num} documents`); + } + return response; + }, + retryOptions + ); + }, + + async getDocumentsInIndex({ + esClient, + indexName, + ruleId, + }: { + esClient: Client; + indexName: string; + ruleId: string; + }): Promise { + return await esClient.search({ + index: indexName, + body: { + query: { + bool: { + must: [ + { + term: { + 'ruleId.keyword': ruleId, + }, + }, + ], + }, + }, + }, + }); + }, + + async waitForAllTasksIdle({ + esClient, + filter, + retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY }, + }: { + esClient: Client; + filter: Date; + retryOptions?: TryWithRetriesOptions; + }): Promise { + return await retry.tryWithRetries( + `Alerting API - waiting.waitForAllTasksIdle, retryOptions: ${JSON.stringify( + retryOptions + )}`, + async () => { + const response = await esClient.search({ + index: '.kibana_task_manager', + body: { + query: { + bool: { + must: [ + { + terms: { + 'task.scope': ['actions', 'alerting'], + }, + }, + { + range: { + 'task.scheduledAt': { + gte: filter.getTime().toString(), + }, + }, + }, + ], + must_not: [ + { + term: { + 'task.status': 'idle', + }, + }, + ], + }, + }, + }, + }); + if (response.hits.hits.length !== 0) { + throw new Error(`Expected 0 hits but received ${response.hits.hits.length}`); + } + return response; + }, + retryOptions + ); + }, + + async waitForExecutionEventLog({ + esClient, + filter, + ruleId, + num = 1, + retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY }, + }: { + esClient: Client; + filter: Date; + ruleId: string; + num?: number; + retryOptions?: TryWithRetriesOptions; + }): Promise { + return await retry.tryWithRetries( + `Alerting API - waiting.waitForExecutionEventLog, retryOptions: ${JSON.stringify( + retryOptions + )}`, + async () => { + const response = await esClient.search({ + index: '.kibana-event-log*', + body: { + query: { + bool: { + filter: [ + { + term: { + 'rule.id': { + value: ruleId, + }, + }, + }, + { + term: { + 'event.provider': { + value: 'alerting', + }, + }, + }, + { + term: { + 'event.action': 'execute', + }, + }, + { + range: { + '@timestamp': { + gte: filter.getTime().toString(), + }, + }, + }, + ], + }, + }, + }, + }); + if (response.hits.hits.length < num) { + throw new Error('No hits found'); + } + return response; + }, + retryOptions + ); + }, + + async createIndex({ esClient, indexName }: { esClient: Client; indexName: string }) { + return await esClient.indices.create( + { + index: indexName, + body: {}, + }, + { meta: true } + ); + }, + + async waitForAllTasks({ + esClient, + filter, + taskType, + attempts, + retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY }, + }: { + esClient: Client; + filter: Date; + taskType: string; + attempts: number; + retryOptions?: TryWithRetriesOptions; + }): Promise { + return await retry.tryWithRetries( + `Alerting API - waiting.waitForAllTasks, retryOptions: ${JSON.stringify(retryOptions)}`, + async () => { + const response = await esClient.search({ + index: '.kibana_task_manager', + body: { + query: { + bool: { + must: [ + { + term: { + 'task.status': 'idle', + }, + }, + { + term: { + 'task.attempts': attempts, + }, + }, + { + terms: { + 'task.scope': ['actions', 'alerting'], + }, + }, + { + term: { + 'task.taskType': taskType, + }, + }, + { + range: { + 'task.scheduledAt': { + gte: filter.getTime().toString(), + }, + }, + }, + ], + }, + }, + }, + }); + if (response.hits.hits.length === 0) { + throw new Error('No hits found'); + } + return response; + }, + retryOptions + ); + }, + + async waitForDisabled({ + esClient, + ruleId, + filter, + retryOptions = { retryCount: RETRY_COUNT, retryDelay: RETRY_DELAY }, + }: { + esClient: Client; + ruleId: string; + filter: Date; + retryOptions?: TryWithRetriesOptions; + }): Promise { + return await retry.tryWithRetries( + `Alerting API - waiting.waitForDisabled, retryOptions: ${JSON.stringify(retryOptions)}`, + async () => { + const response = await esClient.search({ + index: '.kibana_task_manager', + body: { + query: { + bool: { + must: [ + { + term: { + 'task.id': `task:${ruleId}`, + }, + }, + { + terms: { + 'task.scope': ['actions', 'alerting'], + }, + }, + { + range: { + 'task.scheduledAt': { + gte: filter.getTime().toString(), + }, + }, + }, + { + term: { + 'task.enabled': true, + }, + }, + ], + }, + }, + }, + }); + if (response.hits.hits.length !== 0) { + throw new Error(`Expected 0 hits but received ${response.hits.hits.length}`); + } + return response; + }, + retryOptions + ); + }, + }, + }; + + return { + helpers, + + async waitForRuleStatus({ + roleAuthc, + ruleId, + expectedStatus, + }: { + roleAuthc: RoleCredentials; + ruleId: string; + expectedStatus: string; + }) { + if (!ruleId) { + throw new Error(`'ruleId' is undefined`); + } + return await retry.tryForTime(retryTimeout, async () => { + const response = await supertestWithoutAuth + .get(`/api/alerting/rule/${ruleId}`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .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 at least ${docCountTarget}.`); + if (!response.hits.total || (response.hits.total as number) < 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({ + roleAuthc, + name, + indexName, + }: { + roleAuthc: RoleCredentials; + name: string; + indexName: string; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/actions/connector`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + name, + config: { + index: indexName, + refresh: true, + }, + connector_type_id: '.index', + }); + return body.id as string; + }, + + async createRule({ + roleAuthc, + name, + ruleTypeId, + params, + actions = [], + tags = [], + schedule, + consumer, + }: { + roleAuthc: RoleCredentials; + ruleTypeId: string; + name: string; + params: MetricThresholdParams | ThresholdParams | SloBurnRateRuleParams; + actions?: any[]; + tags?: any[]; + schedule?: { interval: string }; + consumer: string; + }) { + const { body } = await supertestWithoutAuth + .post(`/api/alerting/rule`) + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader) + .send({ + params, + consumer, + schedule: schedule || { + interval: '5m', + }, + tags, + name, + rule_type_id: ruleTypeId, + actions, + }); + return body; + }, + + async findRule(roleAuthc: RoleCredentials, ruleId: string) { + if (!ruleId) { + throw new Error(`'ruleId' is undefined`); + } + const response = await supertestWithoutAuth + .get('/api/alerting/rules/_find') + .set(svlCommonApi.getInternalRequestHeader()) + .set(roleAuthc.apiKeyHeader); + return response.body.data.find((obj: any) => obj.id === ruleId); + }, + }; +} diff --git a/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts b/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts index 97a5963bd9e3b..2272890e52eb4 100644 --- a/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts +++ b/x-pack/test_serverless/shared/services/deployment_agnostic_services.ts @@ -8,7 +8,7 @@ import _ from 'lodash'; import { services as apiIntegrationServices } from '@kbn/test-suites-xpack/api_integration/services'; - +import { AlertingApiProvider } from './alerting_api'; /* * Some FTR services from api integration stateful tests are compatible with serverless environment * While adding a new one, make sure to verify that it works on both Kibana CI and MKI @@ -35,4 +35,5 @@ const deploymentAgnosticApiIntegrationServices = _.pick(apiIntegrationServices, export const services = { // deployment agnostic FTR services ...deploymentAgnosticApiIntegrationServices, + alertingApi: AlertingApiProvider, };