Skip to content

Commit

Permalink
[SLO] Synthetics based SLO e2e tests (elastic#183637)
Browse files Browse the repository at this point in the history
## Summary

Setting up Elastic/Synthetics based slo e2e tests !!

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
shahzad31 and kibanamachine authored May 20, 2024
1 parent 743ddd4 commit 8b7fa0d
Show file tree
Hide file tree
Showing 21 changed files with 317 additions and 9 deletions.
1 change: 1 addition & 0 deletions .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ disabled:
- x-pack/plugins/observability_solution/synthetics/e2e/synthetics/synthetics_run.ts
- x-pack/plugins/observability_solution/exploratory_view/e2e/synthetics_run.ts
- x-pack/plugins/observability_solution/ux/e2e/synthetics_run.ts
- x-pack/plugins/observability_solution/slo/e2e/synthetics_run.ts

# Configs that exist but weren't running in CI when this file was introduced
- x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/actions/config.ts
Expand Down
17 changes: 17 additions & 0 deletions .buildkite/pipelines/pull_request/slo_plugin_e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
steps:
- command: .buildkite/scripts/steps/functional/slo_plugin_e2e.sh
label: 'SLO Plugin @elastic/synthetics Tests'
agents:
queue: n2-4-spot
depends_on:
- build
- quick_checks
timeout_in_minutes: 30
artifact_paths:
- 'x-pack/plugins/observability_solution/slo/e2e/.journeys/**/*'
retry:
automatic:
- exit_status: '-1'
limit: 3
- exit_status: '*'
limit: 1
4 changes: 4 additions & 0 deletions .buildkite/scripts/pipelines/pull_request/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ const uploadPipeline = (pipelineContent: string | object) => {
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/ux_plugin_e2e.yml'));
}

if (await doAnyChangesMatch([/^x-pack\/plugins\/observability_solution/])) {
pipeline.push(getPipeline('.buildkite/pipelines/pull_request/slo_plugin_e2e.yml'));
}

if (
GITHUB_PR_LABELS.includes('ci:deploy-cloud') ||
GITHUB_PR_LABELS.includes('ci:cloud-deploy') ||
Expand Down
16 changes: 16 additions & 0 deletions .buildkite/scripts/steps/functional/slo_plugin_e2e.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env bash

set -euo pipefail

source .buildkite/scripts/common/util.sh

.buildkite/scripts/bootstrap.sh
.buildkite/scripts/download_build_artifacts.sh

export JOB=kibana-ux-plugin-synthetics

echo "--- SLO @elastic/synthetics Tests"

cd "$XPACK_DIR"

node plugins/observability_solution/slo/scripts/e2e.js --kibana-install-dir "$KIBANA_BUILD_LOCATION" ${GREP:+--grep \"${GREP}\"}
1 change: 1 addition & 0 deletions x-pack/packages/kbn-data-forge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export { cli } from './src/cli';
export { generate } from './src/generate';
export { cleanup } from './src/cleanup';
export { createConfig, readConfig } from './src/lib/create_config';
export { DEFAULTS } from './src/constants';
5 changes: 3 additions & 2 deletions x-pack/packages/kbn-data-forge/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
*/

import { ToolingLog } from '@kbn/tooling-log';
import { CliOptions } from './types';
import { cliOptionsToPartialConfig } from './lib/cli_to_partial_config';
import { createConfig, readConfig } from './lib/create_config';
import { getEsClient } from './lib/get_es_client';
import { parseCliOptions } from './lib/parse_cli_options';
import { run } from './run';

export async function cli() {
const options = parseCliOptions();
export async function cli(cliOptions?: CliOptions) {
const options = cliOptions ?? parseCliOptions();
const partialConfig = options.config
? await readConfig(options.config)
: cliOptionsToPartialConfig(options);
Expand Down
2 changes: 1 addition & 1 deletion x-pack/packages/kbn-data-forge/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ export const DEFAULTS = {
EVENT_TEMPLATE: 'good',
REDUCE_WEEKEND_TRAFFIC_BY: 0,
EPHEMERAL_PROJECT_IDS: 0,
ALIGN_EVENTS_TO_INTERVAL: 0,
ALIGN_EVENTS_TO_INTERVAL: true,
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function cliOptionsToPartialConfig(options: CliOptions) {
const schedule: Schedule = {
template: options.eventTemplate,
start: options.lookback,
end: false,
end: options.scheduleEnd ?? false,
};

const decodedDataset = DatasetRT.decode(options.dataset);
Expand Down
2 changes: 1 addition & 1 deletion x-pack/packages/kbn-data-forge/src/lib/create_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export function createConfig(partialConfig: PartialConfig = {}) {
concurrency: DEFAULTS.CONCURRENCY,
reduceWeekendTrafficBy: DEFAULTS.REDUCE_WEEKEND_TRAFFIC_BY,
ephemeralProjectIds: DEFAULTS.EPHEMERAL_PROJECT_IDS,
alignEventsToInterval: DEFAULTS.ALIGN_EVENTS_TO_INTERVAL === 1,
alignEventsToInterval: DEFAULTS.ALIGN_EVENTS_TO_INTERVAL,
...(partialConfig.indexing ?? {}),
},
schedule: partialConfig.schedule ?? [schedule],
Expand Down
2 changes: 1 addition & 1 deletion x-pack/packages/kbn-data-forge/src/lib/index_schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export async function indexSchedule(config: Config, client: Client, logger: Tool

logger.info(
`Indexing "${schedule.template}" events from ${startTs.toISOString()} to ${
end === false ? 'indefinatly' : end.toISOString()
end === false ? 'indefinitely' : end.toISOString()
}`
);
await createEvents(
Expand Down
5 changes: 3 additions & 2 deletions x-pack/packages/kbn-data-forge/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export interface Point {
}

export interface CliOptions {
config: string;
config?: string;
lookback: string;
eventsPerCycle: number;
payloadSize: number;
Expand All @@ -170,7 +170,7 @@ export interface CliOptions {
elasticsearchHost: string;
elasticsearchUsername: string;
elasticsearchPassword: string;
elasticsearchApiKey: undefined | string;
elasticsearchApiKey?: undefined | string;
kibanaUrl: string;
kibanaUsername: string;
kibanaPassword: string;
Expand All @@ -179,4 +179,5 @@ export interface CliOptions {
reduceWeekendTrafficBy: number;
ephemeralProjectIds: number;
alignEventsToInterval: boolean;
scheduleEnd?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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.
*/

export * from './slos_overview.journey';
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { journey, step, before, expect } from '@elastic/synthetics';
import { RetryService } from '@kbn/ftr-common-functional-services';
import { SLoDataService } from '../services/slo_data_service';
import { sloAppPageProvider } from '../page_objects/slo_app';

journey(`SLOsOverview`, async ({ page, params }) => {
const sloApp = sloAppPageProvider({ page, kibanaUrl: params.kibanaUrl });
const dataService = new SLoDataService({
kibanaUrl: params.kibanaUrl,
elasticsearchUrl: params.elasticsearchUrl,
getService: params.getService,
});

const retry: RetryService = params.getService('retry');

before(async () => {
await dataService.generateSloData();
await dataService.addSLO();
});

step('Go to slos overview', async () => {
await sloApp.navigateToOverview(true);
});

step('validate data retention tab', async () => {
await retry.tryWithRetries(
'check if slos are displayed',
async () => {
await page.waitForSelector('text="Test Stack SLO"');
const cards = await page.locator('text="Test Stack SLO"').all();
expect(cards.length > 5).toBeTruthy();
},
{
retryCount: 50,
retryDelay: 20000,
timeout: 60 * 20000,
},
async () => {
await page.getByTestId('querySubmitButton').click();
}
);
});
});
Original file line number Diff line number Diff line change
@@ -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 { Page } from '@elastic/synthetics';
import { loginPageProvider } from '@kbn/synthetics-plugin/e2e/page_objects/login';
import { utilsPageProvider } from '@kbn/synthetics-plugin/e2e/page_objects/utils';
import { recordVideo } from '@kbn/synthetics-plugin/e2e/helpers/record_video';

export function sloAppPageProvider({ page, kibanaUrl }: { page: Page; kibanaUrl: string }) {
page.setDefaultTimeout(60 * 1000);
recordVideo(page);

return {
...loginPageProvider({
page,
username: 'elastic',
password: 'changeme',
}),
...utilsPageProvider({ page }),

async navigateToOverview(doLogin = false) {
await page.goto(`${kibanaUrl}/app/slo`, {
waitUntil: 'networkidle',
});
if (doLogin) {
await this.loginToKibana();
}
},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 { KbnClient } from '@kbn/test';
import { cli, DEFAULTS } from '@kbn/data-forge';

export class SLoDataService {
kibanaUrl: string;
elasticsearchUrl: string;
params: Record<string, any>;
requester: KbnClient['requester'];

constructor(params: Record<string, any>) {
this.kibanaUrl = params.kibanaUrl;
this.elasticsearchUrl = params.elasticsearchUrl;
this.requester = params.getService('kibanaServer').requester;
this.params = params;
}

async generateSloData({
lookback = 'now-1d',
eventsPerCycle = 50,
}: {
lookback?: string;
eventsPerCycle?: number;
} = {}) {
await cli({
kibanaUrl: this.kibanaUrl,
elasticsearchHost: this.elasticsearchUrl,
lookback: DEFAULTS.LOOKBACK,
eventsPerCycle: DEFAULTS.EVENTS_PER_CYCLE,
payloadSize: DEFAULTS.PAYLOAD_SIZE,
concurrency: DEFAULTS.CONCURRENCY,
indexInterval: 10_000,
dataset: 'fake_stack',
scenario: DEFAULTS.SCENARIO,
elasticsearchUsername: DEFAULTS.ELASTICSEARCH_USERNAME,
elasticsearchPassword: DEFAULTS.ELASTICSEARCH_PASSWORD,
kibanaUsername: DEFAULTS.KIBANA_USERNAME,
kibanaPassword: DEFAULTS.KIBANA_PASSWORD,
installKibanaAssets: true,
eventTemplate: DEFAULTS.EVENT_TEMPLATE,
reduceWeekendTrafficBy: DEFAULTS.REDUCE_WEEKEND_TRAFFIC_BY,
ephemeralProjectIds: DEFAULTS.EPHEMERAL_PROJECT_IDS,
alignEventsToInterval: DEFAULTS.ALIGN_EVENTS_TO_INTERVAL,
scheduleEnd: 'now+10m',
}).then((res) => {
// eslint-disable-next-line no-console
console.log(res);
});
}

async addSLO() {
const example = {
name: 'Test Stack SLO',
description: '',
indicator: {
type: 'sli.kql.custom',
params: {
index: 'kbn-data-forge-fake_stack.admin-console-*',
filter: '',
good: 'log.level : "INFO" ',
total: '',
timestampField: '@timestamp',
},
},
budgetingMethod: 'occurrences',
timeWindow: {
duration: '30d',
type: 'rolling',
},
objective: {
target: 0.99,
},
tags: [],
groupBy: ['user.id'],
};
try {
const { data } = await this.requester.request({
description: 'get monitor by id',
path: '/api/observability/slos',
body: example,
method: 'POST',
});
return data;
} catch (e) {
console.error(e);
}
}
}
35 changes: 35 additions & 0 deletions x-pack/plugins/observability_solution/slo/e2e/synthetics_run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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';
import { SyntheticsRunner, argv } from '@kbn/synthetics-plugin/e2e';

const { headless, grep, bail: pauseOnError } = argv;

async function runE2ETests({ readConfigFile }: FtrConfigProviderContext) {
const kibanaConfig = await readConfigFile(require.resolve('@kbn/synthetics-plugin/e2e/config'));
return {
...kibanaConfig.getAll(),
testRunner: async ({ getService }: any) => {
const syntheticsRunner = new SyntheticsRunner(getService, {
headless,
match: grep,
pauseOnError,
});

await syntheticsRunner.setup();

await syntheticsRunner.loadTestFiles(async () => {
require('./journeys');
});

await syntheticsRunner.run();
},
};
}

// eslint-disable-next-line import/no-default-export
export default runE2ETests;
18 changes: 18 additions & 0 deletions x-pack/plugins/observability_solution/slo/e2e/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": "../../../../../tsconfig.base.json",
"exclude": ["tmp", "target/**/*"],
"include": ["**/*"],
"compilerOptions": {
"outDir": "target/types",
"types": [ "node"],
"isolatedModules": false,
},
"kbn_references": [
{ "path": "../../../../test/tsconfig.json" },
{ "path": "../../../../../test/tsconfig.json" },
"@kbn/test",
"@kbn/ftr-common-functional-services",
"@kbn/synthetics-plugin/e2e",
"@kbn/data-forge",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet
return (
<>
<EuiPanel
className="sloCardItem"
panelRef={containerRef as React.Ref<HTMLDivElement>}
onMouseOver={() => {
if (!isMouseOver) {
Expand Down
Loading

0 comments on commit 8b7fa0d

Please sign in to comment.