mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Search Sessions] Improve search session errors (#88613)
* Detect ESError correctly Fix bfetch error (was recognized as unknown error) Make sure handleSearchError always returns an error object. * fix tests and improve types * type * normalize search error response format for search and bsearch * type * Added es search exception examples * Normalize and validate errors thrown from oss es_search_strategy Validate abort * Added tests for search service error handling * Update msearch tests to test for errors * Moved bsearch route to routes folder Adjusted bsearch response format Added verification of error's root cause * Align painless error object * eslint * Add to seach interceptor tests * add json to tsconfig * docs * updated xpack search strategy tests * oops * license header * Add test for xpack painless error format * doc * Fix bsearch test potential flakiness * code review * fix * code review 2 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
52f54030c3
commit
841ab704b8
42 changed files with 1502 additions and 298 deletions
172
test/api_integration/apis/search/bsearch.ts
Normal file
172
test/api_integration/apis/search/bsearch.ts
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import request from 'superagent';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { painlessErrReq } from './painless_err_req';
|
||||
import { verifyErrorResponse } from './verify_error';
|
||||
|
||||
function parseBfetchResponse(resp: request.Response): Array<Record<string, any>> {
|
||||
return resp.text
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((item) => JSON.parse(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`).send({
|
||||
batch: [
|
||||
{
|
||||
request: {
|
||||
params: {
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const jsonBody = JSON.parse(resp.text);
|
||||
|
||||
expect(resp.status).to.be(200);
|
||||
expect(jsonBody.id).to.be(0);
|
||||
expect(jsonBody.result.isPartial).to.be(false);
|
||||
expect(jsonBody.result.isRunning).to.be(false);
|
||||
expect(jsonBody.result).to.have.property('rawResponse');
|
||||
});
|
||||
|
||||
it('should return a batch of successful resposes', async () => {
|
||||
const resp = await supertest.post(`/internal/bsearch`).send({
|
||||
batch: [
|
||||
{
|
||||
request: {
|
||||
params: {
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
request: {
|
||||
params: {
|
||||
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.isPartial).to.be(false);
|
||||
expect(responseJson.result.isRunning).to.be(false);
|
||||
expect(responseJson.result).to.have.property('rawResponse');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error for not found strategy', async () => {
|
||||
const resp = await supertest.post(`/internal/bsearch`).send({
|
||||
batch: [
|
||||
{
|
||||
request: {
|
||||
params: {
|
||||
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 OSS', async () => {
|
||||
const resp = await supertest.post(`/internal/bsearch`).send({
|
||||
batch: [
|
||||
{
|
||||
request: {
|
||||
indexType: 'baad',
|
||||
params: {
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
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(
|
||||
'../../../functional/fixtures/es_archiver/logstash_functional'
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('../../../functional/fixtures/es_archiver/logstash_functional');
|
||||
});
|
||||
it('should return 400 for Painless error', async () => {
|
||||
const resp = await supertest.post(`/internal/bsearch`).send({
|
||||
batch: [
|
||||
{
|
||||
request: painlessErrReq,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -11,6 +11,7 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('search', () => {
|
||||
loadTestFile(require.resolve('./search'));
|
||||
loadTestFile(require.resolve('./bsearch'));
|
||||
loadTestFile(require.resolve('./msearch'));
|
||||
});
|
||||
}
|
||||
|
|
44
test/api_integration/apis/search/painless_err_req.ts
Normal file
44
test/api_integration/apis/search/painless_err_req.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
export const painlessErrReq = {
|
||||
params: {
|
||||
index: 'log*',
|
||||
body: {
|
||||
size: 500,
|
||||
fields: ['*'],
|
||||
script_fields: {
|
||||
invalid_scripted_field: {
|
||||
script: {
|
||||
source: 'invalid',
|
||||
lang: 'painless',
|
||||
},
|
||||
},
|
||||
},
|
||||
stored_fields: ['*'],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
match_all: {},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: '2015-01-19T12:27:55.047Z',
|
||||
lte: '2021-01-19T12:27:55.047Z',
|
||||
format: 'strict_date_optional_time',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -8,11 +8,21 @@
|
|||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { painlessErrReq } from './painless_err_req';
|
||||
import { verifyErrorResponse } from './verify_error';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
|
||||
describe('search', () => {
|
||||
before(async () => {
|
||||
await esArchiver.loadIfNeeded('../../../functional/fixtures/es_archiver/logstash_functional');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await esArchiver.unload('../../../functional/fixtures/es_archiver/logstash_functional');
|
||||
});
|
||||
describe('post', () => {
|
||||
it('should return 200 when correctly formatted searches are provided', async () => {
|
||||
const resp = await supertest
|
||||
|
@ -28,13 +38,37 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(resp.status).to.be(200);
|
||||
expect(resp.body.isPartial).to.be(false);
|
||||
expect(resp.body.isRunning).to.be(false);
|
||||
expect(resp.body).to.have.property('rawResponse');
|
||||
});
|
||||
|
||||
it('should return 404 when if no strategy is provided', async () =>
|
||||
await supertest
|
||||
it('should return 200 if terminated early', async () => {
|
||||
const resp = await supertest
|
||||
.post(`/internal/search/es`)
|
||||
.send({
|
||||
params: {
|
||||
terminateAfter: 1,
|
||||
index: 'log*',
|
||||
size: 1000,
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(resp.status).to.be(200);
|
||||
expect(resp.body.isPartial).to.be(false);
|
||||
expect(resp.body.isRunning).to.be(false);
|
||||
expect(resp.body.rawResponse.terminated_early).to.be(true);
|
||||
});
|
||||
|
||||
it('should return 404 when if no strategy is provided', async () => {
|
||||
const resp = await supertest
|
||||
.post(`/internal/search`)
|
||||
.send({
|
||||
body: {
|
||||
|
@ -43,7 +77,10 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
},
|
||||
},
|
||||
})
|
||||
.expect(404));
|
||||
.expect(404);
|
||||
|
||||
verifyErrorResponse(resp.body, 404);
|
||||
});
|
||||
|
||||
it('should return 404 when if unknown strategy is provided', async () => {
|
||||
const resp = await supertest
|
||||
|
@ -56,6 +93,8 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
},
|
||||
})
|
||||
.expect(404);
|
||||
|
||||
verifyErrorResponse(resp.body, 404);
|
||||
expect(resp.body.message).to.contain('banana not found');
|
||||
});
|
||||
|
||||
|
@ -74,11 +113,33 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
})
|
||||
.expect(400);
|
||||
|
||||
verifyErrorResponse(resp.body, 400);
|
||||
|
||||
expect(resp.body.message).to.contain('Unsupported index pattern');
|
||||
});
|
||||
|
||||
it('should return 400 with illegal ES argument', async () => {
|
||||
const resp = await supertest
|
||||
.post(`/internal/search/es`)
|
||||
.send({
|
||||
params: {
|
||||
timeout: 1, // This should be a time range string!
|
||||
index: 'log*',
|
||||
size: 1000,
|
||||
body: {
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
verifyErrorResponse(resp.body, 400, 'illegal_argument_exception', true);
|
||||
});
|
||||
|
||||
it('should return 400 with a bad body', async () => {
|
||||
await supertest
|
||||
const resp = await supertest
|
||||
.post(`/internal/search/es`)
|
||||
.send({
|
||||
params: {
|
||||
|
@ -89,16 +150,26 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
},
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
verifyErrorResponse(resp.body, 400, 'parsing_exception', true);
|
||||
});
|
||||
|
||||
it('should return 400 for a painless error', async () => {
|
||||
const resp = await supertest.post(`/internal/search/es`).send(painlessErrReq).expect(400);
|
||||
|
||||
verifyErrorResponse(resp.body, 400, 'search_phase_execution_exception', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should return 404 when no search id provided', async () => {
|
||||
await supertest.delete(`/internal/search/es`).send().expect(404);
|
||||
const resp = await supertest.delete(`/internal/search/es`).send().expect(404);
|
||||
verifyErrorResponse(resp.body, 404);
|
||||
});
|
||||
|
||||
it('should return 400 when trying a delete on a non supporting strategy', async () => {
|
||||
const resp = await supertest.delete(`/internal/search/es/123`).send().expect(400);
|
||||
verifyErrorResponse(resp.body, 400);
|
||||
expect(resp.body.message).to.contain("Search strategy es doesn't support cancellations");
|
||||
});
|
||||
});
|
||||
|
|
27
test/api_integration/apis/search/verify_error.ts
Normal file
27
test/api_integration/apis/search/verify_error.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* and the Server Side Public License, v 1; you may not use this file except in
|
||||
* compliance with, at your election, the Elastic License or the Server Side
|
||||
* Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
|
||||
export const verifyErrorResponse = (
|
||||
r: any,
|
||||
expectedCode: number,
|
||||
message?: string,
|
||||
shouldHaveAttrs?: boolean
|
||||
) => {
|
||||
expect(r.statusCode).to.be(expectedCode);
|
||||
if (message) {
|
||||
expect(r.message).to.be(message);
|
||||
}
|
||||
if (shouldHaveAttrs) {
|
||||
expect(r).to.have.property('attributes');
|
||||
expect(r.attributes).to.have.property('root_cause');
|
||||
} else {
|
||||
expect(r).not.to.have.property('attributes');
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue