diff --git a/package.json b/package.json index d3c8719928f46..e7ecccbf70404 100644 --- a/package.json +++ b/package.json @@ -181,6 +181,7 @@ "ngreact": "0.5.1", "no-ui-slider": "1.2.0", "node-fetch": "1.3.2", + "object-hash": "^1.3.1", "opn": "^5.4.0", "oppsy": "^2.0.0", "pegjs": "0.9.0", diff --git a/x-pack/plugins/task_manager/lib/template_properties.ts b/x-pack/plugins/task_manager/lib/template_properties.ts new file mode 100644 index 0000000000000..329909d507f7e --- /dev/null +++ b/x-pack/plugins/task_manager/lib/template_properties.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const templateProperties = { + type: { type: 'keyword' }, + task: { + properties: { + taskType: { type: 'keyword' }, + runAt: { type: 'date' }, + interval: { type: 'text' }, + attempts: { type: 'integer' }, + status: { type: 'keyword' }, + params: { type: 'text' }, + state: { type: 'text' }, + user: { type: 'keyword' }, + scope: { type: 'keyword' }, + timeout: { type: 'integer' }, + }, + }, +}; diff --git a/x-pack/plugins/task_manager/task_manager.ts b/x-pack/plugins/task_manager/task_manager.ts index fae633331f4bf..d699fe1bef14f 100644 --- a/x-pack/plugins/task_manager/task_manager.ts +++ b/x-pack/plugins/task_manager/task_manager.ts @@ -58,6 +58,7 @@ export class TaskManager { index: config.get('xpack.task_manager.index'), maxAttempts: config.get('xpack.task_manager.max_attempts'), supportedTypes: Object.keys(this.definitions), + logger, }); const pool = new TaskPool({ logger, @@ -94,7 +95,7 @@ export class TaskManager { this.isInitialized = true; }) .catch((err: Error) => { - logger.warning(err.message); + logger.warning('Poller: ' + err.message); // rety again to initialize store and poller, using the timing of // task_manager's configurable poll interval diff --git a/x-pack/plugins/task_manager/task_poller.test.ts b/x-pack/plugins/task_manager/task_poller.test.ts index 1a581c7b9c037..a8838923cd128 100644 --- a/x-pack/plugins/task_manager/task_poller.test.ts +++ b/x-pack/plugins/task_manager/task_poller.test.ts @@ -20,6 +20,7 @@ describe('TaskPoller', () => { index: 'tasky', maxAttempts: 2, supportedTypes: ['a', 'b', 'c'], + logger: mockLogger(), }); }); diff --git a/x-pack/plugins/task_manager/task_store.test.ts b/x-pack/plugins/task_manager/task_store.test.ts index a76ba03de0faf..f89c3f0c99516 100644 --- a/x-pack/plugins/task_manager/task_store.test.ts +++ b/x-pack/plugins/task_manager/task_store.test.ts @@ -8,6 +8,7 @@ import _ from 'lodash'; import sinon from 'sinon'; import { TaskInstance, TaskStatus } from './task'; import { FetchOpts, TaskStore } from './task_store'; +import { mockLogger } from './test_utils'; describe('TaskStore', () => { describe('init', () => { @@ -18,11 +19,16 @@ describe('TaskStore', () => { index: 'tasky', maxAttempts: 2, supportedTypes: ['a', 'b', 'c'], + logger: mockLogger(), }); await store.init(); - sinon.assert.calledOnce(callCluster); + sinon.assert.calledTwice(callCluster); + + sinon.assert.calledWithMatch(callCluster, 'indices.getTemplate', { + name: 'tasky', + }); sinon.assert.calledWithMatch(callCluster, 'indices.putTemplate', { body: { @@ -50,12 +56,13 @@ describe('TaskStore', () => { index: 'tasky', maxAttempts: 2, supportedTypes: ['report', 'dernstraight', 'yawn'], + logger: mockLogger(), }); const result = await store.schedule(task); - sinon.assert.calledTwice(callCluster); + sinon.assert.calledThrice(callCluster); - return { result, callCluster, arg: callCluster.args[1][1] }; + return { result, callCluster, arg: callCluster.args[2][1] }; } test('serializes the params and state', async () => { @@ -122,6 +129,7 @@ describe('TaskStore', () => { index: 'tasky', maxAttempts: 2, supportedTypes: ['a', 'b', 'c'], + logger: mockLogger(), }); const result = await store.fetch(opts); @@ -286,6 +294,7 @@ describe('TaskStore', () => { supportedTypes: ['a', 'b', 'c'], index: 'tasky', maxAttempts: 2, + logger: mockLogger(), ...opts, }); @@ -307,6 +316,7 @@ describe('TaskStore', () => { supportedTypes: ['a', 'b', 'c'], index: 'tasky', maxAttempts: 2, + logger: mockLogger(), }); const result = await store.fetchAvailableTasks(); @@ -447,6 +457,7 @@ describe('TaskStore', () => { index: 'tasky', maxAttempts: 2, supportedTypes: ['a', 'b', 'c'], + logger: mockLogger(), }); const result = await store.update(task); @@ -491,6 +502,7 @@ describe('TaskStore', () => { index: 'myindex', maxAttempts: 2, supportedTypes: ['a'], + logger: mockLogger(), }); const result = await store.remove(id); diff --git a/x-pack/plugins/task_manager/task_store.ts b/x-pack/plugins/task_manager/task_store.ts index c3c1cd11a261d..3fa7bb5daaf11 100644 --- a/x-pack/plugins/task_manager/task_store.ts +++ b/x-pack/plugins/task_manager/task_store.ts @@ -8,6 +8,9 @@ * This module contains helpers for managing the task manager storage layer. */ +import hash from 'object-hash'; +import { TaskManagerLogger } from './lib/logger'; +import { templateProperties } from './lib/template_properties'; import { ConcreteTaskInstance, ElasticJs, TaskInstance, TaskStatus } from './task'; const DOC_TYPE = '_doc'; @@ -17,6 +20,7 @@ export interface StoreOpts { index: string; maxAttempts: number; supportedTypes: string[]; + logger: TaskManagerLogger; } export interface FetchOpts { @@ -64,11 +68,15 @@ export interface RawTaskDoc { * interface into the index. */ export class TaskStore { + get isInitialized() { + return this._isInitialized; + } public readonly maxAttempts: number; private callCluster: ElasticJs; private index: string; private supportedTypes: string[]; private _isInitialized = false; // tslint:disable-line:variable-name + private logger: TaskManagerLogger; /** * Constructs a new TaskStore. @@ -83,6 +91,7 @@ export class TaskStore { this.index = opts.index; this.maxAttempts = opts.maxAttempts; this.supportedTypes = opts.supportedTypes; + this.logger = opts.logger; this.fetchAvailableTasks = this.fetchAvailableTasks.bind(this); } @@ -103,51 +112,52 @@ export class TaskStore { throw new Error('TaskStore has already been initialized!'); } - const properties = { - type: { type: 'keyword' }, - task: { - properties: { - taskType: { type: 'keyword' }, - runAt: { type: 'date' }, - interval: { type: 'text' }, - attempts: { type: 'integer' }, - status: { type: 'keyword' }, - params: { type: 'text' }, - state: { type: 'text' }, - user: { type: 'keyword' }, - scope: { type: 'keyword' }, - }, - }, - }; - + let shouldUpdate = true; + let existingTemplate; try { - const templateResult = await this.callCluster('indices.putTemplate', { + existingTemplate = await this.callCluster('indices.getTemplate', { name: this.index, - body: { - index_patterns: [this.index], - mappings: { - _doc: { - dynamic: 'strict', - properties, - }, - }, - settings: { - number_of_shards: 1, - auto_expand_replicas: '0-1', - }, - }, }); - this._isInitialized = true; - return templateResult; - } catch (err) { - throw err; + + if (existingTemplate) { + shouldUpdate = + hash(existingTemplate[this.index].mappings._doc.properties) !== hash(templateProperties); + if (shouldUpdate) { + this.logger.info('Found different index template, it will be updated!'); + } + } + } catch (e) { + if (e.message !== 'Not Found') { + this.logger.error(`Could not determine state of index template ${e.message}`); + } } - return; - } + if (shouldUpdate) { + try { + const templateResult = await this.callCluster('indices.putTemplate', { + name: this.index, + body: { + index_patterns: [this.index], + mappings: { + _doc: { + dynamic: 'strict', + properties: templateProperties, + }, + }, + settings: { + number_of_shards: 1, + auto_expand_replicas: '0-1', + }, + }, + }); + this._isInitialized = true; + return templateResult; + } catch (err) { + throw err; + } + } - get isInitialized() { - return this._isInitialized; + return existingTemplate; } /** diff --git a/yarn.lock b/yarn.lock index c4bf0324ab78a..4810eaec6f748 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15633,6 +15633,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" + integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== + object-inspect@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-0.4.0.tgz#f5157c116c1455b243b06ee97703392c5ad89fec"