[SLO] api integration tests stateful (#173236)

## Summary

Adds tests for basic SLO api routes, including:
1. Find slos
2. Get slo by id
3. Get slo definitions
4. Get slo instances
5. Create slo
6. Delete slo
7. Update slo
8. Reset slo

The create slo tests include some basic assertions that the resulting
calculated SLO is correct.

These tests do not cover:
1. SLOs in spaces
2. SLO permissions model

Passed flaky test runner for 200 iterations:
https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/4595#_

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: shahzad31 <shahzad31comp@gmail.com>
Co-authored-by: Kevin Delemme <kdelemme@gmail.com>
This commit is contained in:
Dominique Clarke 2024-01-16 10:37:45 -05:00 committed by GitHub
parent 5d55ab930c
commit 96bfb638ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1942 additions and 2 deletions

View file

@ -214,6 +214,7 @@ enabled:
- x-pack/test/api_integration/apis/stats/config.ts
- x-pack/test/api_integration/apis/status/config.ts
- x-pack/test/api_integration/apis/synthetics/config.ts
- x-pack/test/api_integration/apis/slos/config.ts
- x-pack/test/api_integration/apis/telemetry/config.ts
- x-pack/test/api_integration/apis/transform/config.ts
- x-pack/test/api_integration/apis/upgrade_assistant/config.ts
@ -515,4 +516,4 @@ enabled:
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts
- x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts

View file

@ -21,6 +21,19 @@ const generateNetworkData = lodash.memoize(() => {
return networkDataCount;
});
let currentStatic = 0;
const staticBetween = (end = 1, step = 0.1) => {
{
if (currentStatic + step > end) {
currentStatic = 0;
} else {
currentStatic = currentStatic + step;
}
return currentStatic;
}
};
export const generateEvent = (index: number, timestamp: Moment, interval: number) => {
const groupIndex = createGroupIndex(index);
return [
@ -142,13 +155,14 @@ export const generateEvent = (index: number, timestamp: Moment, interval: number
},
},
user: {
pct: randomBetween(1, 4),
pct: staticBetween(1, 1),
},
system: {
pct: randomBetween(1, 4),
},
},
},
tags: [`${randomBetween(1, 4, 1)}`],
},
];
};

View file

@ -0,0 +1,29 @@
/*
* 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 { FtrConfigProviderContext } from '@kbn/test';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const baseIntegrationTestsConfig = await readConfigFile(require.resolve('../../config.ts'));
return {
...baseIntegrationTestsConfig.getAll(),
testFiles: [require.resolve('.')],
// overriding default timeouts from packages/kbn-test/src/functional_test_runner/lib/config/schema.ts
// so we can easily adjust them for serverless where needed
timeouts: {
find: 10 * 1000,
try: 120 * 1000,
waitFor: 20 * 1000,
esRequestTimeout: 30 * 1000,
kibanaReportCompletion: 60 * 1000,
kibanaStabilize: 15 * 1000,
navigateStatusPageCheck: 250,
waitForExists: 2500,
},
};
}

View file

@ -0,0 +1,273 @@
/*
* 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 { CreateSLOInput } from '@kbn/slo-schema';
import { SO_SLO_TYPE } from '@kbn/observability-plugin/server/saved_objects';
import { FtrProviderContext } from '../../ftr_provider_context';
import { sloData } from './fixtures/create_slo';
export default function ({ getService }: FtrProviderContext) {
describe('Create SLOs', function () {
this.tags('skipCloud');
const supertestAPI = getService('supertest');
const kibanaServer = getService('kibanaServer');
const slo = getService('slo');
let createSLOInput: CreateSLOInput;
before(async () => {
await slo.deleteAllSLOs();
});
beforeEach(() => {
createSLOInput = sloData;
});
afterEach(async () => {
await slo.deleteAllSLOs();
});
it('creates a new slo and transforms', async () => {
const apiResponse = await supertestAPI
.post('/api/observability/slos')
.set('kbn-xsrf', 'true')
.set('x-elastic-internal-origin', 'foo')
.send(createSLOInput)
.expect(200);
expect(apiResponse.body).property('id');
const { id } = apiResponse.body;
const savedObject = await kibanaServer.savedObjects.find({
type: SO_SLO_TYPE,
});
expect(savedObject.saved_objects.length).eql(1);
expect(savedObject.saved_objects[0].attributes).eql({
budgetingMethod: 'occurrences',
updatedAt: savedObject.saved_objects[0].attributes.updatedAt,
createdAt: savedObject.saved_objects[0].attributes.createdAt,
description: 'Fixture for api integration tests',
enabled: true,
groupBy: 'tags',
id,
indicator: {
params: {
filter: 'system.network.name: eth1',
good: 'container.cpu.user.pct < 1',
index: 'kbn-data-forge*',
timestampField: '@timestamp',
total: 'container.cpu.user.pct: *',
},
type: 'sli.kql.custom',
},
name: 'Test SLO for api integration',
objective: {
target: 0.99,
},
revision: 1,
settings: {
frequency: '1m',
syncDelay: '1m',
},
tags: ['test'],
timeWindow: {
duration: '7d',
type: 'rolling',
},
version: 2,
});
const rollUpTransformResponse = await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// expect roll up transform to be created
expect(rollUpTransformResponse.body).eql({
count: 1,
transforms: [
{
id: `slo-${id}-1`,
authorization: { roles: ['superuser'] },
version: '10.0.0',
create_time: rollUpTransformResponse.body.transforms[0].create_time,
source: {
index: ['kbn-data-forge*'],
query: {
bool: {
filter: [
{ range: { '@timestamp': { gte: 'now-7d/d' } } },
{
bool: {
should: [
{
match: {
'system.network.name': 'eth1',
},
},
],
minimum_should_match: 1,
},
},
],
},
},
runtime_mappings: {
'slo.id': {
type: 'keyword',
script: { source: `emit('${id}')` },
},
'slo.revision': { type: 'long', script: { source: 'emit(1)' } },
},
},
dest: {
index: '.slo-observability.sli-v3',
pipeline: '.slo-observability.sli.pipeline-v3',
},
frequency: '1m',
sync: { time: { field: '@timestamp', delay: '1m' } },
pivot: {
group_by: {
'slo.id': { terms: { field: 'slo.id' } },
'slo.revision': { terms: { field: 'slo.revision' } },
'slo.instanceId': { terms: { field: 'tags' } },
'slo.groupings.tags': { terms: { field: 'tags' } },
'@timestamp': { date_histogram: { field: '@timestamp', fixed_interval: '1m' } },
},
aggregations: {
'slo.numerator': {
filter: {
bool: {
should: [{ range: { 'container.cpu.user.pct': { lt: '1' } } }],
minimum_should_match: 1,
},
},
},
'slo.denominator': {
filter: {
bool: {
should: [{ exists: { field: 'container.cpu.user.pct' } }],
minimum_should_match: 1,
},
},
},
},
},
description: `Rolled-up SLI data for SLO: Test SLO for api integration [id: ${id}, revision: 1]`,
settings: { deduce_mappings: false, unattended: true },
_meta: { version: 3, managed: true, managed_by: 'observability' },
},
],
});
const summaryTransform = await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// expect summary transform to be created
expect(summaryTransform.body).eql({
count: 1,
transforms: [
{
id: `slo-summary-${id}-1`,
authorization: { roles: ['superuser'] },
version: '10.0.0',
create_time: summaryTransform.body.transforms[0].create_time,
source: {
index: ['.slo-observability.sli-v3*'],
query: {
bool: {
filter: [
{ range: { '@timestamp': { gte: 'now-7d/m', lte: 'now/m' } } },
{ term: { 'slo.id': id } },
{ term: { 'slo.revision': 1 } },
],
},
},
},
dest: {
index: '.slo-observability.summary-v3',
pipeline: `.slo-observability.summary.pipeline-${id}-1`,
},
frequency: '1m',
sync: { time: { field: 'event.ingested', delay: '65s' } },
pivot: {
group_by: {
'slo.id': { terms: { field: 'slo.id' } },
'slo.revision': { terms: { field: 'slo.revision' } },
'slo.instanceId': { terms: { field: 'slo.instanceId' } },
'slo.groupings.tags': {
terms: { field: 'slo.groupings.tags' },
},
'service.name': { terms: { field: 'service.name', missing_bucket: true } },
'service.environment': {
terms: { field: 'service.environment', missing_bucket: true },
},
'transaction.name': { terms: { field: 'transaction.name', missing_bucket: true } },
'transaction.type': { terms: { field: 'transaction.type', missing_bucket: true } },
},
aggregations: {
goodEvents: { sum: { field: 'slo.numerator' } },
totalEvents: { sum: { field: 'slo.denominator' } },
sliValue: {
bucket_script: {
buckets_path: { goodEvents: 'goodEvents', totalEvents: 'totalEvents' },
script:
'if (params.totalEvents == 0) { return -1 } else if (params.goodEvents >= params.totalEvents) { return 1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: { bucket_script: { buckets_path: {}, script: '1 - 0.99' } },
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: { errorBudgetConsumed: 'errorBudgetConsumed' },
script: '1 - params.errorBudgetConsumed',
},
},
statusCode: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script: {
source:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= 0.99) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
latestSliTimestamp: { max: { field: '@timestamp' } },
},
},
description: `Summarise the rollup data of SLO: Test SLO for api integration [id: ${id}, revision: 1].`,
settings: { deduce_mappings: false, unattended: true },
_meta: { version: 3, managed: true, managed_by: 'observability' },
},
],
});
});
});
}

View file

@ -0,0 +1,130 @@
/*
* 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 } from '@kbn/infra-forge';
import expect from '@kbn/expect';
import type { CreateSLOInput } from '@kbn/slo-schema';
import { SO_SLO_TYPE } from '@kbn/observability-plugin/server/saved_objects';
import { FtrProviderContext } from '../../ftr_provider_context';
import { sloData } from './fixtures/create_slo';
import { loadTestData } from './helper/load_test_data';
import { SloEsClient } from './helper/es';
export default function ({ getService }: FtrProviderContext) {
describe('Delete SLOs', function () {
this.tags('skipCloud');
const supertestAPI = getService('supertest');
const kibanaServer = getService('kibanaServer');
const esClient = getService('es');
const logger = getService('log');
const slo = getService('slo');
const retry = getService('retry');
const sloEsClient = new SloEsClient(esClient);
let createSLOInput: CreateSLOInput;
before(async () => {
await slo.deleteAllSLOs();
await sloEsClient.deleteTestSourceData();
loadTestData(getService);
});
beforeEach(() => {
createSLOInput = sloData;
});
afterEach(async () => {
await slo.deleteAllSLOs();
});
after(async () => {
await cleanup({ esClient, logger });
await sloEsClient.deleteTestSourceData();
});
it('deletes new slo saved object and transforms', async () => {
const id = await slo.create(createSLOInput);
const savedObject = await kibanaServer.savedObjects.find({
type: SO_SLO_TYPE,
});
expect(savedObject.saved_objects.length).eql(1);
expect(savedObject.saved_objects[0].attributes.id).eql(id);
await retry.tryForTime(300 * 1000, async () => {
// expect summary and rollup data to exist
const sloSummaryResponse = await sloEsClient.getSLOSummaryDataById(id);
const sloRollupResponse = await sloEsClient.getSLORollupDataById(id);
expect(sloSummaryResponse.hits.hits.length > 0).eql(true);
expect(sloRollupResponse.hits.hits.length > 0).eql(true);
const rollUpTransformResponse = await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// expect roll up transform to be created
expect(rollUpTransformResponse.body.transforms[0].id).eql(`slo-${id}-1`);
const summaryTransform = await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// expect summary transform to be created
expect(summaryTransform.body.transforms[0].id).eql(`slo-summary-${id}-1`);
await supertestAPI
.delete(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send()
.expect(204);
});
// await retry.tryForTime(150 * 1000, async () => {
const savedObjectAfterDelete = await kibanaServer.savedObjects.find({
type: SO_SLO_TYPE,
});
// SO should now be deleted
expect(savedObjectAfterDelete.saved_objects.length).eql(0);
// roll up transform should be deleted
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
// summary transform should be deleted
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
// expect summary and rollup documents to be deleted
await retry.tryForTime(60 * 1000, async () => {
const sloSummaryResponseAfterDeletion = await sloEsClient.getSLOSummaryDataById(id);
const sloRollupResponseAfterDeletion = await sloEsClient.getSLORollupDataById(id);
expect(sloSummaryResponseAfterDeletion.hits.hits.length).eql(0);
// sometimes the ingest pipeline ingests one extra document after the transform is stopped
expect(sloRollupResponseAfterDeletion.hits.hits.length <= 1).eql(true);
});
});
});
}

View file

@ -0,0 +1,33 @@
/*
* 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 { CreateSLOInput } from '@kbn/slo-schema';
export const sloData: CreateSLOInput = {
name: 'Test SLO for api integration',
description: 'Fixture for api integration tests',
indicator: {
type: 'sli.kql.custom',
params: {
index: 'kbn-data-forge*',
filter: 'system.network.name: eth1',
good: 'container.cpu.user.pct < 1',
total: 'container.cpu.user.pct: *',
timestampField: '@timestamp',
},
},
budgetingMethod: 'occurrences',
timeWindow: {
duration: '7d',
type: 'rolling',
},
objective: {
target: 0.99,
},
tags: ['test'],
groupBy: 'tags',
};

View file

@ -0,0 +1,465 @@
/*
* 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 } from '@kbn/infra-forge';
import expect from '@kbn/expect';
import type { CreateSLOInput } from '@kbn/slo-schema';
import { FtrProviderContext } from '../../ftr_provider_context';
import { loadTestData } from './helper/load_test_data';
import { SloEsClient } from './helper/es';
import { sloData } from './fixtures/create_slo';
export default function ({ getService }: FtrProviderContext) {
describe('Get SLOs', function () {
this.tags('skipCloud');
const supertestAPI = getService('supertest');
const esClient = getService('es');
const logger = getService('log');
const retry = getService('retry');
const slo = getService('slo');
const sloEsClient = new SloEsClient(esClient);
let createSLOInput: CreateSLOInput;
const createSLO = async (requestOverrides?: Record<string, any>) => {
return await slo.create({
...createSLOInput,
...requestOverrides,
});
};
before(async () => {
await slo.deleteAllSLOs();
await sloEsClient.deleteTestSourceData();
await loadTestData(getService);
});
beforeEach(async () => {
createSLOInput = sloData;
});
afterEach(async () => {
await slo.deleteAllSLOs();
});
after(async () => {
await cleanup({ esClient, logger });
await sloEsClient.deleteTestSourceData();
});
it('gets slo by id and calculates SLI - occurances rolling', async () => {
const id = await createSLO({
groupBy: '*',
});
await retry.tryForTime(300 * 1000, async () => {
const getResponse = await supertestAPI
.get(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(getResponse.body).eql({
name: 'Test SLO for api integration',
description: 'Fixture for api integration tests',
indicator: {
type: 'sli.kql.custom',
params: {
index: 'kbn-data-forge*',
filter: `system.network.name: eth1`,
good: 'container.cpu.user.pct < 1',
total: 'container.cpu.user.pct: *',
timestampField: '@timestamp',
},
},
budgetingMethod: 'occurrences',
timeWindow: { duration: '7d', type: 'rolling' },
objective: { target: 0.99 },
tags: ['test'],
groupBy: '*',
id,
settings: { syncDelay: '1m', frequency: '1m' },
revision: 1,
enabled: true,
createdAt: getResponse.body.createdAt,
updatedAt: getResponse.body.updatedAt,
version: 2,
instanceId: '*',
summary: {
sliValue: 0.5,
errorBudget: {
initial: 0.01,
consumed: 50,
remaining: -49,
isEstimated: false,
},
status: 'VIOLATED',
},
});
});
});
it('gets slo by id and calculates SLI - occurences calendarAligned', async () => {
const id = await createSLO({
groupBy: '*',
timeWindow: {
duration: '1w',
type: 'calendarAligned',
},
});
await retry.tryForTime(300 * 1000, async () => {
const getResponse = await supertestAPI
.get(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
// expect summary transform to be created
expect(getResponse.body).eql({
name: 'Test SLO for api integration',
description: 'Fixture for api integration tests',
indicator: {
type: 'sli.kql.custom',
params: {
index: 'kbn-data-forge*',
filter: `system.network.name: eth1`,
good: 'container.cpu.user.pct < 1',
total: 'container.cpu.user.pct: *',
timestampField: '@timestamp',
},
},
budgetingMethod: 'occurrences',
timeWindow: { duration: '1w', type: 'calendarAligned' },
objective: { target: 0.99 },
tags: ['test'],
groupBy: '*',
id,
settings: { syncDelay: '1m', frequency: '1m' },
revision: 1,
enabled: true,
createdAt: getResponse.body.createdAt,
updatedAt: getResponse.body.updatedAt,
version: 2,
instanceId: '*',
summary: {
sliValue: 0.5,
errorBudget: {
initial: 0.01,
consumed: 50,
remaining: -49,
isEstimated: true,
},
status: 'VIOLATED',
},
});
});
});
it('gets slo by id and calculates SLI - timeslices rolling', async () => {
const id = await createSLO({
groupBy: '*',
timeWindow: {
duration: '7d',
type: 'rolling',
},
budgetingMethod: 'timeslices',
objective: {
target: 0.99,
timesliceTarget: 0.95,
timesliceWindow: '1m',
},
});
await retry.tryForTime(300 * 1000, async () => {
const getResponse = await supertestAPI
.get(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
// expect summary transform to be created
expect(getResponse.body).eql({
name: 'Test SLO for api integration',
description: 'Fixture for api integration tests',
indicator: {
type: 'sli.kql.custom',
params: {
index: 'kbn-data-forge*',
filter: `system.network.name: eth1`,
good: 'container.cpu.user.pct < 1',
total: 'container.cpu.user.pct: *',
timestampField: '@timestamp',
},
},
budgetingMethod: 'timeslices',
timeWindow: { duration: '7d', type: 'rolling' },
objective: {
target: 0.99,
timesliceTarget: 0.95,
timesliceWindow: '1m',
},
tags: ['test'],
groupBy: '*',
id,
settings: { syncDelay: '1m', frequency: '1m' },
revision: 1,
enabled: true,
createdAt: getResponse.body.createdAt,
updatedAt: getResponse.body.updatedAt,
version: 2,
instanceId: '*',
summary: {
sliValue: 0.5,
errorBudget: {
initial: 0.01,
consumed: 50,
remaining: -49,
isEstimated: false,
},
status: 'VIOLATED',
},
});
});
});
it('gets slo by id and calculates SLI - timeslices calendarAligned', async () => {
const id = await createSLO({
groupBy: '*',
timeWindow: {
duration: '1w',
type: 'calendarAligned',
},
budgetingMethod: 'timeslices',
objective: {
target: 0.99,
timesliceTarget: 0.95,
timesliceWindow: '10m',
},
});
await retry.tryForTime(300 * 1000, async () => {
const getResponse = await supertestAPI
.get(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(getResponse.body).eql({
name: 'Test SLO for api integration',
description: 'Fixture for api integration tests',
indicator: {
type: 'sli.kql.custom',
params: {
index: 'kbn-data-forge*',
filter: `system.network.name: eth1`,
good: 'container.cpu.user.pct < 1',
total: 'container.cpu.user.pct: *',
timestampField: '@timestamp',
},
},
budgetingMethod: 'timeslices',
timeWindow: { duration: '1w', type: 'calendarAligned' },
objective: {
target: 0.99,
timesliceTarget: 0.95,
timesliceWindow: '10m',
},
tags: ['test'],
groupBy: '*',
id,
settings: { syncDelay: '1m', frequency: '1m' },
revision: 1,
enabled: true,
createdAt: getResponse.body.createdAt,
updatedAt: getResponse.body.updatedAt,
version: 2,
instanceId: '*',
summary: {
sliValue: 0,
errorBudget: {
initial: 0.01,
consumed: 0.198413,
remaining: 0.801587,
isEstimated: false,
},
status: 'DEGRADING',
},
});
});
});
it('gets slos by query', async () => {
const id = await createSLO();
await createSLO({ name: 'test int' });
await retry.tryForTime(300 * 1000, async () => {
const response = await supertestAPI
.get(`/api/observability/slos`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(response.body.results.length).eql(2);
const searchResponse = await supertestAPI
.get(`/api/observability/slos?kqlQuery=slo.name%3Aapi*`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(searchResponse.body.results.length).eql(1);
const searchResponse2 = await supertestAPI
.get(`/api/observability/slos?kqlQuery=slo.name%3Aint`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(searchResponse2.body.results.length).eql(1);
const searchResponse3 = await supertestAPI
.get(`/api/observability/slos?kqlQuery=slo.name%3Aint*`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(searchResponse3.body.results.length).eql(2);
const searchResponse4 = await supertestAPI
.get(`/api/observability/slos?kqlQuery=int*`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(searchResponse4.body.results.length).eql(2);
const instanceResponse = await supertestAPI
.get(`/internal/observability/slos/${id}/_instances`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
// expect 3 instances to be created
expect(instanceResponse.body.groupBy).eql('tags');
expect(instanceResponse.body.instances.sort()).eql(['1', '2', '3']);
});
});
it('gets slo definitions', async () => {
const id = await createSLO();
const secondId = await createSLO({ name: 'test name int' });
const response = await supertestAPI
.get(`/api/observability/slos/_definitions`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(response.body).eql({
page: 1,
perPage: 100,
results: [
{
budgetingMethod: 'occurrences',
createdAt: response.body.results[0].createdAt,
description: 'Fixture for api integration tests',
enabled: true,
groupBy: 'tags',
id,
indicator: {
params: {
filter: 'system.network.name: eth1',
good: 'container.cpu.user.pct < 1',
index: 'kbn-data-forge*',
timestampField: '@timestamp',
total: 'container.cpu.user.pct: *',
},
type: 'sli.kql.custom',
},
name: 'Test SLO for api integration',
objective: {
target: 0.99,
},
revision: 1,
settings: {
frequency: '1m',
syncDelay: '1m',
},
tags: ['test'],
timeWindow: {
duration: '7d',
type: 'rolling',
},
updatedAt: response.body.results[0].updatedAt,
version: 2,
},
{
budgetingMethod: 'occurrences',
createdAt: response.body.results[1].createdAt,
description: 'Fixture for api integration tests',
enabled: true,
groupBy: 'tags',
id: secondId,
indicator: {
params: {
filter: 'system.network.name: eth1',
good: 'container.cpu.user.pct < 1',
index: 'kbn-data-forge*',
timestampField: '@timestamp',
total: 'container.cpu.user.pct: *',
},
type: 'sli.kql.custom',
},
name: 'test name int',
objective: {
target: 0.99,
},
revision: 1,
settings: {
frequency: '1m',
syncDelay: '1m',
},
tags: ['test'],
timeWindow: {
duration: '7d',
type: 'rolling',
},
updatedAt: response.body.results[1].updatedAt,
version: 2,
},
],
total: 2,
});
// can search by name
const searchResponse = await supertestAPI
.get(`/api/observability/slos/_definitions?search=api`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(searchResponse.body.total).eql(1);
const searchResponse2 = await supertestAPI
.get(`/api/observability/slos/_definitions?search=int`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(searchResponse2.body.total).eql(1);
const searchResponse3 = await supertestAPI
.get(`/api/observability/slos/_definitions?search=int*`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(searchResponse3.body.total).eql(2);
});
});
}

View file

@ -0,0 +1,64 @@
/*
* 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 { Client } from '@elastic/elasticsearch';
import {
SLO_DESTINATION_INDEX_PATTERN,
SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
} from '@kbn/observability-plugin/common/slo/constants';
export class SloEsClient {
constructor(private esClient: Client) {}
public async getSLOSummaryDataById(id: string) {
return await this.esClient.search({
index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
body: {
query: {
bool: {
filter: [
{
term: { 'slo.id': id },
},
{
term: { isTempDoc: false },
},
],
},
},
},
});
}
public async getSLORollupDataById(id: string) {
return await this.esClient.search({
index: SLO_DESTINATION_INDEX_PATTERN,
body: {
query: {
bool: {
filter: [
{
term: { 'slo.id': id },
},
],
},
},
},
});
}
public async deleteTestSourceData() {
try {
await this.esClient.deleteByQuery({
index: 'kbn-data-forge-fake_hosts*',
query: { term: { 'system.network.name': 'eth1' } },
});
} catch (e) {
// eslint-disable-next-line no-console
console.warn('SLO api integration test data not found');
}
}
}

View file

@ -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 { generate } from '@kbn/infra-forge';
import { FtrProviderContext } from '../../../ftr_provider_context';
export async function loadTestData(getService: FtrProviderContext['getService']) {
const DATE_VIEW = 'kbn-data-forge-fake_hosts';
const DATA_VIEW_ID = 'data-view-id';
const dataViewApi = getService('dataViewApi');
const esClient = getService('es');
const logger = getService('log');
await generate({ esClient, lookback: 'now-16m', logger });
await dataViewApi.create({
name: DATE_VIEW,
id: DATA_VIEW_ID,
title: DATE_VIEW,
});
}

View file

@ -0,0 +1,18 @@
/*
* 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 ({ loadTestFile }: FtrProviderContext) {
describe('SLO API Tests', () => {
loadTestFile(require.resolve('./create_slo'));
loadTestFile(require.resolve('./delete_slo'));
loadTestFile(require.resolve('./get_slo'));
loadTestFile(require.resolve('./update_slo'));
loadTestFile(require.resolve('./reset_slo'));
});
}

View file

@ -0,0 +1,105 @@
/*
* 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 } from '@kbn/infra-forge';
import expect from '@kbn/expect';
import { SO_SLO_TYPE } from '@kbn/observability-plugin/server/saved_objects';
import { FtrProviderContext } from '../../ftr_provider_context';
import { loadTestData } from './helper/load_test_data';
import { SloEsClient } from './helper/es';
export default function ({ getService }: FtrProviderContext) {
describe('Reset SLOs', function () {
this.tags('skipCloud');
const supertestAPI = getService('supertest');
const kibanaServer = getService('kibanaServer');
const esClient = getService('es');
const logger = getService('log');
const slo = getService('slo');
const sloEsClient = new SloEsClient(esClient);
before(async () => {
await sloEsClient.deleteTestSourceData();
await slo.deleteAllSLOs();
await loadTestData(getService);
});
afterEach(async () => {
await slo.deleteAllSLOs();
});
after(async () => {
await cleanup({ esClient, logger });
await sloEsClient.deleteTestSourceData();
});
it('updates the SO and transforms', async () => {
// create mock old SLO
const id = 'bdaeccdd-dc63-4138-a1d5-92c075f88087';
await kibanaServer.savedObjects.clean({
types: [SO_SLO_TYPE],
});
await kibanaServer.savedObjects.create({
type: SO_SLO_TYPE,
overwrite: true,
id,
attributes: {
name: 'Test SLO for api integration',
description: 'Fixture for api integration tests',
indicator: {
type: 'sli.kql.custom',
params: {
index: 'kbn-data-forge*',
filter: 'system.network.name: eth1',
good: 'container.cpu.user.pct < 1',
total: 'container.cpu.user.pct: *',
timestampField: '@timestamp',
},
},
budgetingMethod: 'occurrences',
timeWindow: { duration: '7d', type: 'rolling' },
objective: { target: 0.99 },
tags: ['test'],
groupBy: '*',
id,
settings: {
syncDelay: '1m',
frequency: '1m',
},
revision: 1,
enabled: true,
createdAt: '2023-12-14T01:12:35.638Z',
updatedAt: '2023-12-14T01:12:35.638Z',
version: 1,
},
});
const responseBeforeReset = await supertestAPI
.get(`/api/observability/slos/_definitions`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(responseBeforeReset.body.results[0].version).eql(1);
await supertestAPI
.post(`/api/observability/slos/${id}/_reset`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
const responseAfterReset = await supertestAPI
.get(`/api/observability/slos/_definitions`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
expect(responseAfterReset.body.results[0].version).eql(2);
});
});
}

View file

@ -0,0 +1,677 @@
/*
* 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 } from '@kbn/infra-forge';
import expect from '@kbn/expect';
import type { CreateSLOInput } from '@kbn/slo-schema';
import { SO_SLO_TYPE } from '@kbn/observability-plugin/server/saved_objects';
import { FtrProviderContext } from '../../ftr_provider_context';
import { loadTestData } from './helper/load_test_data';
import { sloData } from './fixtures/create_slo';
export default function ({ getService }: FtrProviderContext) {
describe('Update SLOs', function () {
this.tags('skipCloud');
const supertestAPI = getService('supertest');
const kibanaServer = getService('kibanaServer');
const esClient = getService('es');
const logger = getService('log');
const slo = getService('slo');
let createSLOInput: CreateSLOInput;
before(async () => {
await slo.deleteAllSLOs();
await loadTestData(getService);
});
beforeEach(() => {
createSLOInput = sloData;
});
afterEach(async () => {
await slo.deleteAllSLOs();
});
after(async () => {
await cleanup({ esClient, logger });
});
it('updates the SO and transforms', async () => {
const apiResponse = await supertestAPI
.post('/api/observability/slos')
.set('kbn-xsrf', 'true')
.send(createSLOInput)
.expect(200);
expect(apiResponse.body).property('id');
const { id } = apiResponse.body;
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...createSLOInput,
groupBy: 'hosts',
})
.expect(200);
const savedObject = await kibanaServer.savedObjects.find({
type: SO_SLO_TYPE,
});
expect(savedObject.saved_objects.length).eql(1);
expect(savedObject.saved_objects[0].attributes).eql({
budgetingMethod: 'occurrences',
updatedAt: savedObject.saved_objects[0].attributes.updatedAt,
createdAt: savedObject.saved_objects[0].attributes.createdAt,
description: 'Fixture for api integration tests',
enabled: true,
groupBy: 'hosts',
id,
indicator: {
params: {
filter: 'system.network.name: eth1',
good: 'container.cpu.user.pct < 1',
index: 'kbn-data-forge*',
timestampField: '@timestamp',
total: 'container.cpu.user.pct: *',
},
type: 'sli.kql.custom',
},
name: 'Test SLO for api integration',
objective: {
target: 0.99,
},
revision: 2,
settings: {
frequency: '1m',
syncDelay: '1m',
},
tags: ['test'],
timeWindow: {
duration: '7d',
type: 'rolling',
},
version: 2,
});
const rollUpTransformResponse = await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-2`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// expect roll up transform to be created
expect(rollUpTransformResponse.body).eql({
count: 1,
transforms: [
{
id: `slo-${id}-2`,
authorization: { roles: ['superuser'] },
version: '10.0.0',
create_time: rollUpTransformResponse.body.transforms[0].create_time,
source: {
index: ['kbn-data-forge*'],
query: {
bool: {
filter: [
{ range: { '@timestamp': { gte: 'now-7d/d' } } },
{
bool: {
should: [
{
match: {
'system.network.name': 'eth1',
},
},
],
minimum_should_match: 1,
},
},
],
},
},
runtime_mappings: {
'slo.id': {
type: 'keyword',
script: { source: `emit('${id}')` },
},
'slo.revision': { type: 'long', script: { source: 'emit(2)' } },
},
},
dest: {
index: '.slo-observability.sli-v3',
pipeline: '.slo-observability.sli.pipeline-v3',
},
frequency: '1m',
sync: { time: { field: '@timestamp', delay: '1m' } },
pivot: {
group_by: {
'slo.id': { terms: { field: 'slo.id' } },
'slo.revision': { terms: { field: 'slo.revision' } },
'slo.instanceId': { terms: { field: 'hosts' } },
'slo.groupings.hosts': { terms: { field: 'hosts' } },
'@timestamp': { date_histogram: { field: '@timestamp', fixed_interval: '1m' } },
},
aggregations: {
'slo.numerator': {
filter: {
bool: {
should: [{ range: { 'container.cpu.user.pct': { lt: '1' } } }],
minimum_should_match: 1,
},
},
},
'slo.denominator': {
filter: {
bool: {
should: [{ exists: { field: 'container.cpu.user.pct' } }],
minimum_should_match: 1,
},
},
},
},
},
description: `Rolled-up SLI data for SLO: Test SLO for api integration [id: ${id}, revision: 2]`,
settings: { deduce_mappings: false, unattended: true },
_meta: { version: 3, managed: true, managed_by: 'observability' },
},
],
});
const summaryTransform = await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-2`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// expect summary transform to be created
expect(summaryTransform.body).eql({
count: 1,
transforms: [
{
id: `slo-summary-${id}-2`,
authorization: { roles: ['superuser'] },
version: '10.0.0',
create_time: summaryTransform.body.transforms[0].create_time,
source: {
index: ['.slo-observability.sli-v3*'],
query: {
bool: {
filter: [
{ range: { '@timestamp': { gte: 'now-7d/m', lte: 'now/m' } } },
{ term: { 'slo.id': id } },
{ term: { 'slo.revision': 2 } },
],
},
},
},
dest: {
index: '.slo-observability.summary-v3',
pipeline: `.slo-observability.summary.pipeline-${id}-2`,
},
frequency: '1m',
sync: { time: { field: 'event.ingested', delay: '65s' } },
pivot: {
group_by: {
'slo.id': { terms: { field: 'slo.id' } },
'slo.revision': { terms: { field: 'slo.revision' } },
'slo.instanceId': { terms: { field: 'slo.instanceId' } },
'slo.groupings.hosts': {
terms: { field: 'slo.groupings.hosts' },
},
'service.name': { terms: { field: 'service.name', missing_bucket: true } },
'service.environment': {
terms: { field: 'service.environment', missing_bucket: true },
},
'transaction.name': { terms: { field: 'transaction.name', missing_bucket: true } },
'transaction.type': { terms: { field: 'transaction.type', missing_bucket: true } },
},
aggregations: {
goodEvents: { sum: { field: 'slo.numerator' } },
totalEvents: { sum: { field: 'slo.denominator' } },
sliValue: {
bucket_script: {
buckets_path: { goodEvents: 'goodEvents', totalEvents: 'totalEvents' },
script:
'if (params.totalEvents == 0) { return -1 } else if (params.goodEvents >= params.totalEvents) { return 1 } else { return params.goodEvents / params.totalEvents }',
},
},
errorBudgetInitial: { bucket_script: { buckets_path: {}, script: '1 - 0.99' } },
errorBudgetConsumed: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetInitial: 'errorBudgetInitial',
},
script:
'if (params.sliValue == -1) { return 0 } else { return (1 - params.sliValue) / params.errorBudgetInitial }',
},
},
errorBudgetRemaining: {
bucket_script: {
buckets_path: { errorBudgetConsumed: 'errorBudgetConsumed' },
script: '1 - params.errorBudgetConsumed',
},
},
statusCode: {
bucket_script: {
buckets_path: {
sliValue: 'sliValue',
errorBudgetRemaining: 'errorBudgetRemaining',
},
script: {
source:
'if (params.sliValue == -1) { return 0 } else if (params.sliValue >= 0.99) { return 4 } else if (params.errorBudgetRemaining > 0) { return 2 } else { return 1 }',
},
},
},
latestSliTimestamp: { max: { field: '@timestamp' } },
},
},
description: `Summarise the rollup data of SLO: Test SLO for api integration [id: ${id}, revision: 2].`,
settings: { deduce_mappings: false, unattended: true },
_meta: { version: 3, managed: true, managed_by: 'observability' },
},
],
});
});
it('updates an existing slo and does not update transforms when relevant fields are changed', async () => {
const request = createSLOInput;
const apiResponse = await supertestAPI
.post('/api/observability/slos')
.set('kbn-xsrf', 'true')
.send(request)
.expect(200);
expect(apiResponse.body).property('id');
const { id } = apiResponse.body;
const savedObject = await kibanaServer.savedObjects.find({
type: SO_SLO_TYPE,
});
expect(savedObject.saved_objects.length).eql(1);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change name
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
name: 'test name',
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change description
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
description: 'test description',
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change tags
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
tags: ['testTag'],
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
});
it('updates an existing slo and updates transforms when relevant fields are changed', async () => {
const request = createSLOInput;
const apiResponse = await supertestAPI
.post('/api/observability/slos')
.set('kbn-xsrf', 'true')
.send(request)
.expect(200);
expect(apiResponse.body).property('id');
const { id } = apiResponse.body;
const savedObject = await kibanaServer.savedObjects.find({
type: SO_SLO_TYPE,
});
expect(savedObject.saved_objects.length).eql(1);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change group by
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
groupBy: 'hosts',
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-1`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-2`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-2`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change indicator
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
indicator: {
...request.indicator,
params: {
...request.indicator.params,
index: 'test-index-*',
},
},
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-2`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-2`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-3`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-3`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change time window
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
timeWindow: {
...request.timeWindow,
duration: '7d',
},
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-3`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-3`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-4`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-4`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change objective
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
objective: {
target: 0.97,
},
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-4`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-4`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-5`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-5`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change budgetingMethod
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
budgetingMethod: 'timeslices',
objective: {
target: 0.99,
timesliceTarget: 0.95,
timesliceWindow: '1m',
},
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-5`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-5`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-6`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-6`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
// change settings
await supertestAPI
.put(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send({
...request,
settings: {
frequency: '2m',
syncDelay: '5m',
},
})
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-6`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-6`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(404);
await supertestAPI
.get(`/internal/transform/transforms/slo-${id}-7`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
await supertestAPI
.get(`/internal/transform/transforms/slo-summary-${id}-7`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send()
.expect(200);
});
});
}

View file

@ -0,0 +1,52 @@
/*
* 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 function DataViewApiProvider({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
return {
async create({ id, name, title }: { id: string; name: string; title: string }) {
const { body } = await supertest
.post(`/api/content_management/rpc/create`)
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo')
.send({
contentTypeId: 'index-pattern',
data: {
fieldAttrs: '{}',
title,
timeFieldName: '@timestamp',
sourceFilters: '[]',
fields: '[]',
fieldFormatMap: '{}',
typeMeta: '{}',
runtimeFieldMap: '{}',
name,
},
options: { id },
version: 1,
});
return body;
},
async delete({ id }: { id: string }) {
const { body } = await supertest
.post(`/api/content_management/rpc/delete`)
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo')
.send({
contentTypeId: 'index-pattern',
id,
options: { force: true },
version: 1,
});
return body;
},
};
}

View file

@ -22,6 +22,8 @@ import { IngestManagerProvider } from '../../common/services/ingest_manager';
import { TransformProvider } from './transform';
import { IngestPipelinesProvider } from './ingest_pipelines';
import { IndexManagementProvider } from './index_management';
import { DataViewApiProvider } from './data_view_api';
import { SloApiProvider } from './slo';
export const services = {
...commonServices,
@ -30,6 +32,7 @@ export const services = {
supertest: kibanaApiIntegrationServices.supertest,
aiops: AiopsProvider,
dataViewApi: DataViewApiProvider,
esSupertestWithoutAuth: EsSupertestWithoutAuthProvider,
infraOpsSourceConfiguration: InfraOpsSourceConfigurationProvider,
supertestWithoutAuth: SupertestWithoutAuthProvider,
@ -39,4 +42,5 @@ export const services = {
transform: TransformProvider,
ingestPipelines: IngestPipelinesProvider,
indexManagement: IndexManagementProvider,
slo: SloApiProvider,
};

View file

@ -0,0 +1,51 @@
/*
* 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 { CreateSLOInput, FindSLODefinitionsResponse } from '@kbn/slo-schema';
import { FtrProviderContext } from '../ftr_provider_context';
export function SloApiProvider({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
return {
async create(params: CreateSLOInput) {
const slo = await supertest
.post('/api/observability/slos')
.set('kbn-xsrf', 'true')
.send(params)
.expect(200);
const { id } = slo.body;
const reqBody = [{ id: `slo-${id}-1` }, { id: `slo-summary-${id}-1` }];
await supertest
.post(`/internal/transform/schedule_now_transforms`)
.set('kbn-xsrf', 'true')
.set('elastic-api-version', '1')
.send(reqBody)
.expect(200);
return id;
},
async deleteAllSLOs() {
const response = await supertest
.get(`/api/observability/slos/_definitions`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
await Promise.all(
(response.body as FindSLODefinitionsResponse).results.map(({ id }) => {
return supertest
.delete(`/api/observability/slos/${id}`)
.set('kbn-xsrf', 'true')
.send()
.expect(204);
})
);
},
};
}

View file

@ -164,6 +164,7 @@
"@kbn/io-ts-utils",
"@kbn/log-explorer-plugin",
"@kbn/security-plugin-types-common",
"@kbn/slo-schema",
"@kbn/typed-react-router-config",
"@kbn/ftr-common-functional-ui-services",
]