mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Elastic: prevents datasource from throwing unexpected errors for invalid JSON (#24999)
* Chore: adds error handling to get requests in elasticsearch datasource * Chore: updates elasticsearch datasource response parsing checks * Chore: updates elasticsearch non-json errors description * Chore: removes error catching from query methods to move it to the request method in elasticsearch * Chore: fixes a typo in elastic response error message * Chore: moves elasticsearch error handling to request method * Chore: replaces datasource url in mock elasticsearch datasource
This commit is contained in:
parent
a9d34a3e6f
commit
1ea8346644
@ -9,6 +9,8 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
|
|||||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||||
import { ElasticsearchOptions, ElasticsearchQuery } from './types';
|
import { ElasticsearchOptions, ElasticsearchQuery } from './types';
|
||||||
|
|
||||||
|
const ELASTICSEARCH_MOCK_URL = 'http://elasticsearch.local';
|
||||||
|
|
||||||
jest.mock('@grafana/runtime', () => ({
|
jest.mock('@grafana/runtime', () => ({
|
||||||
...jest.requireActual('@grafana/runtime'),
|
...jest.requireActual('@grafana/runtime'),
|
||||||
getBackendSrv: () => backendSrv,
|
getBackendSrv: () => backendSrv,
|
||||||
@ -77,7 +79,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
describe('When testing datasource with index pattern', () => {
|
describe('When testing datasource with index pattern', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: '[asd-]YYYY.MM.DD',
|
database: '[asd-]YYYY.MM.DD',
|
||||||
jsonData: { interval: 'Daily', esVersion: 2 } as ElasticsearchOptions,
|
jsonData: { interval: 'Daily', esVersion: 2 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
@ -93,7 +95,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
ctx.ds.testDatasource();
|
ctx.ds.testDatasource();
|
||||||
|
|
||||||
const today = toUtc().format('YYYY.MM.DD');
|
const today = toUtc().format('YYYY.MM.DD');
|
||||||
expect(requestOptions.url).toBe('http://es.com/asd-' + today + '/_mapping');
|
expect(requestOptions.url).toBe(`${ELASTICSEARCH_MOCK_URL}/asd-${today}/_mapping`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -102,7 +104,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: '[asd-]YYYY.MM.DD',
|
database: '[asd-]YYYY.MM.DD',
|
||||||
jsonData: { interval: 'Daily', esVersion: 2 } as ElasticsearchOptions,
|
jsonData: { interval: 'Daily', esVersion: 2 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
@ -171,7 +173,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
describe('When issuing logs query with interval pattern', () => {
|
describe('When issuing logs query with interval pattern', () => {
|
||||||
async function setupDataSource(jsonData?: Partial<ElasticsearchOptions>) {
|
async function setupDataSource(jsonData?: Partial<ElasticsearchOptions>) {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: 'mock-index',
|
database: 'mock-index',
|
||||||
jsonData: {
|
jsonData: {
|
||||||
interval: 'Daily',
|
interval: 'Daily',
|
||||||
@ -236,7 +238,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: 'test',
|
database: 'test',
|
||||||
jsonData: { esVersion: 2 } as ElasticsearchOptions,
|
jsonData: { esVersion: 2 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
@ -274,10 +276,89 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('When getting an error on response', () => {
|
||||||
|
const query = {
|
||||||
|
range: {
|
||||||
|
from: toUtc([2020, 1, 1, 10]),
|
||||||
|
to: toUtc([2020, 2, 1, 10]),
|
||||||
|
},
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
alias: '$varAlias',
|
||||||
|
bucketAggs: [{ type: 'date_histogram', field: '@timestamp', id: '1' }],
|
||||||
|
metrics: [{ type: 'count', id: '1' }],
|
||||||
|
query: 'escape\\:test',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
createDatasource({
|
||||||
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
|
database: '[asd-]YYYY.MM.DD',
|
||||||
|
jsonData: { interval: 'Daily', esVersion: 7 } as ElasticsearchOptions,
|
||||||
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
|
|
||||||
|
it('should process it properly', async () => {
|
||||||
|
datasourceRequestMock.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
took: 1,
|
||||||
|
responses: [
|
||||||
|
{
|
||||||
|
error: {
|
||||||
|
reason: 'all shards failed',
|
||||||
|
},
|
||||||
|
status: 400,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const errObject = {
|
||||||
|
data: '{\n "reason": "all shards failed"\n}',
|
||||||
|
message: 'all shards failed',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ctx.ds.query(query);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).toEqual(errObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly throw an unknown error', async () => {
|
||||||
|
datasourceRequestMock.mockImplementation(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
data: {
|
||||||
|
took: 1,
|
||||||
|
responses: [
|
||||||
|
{
|
||||||
|
error: {},
|
||||||
|
status: 400,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const errObject = {
|
||||||
|
data: '{}',
|
||||||
|
message: 'Unknown elastic error response',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ctx.ds.query(query);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).toEqual(errObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('When getting fields', () => {
|
describe('When getting fields', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: 'metricbeat',
|
database: 'metricbeat',
|
||||||
jsonData: { esVersion: 50 } as ElasticsearchOptions,
|
jsonData: { esVersion: 50 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
@ -411,7 +492,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasourceWithTime(
|
createDatasourceWithTime(
|
||||||
{
|
{
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: '[asd-]YYYY.MM.DD',
|
database: '[asd-]YYYY.MM.DD',
|
||||||
jsonData: { interval: 'Daily', esVersion: 50 } as ElasticsearchOptions,
|
jsonData: { interval: 'Daily', esVersion: 50 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>,
|
} as DataSourceInstanceSettings<ElasticsearchOptions>,
|
||||||
@ -429,9 +510,9 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
.format('YYYY.MM.DD');
|
.format('YYYY.MM.DD');
|
||||||
|
|
||||||
datasourceRequestMock.mockImplementation(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
if (options.url === `http://es.com/asd-${twoDaysBefore}/_mapping`) {
|
if (options.url === `${ELASTICSEARCH_MOCK_URL}/asd-${twoDaysBefore}/_mapping`) {
|
||||||
return Promise.resolve(basicResponse);
|
return Promise.resolve(basicResponse);
|
||||||
} else if (options.url === `http://es.com/asd-${threeDaysBefore}/_mapping`) {
|
} else if (options.url === `${ELASTICSEARCH_MOCK_URL}/asd-${threeDaysBefore}/_mapping`) {
|
||||||
return Promise.resolve(alternateResponse);
|
return Promise.resolve(alternateResponse);
|
||||||
}
|
}
|
||||||
return Promise.reject({ status: 404 });
|
return Promise.reject({ status: 404 });
|
||||||
@ -451,7 +532,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
.format('YYYY.MM.DD');
|
.format('YYYY.MM.DD');
|
||||||
|
|
||||||
datasourceRequestMock.mockImplementation(options => {
|
datasourceRequestMock.mockImplementation(options => {
|
||||||
if (options.url === `http://es.com/asd-${twoDaysBefore}/_mapping`) {
|
if (options.url === `${ELASTICSEARCH_MOCK_URL}/asd-${twoDaysBefore}/_mapping`) {
|
||||||
return Promise.resolve(basicResponse);
|
return Promise.resolve(basicResponse);
|
||||||
}
|
}
|
||||||
return Promise.reject({ status: 500 });
|
return Promise.reject({ status: 500 });
|
||||||
@ -490,7 +571,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
describe('When getting fields from ES 7.0', () => {
|
describe('When getting fields from ES 7.0', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: 'genuine.es7._mapping.response',
|
database: 'genuine.es7._mapping.response',
|
||||||
jsonData: { esVersion: 70 } as ElasticsearchOptions,
|
jsonData: { esVersion: 70 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
@ -643,7 +724,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: 'test',
|
database: 'test',
|
||||||
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
@ -686,7 +767,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: 'test',
|
database: 'test',
|
||||||
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
jsonData: { esVersion: 5 } as ElasticsearchOptions,
|
||||||
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
} as DataSourceInstanceSettings<ElasticsearchOptions>);
|
||||||
@ -750,7 +831,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
it('should replace range as integer not string', () => {
|
it('should replace range as integer not string', () => {
|
||||||
const dataSource = new ElasticDatasource(
|
const dataSource = new ElasticDatasource(
|
||||||
{
|
{
|
||||||
url: 'http://es.com',
|
url: ELASTICSEARCH_MOCK_URL,
|
||||||
database: '[asd-]YYYY.MM.DD',
|
database: '[asd-]YYYY.MM.DD',
|
||||||
jsonData: {
|
jsonData: {
|
||||||
interval: 'Daily',
|
interval: 'Daily',
|
||||||
|
@ -86,7 +86,17 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return getBackendSrv().datasourceRequest(options);
|
return getBackendSrv()
|
||||||
|
.datasourceRequest(options)
|
||||||
|
.catch((err: any) => {
|
||||||
|
if (err.data && err.data.error) {
|
||||||
|
throw {
|
||||||
|
message: 'Elasticsearch error: ' + err.data.error.reason,
|
||||||
|
error: err.data.error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,21 +138,10 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
}
|
}
|
||||||
|
|
||||||
private post(url: string, data: any) {
|
private post(url: string, data: any) {
|
||||||
return this.request('POST', url, data)
|
return this.request('POST', url, data).then((results: any) => {
|
||||||
.then((results: any) => {
|
results.data.$$config = results.config;
|
||||||
results.data.$$config = results.config;
|
return results.data;
|
||||||
return results.data;
|
});
|
||||||
})
|
|
||||||
.catch((err: any) => {
|
|
||||||
if (err.data && err.data.error) {
|
|
||||||
throw {
|
|
||||||
message: 'Elasticsearch error: ' + err.data.error.reason,
|
|
||||||
error: err.data.error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationQuery(options: any): Promise<any> {
|
annotationQuery(options: any): Promise<any> {
|
||||||
|
@ -368,7 +368,7 @@ export class ElasticResponse {
|
|||||||
if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) {
|
if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) {
|
||||||
result.message = err.root_cause[0].reason;
|
result.message = err.root_cause[0].reason;
|
||||||
} else {
|
} else {
|
||||||
result.message = err.reason || 'Unkown elastic error response';
|
result.message = err.reason || 'Unknown elastic error response';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.$$config) {
|
if (response.$$config) {
|
||||||
|
Loading…
Reference in New Issue
Block a user