Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[APM] Migrate apm alerts tests to deployment agnostic #199097

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
*/
import datemath from '@elastic/datemath';
import { ElasticsearchClient, Logger } from '@kbn/core/server';
import { rangeQuery, ScopedAnnotationsClient } from '@kbn/observability-plugin/server';
import { rangeQuery, ScopedAnnotationsClient, termsQuery } from '@kbn/observability-plugin/server';
import {
ALERT_RULE_PRODUCER,
ALERT_STATUS,
ALERT_STATUS_ACTIVE,
} from '@kbn/rule-registry-plugin/common/technical_rule_data_field_names';
import * as t from 'io-ts';
import { observabilityFeatureId } from '@kbn/observability-shared-plugin/common';
import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values';
import { Environment } from '../../../../common/environment_rt';
import { SERVICE_NAME } from '../../../../common/es_fields/apm';
Expand Down Expand Up @@ -139,7 +140,7 @@ export async function getApmServiceSummary({
query: {
bool: {
filter: [
...termQuery(ALERT_RULE_PRODUCER, 'apm'),
...termsQuery(ALERT_RULE_PRODUCER, 'apm', observabilityFeatureId),
crespocarlos marked this conversation as resolved.
Show resolved Hide resolved
...termQuery(ALERT_STATUS, ALERT_STATUS_ACTIVE),
...rangeQuery(start, end),
...termQuery(SERVICE_NAME, serviceName),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
* 2.0.
*/

import { kqlQuery } from '@kbn/observability-plugin/server';
import { ALERT_RULE_PRODUCER, ALERT_STATUS } from '@kbn/rule-data-utils';
import { kqlQuery, termQuery, termsQuery } from '@kbn/observability-plugin/server';
import { ALERT_RULE_PRODUCER, ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils';
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { Logger } from '@kbn/core/server';
import { observabilityFeatureId } from '@kbn/observability-shared-plugin/common';
import { ApmPluginRequestHandlerContext } from '../typings';
import { SavedServiceGroup } from '../../../common/service_groups';
import { ApmAlertsClient } from '../../lib/helpers/get_apm_alerts_client';
Expand Down Expand Up @@ -42,8 +43,8 @@ export async function getServiceGroupAlerts({
query: {
bool: {
filter: [
{ term: { [ALERT_RULE_PRODUCER]: 'apm' } },
{ term: { [ALERT_STATUS]: 'active' } },
...termsQuery(ALERT_RULE_PRODUCER, 'apm', observabilityFeatureId),
...termQuery(ALERT_STATUS, ALERT_STATUS_ACTIVE),
],
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@
* 2.0.
*/

import { kqlQuery, termQuery, rangeQuery, wildcardQuery } from '@kbn/observability-plugin/server';
import {
kqlQuery,
termQuery,
rangeQuery,
wildcardQuery,
termsQuery,
} from '@kbn/observability-plugin/server';
import {
ALERT_RULE_PRODUCER,
ALERT_STATUS,
ALERT_STATUS_ACTIVE,
ALERT_RULE_PARAMETERS,
} from '@kbn/rule-data-utils';
import { observabilityFeatureId } from '@kbn/observability-shared-plugin/common';
import { SERVICE_NAME, TRANSACTION_NAME, TRANSACTION_TYPE } from '../../../common/es_fields/apm';
import { LatencyAggregationType } from '../../../common/latency_aggregation_types';
import { AggregationType } from '../../../common/rules/apm_rule_types';
Expand Down Expand Up @@ -59,7 +66,7 @@ export async function getServiceTransactionGroupsAlerts({
query: {
bool: {
filter: [
...termQuery(ALERT_RULE_PRODUCER, 'apm'),
...termsQuery(ALERT_RULE_PRODUCER, 'apm', observabilityFeatureId),
crespocarlos marked this conversation as resolved.
Show resolved Hide resolved
...termQuery(ALERT_STATUS, ALERT_STATUS_ACTIVE),
...rangeQuery(start, end),
...kqlQuery(kuery),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,20 @@
* 2.0.
*/

import { kqlQuery, termQuery, rangeQuery, wildcardQuery } from '@kbn/observability-plugin/server';
import {
kqlQuery,
termQuery,
rangeQuery,
wildcardQuery,
termsQuery,
} from '@kbn/observability-plugin/server';
import {
ALERT_RULE_PRODUCER,
ALERT_STATUS,
ALERT_STATUS_ACTIVE,
ALERT_UUID,
} from '@kbn/rule-data-utils';
import { observabilityFeatureId } from '@kbn/observability-shared-plugin/common';
import { SERVICE_NAME } from '../../../../common/es_fields/apm';
import { ServiceGroup } from '../../../../common/service_groups';
import { ApmAlertsClient } from '../../../lib/helpers/get_apm_alerts_client';
Expand Down Expand Up @@ -51,7 +58,7 @@ export async function getServicesAlerts({
query: {
bool: {
filter: [
...termQuery(ALERT_RULE_PRODUCER, 'apm'),
...termsQuery(ALERT_RULE_PRODUCER, 'apm', observabilityFeatureId),
...termQuery(ALERT_STATUS, ALERT_STATUS_ACTIVE),
...rangeQuery(start, end),
...kqlQuery(kuery),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
APIClientRequestParamsOf<'GET /internal/apm/get_agents_per_service'>['params']
>
) {
return await apmApiClient.readUser({
return apmApiClient.readUser({
wayneseymour marked this conversation as resolved.
Show resolved Hide resolved
endpoint: 'GET /internal/apm/get_agents_per_service',
params: {
query: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderCon
const unlistedAgentName = 'unlistedAgent';

async function callApi() {
return await apmApiClient.readUser({
wayneseymour marked this conversation as resolved.
Show resolved Hide resolved
return apmApiClient.readUser({
endpoint: 'GET /internal/apm/get_latest_agent_versions',
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,30 @@ 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 { RoleCredentials, SupertestWithRoleScopeType } from '../../../../services';
import type { DeploymentAgnosticFtrProviderContext } from '../../../../ftr_provider_context';
import {
createApmRule,
fetchServiceInventoryAlertCounts,
fetchServiceTabAlertCount,
ApmAlertFields,
createIndexConnector,
getIndexAction,
} from './helpers/alerting_api_helper';
import { cleanupRuleAndAlertState } from './helpers/cleanup_rule_and_alert_state';
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');
const es = getService('es');
const logger = getService('log');
const apmApiClient = getService('apmApiClient');
const apmSynthtraceEsClient = getService('apmSynthtraceEsClient');

registry.when('error count threshold alert', { config: 'basic', archives: [] }, () => {
APM_ACTION_VARIABLE_INDEX,
APM_ALERTS_INDEX,
} from './helpers/alerting_helper';

export default function ApiTest({ getService }: DeploymentAgnosticFtrProviderContext) {
const roleScopedSupertest = getService('roleScopedSupertest');
const apmApiClient = getService('apmApi');
const synthtrace = getService('synthtrace');
const alertingApi = getService('alertingApi');
const samlAuth = getService('samlAuth');

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

const javaErrorMessage = 'a java error';
const phpErrorMessage = 'a php error';

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

before(() => {
before(async () => {
supertestWithRoleScope = await roleScopedSupertest.getSupertestWithRoleScope('viewer', {
withInternalHeaders: true,
useCookieHeader: true,
crespocarlos marked this conversation as resolved.
Show resolved Hide resolved
});

roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin');
wayneseymour marked this conversation as resolved.
Show resolved Hide resolved

const opbeansJava = apm
.service({ name: 'opbeans-java', environment: 'production', agentName: 'java' })
.instance('instance');
Expand Down Expand Up @@ -95,53 +103,97 @@ export default function ApiTest({ getService }: FtrProviderContext) {
];
});

apmSynthtraceEsClient = await synthtrace.createApmSynthtraceEsClient();

return Promise.all([
apmSynthtraceEsClient.index(events),
apmSynthtraceEsClient.index(phpEvents),
]);
});

after(() => apmSynthtraceEsClient.clean());
after(async () => {
await apmSynthtraceEsClient.clean();
await supertestWithRoleScope.destroy();
await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc);
});

describe('create rule without kql filter', () => {
let ruleId: string;
let alerts: ApmAlertFields[];
let actionId: string;

before(async () => {
actionId = await createIndexConnector({ supertest, name: 'Transation error count' });
actionId = await alertingApi.createIndexConnector({
name: 'Transation error count',
indexName: APM_ACTION_VARIABLE_INDEX,
roleAuthc,
});

const indexAction = getIndexAction({
actionId,
actionVariables: errorCountActionVariables,
});
const createdRule = await createApmRule({
supertest,

const createdRule = await alertingApi.createRule({
ruleTypeId: ApmRuleType.ErrorCount,
name: 'Apm error count without kql query',
consumer: 'apm',
schedule: {
interval: '1m',
},
tags: ['apm'],
params: {
...ruleParams,
},
actions: [indexAction],
roleAuthc,
});

ruleId = createdRule.id;
alerts = await waitForAlertsForRule({ es, ruleId, minimumAlertCount: 2 });
alerts = (
await alertingApi.waitForDocumentInIndex({
indexName: APM_ALERTS_INDEX,
ruleId,
docCountTarget: 2,
})
).hits.hits.map((hit) => hit._source) as ApmAlertFields[];
});

after(async () => {
await cleanupRuleAndAlertState({ es, supertest, logger });
});
after(() =>
alertingApi.cleanUpAlerts({
roleAuthc,
ruleId,
alertIndexName: APM_ALERTS_INDEX,
connectorIndexName: APM_ACTION_VARIABLE_INDEX,
consumer: 'apm',
})
);

it('checks if rule is active', async () => {
const ruleStatus = await waitForActiveRule({ ruleId, supertest });
const ruleStatus = await alertingApi.waitForRuleStatus({
ruleId,
roleAuthc,
expectedStatus: 'active',
});
expect(ruleStatus).to.be('active');
});

describe('action variables', () => {
let results: Array<Record<string, string>>;

before(async () => {
results = await waitForIndexConnectorResults({ es, minCount: 2 });
await alertingApi.waitForRuleStatus({
ruleId,
roleAuthc,
expectedStatus: 'active',
});

results = (
await alertingApi.waitForDocumentInIndex({
indexName: APM_ACTION_VARIABLE_INDEX,
docCountTarget: 2,
})
).hits.hits.map((hit) => hit._source) as Array<Record<string, string>>;
});

it('produces a index action document for each service', async () => {
Expand All @@ -151,6 +203,15 @@ export default function ApiTest({ getService }: FtrProviderContext) {
]);
});

it('checks if rule is active', async () => {
const ruleStatus = await alertingApi.waitForRuleStatus({
ruleId,
roleAuthc,
expectedStatus: 'active',
});
expect(ruleStatus).to.be('active');
});

it('has the right keys', async () => {
const phpEntry = results.find((result) => result.serviceName === 'opbeans-php')!;
expect(Object.keys(phpEntry).sort()).to.eql([
Expand All @@ -170,7 +231,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {

it('has the right values', () => {
const phpEntry = results.find((result) => result.serviceName === 'opbeans-php')!;
expect(omit(phpEntry, 'alertDetailsUrl')).to.eql({
expect(omit(phpEntry, 'alertDetailsUrl', 'viewInAppUrl')).to.eql({
environment: 'production',
interval: '1 hr',
reason:
Expand All @@ -181,9 +242,12 @@ export default function ApiTest({ getService }: FtrProviderContext) {
errorGroupingName: 'a php error',
threshold: '1',
triggerValue: '30',
viewInAppUrl:
'http://mockedPublicBaseUrl/app/apm/services/opbeans-php/errors?environment=production',
});

const url = new URL(phpEntry.viewInAppUrl);

expect(url.pathname).to.equal('/app/apm/services/opbeans-php/errors');
expect(url.searchParams.get('environment')).to.equal('production');
});
});

Expand Down Expand Up @@ -255,30 +319,48 @@ export default function ApiTest({ getService }: FtrProviderContext) {
let ruleId: string;

before(async () => {
const createdRule = await createApmRule({
supertest,
const createdRule = await alertingApi.createRule({
ruleTypeId: ApmRuleType.ErrorCount,
name: 'Apm error count with kql query',
consumer: 'apm',
schedule: {
interval: '1m',
},
tags: ['apm'],
params: {
...ruleParams,
searchConfiguration: {
query: {
query: 'service.name: opbeans-php',
language: 'kuery',
},
},
...ruleParams,
},
actions: [],
roleAuthc,
});

ruleId = createdRule.id;
});

after(async () => {
await cleanupRuleAndAlertState({ es, supertest, logger });
});
after(() =>
alertingApi.cleanUpAlerts({
roleAuthc,
ruleId,
alertIndexName: APM_ALERTS_INDEX,
connectorIndexName: APM_ACTION_VARIABLE_INDEX,
consumer: 'apm',
})
);

it('produces one alert for the opbeans-php service', async () => {
const alerts = await waitForAlertsForRule({ es, ruleId });
const alerts = (
await alertingApi.waitForDocumentInIndex({
indexName: APM_ALERTS_INDEX,
ruleId,
})
).hits.hits.map((hit) => hit._source) as ApmAlertFields[];

expect(alerts[0]['kibana.alert.reason']).to.be(
'Error count is 30 in the last 1 hr for service: opbeans-php, env: production, name: tx-php, error key: 000000000000000000000a php error, error name: a php error. Alert when > 1.'
);
Expand Down
Loading