Skip to content

Commit

Permalink
Migrate APM alerts test
Browse files Browse the repository at this point in the history
  • Loading branch information
crespocarlos committed Nov 6, 2024
1 parent 76262fa commit cb201c6
Show file tree
Hide file tree
Showing 19 changed files with 1,607 additions and 1,547 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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 { AnomalyDetectorType } from '@kbn/apm-plugin/common/anomaly_detection/apm_ml_detectors';
import { ApmRuleType } from '@kbn/rule-data-utils';
import { apm, timerange } from '@kbn/apm-synthtrace-client';
import expect from '@kbn/expect';
import { range } from 'lodash';
import { ML_ANOMALY_SEVERITY } from '@kbn/ml-anomaly-utils/anomaly_severity';
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import type { SupertestWithRoleScopeType } from '../../../../services';
import { createAndRunApmMlJobs } from '../../../../../../apm_api_integration/common/utils/create_and_run_apm_ml_jobs';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import { createApmRule } from './helpers/alerting_api_helper';
import { waitForActiveRule } from './helpers/wait_for_active_rule';
import { waitForAlertsForRule } from './helpers/wait_for_alerts_for_rule';
import { cleanupRuleAndAlertState } from './helpers/cleanup_rule_and_alert_state';

export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const roleScopedSupertest = getService('roleScopedSupertest');
const ml = getService('mlApi');
const es = getService('es');
const logger = getService('log');
const synthtrace = getService('synthtrace');

describe('fetching service anomalies with a trial license', () => {
const start = moment().subtract(2, 'days').valueOf();
const end = moment().valueOf();

const spikeStart = moment().subtract(2, 'hours').valueOf();
const spikeEnd = moment().subtract(1, 'hours').valueOf();

let apmSynthtraceEsClient: ApmSynthtraceEsClient;
let supertest: SupertestWithRoleScopeType;

before(async () => {
apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();
supertest = await roleScopedSupertest.getSupertestWithRoleScope('admin');

const serviceA = apm
.service({ name: 'foo', environment: 'production', agentName: 'java' })
.instance('a');

const events = timerange(start, end)
.interval('1m')
.rate(1)
.generator((timestamp) => {
const isInSpike = timestamp >= spikeStart && timestamp < spikeEnd;
const count = isInSpike ? 4 : 1;
const duration = isInSpike ? 5000 : 100;
const outcome = isInSpike ? 'failure' : 'success';

return [
...range(0, count).flatMap((_) =>
serviceA
.transaction({ transactionName: 'tx' })
.timestamp(timestamp)
.duration(duration)
.outcome(outcome)
),
];
});

await apmSynthtraceEsClient.index(events);

await createAndRunApmMlJobs({ es, ml, environments: ['production'], logger });
});

after(async () => {
await cleanup();
});

async function cleanup() {
await apmSynthtraceEsClient.clean();
await cleanupRuleAndAlertState({ es, supertest, logger });
await ml.cleanMlIndices();
await supertest.destroy();
}

describe('with ml jobs', () => {
let createdRule: Awaited<ReturnType<typeof createApmRule>>;

before(async () => {
createdRule = await createApmRule({
supertest,
name: 'Latency anomaly | service-a',
params: {
environment: 'production',
windowSize: 5,
windowUnit: 'h',
anomalySeverityType: ML_ANOMALY_SEVERITY.WARNING,
anomalyDetectorTypes: [AnomalyDetectorType.txLatency],
},
ruleTypeId: ApmRuleType.Anomaly,
});
});
it('checks if alert is active', async () => {
const ruleStatus = await waitForActiveRule({
ruleId: createdRule.id,
supertest,
logger,
});
expect(ruleStatus).to.be('active');
});

it('produces an alert with the correct reason', async () => {
const alerts = await waitForAlertsForRule({ es, ruleId: createdRule.id });

const score = alerts[0]['kibana.alert.evaluation.value'];
expect(alerts[0]['kibana.alert.reason']).to.be(
`warning latency anomaly with a score of ${score}, was detected in the last 5 hrs for foo.`
);
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import { errorCountActionVariables } from '@kbn/apm-plugin/server/routes/alerts/
import { apm, timerange } from '@kbn/apm-synthtrace-client';
import expect from '@kbn/expect';
import { omit } from 'lodash';
import { FtrProviderContext } from '../../common/ftr_provider_context';
import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
import type { SupertestWithRoleScopeType } from '../../../../services';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import {
createApmRule,
fetchServiceInventoryAlertCounts,
Expand All @@ -24,15 +26,16 @@ import { waitForAlertsForRule } from './helpers/wait_for_alerts_for_rule';
import { waitForIndexConnectorResults } from './helpers/wait_for_index_connector_results';
import { waitForActiveRule } from './helpers/wait_for_active_rule';

export default function ApiTest({ getService }: FtrProviderContext) {
const registry = getService('registry');
const supertest = getService('supertest');
export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const roleScopedSupertest = getService('roleScopedSupertest');
const es = getService('es');
const logger = getService('log');
const apmApiClient = getService('apmApiClient');
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');
const apmApiClient = getService('apmApi');
const synthtrace = getService('synthtrace');

describe('error count threshold alert', () => {
let apmSynthtraceEsClient: ApmSynthtraceEsClient;

registry.when('error count threshold alert', { config: 'basic', archives: [] }, () => {
const javaErrorMessage = 'a java error';
const phpErrorMessage = 'a php error';

Expand All @@ -50,7 +53,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
],
};

before(() => {
before(async () => {
const opbeansJava = apm
.service({ name: 'opbeans-java', environment: 'production', agentName: 'java' })
.instance('instance');
Expand Down Expand Up @@ -95,6 +98,8 @@ export default function ApiTest({ getService }: FtrProviderContext) {
];
});

apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();

return Promise.all([
apmSynthtraceEsClient.index(events),
apmSynthtraceEsClient.index(phpEvents),
Expand All @@ -107,8 +112,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let ruleId: string;
let alerts: ApmAlertFields[];
let actionId: string;
let supertest: SupertestWithRoleScopeType;

before(async () => {
supertest = await roleScopedSupertest.getSupertestWithRoleScope('admin');
actionId = await createIndexConnector({ supertest, name: 'Transation error count' });
const indexAction = getIndexAction({
actionId,
Expand Down Expand Up @@ -253,8 +260,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {

describe('create rule with kql filter for opbeans-php', () => {
let ruleId: string;
let supertest: SupertestWithRoleScopeType;

before(async () => {
supertest = await roleScopedSupertest.getSupertestWithRoleScope('admin');

const createdRule = await createApmRule({
supertest,
ruleTypeId: ApmRuleType.ErrorCount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
import { Client, errors } from '@elastic/elasticsearch';
import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common';
import pRetry from 'p-retry';
import type { Agent as SuperTestAgent } from 'supertest';
import { ApmRuleType } from '@kbn/rule-data-utils';
import { ApmRuleParamsType } from '@kbn/apm-plugin/common/rules/schema';
import { ApmDocumentType } from '@kbn/apm-plugin/common/document_type';
import { RollupInterval } from '@kbn/apm-plugin/common/rollup';
import { ObservabilityApmAlert } from '@kbn/alerts-as-data-utils';
import { ApmApiClient } from '../../../common/config';
import type { ApmApiClient } from '../../../../../services/apm_api';
import type { SupertestWithRoleScopeType } from '../../../../../services';

export const APM_ALERTS_INDEX = '.alerts-observability.apm.alerts-*';
export const APM_ACTION_VARIABLE_INDEX = 'apm-index-connector-test';
Expand All @@ -26,7 +26,7 @@ export async function createApmRule<T extends ApmRuleType>({
params,
actions = [],
}: {
supertest: SuperTestAgent;
supertest: SupertestWithRoleScopeType;
ruleTypeId: T;
name: string;
params: ApmRuleParamsType[T];
Expand Down Expand Up @@ -111,7 +111,7 @@ export async function runRuleSoon({
supertest,
}: {
ruleId: string;
supertest: SuperTestAgent;
supertest: SupertestWithRoleScopeType;
}): Promise<Record<string, any>> {
return pRetry(
async () => {
Expand Down Expand Up @@ -143,13 +143,13 @@ export async function deleteRuleById({
supertest,
ruleId,
}: {
supertest: SuperTestAgent;
supertest: SupertestWithRoleScopeType;
ruleId: string;
}) {
await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo');
}

export async function deleteApmRules(supertest: SuperTestAgent) {
export async function deleteApmRules(supertest: SupertestWithRoleScopeType) {
const res = await supertest.get(
`/api/alerting/rules/_find?filter=alert.attributes.consumer:apm&per_page=10000`
);
Expand Down Expand Up @@ -180,20 +180,17 @@ export async function createIndexConnector({
supertest,
name,
}: {
supertest: SuperTestAgent;
supertest: SupertestWithRoleScopeType;
name: string;
}) {
const { body } = await supertest
.post(`/api/actions/connector`)
.set('kbn-xsrf', 'foo')
.send({
name,
config: {
index: APM_ACTION_VARIABLE_INDEX,
refresh: true,
},
connector_type_id: '.index',
});
const { body } = await supertest.post(`/api/actions/connector`).send({
name,
config: {
index: APM_ACTION_VARIABLE_INDEX,
refresh: true,
},
connector_type_id: '.index',
});

return body.id as string;
}
Expand Down Expand Up @@ -228,7 +225,7 @@ export async function deleteAllActionConnectors({
supertest,
es,
}: {
supertest: SuperTestAgent;
supertest: SupertestWithRoleScopeType;
es: Client;
}): Promise<any> {
const res = await supertest.get(`/api/actions/connectors`);
Expand All @@ -245,10 +242,10 @@ async function deleteActionConnector({
supertest,
actionId,
}: {
supertest: SuperTestAgent;
supertest: SupertestWithRoleScopeType;
actionId: string;
}) {
return supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo');
return supertest.delete(`/api/actions/connector/${actionId}`);
}

export async function deleteActionConnectorIndex(es: Client) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { Client } from '@elastic/elasticsearch';
import { ToolingLog } from '@kbn/tooling-log';
import type { Agent as SuperTestAgent } from 'supertest';
import type { SupertestWithRoleScopeType } from '../../../../../services';
import {
clearKibanaApmEventLog,
deleteApmRules,
Expand All @@ -22,7 +22,7 @@ export async function cleanupRuleAndAlertState({
logger,
}: {
es: Client;
supertest: SuperTestAgent;
supertest: SupertestWithRoleScopeType;
logger: ToolingLog;
}) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { ToolingLog } from '@kbn/tooling-log';
import pRetry from 'p-retry';
import type SuperTest from 'supertest';
import type { SupertestWithRoleScopeType } from '../../../../../services';

const RETRIES_COUNT = 10;

Expand All @@ -17,7 +17,7 @@ export async function waitForActiveRule({
logger,
}: {
ruleId: string;
supertest: SuperTest.Agent;
supertest: SupertestWithRoleScopeType;
logger?: ToolingLog;
}): Promise<Record<string, any>> {
return pRetry(
Expand Down
Loading

0 comments on commit cb201c6

Please sign in to comment.