mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* [Actionable Observability] Rewrite APM registry rules for Observability * removing apm's rule registry test * Moving everything under observability_api_integration * removing old observability directory under api_integrations since it's not being used * removing observability from api_integration tests * renaming file * moving test to x-pack/test/rule_registry * Adding error handing to cleanupTargetIndices Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Chris Cowan <chris@elastic.co>
This commit is contained in:
parent
ad3660f3ac
commit
99f26c912e
19 changed files with 834 additions and 591 deletions
|
@ -45,6 +45,7 @@ const onlyNotInCoverageTests = [
|
|||
require.resolve('../test/plugin_api_integration/config.ts'),
|
||||
require.resolve('../test/rule_registry/security_and_spaces/config_basic.ts'),
|
||||
require.resolve('../test/rule_registry/security_and_spaces/config_trial.ts'),
|
||||
require.resolve('../test/rule_registry/spaces_only/config_basic.ts'),
|
||||
require.resolve('../test/rule_registry/spaces_only/config_trial.ts'),
|
||||
require.resolve('../test/security_api_integration/saml.config.ts'),
|
||||
require.resolve('../test/security_api_integration/session_idle.config.ts'),
|
||||
|
|
|
@ -1,14 +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 { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function observabilityApiIntegrationTests({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Observability specs', () => {
|
||||
loadTestFile(require.resolve('./annotations'));
|
||||
});
|
||||
}
|
|
@ -1,576 +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 expect from '@kbn/expect';
|
||||
import {
|
||||
ALERT_DURATION,
|
||||
ALERT_END,
|
||||
ALERT_RULE_UUID,
|
||||
ALERT_START,
|
||||
ALERT_STATUS,
|
||||
ALERT_UUID,
|
||||
EVENT_KIND,
|
||||
VERSION,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { merge, omit } from 'lodash';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
|
||||
interface Alert {
|
||||
schedule: {
|
||||
interval: string;
|
||||
};
|
||||
updatedAt: string;
|
||||
executionStatus: {
|
||||
lastExecutionDate: string;
|
||||
status: string;
|
||||
};
|
||||
updatedBy: string;
|
||||
id: string;
|
||||
params: Record<string, unknown>;
|
||||
scheduledTaskId: string;
|
||||
}
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const registry = getService('registry');
|
||||
const supertest = getService('legacySupertestAsApmWriteUser');
|
||||
const es = getService('es');
|
||||
|
||||
const MAX_POLLS = 10;
|
||||
const BULK_INDEX_DELAY = 1000;
|
||||
const INDEXING_DELAY = 5000;
|
||||
|
||||
const getAlertsTargetIndicesUrl =
|
||||
'/api/observability/rules/alerts/dynamic_index_pattern?namespace=default®istrationContexts=observability.apm®istrationContexts=';
|
||||
|
||||
const getAlertsTargetIndices = async () =>
|
||||
supertest.get(getAlertsTargetIndicesUrl).send().set('kbn-xsrf', 'foo');
|
||||
const APM_METRIC_INDEX_NAME = 'apm-8.0.0-transaction';
|
||||
|
||||
const createTransactionMetric = (override: Record<string, any>) => {
|
||||
const now = Date.now();
|
||||
|
||||
const time = now - INDEXING_DELAY;
|
||||
|
||||
return merge(
|
||||
{
|
||||
'@timestamp': new Date(time).toISOString(),
|
||||
service: {
|
||||
name: 'opbeans-go',
|
||||
},
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
transaction: {
|
||||
duration: {
|
||||
histogram: {
|
||||
values: [1000000],
|
||||
counts: [1],
|
||||
},
|
||||
},
|
||||
type: 'request',
|
||||
},
|
||||
processor: {
|
||||
event: 'metric',
|
||||
},
|
||||
observer: {
|
||||
version_major: 7,
|
||||
},
|
||||
},
|
||||
override
|
||||
);
|
||||
};
|
||||
|
||||
async function waitUntilNextExecution(
|
||||
alert: Alert,
|
||||
intervalInSeconds: number = 1,
|
||||
count: number = 0
|
||||
): Promise<Alert> {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, intervalInSeconds * 1000);
|
||||
});
|
||||
|
||||
const { body, status } = await supertest
|
||||
.get(`/api/alerts/alert/${alert.id}`)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
||||
const { body: targetIndices, status: targetIndicesStatus } = await getAlertsTargetIndices();
|
||||
if (targetIndices.length === 0) {
|
||||
const error = new Error('Error getting alert');
|
||||
Object.assign(error, { response: { body: targetIndices, status: targetIndicesStatus } });
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (status >= 300) {
|
||||
const error = new Error('Error getting alert');
|
||||
Object.assign(error, { response: { body, status } });
|
||||
throw error;
|
||||
}
|
||||
|
||||
const nextAlert = body as Alert;
|
||||
|
||||
if (nextAlert.executionStatus.lastExecutionDate !== alert.executionStatus.lastExecutionDate) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, BULK_INDEX_DELAY);
|
||||
});
|
||||
|
||||
/**
|
||||
* When calling refresh on an index pattern .alerts-observability.apm.alerts* (as was originally the hard-coded string in this test)
|
||||
* The response from Elasticsearch is a 200, even if no indices which match that index pattern have been created.
|
||||
* When calling refresh on a concrete index alias .alerts-observability.apm.alerts-default for instance,
|
||||
* we receive a 404 error index_not_found_exception when no indices have been created which match that alias (obviously).
|
||||
* Since we are receiving a concrete index alias from the observability api instead of a kibana index pattern
|
||||
* and we understand / expect that this index does not exist at certain points of the test, we can try-catch at certain points without caring if the call fails.
|
||||
* There are points in the code where we do want to ensure we get the appropriate error message back
|
||||
*/
|
||||
try {
|
||||
await es.indices.refresh({
|
||||
index: targetIndices[0],
|
||||
});
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (exc) {}
|
||||
return nextAlert;
|
||||
}
|
||||
|
||||
if (count >= MAX_POLLS) {
|
||||
throw new Error('Maximum number of polls exceeded');
|
||||
}
|
||||
|
||||
return waitUntilNextExecution(alert, intervalInSeconds, count + 1);
|
||||
}
|
||||
|
||||
registry.when('Rule registry with write enabled', { config: 'rules', archives: [] }, () => {
|
||||
it('does not bootstrap indices on plugin startup', async () => {
|
||||
const { body: targetIndices } = await getAlertsTargetIndices();
|
||||
try {
|
||||
const res = await es.indices.get({
|
||||
index: targetIndices[0],
|
||||
expand_wildcards: 'open',
|
||||
allow_no_indices: true,
|
||||
});
|
||||
expect(res).to.be.empty();
|
||||
} catch (exc) {
|
||||
expect(exc.statusCode).to.eql(404);
|
||||
}
|
||||
});
|
||||
|
||||
describe('when creating a rule', () => {
|
||||
let createResponse: {
|
||||
alert: Alert;
|
||||
status: number;
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
await es.indices.create({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
body: {
|
||||
mappings: {
|
||||
dynamic: 'strict',
|
||||
properties: {
|
||||
event: {
|
||||
properties: {
|
||||
outcome: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
processor: {
|
||||
properties: {
|
||||
event: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
observer: {
|
||||
properties: {
|
||||
version_major: {
|
||||
type: 'byte',
|
||||
},
|
||||
},
|
||||
},
|
||||
service: {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'keyword',
|
||||
},
|
||||
environment: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
transaction: {
|
||||
properties: {
|
||||
type: {
|
||||
type: 'keyword',
|
||||
},
|
||||
duration: {
|
||||
properties: {
|
||||
histogram: {
|
||||
type: 'histogram',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const body = {
|
||||
params: {
|
||||
threshold: 30,
|
||||
windowSize: 5,
|
||||
windowUnit: 'm',
|
||||
transactionType: 'request',
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
serviceName: 'opbeans-go',
|
||||
},
|
||||
consumer: 'apm',
|
||||
alertTypeId: 'apm.transaction_error_rate',
|
||||
schedule: { interval: '5s' },
|
||||
actions: [],
|
||||
tags: ['apm', 'service.name:opbeans-go'],
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
name: 'Failed transaction rate threshold | opbeans-go',
|
||||
};
|
||||
|
||||
const { body: response, status } = await supertest
|
||||
.post('/api/alerts/alert')
|
||||
.send(body)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
||||
createResponse = {
|
||||
alert: response,
|
||||
status,
|
||||
};
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
const { body: targetIndices } = await getAlertsTargetIndices();
|
||||
if (createResponse.alert) {
|
||||
const { body, status } = await supertest
|
||||
.delete(`/api/alerts/alert/${createResponse.alert.id}`)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
||||
if (status >= 300) {
|
||||
const error = new Error('Error deleting alert');
|
||||
Object.assign(error, { response: { body, status } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
await es.deleteByQuery({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
await es.indices.delete({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
});
|
||||
});
|
||||
|
||||
it('writes alerts data to the alert indices', async () => {
|
||||
expect(createResponse.status).to.be.below(299);
|
||||
|
||||
expect(createResponse.alert).not.to.be(undefined);
|
||||
let alert = await waitUntilNextExecution(createResponse.alert);
|
||||
|
||||
const { body: targetIndices } = await getAlertsTargetIndices();
|
||||
|
||||
try {
|
||||
const res = await es.search({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
[EVENT_KIND]: 'signal',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).to.be.empty();
|
||||
} catch (exc) {
|
||||
expect(exc.message).contain('index_not_found_exception');
|
||||
}
|
||||
|
||||
await es.index({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
body: createTransactionMetric({
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
}),
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
alert = await waitUntilNextExecution(alert);
|
||||
|
||||
try {
|
||||
const res = await es.search({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
[EVENT_KIND]: 'signal',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).to.be.empty();
|
||||
} catch (exc) {
|
||||
expect(exc.message).contain('index_not_found_exception');
|
||||
}
|
||||
|
||||
await es.index({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
body: createTransactionMetric({
|
||||
event: {
|
||||
outcome: 'failure',
|
||||
},
|
||||
}),
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
alert = await waitUntilNextExecution(alert);
|
||||
|
||||
const afterViolatingDataResponse = await es.search({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
[EVENT_KIND]: 'signal',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
_source: false,
|
||||
fields: [{ field: '*', include_unmapped: true }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(afterViolatingDataResponse.hits.hits.length).to.be(1);
|
||||
|
||||
const alertEvent = afterViolatingDataResponse.hits.hits[0].fields as Record<string, any>;
|
||||
|
||||
const exclude = ['@timestamp', ALERT_START, ALERT_UUID, ALERT_RULE_UUID, VERSION];
|
||||
|
||||
const toCompare = omit(alertEvent, exclude);
|
||||
|
||||
expectSnapshot(toCompare).toMatchInline(`
|
||||
Object {
|
||||
"event.action": Array [
|
||||
"open",
|
||||
],
|
||||
"event.kind": Array [
|
||||
"signal",
|
||||
],
|
||||
"kibana.alert.duration.us": Array [
|
||||
0,
|
||||
],
|
||||
"kibana.alert.evaluation.threshold": Array [
|
||||
30,
|
||||
],
|
||||
"kibana.alert.evaluation.value": Array [
|
||||
50,
|
||||
],
|
||||
"kibana.alert.instance.id": Array [
|
||||
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
|
||||
],
|
||||
"kibana.alert.reason": Array [
|
||||
"Failed transactions rate is greater than 30% (current value is 50%) for opbeans-go",
|
||||
],
|
||||
"kibana.alert.rule.category": Array [
|
||||
"Failed transaction rate threshold",
|
||||
],
|
||||
"kibana.alert.rule.consumer": Array [
|
||||
"apm",
|
||||
],
|
||||
"kibana.alert.rule.name": Array [
|
||||
"Failed transaction rate threshold | opbeans-go",
|
||||
],
|
||||
"kibana.alert.rule.producer": Array [
|
||||
"apm",
|
||||
],
|
||||
"kibana.alert.rule.rule_type_id": Array [
|
||||
"apm.transaction_error_rate",
|
||||
],
|
||||
"kibana.alert.status": Array [
|
||||
"active",
|
||||
],
|
||||
"kibana.alert.workflow_status": Array [
|
||||
"open",
|
||||
],
|
||||
"kibana.space_ids": Array [
|
||||
"default",
|
||||
],
|
||||
"processor.event": Array [
|
||||
"transaction",
|
||||
],
|
||||
"service.name": Array [
|
||||
"opbeans-go",
|
||||
],
|
||||
"tags": Array [
|
||||
"apm",
|
||||
"service.name:opbeans-go",
|
||||
],
|
||||
"transaction.type": Array [
|
||||
"request",
|
||||
],
|
||||
}
|
||||
`);
|
||||
|
||||
await es.bulk({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
body: [
|
||||
{ index: {} },
|
||||
createTransactionMetric({
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
}),
|
||||
{ index: {} },
|
||||
createTransactionMetric({
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
}),
|
||||
],
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
alert = await waitUntilNextExecution(alert);
|
||||
|
||||
const afterRecoveryResponse = await es.search({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
[EVENT_KIND]: 'signal',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
_source: false,
|
||||
fields: [{ field: '*', include_unmapped: true }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(afterRecoveryResponse.hits.hits.length).to.be(1);
|
||||
|
||||
const recoveredAlertEvent = afterRecoveryResponse.hits.hits[0].fields as Record<
|
||||
string,
|
||||
any
|
||||
>;
|
||||
|
||||
expect(recoveredAlertEvent[ALERT_STATUS]?.[0]).to.eql('recovered');
|
||||
expect(recoveredAlertEvent[ALERT_DURATION]?.[0]).to.be.greaterThan(0);
|
||||
expect(new Date(recoveredAlertEvent[ALERT_END]?.[0]).getTime()).to.be.greaterThan(0);
|
||||
|
||||
expectSnapshot(omit(recoveredAlertEvent, exclude.concat([ALERT_DURATION, ALERT_END])))
|
||||
.toMatchInline(`
|
||||
Object {
|
||||
"event.action": Array [
|
||||
"close",
|
||||
],
|
||||
"event.kind": Array [
|
||||
"signal",
|
||||
],
|
||||
"kibana.alert.evaluation.threshold": Array [
|
||||
30,
|
||||
],
|
||||
"kibana.alert.evaluation.value": Array [
|
||||
50,
|
||||
],
|
||||
"kibana.alert.instance.id": Array [
|
||||
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
|
||||
],
|
||||
"kibana.alert.reason": Array [
|
||||
"Failed transactions rate is greater than 30% (current value is 50%) for opbeans-go",
|
||||
],
|
||||
"kibana.alert.rule.category": Array [
|
||||
"Failed transaction rate threshold",
|
||||
],
|
||||
"kibana.alert.rule.consumer": Array [
|
||||
"apm",
|
||||
],
|
||||
"kibana.alert.rule.name": Array [
|
||||
"Failed transaction rate threshold | opbeans-go",
|
||||
],
|
||||
"kibana.alert.rule.producer": Array [
|
||||
"apm",
|
||||
],
|
||||
"kibana.alert.rule.rule_type_id": Array [
|
||||
"apm.transaction_error_rate",
|
||||
],
|
||||
"kibana.alert.status": Array [
|
||||
"recovered",
|
||||
],
|
||||
"kibana.alert.workflow_status": Array [
|
||||
"open",
|
||||
],
|
||||
"kibana.space_ids": Array [
|
||||
"default",
|
||||
],
|
||||
"processor.event": Array [
|
||||
"transaction",
|
||||
],
|
||||
"service.name": Array [
|
||||
"opbeans-go",
|
||||
],
|
||||
"tags": Array [
|
||||
"apm",
|
||||
"service.name:opbeans-go",
|
||||
],
|
||||
"transaction.type": Array [
|
||||
"request",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
registry.when('Rule registry with write not enabled', { config: 'basic', archives: [] }, () => {
|
||||
it('does not bootstrap the apm rule indices', async () => {
|
||||
const { body: targetIndices } = await getAlertsTargetIndices();
|
||||
const errorOrUndefined = await es.indices
|
||||
.get({
|
||||
index: targetIndices[0],
|
||||
expand_wildcards: 'open',
|
||||
allow_no_indices: false,
|
||||
})
|
||||
.then(() => {})
|
||||
.catch((error) => {
|
||||
return error.toString();
|
||||
});
|
||||
|
||||
expect(errorOrUndefined).not.to.be(undefined);
|
||||
|
||||
expect(errorOrUndefined).to.contain('index_not_found_exception');
|
||||
});
|
||||
});
|
||||
}
|
13
x-pack/test/rule_registry/common/constants.ts
Normal file
13
x-pack/test/rule_registry/common/constants.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const APM_METRIC_INDEX_NAME = 'apm-8.0.0-transaction';
|
||||
export const MAX_POLLS = 10;
|
||||
export const BULK_INDEX_DELAY = 1000;
|
||||
export const INDEXING_DELAY = 5000;
|
||||
export const ALERTS_TARGET_INDICES_URL =
|
||||
'/api/observability/rules/alerts/dynamic_index_pattern?namespace=default®istrationContexts=observability.apm®istrationContexts=';
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { GetService } from '../../types';
|
||||
import { User } from '../authentication/types';
|
||||
import { getAlertsTargetIndices } from './get_alerts_target_indices';
|
||||
|
||||
export const cleanupTargetIndices = async (getService: GetService, user: User, spaceId: string) => {
|
||||
const es = getService('es');
|
||||
try {
|
||||
const { body: targetIndices } = await getAlertsTargetIndices(getService, user, spaceId);
|
||||
const aliasMap = await es.indices.getAlias({
|
||||
name: targetIndices,
|
||||
allow_no_indices: true,
|
||||
expand_wildcards: 'open',
|
||||
});
|
||||
const indices = Object.keys(aliasMap);
|
||||
expect(indices.length > 0).to.be(true);
|
||||
return es.indices.delete({ index: indices }, { ignore: [404] });
|
||||
} catch (error) {
|
||||
if (error.meta.statusCode !== 404) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
25
x-pack/test/rule_registry/common/lib/helpers/create_alert.ts
Normal file
25
x-pack/test/rule_registry/common/lib/helpers/create_alert.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { User } from '../authentication/types';
|
||||
import { GetService, AlertDef } from '../../types';
|
||||
import { getSpaceUrlPrefix } from '../authentication/spaces';
|
||||
|
||||
export const createAlert = async (
|
||||
getService: GetService,
|
||||
user: User,
|
||||
spaceId: string,
|
||||
alertDef: AlertDef
|
||||
) => {
|
||||
const supertest = getService('supertestWithoutAuth');
|
||||
const { body: response, status } = await supertest
|
||||
.post(`${getSpaceUrlPrefix(spaceId)}/api/alerts/alert`)
|
||||
.auth(user.username, user.password)
|
||||
.send(alertDef)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
return { alert: response, status };
|
||||
};
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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 { APM_METRIC_INDEX_NAME } from '../../constants';
|
||||
import { GetService } from '../../types';
|
||||
|
||||
export const createApmMetricIndex = async (getService: GetService) => {
|
||||
const es = getService('es');
|
||||
await es.indices.create({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
body: {
|
||||
mappings: {
|
||||
dynamic: 'strict',
|
||||
properties: {
|
||||
event: {
|
||||
properties: {
|
||||
outcome: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
processor: {
|
||||
properties: {
|
||||
event: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
observer: {
|
||||
properties: {
|
||||
version_major: {
|
||||
type: 'byte',
|
||||
},
|
||||
},
|
||||
},
|
||||
service: {
|
||||
properties: {
|
||||
name: {
|
||||
type: 'keyword',
|
||||
},
|
||||
environment: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
transaction: {
|
||||
properties: {
|
||||
type: {
|
||||
type: 'keyword',
|
||||
},
|
||||
duration: {
|
||||
properties: {
|
||||
histogram: {
|
||||
type: 'histogram',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { merge } from 'lodash';
|
||||
import { INDEXING_DELAY } from '../../constants';
|
||||
|
||||
export const createTransactionMetric = (override: Record<string, any>) => {
|
||||
const now = Date.now();
|
||||
const time = now - INDEXING_DELAY;
|
||||
|
||||
return merge(
|
||||
{
|
||||
'@timestamp': new Date(time).toISOString(),
|
||||
service: {
|
||||
name: 'opbeans-go',
|
||||
},
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
transaction: {
|
||||
duration: {
|
||||
histogram: {
|
||||
values: [1000000],
|
||||
counts: [1],
|
||||
},
|
||||
},
|
||||
type: 'request',
|
||||
},
|
||||
processor: {
|
||||
event: 'metric',
|
||||
},
|
||||
observer: {
|
||||
version_major: 7,
|
||||
},
|
||||
},
|
||||
override
|
||||
);
|
||||
};
|
49
x-pack/test/rule_registry/common/lib/helpers/delete_alert.ts
Normal file
49
x-pack/test/rule_registry/common/lib/helpers/delete_alert.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { APM_METRIC_INDEX_NAME } from '../../constants';
|
||||
import { GetService } from '../../types';
|
||||
import { getSpaceUrlPrefix } from '../authentication/spaces';
|
||||
import { User } from '../authentication/types';
|
||||
import { getAlertsTargetIndices } from './get_alerts_target_indices';
|
||||
|
||||
export const deleteAlert = async (
|
||||
getService: GetService,
|
||||
user: User,
|
||||
spaceId: string,
|
||||
id: string | undefined
|
||||
) => {
|
||||
const es = getService('es');
|
||||
const supertest = getService('supertestWithoutAuth');
|
||||
const { body: targetIndices } = await getAlertsTargetIndices(getService, user, spaceId);
|
||||
if (id) {
|
||||
const { body, status } = await supertest
|
||||
.delete(`${getSpaceUrlPrefix(spaceId)}/api/alerts/alert/${id}`)
|
||||
.auth(user.username, user.password)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
||||
if (status >= 300) {
|
||||
const error = new Error('Error deleting alert');
|
||||
Object.assign(error, { response: { body, status } });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
await es.deleteByQuery({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
await es.indices.delete({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
});
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ALERTS_TARGET_INDICES_URL } from '../../constants';
|
||||
import { GetService } from '../../types';
|
||||
import { User } from '../authentication/types';
|
||||
import { getSpaceUrlPrefix } from '../authentication/spaces';
|
||||
|
||||
export const getAlertsTargetIndices = async (
|
||||
getService: GetService,
|
||||
user: User,
|
||||
spaceId: string
|
||||
) => {
|
||||
const supertest = getService('supertestWithoutAuth');
|
||||
return supertest
|
||||
.get(`${getSpaceUrlPrefix(spaceId)}${ALERTS_TARGET_INDICES_URL}`)
|
||||
.auth(user.username, user.password)
|
||||
.send()
|
||||
.set('kbn-xsrf', 'foo');
|
||||
};
|
14
x-pack/test/rule_registry/common/lib/helpers/index.ts
Normal file
14
x-pack/test/rule_registry/common/lib/helpers/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './create_alert';
|
||||
export * from './create_apm_metric_index';
|
||||
export * from './create_transaction_metric';
|
||||
export * from './get_alerts_target_indices';
|
||||
export * from './wait_until_next_execution';
|
||||
export * from './cleanup_target_indices';
|
||||
export * from './delete_alert';
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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 { GetService } from '../../types';
|
||||
import { getAlertsTargetIndices } from './get_alerts_target_indices';
|
||||
import { BULK_INDEX_DELAY, MAX_POLLS } from '../../constants';
|
||||
import { Alert } from '../../../../../plugins/alerting/common';
|
||||
import { getSpaceUrlPrefix } from '../authentication/spaces';
|
||||
import { User } from '../authentication/types';
|
||||
|
||||
export async function waitUntilNextExecution(
|
||||
getService: GetService,
|
||||
user: User,
|
||||
alert: Alert,
|
||||
spaceId: string,
|
||||
intervalInSeconds: number = 1,
|
||||
count: number = 0
|
||||
): Promise<Alert> {
|
||||
const supertest = getService('supertestWithoutAuth');
|
||||
const es = getService('es');
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, intervalInSeconds * 1000);
|
||||
});
|
||||
|
||||
const { body, status } = await supertest
|
||||
.get(`${getSpaceUrlPrefix(spaceId)}/api/alerts/alert/${alert.id}`)
|
||||
.auth(user.username, user.password)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
||||
const { body: targetIndices, status: targetIndicesStatus } = await getAlertsTargetIndices(
|
||||
getService,
|
||||
user,
|
||||
spaceId
|
||||
);
|
||||
if (targetIndices.length === 0) {
|
||||
const error = new Error('Error getting target indices');
|
||||
Object.assign(error, { response: { body: targetIndices, status: targetIndicesStatus } });
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (status >= 300) {
|
||||
const error = new Error('Error getting alert');
|
||||
Object.assign(error, { response: { body, status } });
|
||||
throw error;
|
||||
}
|
||||
|
||||
const nextAlert = body as Alert;
|
||||
|
||||
if (nextAlert.executionStatus.lastExecutionDate !== alert.executionStatus.lastExecutionDate) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, BULK_INDEX_DELAY);
|
||||
});
|
||||
|
||||
/**
|
||||
* When calling refresh on an index pattern .alerts-observability.apm.alerts* (as was originally the hard-coded string in this test)
|
||||
* The response from Elasticsearch is a 200, even if no indices which match that index pattern have been created.
|
||||
* When calling refresh on a concrete index alias .alerts-observability.apm.alerts-default for instance,
|
||||
* we receive a 404 error index_not_found_exception when no indices have been created which match that alias (obviously).
|
||||
* Since we are receiving a concrete index alias from the observability api instead of a kibana index pattern
|
||||
* and we understand / expect that this index does not exist at certain points of the test, we can try-catch at certain points without caring if the call fails.
|
||||
* There are points in the code where we do want to ensure we get the appropriate error message back
|
||||
*/
|
||||
try {
|
||||
await es.indices.refresh({
|
||||
index: targetIndices[0],
|
||||
});
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (exc) {}
|
||||
return nextAlert;
|
||||
}
|
||||
|
||||
if (count >= MAX_POLLS) {
|
||||
throw new Error('Maximum number of polls exceeded');
|
||||
}
|
||||
|
||||
return waitUntilNextExecution(getService, user, alert, spaceId, intervalInSeconds, count + 1);
|
||||
}
|
22
x-pack/test/rule_registry/common/types.ts
Normal file
22
x-pack/test/rule_registry/common/types.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { GenericFtrProviderContext } from '@kbn/test';
|
||||
import { Alert, AlertTypeParams } from '../../../plugins/alerting/common';
|
||||
import { services } from './services';
|
||||
|
||||
export type GetService = GenericFtrProviderContext<typeof services, {}>['getService'];
|
||||
|
||||
export interface AlertParams extends AlertTypeParams {
|
||||
windowSize?: number;
|
||||
windowUnit?: string;
|
||||
threshold?: number;
|
||||
serviceName?: string;
|
||||
transactionType?: string;
|
||||
environment?: string;
|
||||
}
|
||||
|
||||
export type AlertDef<Params extends AlertTypeParams = {}> = Partial<Alert<Params>>;
|
16
x-pack/test/rule_registry/spaces_only/config_basic.ts
Normal file
16
x-pack/test/rule_registry/spaces_only/config_basic.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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 { createTestConfig } from '../common/config';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default createTestConfig('spaces_only', {
|
||||
license: 'basic',
|
||||
disabledPlugins: ['security'],
|
||||
ssl: false,
|
||||
testFiles: [require.resolve('./tests/basic')],
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import type { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { obsOnlyRead } from '../../../common/lib/authentication/users';
|
||||
import { getAlertsTargetIndices } from '../../../common/lib/helpers';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function registryRulesApiTest({ getService }: FtrProviderContext) {
|
||||
const es = getService('es');
|
||||
|
||||
describe('Rule Registry API', () => {
|
||||
describe('with read permissions', () => {
|
||||
it('does not bootstrap the apm rule indices', async () => {
|
||||
const { body: targetIndices } = await getAlertsTargetIndices(
|
||||
getService,
|
||||
obsOnlyRead,
|
||||
'space1'
|
||||
);
|
||||
const errorOrUndefined = await es.indices
|
||||
.get({
|
||||
index: targetIndices[0],
|
||||
expand_wildcards: 'open',
|
||||
allow_no_indices: false,
|
||||
})
|
||||
.then(() => {})
|
||||
.catch((error) => {
|
||||
return error.toString();
|
||||
});
|
||||
|
||||
expect(errorOrUndefined).not.to.be(undefined);
|
||||
|
||||
expect(errorOrUndefined).to.contain('index_not_found_exception');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
28
x-pack/test/rule_registry/spaces_only/tests/basic/index.ts
Normal file
28
x-pack/test/rule_registry/spaces_only/tests/basic/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import { createSpaces, deleteSpaces } from '../../../common/lib/authentication';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ loadTestFile, getService }: FtrProviderContext): void => {
|
||||
describe('rule registry spaces only: trial', function () {
|
||||
// Fastest ciGroup for the moment.
|
||||
this.tags('ciGroup5');
|
||||
|
||||
before(async () => {
|
||||
await createSpaces(getService);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteSpaces(getService);
|
||||
});
|
||||
|
||||
// Basic
|
||||
loadTestFile(require.resolve('./bootstrap'));
|
||||
});
|
||||
};
|
124
x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap
generated
Normal file
124
x-pack/test/rule_registry/spaces_only/tests/trial/__snapshots__/create_rule.snap
generated
Normal file
|
@ -0,0 +1,124 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rule registry spaces only: trial Rule Registry API with write permissions when creating a rule writes alerts data to the alert indices 1`] = `
|
||||
Object {
|
||||
"event.action": Array [
|
||||
"open",
|
||||
],
|
||||
"event.kind": Array [
|
||||
"signal",
|
||||
],
|
||||
"kibana.alert.duration.us": Array [
|
||||
0,
|
||||
],
|
||||
"kibana.alert.evaluation.threshold": Array [
|
||||
30,
|
||||
],
|
||||
"kibana.alert.evaluation.value": Array [
|
||||
50,
|
||||
],
|
||||
"kibana.alert.instance.id": Array [
|
||||
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
|
||||
],
|
||||
"kibana.alert.reason": Array [
|
||||
"Failed transactions rate is greater than 30% (current value is 50%) for opbeans-go",
|
||||
],
|
||||
"kibana.alert.rule.category": Array [
|
||||
"Failed transaction rate threshold",
|
||||
],
|
||||
"kibana.alert.rule.consumer": Array [
|
||||
"apm",
|
||||
],
|
||||
"kibana.alert.rule.name": Array [
|
||||
"Failed transaction rate threshold | opbeans-go",
|
||||
],
|
||||
"kibana.alert.rule.producer": Array [
|
||||
"apm",
|
||||
],
|
||||
"kibana.alert.rule.rule_type_id": Array [
|
||||
"apm.transaction_error_rate",
|
||||
],
|
||||
"kibana.alert.status": Array [
|
||||
"active",
|
||||
],
|
||||
"kibana.alert.workflow_status": Array [
|
||||
"open",
|
||||
],
|
||||
"kibana.space_ids": Array [
|
||||
"space1",
|
||||
],
|
||||
"processor.event": Array [
|
||||
"transaction",
|
||||
],
|
||||
"service.name": Array [
|
||||
"opbeans-go",
|
||||
],
|
||||
"tags": Array [
|
||||
"apm",
|
||||
"service.name:opbeans-go",
|
||||
],
|
||||
"transaction.type": Array [
|
||||
"request",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`rule registry spaces only: trial Rule Registry API with write permissions when creating a rule writes alerts data to the alert indices 2`] = `
|
||||
Object {
|
||||
"event.action": Array [
|
||||
"close",
|
||||
],
|
||||
"event.kind": Array [
|
||||
"signal",
|
||||
],
|
||||
"kibana.alert.evaluation.threshold": Array [
|
||||
30,
|
||||
],
|
||||
"kibana.alert.evaluation.value": Array [
|
||||
50,
|
||||
],
|
||||
"kibana.alert.instance.id": Array [
|
||||
"apm.transaction_error_rate_opbeans-go_request_ENVIRONMENT_NOT_DEFINED",
|
||||
],
|
||||
"kibana.alert.reason": Array [
|
||||
"Failed transactions rate is greater than 30% (current value is 50%) for opbeans-go",
|
||||
],
|
||||
"kibana.alert.rule.category": Array [
|
||||
"Failed transaction rate threshold",
|
||||
],
|
||||
"kibana.alert.rule.consumer": Array [
|
||||
"apm",
|
||||
],
|
||||
"kibana.alert.rule.name": Array [
|
||||
"Failed transaction rate threshold | opbeans-go",
|
||||
],
|
||||
"kibana.alert.rule.producer": Array [
|
||||
"apm",
|
||||
],
|
||||
"kibana.alert.rule.rule_type_id": Array [
|
||||
"apm.transaction_error_rate",
|
||||
],
|
||||
"kibana.alert.status": Array [
|
||||
"recovered",
|
||||
],
|
||||
"kibana.alert.workflow_status": Array [
|
||||
"open",
|
||||
],
|
||||
"kibana.space_ids": Array [
|
||||
"space1",
|
||||
],
|
||||
"processor.event": Array [
|
||||
"transaction",
|
||||
],
|
||||
"service.name": Array [
|
||||
"opbeans-go",
|
||||
],
|
||||
"tags": Array [
|
||||
"apm",
|
||||
"service.name:opbeans-go",
|
||||
],
|
||||
"transaction.type": Array [
|
||||
"request",
|
||||
],
|
||||
}
|
||||
`;
|
252
x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts
Normal file
252
x-pack/test/rule_registry/spaces_only/tests/trial/create_rule.ts
Normal file
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import {
|
||||
ALERT_DURATION,
|
||||
ALERT_END,
|
||||
ALERT_RULE_UUID,
|
||||
ALERT_START,
|
||||
ALERT_STATUS,
|
||||
ALERT_UUID,
|
||||
EVENT_KIND,
|
||||
VERSION,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { omit } from 'lodash';
|
||||
import type { FtrProviderContext } from '../../../common/ftr_provider_context';
|
||||
import {
|
||||
getAlertsTargetIndices,
|
||||
createApmMetricIndex,
|
||||
createAlert,
|
||||
waitUntilNextExecution,
|
||||
createTransactionMetric,
|
||||
cleanupTargetIndices,
|
||||
deleteAlert,
|
||||
} from '../../../common/lib/helpers';
|
||||
import { AlertDef, AlertParams } from '../../../common/types';
|
||||
import { Alert } from '../../../../../plugins/alerting/common';
|
||||
import { APM_METRIC_INDEX_NAME } from '../../../common/constants';
|
||||
import { obsOnly } from '../../../common/lib/authentication/users';
|
||||
|
||||
const SPACE_ID = 'space1';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function registryRulesApiTest({ getService }: FtrProviderContext) {
|
||||
const es = getService('es');
|
||||
|
||||
describe('Rule Registry API', () => {
|
||||
describe('with write permissions', () => {
|
||||
it('does not bootstrap indices on plugin startup', async () => {
|
||||
const { body: targetIndices } = await getAlertsTargetIndices(getService, obsOnly, SPACE_ID);
|
||||
try {
|
||||
const res = await es.indices.get({
|
||||
index: targetIndices[0],
|
||||
expand_wildcards: 'open',
|
||||
allow_no_indices: true,
|
||||
});
|
||||
expect(res).to.be.empty();
|
||||
} catch (exc) {
|
||||
expect(exc.statusCode).to.eql(404);
|
||||
}
|
||||
});
|
||||
|
||||
describe('when creating a rule', () => {
|
||||
let createResponse: {
|
||||
alert: Alert;
|
||||
status: number;
|
||||
};
|
||||
before(async () => {
|
||||
await createApmMetricIndex(getService);
|
||||
const alertDef: AlertDef<AlertParams> = {
|
||||
params: {
|
||||
threshold: 30,
|
||||
windowSize: 5,
|
||||
windowUnit: 'm',
|
||||
transactionType: 'request',
|
||||
environment: 'ENVIRONMENT_ALL',
|
||||
serviceName: 'opbeans-go',
|
||||
},
|
||||
consumer: 'apm',
|
||||
alertTypeId: 'apm.transaction_error_rate',
|
||||
schedule: { interval: '5s' },
|
||||
actions: [],
|
||||
tags: ['apm', 'service.name:opbeans-go'],
|
||||
notifyWhen: 'onActionGroupChange',
|
||||
name: 'Failed transaction rate threshold | opbeans-go',
|
||||
};
|
||||
createResponse = await createAlert(getService, obsOnly, SPACE_ID, alertDef);
|
||||
});
|
||||
after(async () => {
|
||||
await deleteAlert(getService, obsOnly, SPACE_ID, createResponse.alert.id);
|
||||
await cleanupTargetIndices(getService, obsOnly, SPACE_ID);
|
||||
});
|
||||
|
||||
it('writes alerts data to the alert indices', async () => {
|
||||
expect(createResponse.status).to.be.below(299);
|
||||
|
||||
expect(createResponse.alert).not.to.be(undefined);
|
||||
let alert = await waitUntilNextExecution(
|
||||
getService,
|
||||
obsOnly,
|
||||
createResponse.alert,
|
||||
SPACE_ID
|
||||
);
|
||||
|
||||
const { body: targetIndices } = await getAlertsTargetIndices(
|
||||
getService,
|
||||
obsOnly,
|
||||
SPACE_ID
|
||||
);
|
||||
|
||||
try {
|
||||
const res = await es.search({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
[EVENT_KIND]: 'signal',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).to.be.empty();
|
||||
} catch (exc) {
|
||||
expect(exc.message).contain('index_not_found_exception');
|
||||
}
|
||||
|
||||
await es.index({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
body: createTransactionMetric({
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
}),
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
alert = await waitUntilNextExecution(getService, obsOnly, alert, SPACE_ID);
|
||||
|
||||
try {
|
||||
const res = await es.search({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
[EVENT_KIND]: 'signal',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(res).to.be.empty();
|
||||
} catch (exc) {
|
||||
expect(exc.message).contain('index_not_found_exception');
|
||||
}
|
||||
|
||||
await es.index({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
body: createTransactionMetric({
|
||||
event: {
|
||||
outcome: 'failure',
|
||||
},
|
||||
}),
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
alert = await waitUntilNextExecution(getService, obsOnly, alert, SPACE_ID);
|
||||
|
||||
const afterViolatingDataResponse = await es.search({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
[EVENT_KIND]: 'signal',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
_source: false,
|
||||
fields: [{ field: '*', include_unmapped: true }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(afterViolatingDataResponse.hits.hits.length).to.be(1);
|
||||
|
||||
const alertEvent = afterViolatingDataResponse.hits.hits[0].fields as Record<string, any>;
|
||||
|
||||
const exclude = ['@timestamp', ALERT_START, ALERT_UUID, ALERT_RULE_UUID, VERSION];
|
||||
|
||||
const toCompare = omit(alertEvent, exclude);
|
||||
|
||||
expectSnapshot(toCompare).toMatch();
|
||||
|
||||
await es.bulk({
|
||||
index: APM_METRIC_INDEX_NAME,
|
||||
body: [
|
||||
{ index: {} },
|
||||
createTransactionMetric({
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
}),
|
||||
{ index: {} },
|
||||
createTransactionMetric({
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
}),
|
||||
],
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
alert = await waitUntilNextExecution(getService, obsOnly, alert, SPACE_ID);
|
||||
|
||||
const afterRecoveryResponse = await es.search({
|
||||
index: targetIndices[0],
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
[EVENT_KIND]: 'signal',
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
sort: {
|
||||
'@timestamp': 'desc',
|
||||
},
|
||||
_source: false,
|
||||
fields: [{ field: '*', include_unmapped: true }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(afterRecoveryResponse.hits.hits.length).to.be(1);
|
||||
|
||||
const recoveredAlertEvent = afterRecoveryResponse.hits.hits[0].fields as Record<
|
||||
string,
|
||||
any
|
||||
>;
|
||||
|
||||
expect(recoveredAlertEvent[ALERT_STATUS]?.[0]).to.eql('recovered');
|
||||
expect(recoveredAlertEvent[ALERT_DURATION]?.[0]).to.be.greaterThan(0);
|
||||
expect(new Date(recoveredAlertEvent[ALERT_END]?.[0]).getTime()).to.be.greaterThan(0);
|
||||
|
||||
expectSnapshot(
|
||||
omit(recoveredAlertEvent, exclude.concat([ALERT_DURATION, ALERT_END]))
|
||||
).toMatch();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -22,8 +22,9 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => {
|
|||
await deleteSpaces(getService);
|
||||
});
|
||||
|
||||
// Basic
|
||||
// Trial
|
||||
loadTestFile(require.resolve('./get_alert_by_id'));
|
||||
loadTestFile(require.resolve('./update_alert'));
|
||||
loadTestFile(require.resolve('./create_rule'));
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue