Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SLO] [Alerting] deployment agnostic slo burn rate rule tests #187924

Merged
merged 38 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f495936
deployment agnostic slo burn rate rule tests
mgiota Jul 9, 2024
95feb99
merge main
dominiqueclarke Aug 12, 2024
647e38b
add burn rate tests to agnostic api test directory
dominiqueclarke Aug 12, 2024
4876a31
add builtkite config
dominiqueclarke Aug 12, 2024
2ee040b
remove unused directory
dominiqueclarke Aug 12, 2024
f28913a
update codeowners
dominiqueclarke Aug 12, 2024
fb4d553
adjust alerting api
dominiqueclarke Aug 12, 2024
f1d7143
Update x-pack/test/api_integration/services/index.ts
dominiqueclarke Aug 13, 2024
7d70b1a
sUpdate x-pack/test/api_integration/deployment_agnostic/oblt.stateful…
dominiqueclarke Aug 13, 2024
390ebb6
Update .eslintrc.js
dominiqueclarke Aug 13, 2024
ad12156
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Aug 13, 2024
c02f861
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Aug 13, 2024
145ed27
adjust tests
dominiqueclarke Aug 13, 2024
6ee3000
update service and test
dmlemeshko Aug 14, 2024
d43a0c7
Merge remote-tracking branch 'upstream/main' into slo_burn_rate_deplo…
dmlemeshko Aug 14, 2024
34fc89c
avoid loading stateful services
dmlemeshko Aug 14, 2024
770101b
remove duplicated service
dmlemeshko Aug 14, 2024
9356c2a
remove unused alerting_api service
dmlemeshko Aug 14, 2024
65e4e17
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Aug 14, 2024
4978fe7
restore original services
dmlemeshko Aug 14, 2024
b7ae99a
Merge branch 'slo_burn_rate_deployment_agnostic_tests' of github.com:…
dmlemeshko Aug 14, 2024
1facfd1
fix type errors
dmlemeshko Aug 14, 2024
dea02c6
remove tags
dmlemeshko Aug 14, 2024
0be3024
Merge branch 'main' into slo_burn_rate_deployment_agnostic_tests
dmlemeshko Aug 14, 2024
7053b2b
fix test loading and move types
dmlemeshko Aug 15, 2024
daa1b8f
Merge branch 'slo_burn_rate_deployment_agnostic_tests' of github.com:…
dmlemeshko Aug 15, 2024
9b76f4f
add missing auth header
dmlemeshko Aug 15, 2024
c4cfa24
remove unused arg in alerting_api service
dmlemeshko Aug 15, 2024
5f9ae2c
Merge branch 'main' into slo_burn_rate_deployment_agnostic_tests
dmlemeshko Aug 15, 2024
7dd2649
fix types
dmlemeshko Aug 15, 2024
52ecd8d
Merge branch 'slo_burn_rate_deployment_agnostic_tests' of github.com:…
dmlemeshko Aug 15, 2024
dd8ee61
Merge branch 'main' into slo_burn_rate_deployment_agnostic_tests
dominiqueclarke Aug 20, 2024
ea47dbf
remove unused path
mgiota Aug 20, 2024
0a2b008
configs for deployment agnostic tests
mgiota Aug 20, 2024
91a4511
Merge branch 'main' into slo_burn_rate_deployment_agnostic_tests
dmlemeshko Aug 21, 2024
364eea3
Update x-pack/test/api_integration/deployment_agnostic/services/alert…
mgiota Aug 21, 2024
c3f0329
Merge branch 'main' into slo_burn_rate_deployment_agnostic_tests
elasticmachine Aug 21, 2024
85273a2
better error message
mgiota Aug 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .buildkite/ftr_oblt_stateful_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ enabled:
- x-pack/test/observability_ai_assistant_functional/enterprise/config.ts
- x-pack/test/profiling_api_integration/cloud/config.ts
- x-pack/test/functional/apps/apm/config.ts
# Stateful configs that run deployment-agnostic tests
- x-pack/test/api_integration/deployment_agnostic/oblt.stateful.config.ts
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,8 @@ module.exports = {
'x-pack/test/profiling_api_integration/**/*.ts',
'x-pack/test/security_solution_api_integration/*/test_suites/**/*',
'x-pack/test/security_solution_api_integration/**/config*.ts',
'x-pack/test/observability_solution_api_integration/*/test_suites/**/*',
'x-pack/test/observability_solution_api_integration/**/config*.ts',
dominiqueclarke marked this conversation as resolved.
Show resolved Hide resolved
],
rules: {
'import/no-default-export': 'off',
Expand Down
4 changes: 3 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,9 @@ x-pack/test/observability_ai_assistant_functional @elastic/obs-ai-assistant
/x-pack/test_serverless/**/test_suites/observability/custom_threshold_rule/ @elastic/obs-ux-management-team
/x-pack/test_serverless/**/test_suites/observability/slos/ @elastic/obs-ux-management-team
/x-pack/test_serverless/api_integration/test_suites/observability/es_query_rule @elastic/obs-ux-management-team
/x-pack/test_serverless/api_integration/test_suites/observability/burn_rate_rule @elastic/obs-ux-management-team
/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/burn_rate_rule @elastic/obs-ux-management-team
/x-pack/test/api_integration/deployment_agnostic/services/alerting_api @elastic/obs-ux-management-team
/x-pack/test/api_integration/deployment_agnostic/services/slo_api @elastic/obs-ux-management-team
/x-pack/test_serverless/**/test_suites/observability/infra/ @elastic/obs-ux-infra_services-team

# Elastic Stack Monitoring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,10 @@
* 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, Dataset, generate, PartialConfig } from '@kbn/data-forge';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services';

export default function ({ getService }: FtrProviderContext) {
const esClient = getService('es');
Expand All @@ -24,10 +17,9 @@ export default function ({ getService }: FtrProviderContext) {
const alertingApi = getService('alertingApi');
const dataViewApi = getService('dataViewApi');
const sloApi = getService('sloApi');
const svlUserManager = getService('svlUserManager');
const svlCommonApi = getService('svlCommonApi');
let roleAuthc: RoleCredentials;
let internalReqHeader: InternalRequestHeader;
const config = getService('config');
const isServerless = config.get('serverless');
const expectedConsumer = isServerless ? 'observability' : 'slo';

describe('Burn rate rule', () => {
const RULE_TYPE_ID = 'slo.rules.burnRate';
Expand All @@ -41,9 +33,14 @@ export default function ({ getService }: FtrProviderContext) {
let actionId: string;
let ruleId: string;

const samlAuth = getService('samlAuth');
const supertestWithoutAuth = getService('supertestWithoutAuth');
let roleAuthc: RoleCredentials;
let internalHeaders: InternalRequestHeader;

before(async () => {
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
internalReqHeader = svlCommonApi.getInternalRequestHeader();
roleAuthc = await samlAuth.createM2mApiKeyWithRoleScope('admin');
internalHeaders = samlAuth.getInternalRequestHeader();
dataForgeConfig = {
schedule: [
{
Expand All @@ -60,37 +57,52 @@ export default function ({ getService }: FtrProviderContext) {
indexing: { dataset: 'fake_hosts' as Dataset, eventsPerCycle: 1, interval: 10000 },
};
dataForgeIndices = await generate({ client: esClient, config: dataForgeConfig, logger });
await alertingApi.waitForDocumentInIndex({ indexName: DATA_VIEW, docCountTarget: 360 });
await alertingApi.waitForDocumentInIndex({
indexName: DATA_VIEW,
docCountTarget: 360,
roleAuthc,
});
await dataViewApi.create({
roleAuthc,
name: DATA_VIEW,
id: DATA_VIEW_ID,
title: DATA_VIEW,
});
roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('admin');
});

after(async () => {
await supertest.delete(`/api/alerting/rule/${ruleId}`).set(internalReqHeader);
await supertest.delete(`/api/actions/connector/${actionId}`).set(internalReqHeader);
await supertest
.delete(`/api/alerting/rule/${ruleId}`)
.set(roleAuthc.apiKeyHeader)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

supertest is initialized with operator user and its Cookie persist. Passing custom api key for specific role has no affect, API call will be executed with operator privileges.

If you test the APIs, it is critical to use supertestWithoutAuth only. If it is env cleanup and admin has no permissions to delete, using supertest is reasonable but not need to pass api key.

.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo');
await supertest
.delete(`/api/actions/connector/${actionId}`)
.set('kbn-xsrf', 'foo')
.set(roleAuthc.apiKeyHeader)
.set('x-elastic-internal-origin', 'foo');
await esClient.deleteByQuery({
index: '.kibana-event-log-*',
query: { term: { 'rule.id': ruleId } },
conflicts: 'proceed',
});
await dataViewApi.delete({
roleAuthc,
id: DATA_VIEW_ID,
});
await supertest.delete('/api/observability/slos/my-custom-id').set(internalReqHeader);
await supertest
.delete('/api/observability/slos/my-custom-id')
.set('kbn-xsrf', 'foo')
.set('x-elastic-internal-origin', 'foo');

await esDeleteAllIndices([ALERT_ACTION_INDEX, ...dataForgeIndices]);
await cleanup({ client: esClient, config: dataForgeConfig, logger });
await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc);
await samlAuth.invalidateM2mApiKeyWithRoleScope(roleAuthc);
});

describe('Rule creation', () => {
it('creates rule successfully', async () => {
actionId = await alertingApi.createIndexConnector({
roleAuthc,
name: 'Index Connector: Slo Burn rate API test',
indexName: ALERT_ACTION_INDEX,
});
Expand Down Expand Up @@ -123,9 +135,8 @@ export default function ({ getService }: FtrProviderContext) {
);

const dependencyRule = await alertingApi.createRule({
roleAuthc,
tags: ['observability'],
consumer: 'observability',
consumer: expectedConsumer,
name: 'SLO Burn Rate rule - Dependency',
ruleTypeId: RULE_TYPE_ID,
schedule: {
Expand Down Expand Up @@ -196,9 +207,8 @@ export default function ({ getService }: FtrProviderContext) {
});

const createdRule = await alertingApi.createRule({
roleAuthc,
tags: ['observability'],
consumer: 'observability',
consumer: expectedConsumer,
name: 'SLO Burn Rate rule',
ruleTypeId: RULE_TYPE_ID,
schedule: {
Expand Down Expand Up @@ -299,9 +309,9 @@ export default function ({ getService }: FtrProviderContext) {
});

it('should find the created rule with correct information about the consumer', async () => {
const match = await alertingApi.findRule(roleAuthc, ruleId);
const match = await alertingApi.findRule(ruleId);
expect(match).not.to.be(undefined);
expect(match.consumer).to.be('observability');
expect(match.consumer).to.be(expectedConsumer);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 - Burn rate rule', function () {
this.tags(['ess', 'serverless']);
Copy link
Member

@dmlemeshko dmlemeshko Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these tags has no affect. We don't use any custom tagging because tests in deployment_agnostic/ path are assumed to be compatible for both stateful and serverless.

You just need to load test in both stateful and serverless FTR configs.

loadTestFile(require.resolve('./burn_rate_rule'));
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export default function ({ loadTestFile }: DeploymentAgnosticFtrProviderContext)
loadTestFile(require.resolve('./apis/console'));
loadTestFile(require.resolve('./apis/core'));
loadTestFile(require.resolve('./apis/painless_lab'));
loadTestFile(require.resolve('./apis/observability/alerting'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 { createStatefulTestConfig } from './default_configs/stateful.config.base';

export default createStatefulTestConfig({
testFiles: [require.resolve('./oblt.index.ts')],
junit: {
reportName: 'Stateful Observability - Deployment-agnostic API Integration Tests',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
* 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 { RoleCredentials } from '@kbn/ftr-common-functional-services';
import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context';

export function AlertingApiProvider({ getService }: DeploymentAgnosticFtrProviderContext) {
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,
roleAuthc,
}: {
ruleId: string;
expectedStatus: string;
roleAuthc: RoleCredentials;
}) {
if (!ruleId) {
throw new Error(`'ruleId' is undefined`);
}
return await retry.tryForTime(retryTimeout, async () => {
const response = await supertest
.get(`/api/alerting/rule/${ruleId}`)
.set(roleAuthc.apiKeyHeader)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Please use only supertestWithoutAuth to make API call with api key for the specified role

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.set(roleAuthc.getInternalRequestHeader())
.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,
roleAuthc,
}: {
indexName: string;
docCountTarget?: number;
roleAuthc: RoleCredentials;
}): 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}.`);
mgiota marked this conversation as resolved.
Show resolved Hide resolved
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,
roleAuthc,
}: {
name: string;
indexName: string;
roleAuthC: RoleCredentials;
}) {
const { body } = await supertest
.post(`/api/actions/connector`)
.set(roleAuthc.apiKeyHeader)
.set(roleAuthC.getInternalRequestHeader())
.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,
roleAuthc,
}: {
ruleTypeId: string;
name: string;
params: MetricThresholdParams | ThresholdParams | SloBurnRateRuleParams;
actions?: any[];
tags?: any[];
schedule?: { interval: string };
consumer: string;
roleAuthc: RoleCredentials;
}) {
const { body } = await supertest
.post(`/api/alerting/rule`)
.set(roleAuthc.apiKeyHeader)
.set(roleAuthC.getInternalRequestHeader())
.send({
params,
consumer,
schedule: schedule || {
interval: '5m',
},
tags,
name,
rule_type_id: ruleTypeId,
actions,
});
return body;
},

async findRule(ruleId: string, roleAuthc: RoleCredentials) {
if (!ruleId) {
throw new Error(`'ruleId' is undefined`);
}
const response = await supertest
.get('/api/alerting/rules/_find')
.set(roleAuthc.apiKeyHeader)
.set(roleAuthC.getInternalRequestHeader());
return response.body.data.find((obj: any) => obj.id === ruleId);
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
import { deploymentAgnosticServices } from './deployment_agnostic_services';
import { DataViewApiProvider } from './data_view_api';
import { SloApiProvider } from './slo_api';
import { AlertingApiProvider } from './alerting_api';
import { services as commonServices } from '../../../common/services';

export type {
InternalRequestHeader,
Expand All @@ -17,7 +19,9 @@ export type {
} from '@kbn/ftr-common-functional-services';

export const services = {
...commonServices,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not every service in '../../../common/services' is deployment-agnostic and we shouldn't import it like this.
Instead I picked up the ones that work both in serverless/stateful and import as deploymentAgnosticServices

If you need any other services from common/services let me know which ones so I can review/modify it and load in

export const deploymentAgnosticServices = _.pick(apiIntegrationServices, [
'supertest', // TODO: review its behaviour
'es',
'esArchiver',
'esSupertest', // TODO: review its behaviour
'indexPatterns',
'ingestPipelines',
'kibanaServer',
// 'ml', depends on 'esDeleteAllIndices', can we make it deployment agnostic?
'randomness',
'retry',
'security',
'usageAPI',
]);

...deploymentAgnosticServices,
alertingApi: AlertingApiProvider,
supertestWithoutAuth: commonFunctionalServices.supertestWithoutAuth,
samlAuth: commonFunctionalServices.samlAuth,
dataViewApi: DataViewApiProvider,
Expand Down
Loading