Skip to content

Commit

Permalink
[AO][SERVERLESS] Create serverless integration tests for the Threshol…
Browse files Browse the repository at this point in the history
…d rule #161458 (#161569)

## 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.

---------
  • Loading branch information
fkanout authored Jul 24, 2023
1 parent c3a5da8 commit 6bae659
Show file tree
Hide file tree
Showing 14 changed files with 1,252 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const generateEvent = (index: number, timestamp: Moment, interval: number
cores: 4,
total: {
norm: {
pct: randomBetween(),
pct: 0.8,
},
},
user: {
Expand Down Expand Up @@ -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),
},
},
},
},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
},
},
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ export interface ThresholdParams {
alertOnNoData?: boolean;
alertOnGroupDisappear?: boolean;
searchConfiguration: SerializedSearchSourceFields;
groupBy?: string[];
}

interface BaseMetricExpressionParams {
Expand Down
5 changes: 4 additions & 1 deletion x-pack/test_serverless/api_integration/config.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 }
);
}
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});
}
Loading

0 comments on commit 6bae659

Please sign in to comment.