Skip to content

Commit

Permalink
[SLO] api integration tests stateful (#173236)
Browse files Browse the repository at this point in the history
## 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 <[email protected]>
Co-authored-by: shahzad31 <[email protected]>
Co-authored-by: Kevin Delemme <[email protected]>
  • Loading branch information
4 people authored Jan 16, 2024
1 parent 5d55ab9 commit 96bfb63
Show file tree
Hide file tree
Showing 16 changed files with 1,942 additions and 2 deletions.
3 changes: 2 additions & 1 deletion .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down Expand Up @@ -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)}`],
},
];
};
29 changes: 29 additions & 0 deletions x-pack/test/api_integration/apis/slos/config.ts
Original file line number Diff line number Diff line change
@@ -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,
},
};
}
273 changes: 273 additions & 0 deletions x-pack/test/api_integration/apis/slos/create_slo.ts
Original file line number Diff line number Diff line change
@@ -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' },
},
],
});
});
});
}
Loading

0 comments on commit 96bfb63

Please sign in to comment.