Skip to content

Commit

Permalink
move alertingApi and sloApi services in the new observability_solutio…
Browse files Browse the repository at this point in the history
…n_api_integration folder
  • Loading branch information
mgiota committed May 10, 2024
1 parent 5b687c0 commit 265b687
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export function createTestConfig(options) {
return {
...xPackApiIntegrationTestsConfig.getAll(),
testFiles: options.testFiles,
services,
services: {
...services,
...options.services,
},
junit: {
reportName: 'X-Pack Οbservability Solution API Integration Tests',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export function createTestConfig(options: CreateTestConfigOptions) {
...svlSharedConfig.getAll(),
services: {
...services,
...options.services,
},
kbnTestServer: {
...svlSharedConfig.get('kbnTestServer'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
*/
import { GenericFtrProviderContext } from '@kbn/test';

import { services } from '../../test_serverless/api_integration/services';
import { services } from '../api_integration/services';

export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* 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 type {
AggregationsAggregate,
SearchResponse,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';

import { MetricThresholdParams } from '@kbn/infra-plugin/common/alerting/metrics';
import { ThresholdParams } from '@kbn/observability-plugin/common/custom_threshold_rule/types';
import { SloBurnRateRuleParams } from './slo_api';
import { FtrProviderContext } from '../ftr_provider_context';

export function AlertingApiProvider({ getService }: FtrProviderContext) {
const retry = getService('retry');
const supertest = getService('supertest');
const es = getService('es');
const requestTimeout = 30 * 1000;
const retryTimeout = 120 * 1000;
const logger = getService('log');

return {
async waitForRuleStatus({
ruleId,
expectedStatus,
}: {
ruleId: string;
expectedStatus: string;
}) {
if (!ruleId) {
throw new Error(`'ruleId' is undefined`);
}
return await retry.tryForTime(retryTimeout, async () => {
const response = await supertest
.get(`/api/alerting/rule/${ruleId}`)
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo')
.timeout(requestTimeout);
const { execution_status: executionStatus } = response.body || {};
const { status } = executionStatus || {};
if (status !== expectedStatus) {
throw new Error(`waitForStatus(${expectedStatus}): got ${status}`);
}
return executionStatus?.status;
});
},

async waitForDocumentInIndex<T>({
indexName,
docCountTarget = 1,
}: {
indexName: string;
docCountTarget?: number;
}): Promise<SearchResponse<T, Record<string, AggregationsAggregate>>> {
return await retry.tryForTime(retryTimeout, async () => {
const response = await es.search<T>({
index: indexName,
rest_total_hits_as_int: true,
});
logger.debug(`Found ${response.hits.total} docs, looking for atleast ${docCountTarget}.`);
if (!response.hits.total || response.hits.total < docCountTarget) {
throw new Error('No hits found');
}
return response;
});
},

async waitForAlertInIndex<T>({
indexName,
ruleId,
}: {
indexName: string;
ruleId: string;
}): Promise<SearchResponse<T, Record<string, AggregationsAggregate>>> {
if (!ruleId) {
throw new Error(`'ruleId' is undefined`);
}
return await retry.tryForTime(retryTimeout, async () => {
const response = await es.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;
});
},

async createIndexConnector({ name, indexName }: { name: string; indexName: string }) {
const { body } = await supertest
.post(`/api/actions/connector`)
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo')
.send({
name,
config: {
index: indexName,
refresh: true,
},
connector_type_id: '.index',
});
return body.id as string;
},

async createRule({
name,
ruleTypeId,
params,
actions = [],
tags = [],
schedule,
consumer,
}: {
ruleTypeId: string;
name: string;
params: MetricThresholdParams | ThresholdParams | SloBurnRateRuleParams;
actions?: any[];
tags?: any[];
schedule?: { interval: string };
consumer: string;
}) {
const { body } = await supertest
.post(`/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
// .set('x-elastic-internal-origin', 'foo')
.send({
params,
consumer,
schedule: schedule || {
interval: '5m',
},
tags,
name,
rule_type_id: ruleTypeId,
actions,
});
return body;
},

async findRule(ruleId: string) {
if (!ruleId) {
throw new Error(`'ruleId' is undefined`);
}
const response = await supertest
.get('/api/alerting/rules/_find')
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo');
return response.body.data.find((obj: any) => obj.id === ruleId);
},
};
}
185 changes: 185 additions & 0 deletions x-pack/test/observability_solution_api_integration/services/slo_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
* 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';

type DurationUnit = 'm' | 'h' | 'd' | 'w' | 'M';

interface Duration {
value: number;
unit: DurationUnit;
}

interface WindowSchema {
id: string;
burnRateThreshold: number;
maxBurnRateThreshold: number;
longWindow: Duration;
shortWindow: Duration;
actionGroup: string;
}

interface Dependency {
ruleId: string;
actionGroupsToSuppressOn: string[];
}

export interface SloBurnRateRuleParams {
sloId: string;
windows: WindowSchema[];
dependencies?: Dependency[];
}

interface SloParams {
id?: string;
name: string;
description: string;
indicator: {
type: 'sli.kql.custom';
params: {
index: string;
good: string;
total: string;
timestampField: string;
};
};
timeWindow: {
duration: string;
type: string;
};
budgetingMethod: string;
objective: {
target: number;
};
groupBy: string;
}

export function SloApiProvider({ getService }: FtrProviderContext) {
const es = getService('es');
const supertest = getService('supertest');
const retry = getService('retry');
const requestTimeout = 30 * 1000;
const retryTimeout = 180 * 1000;

return {
async create(slo: SloParams) {
const { body } = await supertest
.post(`/api/observability/slos`)
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo')
.send(slo);

return body;
},

async delete(sloId: string) {
const response = await supertest
.delete(`/api/observability/slos/${sloId}`)
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo');
return response;
},

async waitForSloToBeDeleted(sloId: string) {
if (!sloId) {
throw new Error(`sloId is undefined`);
}
return await retry.tryForTime(retryTimeout, async () => {
const response = await supertest
.delete(`/api/observability/slos/${sloId}`)
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo')
.timeout(requestTimeout);
if (!response.ok) {
throw new Error(`slodId [${sloId}] was not deleted`);
}
return response;
});
},

async waitForSloCreated({ sloId }: { sloId: string }) {
if (!sloId) {
throw new Error(`'sloId is undefined`);
}
return await retry.tryForTime(retryTimeout, async () => {
const response = await supertest
.get(`/api/observability/slos/${sloId}`)
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo')
.timeout(requestTimeout);
if (response.body.id === undefined) {
throw new Error(`No slo with id ${sloId} found`);
}
return response.body;
});
},

async waitForSloSummaryTempIndexToExist(index: string) {
if (!index) {
throw new Error(`index is undefined`);
}

return await retry.tryForTime(retryTimeout, async () => {
const indexExists = await es.indices.exists({ index, allow_no_indices: false });
if (!indexExists) {
throw new Error(`index ${index} should exist`);
}
return indexExists;
});
},

async getSloData({ sloId, indexName }: { sloId: string; indexName: string }) {
const response = await es.search({
index: indexName,
body: {
query: {
bool: {
filter: [{ term: { 'slo.id': sloId } }],
},
},
},
});
return response;
},
async waitForSloData({ sloId, indexName }: { sloId: string; indexName: string }) {
return await retry.tryForTime(retryTimeout, async () => {
const response = await es.search({
index: indexName,
body: {
query: {
bool: {
filter: [{ term: { 'slo.id': sloId } }],
},
},
},
});
if (response.hits.hits.length === 0) {
throw new Error(`No hits found at index [${indexName}] for slo [${sloId}] `);
}
return response;
});
},
async deleteAllSLOs() {
const response = await supertest
.get(`/api/observability/slos/_definitions`)
.set('kbn-xsrf', 'true')
.set('x-elastic-internal-origin', 'foo')
.send()
.expect(200);
await Promise.all(
response.body.results.map(({ id }: { id: string }) => {
return supertest
.delete(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.set('x-elastic-internal-origin', 'foo')
.send()
.expect(204);
})
);
},
};
}
Loading

0 comments on commit 265b687

Please sign in to comment.