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

[Security Solutions] Adds bsearch service to FTR e2e tests to reduce flake, boilerplate, and technique choices #116211

Merged
merged 9 commits into from
Oct 27, 2021
122 changes: 122 additions & 0 deletions test/common/services/bsearch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import expect from '@kbn/expect';
import request from 'superagent';
import type SuperTest from 'supertest';
import { IEsSearchResponse } from 'src/plugins/data/common';
import { FtrProviderContext } from '../ftr_provider_context';
import { RetryService } from './retry/retry';

/**
* Function copied from here:
* test/api_integration/apis/search/bsearch.ts without the compress
*
* Splits the JSON lines from bsearch
*/
const parseBfetchResponse = (resp: request.Response): Array<Record<string, any>> => {
return resp.text
.trim()
.split('\n')
.map((item) => JSON.parse(item));
};

/**
* Function copied from here:
* x-pack/test/rule_registry/common/lib/authentication/spaces.ts
* @param spaceId The space id we want to utilize
*/
const getSpaceUrlPrefix = (spaceId?: string): string => {
return spaceId && spaceId !== 'default' ? `/s/${spaceId}` : ``;
};

/**
* Options for the send method
*/
interface SendOptions {
supertest: SuperTest.SuperTest<SuperTest.Test>;
options: object;
strategy: string;
space?: string;
}

/**
* Bsearch factory which will return a new bsearch capable service that can reduce flake
* on the CI systems when they are under pressure and bsearch returns an async search
* response or a sync response.
*
* @example
* const supertest = getService('supertest');
* const bsearch = getService('bsearch');
* const response = await bsearch.send<MyType>({
* supertest,
* options: {
* defaultIndex: ['large_volume_dns_data'],
* }
* strategy: 'securitySolutionSearchStrategy',
* });
* expect(response).eql({ ... your value ... });
*/
export const BSearchFactory = (retry: RetryService) => ({
/** Send method to send in your supertest, url, options, and strategy name */
send: async <T extends IEsSearchResponse>({
supertest,
options,
strategy,
space,
}: SendOptions): Promise<T> => {
const spaceUrl = getSpaceUrlPrefix(space);
const { body } = await retry.try(async () => {
return supertest
.post(`${spaceUrl}/internal/search/${strategy}`)
.set('kbn-xsrf', 'true')
.send(options)
.expect(200);
Copy link
Contributor

Choose a reason for hiding this comment

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

I haven't found other services using expect inside of their functions. Not sure I see any issue with keeping it there but just wanted to see if there are other instances of expect used within FTR services.

Copy link
Contributor

Choose a reason for hiding this comment

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

It might be cool to provide a parameter where users of the bsearch service could specifiy what HTTP status code to expect.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The expect errors will trigger the retries which is why they're here. I will add the HTTP status for people to expect if they have the need for other than 200, but I think at the moment we aren't concerned about testing bsearch results as we are just trying to ensure the endpoints all work.

});

if (body.isRunning) {
const result = await retry.try(async () => {
const resp = await supertest
.post(`${spaceUrl}/internal/bsearch`)
.set('kbn-xsrf', 'true')
.send({
batch: [
{
request: {
id: body.id,
...options,
},
options: {
strategy,
},
},
],
})
.expect(200);
const [parsedResponse] = parseBfetchResponse(resp);
expect(parsedResponse.result.isRunning).equal(false);
return parsedResponse.result;
});
return result;
} else {
return body;
}
},
});

/**
* Bsearch provider which will return a new bsearch capable service that can reduce flake
* on the CI systems when they are under pressure and bsearch returns an async search response
* or a sync response.
*/
export function BSearchProvider({
getService,
}: FtrProviderContext): ReturnType<typeof BSearchFactory> {
const retry = getService('retry');
return BSearchFactory(retry);
}
2 changes: 2 additions & 0 deletions test/common/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SecurityServiceProvider } from './security';
import { EsDeleteAllIndicesProvider } from './es_delete_all_indices';
import { SavedObjectInfoService } from './saved_object_info';
import { IndexPatternsService } from './index_patterns';
import { BSearchProvider } from './bsearch';

export const services = {
deployment: DeploymentService,
Expand All @@ -28,4 +29,5 @@ export const services = {
esDeleteAllIndices: EsDeleteAllIndicesProvider,
savedObjectInfo: SavedObjectInfoService,
indexPatterns: IndexPatternsService,
bsearch: BSearchProvider,
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
*/

import expect from '@kbn/expect';
import { HostsQueries } from '../../../../plugins/security_solution/common/search_strategy';
import {
HostAuthenticationsStrategyResponse,
HostsQueries,
} from '../../../../plugins/security_solution/common/search_strategy';

import { FtrProviderContext } from '../../ftr_provider_context';

Expand All @@ -22,16 +25,19 @@ const EDGE_LENGTH = 1;
export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
const bsearch = getService('bsearch');

describe('authentications', () => {
before(() => esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'));
after(() => esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts'));
before(async () => await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts'));

after(
async () => await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts')
);

it('Make sure that we get Authentication data', async () => {
const { body: authentications } = await supertest
.post('/internal/search/securitySolutionSearchStrategy/')
.set('kbn-xsrf', 'true')
.send({
const authentications = await bsearch.send<HostAuthenticationsStrategyResponse>({
supertest,
options: {
factoryQueryType: HostsQueries.authentications,
timerange: {
interval: '12h',
Expand All @@ -47,20 +53,19 @@ export default function ({ getService }: FtrProviderContext) {
defaultIndex: ['auditbeat-*'],
docValueFields: [],
inspect: false,
wait_for_completion_timeout: '10s',
})
.expect(200);
},
strategy: 'securitySolutionSearchStrategy',
});

expect(authentications.edges.length).to.be(EDGE_LENGTH);
expect(authentications.totalCount).to.be(TOTAL_COUNT);
expect(authentications.pageInfo.fakeTotalCount).to.equal(3);
});

it('Make sure that pagination is working in Authentications query', async () => {
const { body: authentications } = await supertest
.post('/internal/search/securitySolutionSearchStrategy/')
.set('kbn-xsrf', 'true')
.send({
const authentications = await bsearch.send<HostAuthenticationsStrategyResponse>({
supertest,
options: {
factoryQueryType: HostsQueries.authentications,
timerange: {
interval: '12h',
Expand All @@ -76,16 +81,16 @@ export default function ({ getService }: FtrProviderContext) {
defaultIndex: ['auditbeat-*'],
docValueFields: [],
inspect: false,
wait_for_completion_timeout: '10s',
})
.expect(200);
},
strategy: 'securitySolutionSearchStrategy',
});

expect(authentications.edges.length).to.be(EDGE_LENGTH);
expect(authentications.totalCount).to.be(TOTAL_COUNT);
expect(authentications.edges[0]!.node.lastSuccess!.source!.ip).to.eql([
expect(authentications.edges[0].node.lastSuccess?.source?.ip).to.eql([
LAST_SUCCESS_SOURCE_IP,
]);
expect(authentications.edges[0]!.node.lastSuccess!.host!.name).to.eql([HOST_NAME]);
expect(authentications.edges[0].node.lastSuccess?.host?.name).to.eql([HOST_NAME]);
});
});
}
33 changes: 16 additions & 17 deletions x-pack/test/api_integration/apis/security_solution/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import { JsonObject } from '@kbn/utility-types';
import {
Direction,
TimelineEventsQueries,
TimelineEventsAllStrategyResponse,
} from '../../../../plugins/security_solution/common/search_strategy';
import { FtrProviderContext } from '../../ftr_provider_context';
import { getDocValueFields, getFieldsToRequest, getFilterValue } from './utils';

const TO = '3000-01-01T00:00:00.000Z';
const FROM = '2000-01-01T00:00:00.000Z';
const TEST_URL = '/internal/search/timelineSearchStrategy/';
// typical values that have to change after an update from "scripts/es_archiver"
const DATA_COUNT = 7;
const HOST_NAME = 'suricata-sensor-amsterdam';
Expand All @@ -30,6 +30,8 @@ const LIMITED_PAGE_SIZE = 2;
export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
const bsearch = getService('bsearch');

const getPostBody = (): JsonObject => ({
defaultIndex: ['auditbeat-*'],
docValueFields: getDocValueFields(),
Expand Down Expand Up @@ -66,14 +68,14 @@ export default function ({ getService }: FtrProviderContext) {
});

it('returns Timeline data', async () => {
const resp = await supertest
.post(TEST_URL)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send(getPostBody())
.expect(200);
const timeline = await bsearch.send<TimelineEventsAllStrategyResponse>({
supertest,
options: {
...getPostBody(),
},
strategy: 'timelineSearchStrategy',
});

const timeline = resp.body;
expect(timeline.edges.length).to.be(EDGE_LENGTH);
expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT);
expect(timeline.totalCount).to.be(TOTAL_COUNT);
Expand All @@ -82,20 +84,17 @@ export default function ({ getService }: FtrProviderContext) {
});

it('returns paginated Timeline query', async () => {
const resp = await supertest
.post(TEST_URL)
.set('kbn-xsrf', 'true')
.set('Content-Type', 'application/json')
.send({
const timeline = await bsearch.send<TimelineEventsAllStrategyResponse>({
supertest,
options: {
...getPostBody(),
pagination: {
activePage: 0,
querySize: LIMITED_PAGE_SIZE,
},
})
.expect(200);

const timeline = resp.body;
},
strategy: 'timelineSearchStrategy',
});
expect(timeline.edges.length).to.be(LIMITED_PAGE_SIZE);
expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT);
expect(timeline.totalCount).to.be(TOTAL_COUNT);
Expand Down
29 changes: 17 additions & 12 deletions x-pack/test/api_integration/apis/security_solution/host_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,24 @@

import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { HostsQueries } from '../../../../plugins/security_solution/common/search_strategy';
import {
HostDetailsStrategyResponse,
HostsQueries,
} from '../../../../plugins/security_solution/common/search_strategy';

export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
const bsearch = getService('bsearch');

describe('Host Details', () => {
describe('With filebeat', () => {
before(() => esArchiver.load('x-pack/test/functional/es_archives/filebeat/default'));
after(() => esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default'));
before(
async () => await esArchiver.load('x-pack/test/functional/es_archives/filebeat/default')
);
after(
async () => await esArchiver.unload('x-pack/test/functional/es_archives/filebeat/default')
);

const FROM = '2000-01-01T00:00:00.000Z';
const TO = '3000-01-01T00:00:00.000Z';
Expand Down Expand Up @@ -213,12 +221,9 @@ export default function ({ getService }: FtrProviderContext) {
};

it('Make sure that we get HostDetails data', async () => {
const {
body: { hostDetails },
} = await supertest
.post('/internal/search/securitySolutionSearchStrategy/')
.set('kbn-xsrf', 'true')
.send({
const { hostDetails } = await bsearch.send<HostDetailsStrategyResponse>({
supertest,
options: {
factoryQueryType: HostsQueries.details,
timerange: {
interval: '12h',
Expand All @@ -229,9 +234,9 @@ export default function ({ getService }: FtrProviderContext) {
docValueFields: [],
hostName: 'raspberrypi',
inspect: false,
wait_for_completion_timeout: '10s',
})
.expect(200);
},
strategy: 'securitySolutionSearchStrategy',
});
expect(hostDetails).to.eql(expectedResult.hostDetails);
});
});
Expand Down
Loading