mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
## Summary It fixes #161458 by adding API integration tests for the Threshold rule, with many scenarios (file per scenario), and each scenario has a complete life-cycle ### The scenario life-cycle - Generating data using the `fake_host` dataset from the high-card - Create a DataView based on the generated data - Create the rule and wait to be active - Get the fired alert and matches its value - Clean up ### The covered scenarios - Avg. percentage, fires alert - Avg. percentage, fires alert with no data - Custom equation on bytes filed, fires alert - Doc count, fires alert - Group by two fields, fires alert. ---------
This commit is contained in:
parent
c3a5da8825
commit
6bae659a1d
14 changed files with 1252 additions and 2 deletions
|
@ -47,7 +47,7 @@ export const generateEvent = (index: number, timestamp: Moment, interval: number
|
|||
cores: 4,
|
||||
total: {
|
||||
norm: {
|
||||
pct: randomBetween(),
|
||||
pct: 0.8,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
|
@ -134,6 +134,20 @@ export const generateEvent = (index: number, timestamp: Moment, interval: number
|
|||
container: {
|
||||
id: `container-${index}`,
|
||||
name: 'container-name',
|
||||
cpu: {
|
||||
cores: 4,
|
||||
total: {
|
||||
norm: {
|
||||
pct: 0.8,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
pct: randomBetween(1, 4),
|
||||
},
|
||||
system: {
|
||||
pct: randomBetween(1, 4),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -165,6 +165,49 @@ export const template = {
|
|||
type: 'keyword',
|
||||
ignore_above: 256,
|
||||
},
|
||||
cpu: {
|
||||
properties: {
|
||||
cores: {
|
||||
type: 'long',
|
||||
},
|
||||
total: {
|
||||
properties: {
|
||||
norm: {
|
||||
properties: {
|
||||
pct: {
|
||||
scaling_factor: 1000,
|
||||
type: 'scaled_float',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
properties: {
|
||||
pct: {
|
||||
scaling_factor: 1000,
|
||||
type: 'scaled_float',
|
||||
},
|
||||
norm: {
|
||||
properties: {
|
||||
pct: {
|
||||
scaling_factor: 1000,
|
||||
type: 'scaled_float',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
system: {
|
||||
properties: {
|
||||
pct: {
|
||||
scaling_factor: 1000,
|
||||
type: 'scaled_float',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -209,6 +209,7 @@ export interface ThresholdParams {
|
|||
alertOnNoData?: boolean;
|
||||
alertOnGroupDisappear?: boolean;
|
||||
searchConfiguration: SerializedSearchSourceFields;
|
||||
groupBy?: string[];
|
||||
}
|
||||
|
||||
interface BaseMetricExpressionParams {
|
||||
|
|
|
@ -15,13 +15,16 @@ export function createTestConfig(options: CreateTestConfigOptions) {
|
|||
|
||||
return {
|
||||
...svlSharedConfig.getAll(),
|
||||
|
||||
services,
|
||||
kbnTestServer: {
|
||||
...svlSharedConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...svlSharedConfig.get('kbnTestServer.serverArgs'),
|
||||
`--serverless=${options.serverlessProject}`,
|
||||
`--xpack.alerting.enableFrameworkAlerts=true`,
|
||||
'--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"',
|
||||
'--xpack.observability.unsafe.thresholdRule.enabled=true',
|
||||
'--server.publicBaseUrl=https://localhost:5601',
|
||||
],
|
||||
},
|
||||
testFiles: options.testFiles,
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics';
|
||||
import { ThresholdParams } from '@kbn/observability-plugin/common/threshold_rule/types';
|
||||
import type { SuperTest, Test } from 'supertest';
|
||||
|
||||
export async function createIndexConnector({
|
||||
supertest,
|
||||
name,
|
||||
indexName,
|
||||
}: {
|
||||
supertest: SuperTest<Test>;
|
||||
name: string;
|
||||
indexName: string;
|
||||
}) {
|
||||
const { body } = await supertest
|
||||
.post(`/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name,
|
||||
config: {
|
||||
index: indexName,
|
||||
refresh: true,
|
||||
},
|
||||
connector_type_id: '.index',
|
||||
});
|
||||
return body.id as string;
|
||||
}
|
||||
|
||||
export async function createRule({
|
||||
supertest,
|
||||
name,
|
||||
ruleTypeId,
|
||||
params,
|
||||
actions = [],
|
||||
tags = [],
|
||||
schedule,
|
||||
consumer,
|
||||
}: {
|
||||
supertest: SuperTest<Test>;
|
||||
ruleTypeId: string;
|
||||
name: string;
|
||||
params: MetricThresholdParams | ThresholdParams;
|
||||
actions?: any[];
|
||||
tags?: any[];
|
||||
schedule?: { interval: string };
|
||||
consumer: string;
|
||||
}) {
|
||||
const { body } = await supertest
|
||||
.post(`/api/alerting/rule`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
params,
|
||||
consumer,
|
||||
schedule: schedule || {
|
||||
interval: '5m',
|
||||
},
|
||||
tags,
|
||||
name,
|
||||
rule_type_id: ruleTypeId,
|
||||
actions,
|
||||
});
|
||||
return body;
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 SuperTest from 'supertest';
|
||||
import type { Client } from '@elastic/elasticsearch';
|
||||
import type {
|
||||
AggregationsAggregate,
|
||||
SearchResponse,
|
||||
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
export async function waitForRuleStatus({
|
||||
id,
|
||||
expectedStatus,
|
||||
supertest,
|
||||
}: {
|
||||
id: string;
|
||||
expectedStatus: string;
|
||||
supertest: SuperTest.SuperTest<SuperTest.Test>;
|
||||
}): Promise<Record<string, any>> {
|
||||
return pRetry(
|
||||
async () => {
|
||||
const response = await supertest.get(`/api/alerting/rule/${id}`);
|
||||
const { execution_status: executionStatus } = response.body || {};
|
||||
const { status } = executionStatus || {};
|
||||
if (status !== expectedStatus) {
|
||||
throw new Error(`waitForStatus(${expectedStatus}): got ${status}`);
|
||||
}
|
||||
return executionStatus;
|
||||
},
|
||||
{ retries: 10 }
|
||||
);
|
||||
}
|
||||
|
||||
export async function waitForDocumentInIndex<T>({
|
||||
esClient,
|
||||
indexName,
|
||||
}: {
|
||||
esClient: Client;
|
||||
indexName: string;
|
||||
}): Promise<SearchResponse<T, Record<string, AggregationsAggregate>>> {
|
||||
return pRetry(
|
||||
async () => {
|
||||
const response = await esClient.search<T>({ index: indexName });
|
||||
if (response.hits.hits.length === 0) {
|
||||
throw new Error('No hits found');
|
||||
}
|
||||
return response;
|
||||
},
|
||||
{ retries: 10 }
|
||||
);
|
||||
}
|
||||
|
||||
export async function waitForAlertInIndex<T>({
|
||||
esClient,
|
||||
indexName,
|
||||
ruleId,
|
||||
}: {
|
||||
esClient: Client;
|
||||
indexName: string;
|
||||
ruleId: string;
|
||||
}): Promise<SearchResponse<T, Record<string, AggregationsAggregate>>> {
|
||||
return pRetry(
|
||||
async () => {
|
||||
const response = await esClient.search<T>({
|
||||
index: indexName,
|
||||
body: {
|
||||
query: {
|
||||
term: {
|
||||
'kibana.alert.rule.uuid': ruleId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (response.hits.hits.length === 0) {
|
||||
throw new Error('No hits found');
|
||||
}
|
||||
return response;
|
||||
},
|
||||
{ retries: 10 }
|
||||
);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 { SuperTest, Test } from 'supertest';
|
||||
|
||||
export const createDataView = async ({
|
||||
supertest,
|
||||
id,
|
||||
name,
|
||||
title,
|
||||
}: {
|
||||
supertest: SuperTest<Test>;
|
||||
id: string;
|
||||
name: string;
|
||||
title: string;
|
||||
}) => {
|
||||
const { body } = await supertest
|
||||
.post(`/api/content_management/rpc/create`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
contentTypeId: 'index-pattern',
|
||||
data: {
|
||||
fieldAttrs: '{}',
|
||||
title,
|
||||
timeFieldName: '@timestamp',
|
||||
sourceFilters: '[]',
|
||||
fields: '[]',
|
||||
fieldFormatMap: '{}',
|
||||
typeMeta: '{}',
|
||||
runtimeFieldMap: '{}',
|
||||
name,
|
||||
},
|
||||
options: { id },
|
||||
version: 1,
|
||||
});
|
||||
return body;
|
||||
};
|
||||
export const deleteDataView = async ({
|
||||
supertest,
|
||||
id,
|
||||
}: {
|
||||
supertest: SuperTest<Test>;
|
||||
id: string;
|
||||
}) => {
|
||||
const { body } = await supertest
|
||||
.post(`/api/content_management/rpc/delete`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
contentTypeId: 'index-pattern',
|
||||
id,
|
||||
options: { force: true },
|
||||
version: 1,
|
||||
});
|
||||
return body;
|
||||
};
|
|
@ -11,5 +11,10 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
describe('serverless observability API', function () {
|
||||
loadTestFile(require.resolve('./fleet'));
|
||||
loadTestFile(require.resolve('./snapshot_telemetry'));
|
||||
loadTestFile(require.resolve('./threshold_rule/avg_pct_fired'));
|
||||
loadTestFile(require.resolve('./threshold_rule/avg_pct_no_data'));
|
||||
loadTestFile(require.resolve('./threshold_rule/documents_count_fired'));
|
||||
loadTestFile(require.resolve('./threshold_rule/custom_eq_avg_bytes_fired'));
|
||||
loadTestFile(require.resolve('./threshold_rule/group_by_fired'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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 { cleanup, generate } from '@kbn/infra-forge';
|
||||
import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types';
|
||||
import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor';
|
||||
import expect from '@kbn/expect';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { createIndexConnector, createRule } from '../helpers/alerting_api_helper';
|
||||
import { createDataView, deleteDataView } from '../helpers/data_view';
|
||||
import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esClient = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const esDeleteAllIndices = getService('esDeleteAllIndices');
|
||||
const logger = getService('log');
|
||||
|
||||
describe('Threshold rule - AVG - PCT - FIRED', () => {
|
||||
const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default';
|
||||
const ALERT_ACTION_INDEX = 'alert-action-threshold';
|
||||
const DATA_VIEW_ID = 'data-view-id';
|
||||
let infraDataIndex: string;
|
||||
let actionId: string;
|
||||
let ruleId: string;
|
||||
|
||||
before(async () => {
|
||||
infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger });
|
||||
await createDataView({
|
||||
supertest,
|
||||
name: 'metrics-fake_hosts',
|
||||
id: DATA_VIEW_ID,
|
||||
title: 'metrics-fake_hosts',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo');
|
||||
await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo');
|
||||
await esClient.deleteByQuery({
|
||||
index: THRESHOLD_RULE_ALERT_INDEX,
|
||||
query: { term: { 'kibana.alert.rule.uuid': ruleId } },
|
||||
});
|
||||
await esClient.deleteByQuery({
|
||||
index: '.kibana-event-log-*',
|
||||
query: { term: { 'kibana.alert.rule.consumer': 'alerts' } },
|
||||
});
|
||||
await deleteDataView({
|
||||
supertest,
|
||||
id: DATA_VIEW_ID,
|
||||
});
|
||||
await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]);
|
||||
await cleanup({ esClient, logger });
|
||||
});
|
||||
|
||||
describe('Rule creation', () => {
|
||||
it('creates rule successfully', async () => {
|
||||
actionId = await createIndexConnector({
|
||||
supertest,
|
||||
name: 'Index Connector: Threshold API test',
|
||||
indexName: ALERT_ACTION_INDEX,
|
||||
});
|
||||
|
||||
const createdRule = await createRule({
|
||||
supertest,
|
||||
tags: ['observability'],
|
||||
consumer: 'alerts',
|
||||
name: 'Threshold rule',
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
params: {
|
||||
criteria: [
|
||||
{
|
||||
aggType: Aggregators.CUSTOM,
|
||||
comparator: Comparator.GT,
|
||||
threshold: [0.5],
|
||||
timeSize: 5,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [
|
||||
{ name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE },
|
||||
],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: {
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
index: DATA_VIEW_ID,
|
||||
},
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: FIRED_ACTIONS_ID,
|
||||
id: actionId,
|
||||
params: {
|
||||
documents: [
|
||||
{
|
||||
ruleType: '{{rule.type}}',
|
||||
},
|
||||
],
|
||||
},
|
||||
frequency: {
|
||||
notify_when: 'onActionGroupChange',
|
||||
throttle: null,
|
||||
summary: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
ruleId = createdRule.id;
|
||||
expect(ruleId).not.to.be(undefined);
|
||||
});
|
||||
|
||||
it('should be active', async () => {
|
||||
const executionStatus = await waitForRuleStatus({
|
||||
id: ruleId,
|
||||
expectedStatus: 'active',
|
||||
supertest,
|
||||
});
|
||||
expect(executionStatus.status).to.be('active');
|
||||
});
|
||||
|
||||
it('should set correct information in the alert document', async () => {
|
||||
const resp = await waitForAlertInIndex({
|
||||
esClient,
|
||||
indexName: THRESHOLD_RULE_ALERT_INDEX,
|
||||
ruleId,
|
||||
});
|
||||
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.category',
|
||||
'Threshold (Technical Preview)'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0);
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.rule_type_id',
|
||||
'observability.rules.threshold'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default');
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.tags')
|
||||
.contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired');
|
||||
expect(resp.hits.hits[0]._source).property('tags').contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open');
|
||||
expect(resp.hits.hits[0]._source).property('event.kind', 'signal');
|
||||
expect(resp.hits.hits[0]._source).property('event.action', 'open');
|
||||
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.parameters')
|
||||
.eql({
|
||||
criteria: [
|
||||
{
|
||||
aggType: 'custom',
|
||||
comparator: '>',
|
||||
threshold: [0.5],
|
||||
timeSize: 5,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* 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 { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types';
|
||||
import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor';
|
||||
import expect from '@kbn/expect';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { createIndexConnector, createRule } from '../helpers/alerting_api_helper';
|
||||
import { createDataView, deleteDataView } from '../helpers/data_view';
|
||||
import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esClient = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
|
||||
describe('Threshold rule - AVG - PCT - NoData', () => {
|
||||
const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default';
|
||||
const ALERT_ACTION_INDEX = 'alert-action-threshold';
|
||||
const DATA_VIEW_ID = 'data-view-id-no-data';
|
||||
let actionId: string;
|
||||
let ruleId: string;
|
||||
|
||||
before(async () => {
|
||||
await createDataView({
|
||||
supertest,
|
||||
name: 'no-data-pattern',
|
||||
id: DATA_VIEW_ID,
|
||||
title: 'no-data-pattern',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo');
|
||||
await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo');
|
||||
await esClient.deleteByQuery({
|
||||
index: THRESHOLD_RULE_ALERT_INDEX,
|
||||
query: { term: { 'kibana.alert.rule.uuid': ruleId } },
|
||||
});
|
||||
await esClient.deleteByQuery({
|
||||
index: '.kibana-event-log-*',
|
||||
query: { term: { 'kibana.alert.rule.consumer': 'alerts' } },
|
||||
});
|
||||
await deleteDataView({
|
||||
supertest,
|
||||
id: DATA_VIEW_ID,
|
||||
});
|
||||
});
|
||||
|
||||
describe('Rule creation', () => {
|
||||
it('creates rule successfully', async () => {
|
||||
actionId = await createIndexConnector({
|
||||
supertest,
|
||||
name: 'Index Connector: Threshold API test',
|
||||
indexName: ALERT_ACTION_INDEX,
|
||||
});
|
||||
|
||||
const createdRule = await createRule({
|
||||
supertest,
|
||||
tags: ['observability'],
|
||||
consumer: 'alerts',
|
||||
name: 'Threshold rule',
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
params: {
|
||||
criteria: [
|
||||
{
|
||||
aggType: Aggregators.CUSTOM,
|
||||
comparator: Comparator.GT,
|
||||
threshold: [0.5],
|
||||
timeSize: 5,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [
|
||||
{ name: 'A', field: 'system.cpu.user.pct', aggType: Aggregators.AVERAGE },
|
||||
],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: {
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
index: DATA_VIEW_ID,
|
||||
},
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: FIRED_ACTIONS_ID,
|
||||
id: actionId,
|
||||
params: {
|
||||
documents: [
|
||||
{
|
||||
ruleType: '{{rule.type}}',
|
||||
},
|
||||
],
|
||||
},
|
||||
frequency: {
|
||||
notify_when: 'onActionGroupChange',
|
||||
throttle: null,
|
||||
summary: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
ruleId = createdRule.id;
|
||||
expect(ruleId).not.to.be(undefined);
|
||||
});
|
||||
|
||||
it('should be active', async () => {
|
||||
const executionStatus = await waitForRuleStatus({
|
||||
id: ruleId,
|
||||
expectedStatus: 'active',
|
||||
supertest,
|
||||
});
|
||||
expect(executionStatus.status).to.be('active');
|
||||
});
|
||||
|
||||
it('should set correct information in the alert document', async () => {
|
||||
const resp = await waitForAlertInIndex({
|
||||
esClient,
|
||||
indexName: THRESHOLD_RULE_ALERT_INDEX,
|
||||
ruleId,
|
||||
});
|
||||
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.category',
|
||||
'Threshold (Technical Preview)'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0);
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.rule_type_id',
|
||||
'observability.rules.threshold'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default');
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.tags')
|
||||
.contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.nodata');
|
||||
expect(resp.hits.hits[0]._source).property('tags').contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open');
|
||||
expect(resp.hits.hits[0]._source).property('event.kind', 'signal');
|
||||
expect(resp.hits.hits[0]._source).property('event.action', 'open');
|
||||
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.parameters')
|
||||
.eql({
|
||||
criteria: [
|
||||
{
|
||||
aggType: 'custom',
|
||||
comparator: '>',
|
||||
threshold: [0.5],
|
||||
timeSize: 5,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [{ name: 'A', field: 'system.cpu.user.pct', aggType: 'avg' }],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: {
|
||||
index: 'data-view-id-no-data',
|
||||
query: { query: '', language: 'kuery' },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* 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 { cleanup, generate } from '@kbn/infra-forge';
|
||||
import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types';
|
||||
import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor';
|
||||
import expect from '@kbn/expect';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { createIndexConnector, createRule } from '../helpers/alerting_api_helper';
|
||||
import { createDataView, deleteDataView } from '../helpers/data_view';
|
||||
import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esClient = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const esDeleteAllIndices = getService('esDeleteAllIndices');
|
||||
const logger = getService('log');
|
||||
|
||||
describe('Threshold rule - CUSTOM_EQ - AVG - BYTES - FIRED', () => {
|
||||
const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default';
|
||||
const ALERT_ACTION_INDEX = 'alert-action-threshold';
|
||||
const DATA_VIEW_ID = 'data-view-id';
|
||||
let infraDataIndex: string;
|
||||
let actionId: string;
|
||||
let ruleId: string;
|
||||
|
||||
before(async () => {
|
||||
infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger });
|
||||
await createDataView({
|
||||
supertest,
|
||||
name: 'metrics-fake_hosts',
|
||||
id: DATA_VIEW_ID,
|
||||
title: 'metrics-fake_hosts',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo');
|
||||
await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo');
|
||||
await esClient.deleteByQuery({
|
||||
index: THRESHOLD_RULE_ALERT_INDEX,
|
||||
query: { term: { 'kibana.alert.rule.uuid': ruleId } },
|
||||
});
|
||||
await esClient.deleteByQuery({
|
||||
index: '.kibana-event-log-*',
|
||||
query: { term: { 'kibana.alert.rule.consumer': 'alerts' } },
|
||||
});
|
||||
await deleteDataView({
|
||||
supertest,
|
||||
id: DATA_VIEW_ID,
|
||||
});
|
||||
await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]);
|
||||
await cleanup({ esClient, logger });
|
||||
});
|
||||
|
||||
describe('Rule creation', () => {
|
||||
it('creates rule successfully', async () => {
|
||||
actionId = await createIndexConnector({
|
||||
supertest,
|
||||
name: 'Index Connector: Threshold API test',
|
||||
indexName: ALERT_ACTION_INDEX,
|
||||
});
|
||||
|
||||
const createdRule = await createRule({
|
||||
supertest,
|
||||
tags: ['observability'],
|
||||
consumer: 'alerts',
|
||||
name: 'Threshold rule',
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
params: {
|
||||
criteria: [
|
||||
{
|
||||
aggType: Aggregators.CUSTOM,
|
||||
comparator: Comparator.GT,
|
||||
threshold: [0.9],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [
|
||||
{ name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE },
|
||||
{ name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE },
|
||||
],
|
||||
equation: 'A / B ',
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: {
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
index: DATA_VIEW_ID,
|
||||
},
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: FIRED_ACTIONS_ID,
|
||||
id: actionId,
|
||||
params: {
|
||||
documents: [
|
||||
{
|
||||
ruleType: '{{rule.type}}',
|
||||
},
|
||||
],
|
||||
},
|
||||
frequency: {
|
||||
notify_when: 'onActionGroupChange',
|
||||
throttle: null,
|
||||
summary: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
ruleId = createdRule.id;
|
||||
expect(ruleId).not.to.be(undefined);
|
||||
});
|
||||
|
||||
it('should be active', async () => {
|
||||
const executionStatus = await waitForRuleStatus({
|
||||
id: ruleId,
|
||||
expectedStatus: 'active',
|
||||
supertest,
|
||||
});
|
||||
expect(executionStatus.status).to.be('active');
|
||||
});
|
||||
|
||||
it('should set correct information in the alert document', async () => {
|
||||
const resp = await waitForAlertInIndex({
|
||||
esClient,
|
||||
indexName: THRESHOLD_RULE_ALERT_INDEX,
|
||||
ruleId,
|
||||
});
|
||||
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.category',
|
||||
'Threshold (Technical Preview)'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0);
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.rule_type_id',
|
||||
'observability.rules.threshold'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default');
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.tags')
|
||||
.contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired');
|
||||
expect(resp.hits.hits[0]._source).property('tags').contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open');
|
||||
expect(resp.hits.hits[0]._source).property('event.kind', 'signal');
|
||||
expect(resp.hits.hits[0]._source).property('event.action', 'open');
|
||||
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.parameters')
|
||||
.eql({
|
||||
criteria: [
|
||||
{
|
||||
aggType: Aggregators.CUSTOM,
|
||||
comparator: Comparator.GT,
|
||||
threshold: [0.9],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [
|
||||
{ name: 'A', field: 'system.network.in.bytes', aggType: Aggregators.AVERAGE },
|
||||
{ name: 'B', field: 'system.network.out.bytes', aggType: Aggregators.AVERAGE },
|
||||
],
|
||||
equation: 'A / B ',
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* 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 { cleanup, generate } from '@kbn/infra-forge';
|
||||
import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types';
|
||||
import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor';
|
||||
import expect from '@kbn/expect';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { createIndexConnector, createRule } from '../helpers/alerting_api_helper';
|
||||
import { createDataView, deleteDataView } from '../helpers/data_view';
|
||||
import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esClient = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const esDeleteAllIndices = getService('esDeleteAllIndices');
|
||||
const logger = getService('log');
|
||||
|
||||
describe('Threshold rule - DOCUMENTS_COUNT - FIRED', () => {
|
||||
const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default';
|
||||
const ALERT_ACTION_INDEX = 'alert-action-threshold';
|
||||
const DATA_VIEW_ID = 'data-view-id';
|
||||
let infraDataIndex: string;
|
||||
let actionId: string;
|
||||
let ruleId: string;
|
||||
|
||||
before(async () => {
|
||||
infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger });
|
||||
await createDataView({
|
||||
supertest,
|
||||
name: 'metrics-fake_hosts',
|
||||
id: DATA_VIEW_ID,
|
||||
title: 'metrics-fake_hosts',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo');
|
||||
await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo');
|
||||
await esClient.deleteByQuery({
|
||||
index: THRESHOLD_RULE_ALERT_INDEX,
|
||||
query: { term: { 'kibana.alert.rule.uuid': ruleId } },
|
||||
});
|
||||
await esClient.deleteByQuery({
|
||||
index: '.kibana-event-log-*',
|
||||
query: { term: { 'kibana.alert.rule.consumer': 'alerts' } },
|
||||
});
|
||||
await deleteDataView({
|
||||
supertest,
|
||||
id: DATA_VIEW_ID,
|
||||
});
|
||||
await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]);
|
||||
await cleanup({ esClient, logger });
|
||||
});
|
||||
|
||||
describe('Rule creation', () => {
|
||||
it('creates rule successfully', async () => {
|
||||
actionId = await createIndexConnector({
|
||||
supertest,
|
||||
name: 'Index Connector: Threshold API test',
|
||||
indexName: ALERT_ACTION_INDEX,
|
||||
});
|
||||
|
||||
const createdRule = await createRule({
|
||||
supertest,
|
||||
tags: ['observability'],
|
||||
consumer: 'alerts',
|
||||
name: 'Threshold rule',
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
params: {
|
||||
criteria: [
|
||||
{
|
||||
aggType: Aggregators.CUSTOM,
|
||||
comparator: Comparator.GT,
|
||||
threshold: [2],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [{ name: 'A', filter: '', aggType: Aggregators.COUNT }],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: {
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
index: DATA_VIEW_ID,
|
||||
},
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: FIRED_ACTIONS_ID,
|
||||
id: actionId,
|
||||
params: {
|
||||
documents: [
|
||||
{
|
||||
ruleType: '{{rule.type}}',
|
||||
},
|
||||
],
|
||||
},
|
||||
frequency: {
|
||||
notify_when: 'onActionGroupChange',
|
||||
throttle: null,
|
||||
summary: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
ruleId = createdRule.id;
|
||||
expect(ruleId).not.to.be(undefined);
|
||||
});
|
||||
|
||||
it('should be active', async () => {
|
||||
const executionStatus = await waitForRuleStatus({
|
||||
id: ruleId,
|
||||
expectedStatus: 'active',
|
||||
supertest,
|
||||
});
|
||||
expect(executionStatus.status).to.be('active');
|
||||
});
|
||||
|
||||
it('should set correct information in the alert document', async () => {
|
||||
const resp = await waitForAlertInIndex({
|
||||
esClient,
|
||||
indexName: THRESHOLD_RULE_ALERT_INDEX,
|
||||
ruleId,
|
||||
});
|
||||
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.category',
|
||||
'Threshold (Technical Preview)'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0);
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.rule_type_id',
|
||||
'observability.rules.threshold'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default');
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.tags')
|
||||
.contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired');
|
||||
expect(resp.hits.hits[0]._source).property('tags').contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', '*');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open');
|
||||
expect(resp.hits.hits[0]._source).property('event.kind', 'signal');
|
||||
expect(resp.hits.hits[0]._source).property('event.action', 'open');
|
||||
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.parameters')
|
||||
.eql({
|
||||
criteria: [
|
||||
{
|
||||
aggType: 'custom',
|
||||
comparator: '>',
|
||||
threshold: [2],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [{ name: 'A', filter: '', aggType: 'count' }],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* 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 { cleanup, generate } from '@kbn/infra-forge';
|
||||
import { Aggregators, Comparator } from '@kbn/observability-plugin/common/threshold_rule/types';
|
||||
import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/threshold/threshold_executor';
|
||||
import expect from '@kbn/expect';
|
||||
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
import { createIndexConnector, createRule } from '../helpers/alerting_api_helper';
|
||||
import { createDataView, deleteDataView } from '../helpers/data_view';
|
||||
import {
|
||||
waitForAlertInIndex,
|
||||
waitForDocumentInIndex,
|
||||
waitForRuleStatus,
|
||||
} from '../helpers/alerting_wait_for_helpers';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esClient = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const esDeleteAllIndices = getService('esDeleteAllIndices');
|
||||
const logger = getService('log');
|
||||
let alertId: string;
|
||||
let startedAt: string;
|
||||
|
||||
describe('Threshold rule - GROUP_BY - FIRED', () => {
|
||||
const THRESHOLD_RULE_ALERT_INDEX = '.alerts-observability.threshold.alerts-default';
|
||||
const ALERT_ACTION_INDEX = 'alert-action-threshold';
|
||||
const DATA_VIEW_ID = 'data-view-id';
|
||||
let infraDataIndex: string;
|
||||
let actionId: string;
|
||||
let ruleId: string;
|
||||
|
||||
before(async () => {
|
||||
infraDataIndex = await generate({ esClient, lookback: 'now-15m', logger });
|
||||
await createDataView({
|
||||
supertest,
|
||||
name: 'metrics-fake_hosts',
|
||||
id: DATA_VIEW_ID,
|
||||
title: 'metrics-fake_hosts',
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await supertest.delete(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'foo');
|
||||
await supertest.delete(`/api/actions/connector/${actionId}`).set('kbn-xsrf', 'foo');
|
||||
await esClient.deleteByQuery({
|
||||
index: THRESHOLD_RULE_ALERT_INDEX,
|
||||
query: { term: { 'kibana.alert.rule.uuid': ruleId } },
|
||||
});
|
||||
await esClient.deleteByQuery({
|
||||
index: '.kibana-event-log-*',
|
||||
query: { term: { 'kibana.alert.rule.consumer': 'alerts' } },
|
||||
});
|
||||
await deleteDataView({
|
||||
supertest,
|
||||
id: DATA_VIEW_ID,
|
||||
});
|
||||
await esDeleteAllIndices([ALERT_ACTION_INDEX, infraDataIndex]);
|
||||
await cleanup({ esClient, logger });
|
||||
});
|
||||
|
||||
describe('Rule creation', () => {
|
||||
it('creates rule successfully', async () => {
|
||||
actionId = await createIndexConnector({
|
||||
supertest,
|
||||
name: 'Index Connector: Threshold API test',
|
||||
indexName: ALERT_ACTION_INDEX,
|
||||
});
|
||||
|
||||
const createdRule = await createRule({
|
||||
supertest,
|
||||
tags: ['observability'],
|
||||
consumer: 'alerts',
|
||||
name: 'Threshold rule',
|
||||
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
|
||||
params: {
|
||||
criteria: [
|
||||
{
|
||||
aggType: Aggregators.CUSTOM,
|
||||
comparator: Comparator.GT_OR_EQ,
|
||||
threshold: [0.2],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [
|
||||
{ name: 'A', field: 'system.cpu.total.norm.pct', aggType: Aggregators.AVERAGE },
|
||||
],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: {
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kuery',
|
||||
},
|
||||
index: DATA_VIEW_ID,
|
||||
},
|
||||
groupBy: ['host.name'],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
group: FIRED_ACTIONS_ID,
|
||||
id: actionId,
|
||||
params: {
|
||||
documents: [
|
||||
{
|
||||
ruleType: '{{rule.type}}',
|
||||
alertDetailsUrl: '{{context.alertDetailsUrl}}',
|
||||
reason: '{{context.reason}}',
|
||||
value: '{{context.value}}',
|
||||
host: '{{context.host}}',
|
||||
},
|
||||
],
|
||||
},
|
||||
frequency: {
|
||||
notify_when: 'onActionGroupChange',
|
||||
throttle: null,
|
||||
summary: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
ruleId = createdRule.id;
|
||||
expect(ruleId).not.to.be(undefined);
|
||||
});
|
||||
|
||||
it('should be active', async () => {
|
||||
const executionStatus = await waitForRuleStatus({
|
||||
id: ruleId,
|
||||
expectedStatus: 'active',
|
||||
supertest,
|
||||
});
|
||||
expect(executionStatus.status).to.be('active');
|
||||
});
|
||||
|
||||
it('should set correct information in the alert document', async () => {
|
||||
const resp = await waitForAlertInIndex({
|
||||
esClient,
|
||||
indexName: THRESHOLD_RULE_ALERT_INDEX,
|
||||
ruleId,
|
||||
});
|
||||
alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid'];
|
||||
startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start'];
|
||||
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.category',
|
||||
'Threshold (Technical Preview)'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0);
|
||||
expect(resp.hits.hits[0]._source).property(
|
||||
'kibana.alert.rule.rule_type_id',
|
||||
'observability.rules.threshold'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.rule.uuid', ruleId);
|
||||
expect(resp.hits.hits[0]._source).property('kibana.space_ids').contain('default');
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.tags')
|
||||
.contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.action_group', 'threshold.fired');
|
||||
expect(resp.hits.hits[0]._source).property('tags').contain('observability');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.instance.id', 'host-0');
|
||||
expect(resp.hits.hits[0]._source).property('kibana.alert.workflow_status', 'open');
|
||||
expect(resp.hits.hits[0]._source).property('event.kind', 'signal');
|
||||
expect(resp.hits.hits[0]._source).property('event.action', 'open');
|
||||
|
||||
expect(resp.hits.hits[0]._source).property('host.name', 'host-0');
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('host.mac')
|
||||
.eql(['00-00-5E-00-53-23', '00-00-5E-00-53-24']);
|
||||
expect(resp.hits.hits[0]._source).property('container.id', 'container-0');
|
||||
expect(resp.hits.hits[0]._source).property('container.name', 'container-name');
|
||||
expect(resp.hits.hits[0]._source).not.property('container.cpu');
|
||||
|
||||
expect(resp.hits.hits[0]._source)
|
||||
.property('kibana.alert.rule.parameters')
|
||||
.eql({
|
||||
criteria: [
|
||||
{
|
||||
aggType: 'custom',
|
||||
comparator: '>=',
|
||||
threshold: [0.2],
|
||||
timeSize: 1,
|
||||
timeUnit: 'm',
|
||||
customMetrics: [{ name: 'A', field: 'system.cpu.total.norm.pct', aggType: 'avg' }],
|
||||
},
|
||||
],
|
||||
alertOnNoData: true,
|
||||
alertOnGroupDisappear: true,
|
||||
searchConfiguration: { index: 'data-view-id', query: { query: '', language: 'kuery' } },
|
||||
groupBy: ['host.name'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should set correct action variables', async () => {
|
||||
const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString();
|
||||
const resp = await waitForDocumentInIndex<{
|
||||
ruleType: string;
|
||||
alertDetailsUrl: string;
|
||||
reason: string;
|
||||
value: string;
|
||||
host: string;
|
||||
}>({
|
||||
esClient,
|
||||
indexName: ALERT_ACTION_INDEX,
|
||||
});
|
||||
|
||||
expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.threshold');
|
||||
expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql(
|
||||
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
|
||||
);
|
||||
expect(resp.hits.hits[0]._source?.reason).eql(
|
||||
'Custom equation is 0.8 in the last 1 min for host-0. Alert when >= 0.2.'
|
||||
);
|
||||
expect(resp.hits.hits[0]._source?.value).eql('0.8');
|
||||
expect(resp.hits.hits[0]._source?.host).eql(
|
||||
'{"name":"host-0","mac":["00-00-5E-00-53-23","00-00-5E-00-53-24"]}'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -26,6 +26,9 @@
|
|||
"@kbn/telemetry-plugin",
|
||||
"@kbn/telemetry-collection-xpack-plugin",
|
||||
"@kbn/telemetry-tools",
|
||||
"@kbn/infra-plugin",
|
||||
"@kbn/observability-plugin",
|
||||
"@kbn/infra-forge",
|
||||
"@kbn/ftr-common-functional-services",
|
||||
"@kbn/core-chrome-browser",
|
||||
"@kbn/default-nav-ml",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue