From 3c21a37bbeb6aea86367a0417ac2998f658a325f Mon Sep 17 00:00:00 2001 From: Chadi El Masri <1502811+ChadiEM@users.noreply.github.com> Date: Mon, 2 Mar 2020 10:45:31 +0100 Subject: [PATCH] Elastic: To get fields, start with today's index and go backwards (#22318) * Elastic: To get fields, start with today's index and go backwards * Elastic: distinguish non-existing indices from other issues; change index traversal from recursive to iterative; go through a max of 7 days * Elastic: fix the comments Co-authored-by: Andrej Ocenas --- .../elasticsearch/datasource.test.ts | 158 ++++++++++++++++-- .../datasource/elasticsearch/datasource.ts | 24 ++- 2 files changed, 168 insertions(+), 14 deletions(-) diff --git a/public/app/plugins/datasource/elasticsearch/datasource.test.ts b/public/app/plugins/datasource/elasticsearch/datasource.test.ts index c3ff6d9f7b3..8eeb89417f4 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.test.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.test.ts @@ -37,26 +37,41 @@ describe('ElasticDatasource', function(this: any) { getAdhocFilters: jest.fn(() => []), }; - const timeSrv: any = { - time: { from: 'now-1h', to: 'now' }, - timeRange: jest.fn(() => { - return { - from: dateMath.parse(timeSrv.time.from, false), - to: dateMath.parse(timeSrv.time.to, true), - }; - }), - setTime: jest.fn(time => { - this.time = time; - }), - }; + const timeSrv: any = createTimeSrv('now-1h'); const ctx = { $rootScope, } as any; + function createTimeSrv(from: string) { + const srv: any = { + time: { from: from, to: 'now' }, + }; + + srv.timeRange = jest.fn(() => { + return { + from: dateMath.parse(srv.time.from, false), + to: dateMath.parse(srv.time.to, true), + }; + }); + + srv.setTime = jest.fn(time => { + srv.time = time; + }); + + return srv; + } + function createDatasource(instanceSettings: DataSourceInstanceSettings) { + createDatasourceWithTime(instanceSettings, timeSrv as TimeSrv); + } + + function createDatasourceWithTime( + instanceSettings: DataSourceInstanceSettings, + timeSrv: TimeSrv + ) { instanceSettings.jsonData = instanceSettings.jsonData || ({} as ElasticsearchOptions); - ctx.ds = new ElasticDatasource(instanceSettings, templateSrv as TemplateSrv, timeSrv as TimeSrv); + ctx.ds = new ElasticDatasource(instanceSettings, templateSrv as TemplateSrv, timeSrv); } describe('When testing datasource with index pattern', () => { @@ -355,6 +370,123 @@ describe('ElasticDatasource', function(this: any) { }); }); + describe('When getting field mappings on indices with gaps', () => { + const twoWeekTimeSrv: any = createTimeSrv('now-2w'); + + const basicResponse = { + data: { + metricbeat: { + mappings: { + metricsets: { + _all: {}, + properties: { + '@timestamp': { type: 'date' }, + beat: { + properties: { + hostname: { type: 'string' }, + }, + }, + }, + }, + }, + }, + }, + }; + + const alternateResponse = { + data: { + metricbeat: { + mappings: { + metricsets: { + _all: {}, + properties: { + '@timestamp': { type: 'date' }, + }, + }, + }, + }, + }, + }; + + beforeEach(() => { + createDatasourceWithTime( + { + url: 'http://es.com', + database: '[asd-]YYYY.MM.DD', + jsonData: { interval: 'Daily', esVersion: 50 } as ElasticsearchOptions, + } as DataSourceInstanceSettings, + twoWeekTimeSrv + ); + }); + + it('should return fields of the newest available index', async () => { + const twoDaysBefore = toUtc() + .subtract(2, 'day') + .format('YYYY.MM.DD'); + + const threeDaysBefore = toUtc() + .subtract(3, 'day') + .format('YYYY.MM.DD'); + + datasourceRequestMock.mockImplementation(options => { + if (options.url === `http://es.com/asd-${twoDaysBefore}/_mapping`) { + return Promise.resolve(basicResponse); + } else if (options.url === `http://es.com/asd-${threeDaysBefore}/_mapping`) { + return Promise.resolve(alternateResponse); + } + return Promise.reject({ status: 404 }); + }); + + const fieldObjects = await ctx.ds.getFields({ + find: 'fields', + query: '*', + }); + const fields = _.map(fieldObjects, 'text'); + expect(fields).toEqual(['@timestamp', 'beat.hostname']); + }); + + it('should not retry when ES is down', async () => { + const twoDaysBefore = toUtc() + .subtract(2, 'day') + .format('YYYY.MM.DD'); + + datasourceRequestMock.mockImplementation(options => { + if (options.url === `http://es.com/asd-${twoDaysBefore}/_mapping`) { + return Promise.resolve(basicResponse); + } + return Promise.reject({ status: 500 }); + }); + + expect.assertions(2); + try { + await ctx.ds.getFields({ + find: 'fields', + query: '*', + }); + } catch (e) { + expect(e).toStrictEqual({ status: 500 }); + expect(datasourceRequestMock).toBeCalledTimes(1); + } + }); + + it('should not retry more than 7 indices', async () => { + datasourceRequestMock.mockImplementation(() => { + return Promise.reject({ status: 404 }); + }); + + expect.assertions(2); + try { + await ctx.ds.getFields({ + find: 'fields', + query: '*', + }); + } catch (e) { + expect(e).toStrictEqual({ status: 404 }); + expect(datasourceRequestMock).toBeCalledTimes(7); + } + }); + }); + describe('When getting fields from ES 7.0', () => { beforeEach(() => { createDatasource({ diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index 3c61c458426..ec72a84c7ee 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -89,11 +89,19 @@ export class ElasticDatasource extends DataSourceApi { + return this.requestAllIndices(indexList, url).then((results: any) => { results.data.$$config = results.config; return results.data; }); @@ -105,6 +113,20 @@ export class ElasticDatasource extends DataSourceApi { + const maxTraversals = 7; // do not go beyond one week (for a daily pattern) + const listLen = indexList.length; + for (let i = 0; i < Math.min(listLen, maxTraversals); i++) { + try { + return await this.request('GET', indexList[listLen - i - 1] + url); + } catch (err) { + if (err.status !== 404 || i === maxTraversals - 1) { + throw err; + } + } + } + } + private post(url: string, data: any) { return this.request('POST', url, data) .then((results: any) => {