Remove bsearch endpoint (#197150)

This commit is contained in:
Lukas Olson 2024-12-06 07:23:46 -07:00 committed by GitHub
parent 132eb8162e
commit efe06a3357
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 433 additions and 1223 deletions

View file

@ -71,7 +71,6 @@ export class DataPublicPlugin
public setup(
core: CoreSetup<DataStartDependencies, DataPublicPluginStart>,
{
bfetch,
expressions,
uiActions,
usageCollection,
@ -85,7 +84,6 @@ export class DataPublicPlugin
setTheme(core.theme);
const searchService = this.searchService.setup(core, {
bfetch,
usageCollection,
expressions,
management,

View file

@ -49,10 +49,8 @@ import type {
ToastsSetup,
} from '@kbn/core/public';
import { BatchedFunc, BfetchPublicSetup, DISABLE_BFETCH } from '@kbn/bfetch-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { AbortError, KibanaServerError } from '@kbn/kibana-utils-plugin/public';
import { BfetchRequestError } from '@kbn/bfetch-error';
import type {
SanitizedConnectionRequestParams,
IKibanaSearchRequest,
@ -87,7 +85,6 @@ import type { SearchServiceStartDependencies } from '../search_service';
import { createRequestHash } from './create_request_hash';
export interface SearchInterceptorDeps {
bfetch: BfetchPublicSetup;
http: HttpSetup;
executionContext: ExecutionContextSetup;
uiSettings: IUiSettingsClient;
@ -104,7 +101,6 @@ const MAX_CACHE_SIZE_MB = 10;
export class SearchInterceptor {
private uiSettingsSubs: Subscription[] = [];
private searchTimeout: number;
private bFetchDisabled: boolean;
private readonly responseCache: SearchResponseCache = new SearchResponseCache(
MAX_CACHE_ITEMS,
MAX_CACHE_SIZE_MB
@ -121,10 +117,6 @@ export class SearchInterceptor {
*/
private application!: ApplicationStart;
private docLinks!: DocLinksStart;
private batchedFetch!: BatchedFunc<
{ request: IKibanaSearchRequest; options: ISearchOptionsSerializable },
IKibanaSearchResponse
>;
private inspector!: InspectorStart;
/*
@ -151,19 +143,11 @@ export class SearchInterceptor {
this.inspector = (depsStart as SearchServiceStartDependencies).inspector;
});
this.batchedFetch = deps.bfetch.batchedFunction({
url: '/internal/bsearch',
});
this.searchTimeout = deps.uiSettings.get(UI_SETTINGS.SEARCH_TIMEOUT);
this.bFetchDisabled = deps.uiSettings.get(DISABLE_BFETCH);
this.uiSettingsSubs.push(
deps.uiSettings.get$(UI_SETTINGS.SEARCH_TIMEOUT).subscribe((timeout: number) => {
this.searchTimeout = timeout;
}),
deps.uiSettings.get$(DISABLE_BFETCH).subscribe((bFetchDisabled: boolean) => {
this.bFetchDisabled = bFetchDisabled;
})
);
}
@ -223,8 +207,8 @@ export class SearchInterceptor {
return err;
}
if (e instanceof AbortError || e instanceof BfetchRequestError) {
// In the case an application initiated abort, throw the existing AbortError, same with BfetchRequestErrors
if (e instanceof AbortError) {
// In the case an application initiated abort, throw the existing AbortError
return e;
}
@ -450,99 +434,85 @@ export class SearchInterceptor {
): Promise<IKibanaSearchResponse> {
const { abortSignal } = options || {};
if (this.bFetchDisabled) {
const { executionContext, strategy, ...searchOptions } = this.getSerializableOptions(options);
return this.deps.http
.post<IKibanaSearchResponse | ErrorResponseBase>(
`/internal/search/${strategy}${request.id ? `/${request.id}` : ''}`,
{
version: '1',
signal: abortSignal,
context: executionContext,
body: JSON.stringify({
...request,
...searchOptions,
stream:
strategy === ESQL_ASYNC_SEARCH_STRATEGY ||
strategy === ENHANCED_ES_SEARCH_STRATEGY ||
strategy === undefined, // undefined strategy is treated as enhanced ES
}),
asResponse: true,
}
)
.then((rawResponse) => {
const warning = rawResponse.response?.headers.get('warning');
const requestParams =
rawResponse.body && 'requestParams' in rawResponse.body
? rawResponse.body.requestParams
: JSON.parse(rawResponse.response?.headers.get('kbn-search-request-params') || '{}');
const isRestored =
rawResponse.body && 'isRestored' in rawResponse.body
? rawResponse.body.isRestored
: rawResponse.response?.headers.get('kbn-search-is-restored') === '?1';
if (rawResponse.body && 'error' in rawResponse.body) {
// eslint-disable-next-line no-throw-literal
throw {
attributes: {
error: rawResponse.body.error,
rawResponse: rawResponse.body,
requestParams,
isRestored,
},
};
}
switch (strategy) {
case ENHANCED_ES_SEARCH_STRATEGY:
if (rawResponse.body?.rawResponse) return rawResponse.body;
const typedResponse = rawResponse.body as unknown as AsyncSearchGetResponse;
const shimmedResponse = shimHitsTotal(typedResponse.response, {
legacyHitsTotal: searchOptions.legacyHitsTotal,
});
return {
id: typedResponse.id,
isPartial: typedResponse.is_partial,
isRunning: typedResponse.is_running,
rawResponse: shimmedResponse,
warning,
requestParams,
isRestored,
...getTotalLoaded(shimmedResponse),
};
case ESQL_ASYNC_SEARCH_STRATEGY:
const esqlResponse = rawResponse.body as unknown as SqlGetAsyncResponse;
return {
id: esqlResponse.id,
rawResponse: esqlResponse,
isPartial: esqlResponse.is_partial,
isRunning: esqlResponse.is_running,
warning,
};
default:
return rawResponse.body;
}
})
.catch((e: IHttpFetchError<KibanaServerError>) => {
if (e?.body) {
throw e.body;
} else {
throw e;
}
}) as Promise<IKibanaSearchResponse>;
} else {
const { executionContext, ...rest } = options || {};
return this.batchedFetch(
const { executionContext, strategy, ...searchOptions } = this.getSerializableOptions(options);
return this.deps.http
.post<IKibanaSearchResponse | ErrorResponseBase>(
`/internal/search/${strategy}${request.id ? `/${request.id}` : ''}`,
{
request,
options: this.getSerializableOptions({
...rest,
executionContext: this.deps.executionContext.withGlobalContext(executionContext),
version: '1',
signal: abortSignal,
context: executionContext,
body: JSON.stringify({
...request,
...searchOptions,
stream:
strategy === ESQL_ASYNC_SEARCH_STRATEGY ||
strategy === ENHANCED_ES_SEARCH_STRATEGY ||
strategy === undefined, // undefined strategy is treated as enhanced ES
}),
},
abortSignal
);
}
asResponse: true,
}
)
.then((rawResponse) => {
const warning = rawResponse.response?.headers.get('warning');
const requestParams =
rawResponse.body && 'requestParams' in rawResponse.body
? rawResponse.body.requestParams
: JSON.parse(rawResponse.response?.headers.get('kbn-search-request-params') || '{}');
const isRestored =
rawResponse.body && 'isRestored' in rawResponse.body
? rawResponse.body.isRestored
: rawResponse.response?.headers.get('kbn-search-is-restored') === '?1';
if (rawResponse.body && 'error' in rawResponse.body) {
// eslint-disable-next-line no-throw-literal
throw {
attributes: {
error: rawResponse.body.error,
rawResponse: rawResponse.body,
requestParams,
isRestored,
},
};
}
switch (strategy) {
case ENHANCED_ES_SEARCH_STRATEGY:
if (rawResponse.body?.rawResponse) return rawResponse.body;
const typedResponse = rawResponse.body as unknown as AsyncSearchGetResponse;
const shimmedResponse = shimHitsTotal(typedResponse.response, {
legacyHitsTotal: searchOptions.legacyHitsTotal,
});
return {
id: typedResponse.id,
isPartial: typedResponse.is_partial,
isRunning: typedResponse.is_running,
rawResponse: shimmedResponse,
warning,
requestParams,
isRestored,
...getTotalLoaded(shimmedResponse),
};
case ESQL_ASYNC_SEARCH_STRATEGY:
const esqlResponse = rawResponse.body as unknown as SqlGetAsyncResponse;
return {
id: esqlResponse.id,
rawResponse: esqlResponse,
isPartial: esqlResponse.is_partial,
isRunning: esqlResponse.is_running,
warning,
};
default:
return rawResponse.body;
}
})
.catch((e: IHttpFetchError<KibanaServerError>) => {
if (e?.body) {
throw e.body;
} else {
throw e;
}
}) as Promise<IKibanaSearchResponse>;
}
/**

View file

@ -9,7 +9,6 @@
import { i18n } from '@kbn/i18n';
import { estypes } from '@elastic/elasticsearch';
import { BfetchPublicSetup } from '@kbn/bfetch-plugin/public';
import { handleWarnings } from '@kbn/search-response-warnings';
import {
CoreSetup,
@ -78,7 +77,6 @@ import { ISearchSetup, ISearchStart } from './types';
/** @internal */
export interface SearchServiceSetupDependencies {
bfetch: BfetchPublicSetup;
expressions: ExpressionsSetup;
usageCollection?: UsageCollectionSetup;
management: ManagementSetup;
@ -106,13 +104,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
public setup(
core: CoreSetup,
{
bfetch,
expressions,
usageCollection,
nowProvider,
management,
}: SearchServiceSetupDependencies
{ expressions, usageCollection, nowProvider, management }: SearchServiceSetupDependencies
): ISearchSetup {
const { http, getStartServices, notifications, uiSettings, executionContext } = core;
this.usageCollector = createUsageCollector(getStartServices, usageCollection);
@ -130,7 +122,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
* all pending search requests, as well as getting the number of pending search requests.
*/
this.searchInterceptor = new SearchInterceptor({
bfetch,
toasts: notifications.toasts,
executionContext,
http,

View file

@ -1,67 +0,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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { firstValueFrom } from 'rxjs';
import { catchError } from 'rxjs';
import { BfetchServerSetup } from '@kbn/bfetch-plugin/server';
import type { ExecutionContextSetup } from '@kbn/core/server';
import apm from 'elastic-apm-node';
import type {
IKibanaSearchResponse,
IKibanaSearchRequest,
ISearchOptionsSerializable,
} from '@kbn/search-types';
import { getRequestAbortedSignal } from '../..';
import type { ISearchStart } from '../types';
export function registerBsearchRoute(
bfetch: BfetchServerSetup,
getScoped: ISearchStart['asScoped'],
executionContextService: ExecutionContextSetup
): void {
bfetch.addBatchProcessingRoute<
{ request: IKibanaSearchRequest; options?: ISearchOptionsSerializable },
IKibanaSearchResponse
>('/internal/bsearch', (request) => {
const search = getScoped(request);
const abortSignal = getRequestAbortedSignal(request.events.aborted$);
return {
/**
* @param requestOptions
* @throws `KibanaServerError`
*/
onBatchItem: async ({ request: requestData, options }) => {
const { executionContext, ...restOptions } = options || {};
return executionContextService.withContext(executionContext, () => {
apm.addLabels(executionContextService.getAsLabels());
return firstValueFrom(
search.search(requestData, { ...restOptions, abortSignal }).pipe(
catchError((err) => {
// Re-throw as object, to get attributes passed to the client
// eslint-disable-next-line no-throw-literal
throw {
message: err.message,
statusCode: err.statusCode,
attributes: err.errBody
? {
error: err.errBody.error,
rawResponse: err.errBody.response,
...(err.requestParams ? { requestParams: err.requestParams } : {}),
}
: undefined,
};
})
)
);
});
},
};
});
}

View file

@ -93,7 +93,6 @@ import {
import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn';
import { ConfigSchema } from '../config';
import { SearchSessionService } from './session';
import { registerBsearchRoute } from './routes/bsearch';
import { enhancedEsSearchStrategyProvider } from './strategies/ese_search';
import { eqlSearchStrategyProvider } from './strategies/eql_search';
import { NoSearchIdInSessionError } from './errors/no_search_id_in_session';
@ -209,12 +208,6 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
sqlSearchStrategyProvider(this.initializerContext.config.get().search, this.logger)
);
registerBsearchRoute(
bfetch,
(request: KibanaRequest) => this.asScoped(request),
core.executionContext
);
core.savedObjects.registerType(searchTelemetry);
if (usageCollection) {
const getIndexForType = (type: string) =>

View file

@ -47,7 +47,6 @@
"@kbn/search-errors",
"@kbn/search-response-warnings",
"@kbn/shared-ux-link-redirect-app",
"@kbn/bfetch-error",
"@kbn/es-types",
"@kbn/code-editor",
"@kbn/core-test-helpers-model-versions",

View file

@ -1,577 +0,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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import expect from '@kbn/expect';
import request from 'superagent';
import { inflateResponse } from '@kbn/bfetch-plugin/public/streaming';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common';
import { FtrProviderContext } from '../../ftr_provider_context';
import { painlessErrReq } from './painless_err_req';
import { verifyErrorResponse } from './verify_error';
function parseBfetchResponse(resp: request.Response, compressed: boolean = false) {
return resp.text
.trim()
.split('\n')
.map((item) => {
return JSON.parse(compressed ? inflateResponse<any>(item) : item);
});
}
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('bsearch', () => {
describe('post', () => {
it('should return 200 a single response', async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'es',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].id).to.be(0);
expect(jsonBody[0].result.isPartial).to.be(false);
expect(jsonBody[0].result.isRunning).to.be(false);
expect(jsonBody[0].result).to.have.property('rawResponse');
});
it('should return 200 a single response from compressed', async () => {
const resp = await supertest
.post(`/internal/bsearch?compress=true`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'es',
},
},
],
});
const jsonBody = parseBfetchResponse(resp, true);
expect(resp.status).to.be(200);
expect(jsonBody[0].id).to.be(0);
expect(jsonBody[0].result.isPartial).to.be(false);
expect(jsonBody[0].result.isRunning).to.be(false);
expect(jsonBody[0].result).to.have.property('rawResponse');
});
it('should return a batch of successful responses', async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
},
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
},
],
});
expect(resp.status).to.be(200);
const parsedResponse = parseBfetchResponse(resp);
expect(parsedResponse).to.have.length(2);
parsedResponse.forEach((responseJson) => {
expect(responseJson.result).to.have.property('isPartial');
expect(responseJson.result).to.have.property('isRunning');
expect(responseJson.result).to.have.property('rawResponse');
});
});
it('should return error for not found strategy', async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'wtf',
},
},
],
});
expect(resp.status).to.be(200);
parseBfetchResponse(resp).forEach((responseJson, i) => {
expect(responseJson.id).to.be(i);
verifyErrorResponse(responseJson.error, 404, 'Search strategy wtf not found');
});
});
it('should return 400 when index type is provided in "es" strategy', async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
index: '.kibana',
indexType: 'baad',
params: {
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'es',
},
},
],
});
expect(resp.status).to.be(200);
parseBfetchResponse(resp).forEach((responseJson, i) => {
expect(responseJson.id).to.be(i);
verifyErrorResponse(responseJson.error, 400, 'Unsupported index pattern type baad');
});
});
describe('painless', () => {
before(async () => {
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
});
after(async () => {
await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
});
it('should return 400 "search_phase_execution_exception" for Painless error in "es" strategy', async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: painlessErrReq,
options: {
strategy: 'es',
},
},
],
});
expect(resp.status).to.be(200);
parseBfetchResponse(resp).forEach((responseJson, i) => {
expect(responseJson.id).to.be(i);
verifyErrorResponse(responseJson.error, 400, 'search_phase_execution_exception', true);
});
});
});
describe('request meta', () => {
describe('es', () => {
it(`should return request meta`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'es',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].result.requestParams).to.eql({
method: 'POST',
path: '/.kibana/_search',
querystring: 'ignore_unavailable=true',
});
});
it(`should return request meta when request fails`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
bool: {
filter: [
{
error_query: {
indices: [
{
error_type: 'exception',
message: 'simulated failure',
name: '.kibana',
},
],
},
},
],
},
},
},
},
},
options: {
strategy: 'es',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].error.attributes.requestParams).to.eql({
method: 'POST',
path: '/.kibana/_search',
querystring: 'ignore_unavailable=true',
});
});
});
describe('ese', () => {
it(`should return request meta`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'ese',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].result.requestParams).to.eql({
method: 'POST',
path: '/.kibana/_async_search',
querystring:
'batched_reduce_size=64&ccs_minimize_roundtrips=true&wait_for_completion_timeout=200ms&keep_on_completion=false&keep_alive=60000ms&ignore_unavailable=true',
});
});
it(`should return request meta when request fails`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
bool: {
filter: [
{
error_query: {
indices: [
{
error_type: 'exception',
message: 'simulated failure',
name: '.kibana',
},
],
},
},
],
},
},
},
},
options: {
strategy: 'ese',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].error.attributes.requestParams).to.eql({
method: 'POST',
path: '/.kibana/_async_search',
querystring:
'batched_reduce_size=64&ccs_minimize_roundtrips=true&wait_for_completion_timeout=200ms&keep_on_completion=false&keep_alive=60000ms&ignore_unavailable=true',
});
});
});
describe('esql', () => {
it(`should return request meta`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
query: 'from .kibana | limit 1',
},
},
options: {
strategy: 'esql',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].result.requestParams).to.eql({
method: 'POST',
path: '/_query',
});
});
it(`should return request meta when request fails`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
query: 'fro .kibana | limit 1',
},
},
options: {
strategy: 'esql',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].error.attributes.requestParams).to.eql({
method: 'POST',
path: '/_query',
});
});
});
describe('sql', () => {
it(`should return request meta`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
query: 'SELECT * FROM ".kibana" LIMIT 1',
},
},
options: {
strategy: 'sql',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].result.requestParams).to.eql({
method: 'POST',
path: '/_sql',
querystring: 'format=json',
});
});
it(`should return request meta when request fails`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
query: 'SELEC * FROM ".kibana" LIMIT 1',
},
},
options: {
strategy: 'sql',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].error.attributes.requestParams).to.eql({
method: 'POST',
path: '/_sql',
querystring: 'format=json',
});
});
});
describe('eql', () => {
it(`should return request meta`, async () => {
const resp = await supertest
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.send({
batch: [
{
request: {
params: {
index: '.kibana',
query: 'any where true',
timestamp_field: 'created_at',
},
},
options: {
strategy: 'eql',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].result.requestParams).to.eql({
method: 'POST',
path: '/.kibana/_eql/search',
querystring: 'ignore_unavailable=true',
});
});
});
});
});
});
}

View file

@ -13,6 +13,5 @@ export default function ({ loadTestFile }: FtrProviderContext) {
describe('search', () => {
loadTestFile(require.resolve('./search'));
loadTestFile(require.resolve('./sql_search'));
loadTestFile(require.resolve('./bsearch'));
});
}

View file

@ -23,7 +23,6 @@
"kbn_references": [
"@kbn/core",
{ "path": "../src/setup_node_env/tsconfig.json" },
"@kbn/bfetch-plugin",
"@kbn/dashboard-plugin",
"@kbn/expressions-plugin",
"@kbn/saved-objects-management-plugin",

View file

@ -11,7 +11,6 @@
import expect from '@kbn/expect';
import type { IEsSearchResponse } from '@kbn/search-types';
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common';
import { SupertestWithoutAuthProviderType } from '@kbn/ftr-common-functional-services';
import { FtrService } from '../ftr_provider_context';
@ -109,7 +108,7 @@ export class SearchSecureService extends FtrService {
.auth(auth.username, auth.password)
.set('kbn-xsrf', 'true')
.set('x-elastic-internal-origin', 'Kibana')
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.send(options)
.expect(200);
expect(resp.body.isRunning).equal(false);

View file

@ -1,247 +0,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 expect from '@kbn/expect';
import request from 'superagent';
import { inflateResponse } from '@kbn/bfetch-plugin/public/streaming';
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
import { BFETCH_ROUTE_VERSION_LATEST } from '@kbn/bfetch-plugin/common';
import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services';
import type { FtrProviderContext } from '../../../ftr_provider_context';
import { painlessErrReq } from './painless_err_req';
import { verifyErrorResponse } from './verify_error';
function parseBfetchResponse(resp: request.Response, compressed: boolean = false) {
return resp.text
.trim()
.split('\n')
.map((item) => {
return JSON.parse(compressed ? inflateResponse<any>(item) : item);
});
}
export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const roleScopedSupertest = getService('roleScopedSupertest');
let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType;
describe('bsearch', () => {
before(async () => {
supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
'admin',
{
useCookieHeader: true,
withInternalHeaders: true,
withCustomHeaders: {
[ELASTIC_HTTP_VERSION_HEADER]: BFETCH_ROUTE_VERSION_LATEST,
},
}
);
});
describe('post', () => {
it('should return 200 a single response', async () => {
const resp = await supertestAdminWithCookieCredentials.post(`/internal/bsearch`).send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'es',
},
},
],
});
const jsonBody = parseBfetchResponse(resp);
expect(resp.status).to.be(200);
expect(jsonBody[0].id).to.be(0);
expect(jsonBody[0].result.isPartial).to.be(false);
expect(jsonBody[0].result.isRunning).to.be(false);
expect(jsonBody[0].result).to.have.property('rawResponse');
});
it('should return 200 a single response from compressed', async () => {
const resp = await supertestAdminWithCookieCredentials
.post(`/internal/bsearch?compress=true`)
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'es',
},
},
],
});
const jsonBody = parseBfetchResponse(resp, true);
expect(resp.status).to.be(200);
expect(jsonBody[0].id).to.be(0);
expect(jsonBody[0].result.isPartial).to.be(false);
expect(jsonBody[0].result.isRunning).to.be(false);
expect(jsonBody[0].result).to.have.property('rawResponse');
});
it('should return a batch of successful responses', async () => {
const resp = await supertestAdminWithCookieCredentials
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
},
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
},
],
});
expect(resp.status).to.be(200);
const parsedResponse = parseBfetchResponse(resp);
expect(parsedResponse).to.have.length(2);
parsedResponse.forEach((responseJson) => {
expect(responseJson.result).to.have.property('isPartial');
expect(responseJson.result).to.have.property('isRunning');
expect(responseJson.result).to.have.property('rawResponse');
});
});
it('should return error for not found strategy', async () => {
const resp = await supertestAdminWithCookieCredentials
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.send({
batch: [
{
request: {
params: {
index: '.kibana',
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'wtf',
},
},
],
});
expect(resp.status).to.be(200);
parseBfetchResponse(resp).forEach((responseJson, i) => {
expect(responseJson.id).to.be(i);
verifyErrorResponse(responseJson.error, 404, 'Search strategy wtf not found');
});
});
it('should return 400 when index type is provided in "es" strategy', async () => {
const resp = await supertestAdminWithCookieCredentials
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.send({
batch: [
{
request: {
index: '.kibana',
indexType: 'baad',
params: {
body: {
query: {
match_all: {},
},
},
},
},
options: {
strategy: 'es',
},
},
],
});
expect(resp.status).to.be(200);
parseBfetchResponse(resp).forEach((responseJson, i) => {
expect(responseJson.id).to.be(i);
verifyErrorResponse(responseJson.error, 400, 'Unsupported index pattern type baad');
});
});
describe('painless', () => {
before(async () => {
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
});
after(async () => {
await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional');
});
it('should return 400 "search_phase_execution_exception" for Painless error in "es" strategy', async () => {
const resp = await supertestAdminWithCookieCredentials
.post(`/internal/bsearch`)
.set(ELASTIC_HTTP_VERSION_HEADER, BFETCH_ROUTE_VERSION_LATEST)
.send({
batch: [
{
request: painlessErrReq,
options: {
strategy: 'es',
},
},
],
});
expect(resp.status).to.be(200);
parseBfetchResponse(resp).forEach((responseJson, i) => {
expect(responseJson.id).to.be(i);
verifyErrorResponse(responseJson.error, 400, 'search_phase_execution_exception', true);
});
});
});
});
});
}

View file

@ -16,6 +16,5 @@ export default function ({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./search'));
// TODO: Removed `sql_search` since
// SQL is not supported in Serverless
loadTestFile(require.resolve('./bsearch'));
});
}

View file

@ -50,7 +50,6 @@
"@kbn/data-view-field-editor-plugin",
"@kbn/data-plugin",
"@kbn/dev-utils",
"@kbn/bfetch-plugin",
"@kbn/es-archiver",
"@kbn/rule-data-utils",
"@kbn/rison",