From 4d6c7313c695b6a9cdf2eab2814546657ee3d0bb Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 21 Nov 2017 17:05:46 -0700 Subject: [PATCH] [savedObjects] Use index template (#14271) * [es][savedObjects/index] put template on each savedObject write The elasticsearch plugin currently checks for the Kibana index on each iteration of the healthCheck, and creates it if it does not exist. This removes that step from the healthCheck and instead, before each savedObject is written to elasticsearch, ensures that Elasticsearch has the necessary index template should the write result in index creation. The healthCheck still has the `patchKibanaIndex()` logic, which checks the type in the Kibana index and adds any missing types. This step now does nothing when the Kibana index does not exist, and does what it has always done when it does. * [ftr] remove unused kibanaIndex service (cherry picked from commit b1ef897dafeb6d43fe279776e44a9d793a389dc3) * [savedObjects/integration] create now creates kibana index * [es/healthCheck] remove use of format() * [es/healthCheck/tests] use sinon assertions * [es/patchKibanaIndex] test for kibana index missing behavior * [savedObjects/errors] add tests for EsAutoCreateIndexError * [savedObjects/config] deprecate and remove savedObjects.indexCheckTimeout config * use dangling commas consistently * [ui/error_auto_create_index] fix class names * [ui/savedObjectsClient] no need to specify basePath * [eslint] fix linting issue --- .../lib/__tests__/create_kibana_index.js | 120 ------------------ .../lib/__tests__/health_check.js | 74 +---------- .../lib/__tests__/patch_kibana_index.js | 26 +++- .../elasticsearch/lib/create_kibana_index.js | 25 ---- .../elasticsearch/lib/health_check.js | 64 +--------- .../elasticsearch/lib/patch_kibana_index.js | 23 +++- .../__tests__/transform_deprecations.js | 40 ++++++ src/server/config/schema.js | 4 - src/server/config/transform_deprecations.js | 13 ++ .../client/lib/__tests__/errors.js | 37 ++++++ src/server/saved_objects/client/lib/errors.js | 15 ++- .../client/saved_objects_client.js | 17 ++- .../saved_objects/saved_objects_mixin.js | 52 ++++---- .../saved_object/saved_object_loader.js | 4 +- .../error_auto_create_index.html | 42 ++++++ .../error_auto_create_index.js | 28 ++++ .../error_auto_create_index.less | 3 + .../public/error_auto_create_index/index.js | 1 + .../__tests__/saved_objects_client.js | 5 +- .../saved_objects/saved_objects_client.js | 18 ++- .../saved_objects_client_provider.js | 18 ++- .../routes/__tests__/doc_exists.js | 21 ++- .../routes/__tests__/doc_missing.js | 14 +- src/ui/ui_settings/routes/__tests__/index.js | 4 +- .../routes/__tests__/index_missing.js | 103 +++++++++------ .../routes/__tests__/lib/assert.js | 8 -- .../ui_settings/routes/__tests__/lib/index.js | 2 - .../routes/__tests__/lib/servers.js | 20 +-- tasks/config/simplemocha.js | 1 + .../apis/saved_objects/create.js | 27 ++-- test/api_integration/config.js | 1 - test/common/config.js | 2 - test/common/services/index.js | 1 - test/common/services/kibana_index.js | 21 --- .../services/kibana_server/kibana_server.js | 4 +- .../services/kibana_server/ui_settings.js | 2 +- test/functional/config.js | 1 - 37 files changed, 422 insertions(+), 439 deletions(-) delete mode 100644 src/core_plugins/elasticsearch/lib/__tests__/create_kibana_index.js delete mode 100644 src/core_plugins/elasticsearch/lib/create_kibana_index.js create mode 100644 src/ui/public/error_auto_create_index/error_auto_create_index.html create mode 100644 src/ui/public/error_auto_create_index/error_auto_create_index.js create mode 100644 src/ui/public/error_auto_create_index/error_auto_create_index.less create mode 100644 src/ui/public/error_auto_create_index/index.js delete mode 100644 test/common/services/kibana_index.js diff --git a/src/core_plugins/elasticsearch/lib/__tests__/create_kibana_index.js b/src/core_plugins/elasticsearch/lib/__tests__/create_kibana_index.js deleted file mode 100644 index 8b243ea962001..0000000000000 --- a/src/core_plugins/elasticsearch/lib/__tests__/create_kibana_index.js +++ /dev/null @@ -1,120 +0,0 @@ -import _ from 'lodash'; -import sinon from 'sinon'; -import expect from 'expect.js'; -import Promise from 'bluebird'; -import mappings from './fixtures/mappings'; -import createKibanaIndex from '../create_kibana_index'; - -describe('plugins/elasticsearch', function () { - describe('lib/create_kibana_index', function () { - - let server; - let callWithInternalUser; - let cluster; - - beforeEach(function () { - server = {}; - - let config = { kibana: { index: '.my-kibana' } }; - const get = sinon.stub(); - - get.returns(config); - get.withArgs('kibana.index').returns(config.kibana.index); - config = function () { return { get: get }; }; - - _.set(server, 'config', config); - _.set(server, 'getKibanaIndexMappingsDsl', sinon.stub().returns(mappings)); - - callWithInternalUser = sinon.stub(); - cluster = { callWithInternalUser: callWithInternalUser }; - - _.set(server, 'plugins.elasticsearch.getCluster', sinon.stub().withArgs('admin').returns(cluster)); - }); - - describe('successful requests', function () { - beforeEach(function () { - callWithInternalUser.withArgs('indices.create', sinon.match.any).returns(Promise.resolve()); - callWithInternalUser.withArgs('cluster.health', sinon.match.any).returns(Promise.resolve()); - }); - - it('should check cluster.health upon successful index creation', function () { - const fn = createKibanaIndex(server); - return fn.then(function () { - sinon.assert.calledOnce(callWithInternalUser.withArgs('cluster.health', sinon.match.any)); - }); - }); - - it('should be created with mappings for config.buildNum', function () { - const fn = createKibanaIndex(server); - return fn.then(function () { - const params = callWithInternalUser.args[0][1]; - expect(params) - .to.have.property('body'); - expect(params.body) - .to.have.property('mappings'); - expect(params.body.mappings) - .to.have.property('config'); - expect(params.body.mappings.config) - .to.have.property('properties'); - expect(params.body.mappings.config.properties) - .to.have.property('buildNum'); - expect(params.body.mappings.config.properties.buildNum) - .to.have.property('type', 'keyword'); - }); - }); - - it('should be created with 1 shard and default replica', function () { - const fn = createKibanaIndex(server); - return fn.then(function () { - const params = callWithInternalUser.args[0][1]; - expect(params) - .to.have.property('body'); - expect(params.body) - .to.have.property('settings'); - expect(params.body.settings) - .to.have.property('number_of_shards', 1); - expect(params.body.settings) - .to.not.have.property('number_of_replicas'); - }); - }); - - it('should be created with index name set in the config', function () { - const fn = createKibanaIndex(server); - return fn.then(function () { - const params = callWithInternalUser.args[0][1]; - expect(params) - .to.have.property('index', '.my-kibana'); - }); - }); - }); - - describe('failure requests', function () { - it('should reject with an Error', function () { - const error = new Error('Oops!'); - callWithInternalUser.withArgs('indices.create', sinon.match.any).returns(Promise.reject(error)); - const fn = createKibanaIndex(server); - return fn.catch(function (err) { - expect(err).to.be.a(Error); - }); - }); - - it('should reject with an error if index creation fails', function () { - const error = new Error('Oops!'); - callWithInternalUser.withArgs('indices.create', sinon.match.any).returns(Promise.reject(error)); - const fn = createKibanaIndex(server); - return fn.catch(function (err) { - expect(err.message).to.be('Unable to create Kibana index ".my-kibana"'); - }); - }); - - it('should reject with an error if health check fails', function () { - callWithInternalUser.withArgs('indices.create', sinon.match.any).returns(Promise.resolve()); - callWithInternalUser.withArgs('cluster.health', sinon.match.any).returns(Promise.reject(new Error())); - const fn = createKibanaIndex(server); - return fn.catch(function (err) { - expect(err.message).to.be('Waiting for Kibana index ".my-kibana" to come online failed.'); - }); - }); - }); - }); -}); diff --git a/src/core_plugins/elasticsearch/lib/__tests__/health_check.js b/src/core_plugins/elasticsearch/lib/__tests__/health_check.js index 56048dce85426..43b6ddf2b9344 100644 --- a/src/core_plugins/elasticsearch/lib/__tests__/health_check.js +++ b/src/core_plugins/elasticsearch/lib/__tests__/health_check.js @@ -113,22 +113,17 @@ describe('plugins/elasticsearch', () => { it('should set the cluster green if everything is ready', function () { cluster.callWithInternalUser.withArgs('ping').returns(Promise.resolve()); - cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any).returns( - Promise.resolve({ timed_out: false, status: 'green' }) - ); return health.run() .then(function () { sinon.assert.calledOnce(plugin.status.yellow); - expect(plugin.status.yellow.args[0][0]).to.be('Waiting for Elasticsearch'); + sinon.assert.calledWithExactly(plugin.status.yellow, 'Waiting for Elasticsearch'); sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('ping')); sinon.assert.calledTwice(cluster.callWithInternalUser.withArgs('nodes.info', sinon.match.any)); - sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any)); sinon.assert.notCalled(plugin.status.red); sinon.assert.calledOnce(plugin.status.green); - - expect(plugin.status.green.args[0][0]).to.be('Kibana index ready'); + sinon.assert.calledWithExactly(plugin.status.green, 'Ready'); }); }); @@ -137,80 +132,26 @@ describe('plugins/elasticsearch', () => { ping.onCall(0).returns(Promise.reject(new NoConnections())); ping.onCall(1).returns(Promise.resolve()); - cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any).returns( - Promise.resolve({ timed_out: false, status: 'green' }) - ); - return health.run() .then(function () { sinon.assert.calledOnce(plugin.status.yellow); - expect(plugin.status.yellow.args[0][0]).to.be('Waiting for Elasticsearch'); + sinon.assert.calledWithExactly(plugin.status.yellow, 'Waiting for Elasticsearch'); sinon.assert.calledOnce(plugin.status.red); - expect(plugin.status.red.args[0][0]).to.be( + sinon.assert.calledWithExactly( + plugin.status.red, `Unable to connect to Elasticsearch at ${esUrl}.` ); sinon.assert.calledTwice(ping); sinon.assert.calledTwice(cluster.callWithInternalUser.withArgs('nodes.info', sinon.match.any)); - sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any)); sinon.assert.calledOnce(plugin.status.green); - expect(plugin.status.green.args[0][0]).to.be('Kibana index ready'); - }); - }); - - it('should set the cluster red if the health check status is red, then to green', function () { - cluster.callWithInternalUser.withArgs('ping').returns(Promise.resolve()); - - const clusterHealth = cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any); - clusterHealth.onCall(0).returns(Promise.resolve({ timed_out: false, status: 'red' })); - clusterHealth.onCall(1).returns(Promise.resolve({ timed_out: false, status: 'green' })); - - return health.run() - .then(function () { - sinon.assert.calledOnce(plugin.status.yellow); - expect(plugin.status.yellow.args[0][0]).to.be('Waiting for Elasticsearch'); - sinon.assert.calledOnce(plugin.status.red); - expect(plugin.status.red.args[0][0]).to.be( - 'Elasticsearch is still initializing the kibana index.' - ); - sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('ping')); - sinon.assert.calledTwice(cluster.callWithInternalUser.withArgs('nodes.info', sinon.match.any)); - sinon.assert.calledTwice(cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any)); - sinon.assert.calledOnce(plugin.status.green); - expect(plugin.status.green.args[0][0]).to.be('Kibana index ready'); - }); - }); - - it('should set the cluster yellow if the health check timed_out and create index', function () { - cluster.callWithInternalUser.withArgs('ping').returns(Promise.resolve()); - - const clusterHealth = cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any); - clusterHealth.onCall(0).returns(Promise.resolve({ timed_out: true, status: 'red' })); - clusterHealth.onCall(1).returns(Promise.resolve({ timed_out: false, status: 'green' })); - - cluster.callWithInternalUser.withArgs('indices.create', sinon.match.any).returns(Promise.resolve()); - - return health.run() - .then(function () { - sinon.assert.calledTwice(plugin.status.yellow); - expect(plugin.status.yellow.args[0][0]).to.be('Waiting for Elasticsearch'); - expect(plugin.status.yellow.args[1][0]).to.be('No existing Kibana index found'); - - sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('ping')); - sinon.assert.calledOnce(cluster.callWithInternalUser.withArgs('indices.create', sinon.match.any)); - sinon.assert.calledTwice(cluster.callWithInternalUser.withArgs('nodes.info', sinon.match.any)); - sinon.assert.calledTwice(clusterHealth); + sinon.assert.calledWithExactly(plugin.status.green, 'Ready'); }); }); describe('#waitUntilReady', function () { - it('polls health until index is ready, then waits for green status', function () { - const clusterHealth = cluster.callWithInternalUser.withArgs('cluster.health', sinon.match.any); - clusterHealth.onCall(0).returns(Promise.resolve({ timed_out: true })); - clusterHealth.onCall(1).returns(Promise.resolve({ status: 'red' })); - clusterHealth.onCall(2).returns(Promise.resolve({ status: 'green' })); - + it('waits for green status', function () { plugin.status.once = sinon.spy(function (event, handler) { expect(event).to.be('green'); setImmediate(handler); @@ -218,7 +159,6 @@ describe('plugins/elasticsearch', () => { return health.waitUntilReady().then(function () { sinon.assert.calledOnce(plugin.status.once); - sinon.assert.calledThrice(clusterHealth); }); }); }); diff --git a/src/core_plugins/elasticsearch/lib/__tests__/patch_kibana_index.js b/src/core_plugins/elasticsearch/lib/__tests__/patch_kibana_index.js index bc77a025ae8cc..13460a9e49a51 100644 --- a/src/core_plugins/elasticsearch/lib/__tests__/patch_kibana_index.js +++ b/src/core_plugins/elasticsearch/lib/__tests__/patch_kibana_index.js @@ -46,8 +46,12 @@ function createCallCluster(index) { return sinon.spy(async (method, params) => { switch (method) { case 'indices.get': - expect(params).to.have.property('index', Object.keys(index)[0]); - return cloneDeep(index); + if (!index) { + return { status: 404 }; + } else { + expect(params).to.have.property('index', Object.keys(index)[0]); + return cloneDeep(index); + } case 'indices.putMapping': return { ok: true }; default: @@ -76,6 +80,24 @@ describe('es/healthCheck/patchKibanaIndex()', () => { }); }); + describe('missing index', () => { + it('returns without doing anything', async () => { + const indexName = chance.word(); + const mappings = createRandomMappings(); + const callCluster = createCallCluster(null); + const log = sinon.stub(); + await patchKibanaIndex({ + callCluster, + indexName, + kibanaIndexMappingsDsl: mappings, + log + }); + + sinon.assert.calledOnce(callCluster); + sinon.assert.notCalled(log); + }); + }); + describe('multi-type index', () => { it('rejects', async () => { try { diff --git a/src/core_plugins/elasticsearch/lib/create_kibana_index.js b/src/core_plugins/elasticsearch/lib/create_kibana_index.js deleted file mode 100644 index 4447664c0c05c..0000000000000 --- a/src/core_plugins/elasticsearch/lib/create_kibana_index.js +++ /dev/null @@ -1,25 +0,0 @@ -export default function (server) { - const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); - const index = server.config().get('kibana.index'); - return callWithInternalUser('indices.create', { - index: index, - body: { - settings: { - number_of_shards: 1 - }, - mappings: server.getKibanaIndexMappingsDsl() - } - }) - .catch(() => { - throw new Error(`Unable to create Kibana index "${index}"`); - }) - .then(function () { - return callWithInternalUser('cluster.health', { - waitForStatus: 'yellow', - index: index - }) - .catch(() => { - throw new Error(`Waiting for Kibana index "${index}" to come online failed.`); - }); - }); -} diff --git a/src/core_plugins/elasticsearch/lib/health_check.js b/src/core_plugins/elasticsearch/lib/health_check.js index 7667180218fc3..df2660858e5fb 100644 --- a/src/core_plugins/elasticsearch/lib/health_check.js +++ b/src/core_plugins/elasticsearch/lib/health_check.js @@ -1,18 +1,11 @@ import Promise from 'bluebird'; import elasticsearch from 'elasticsearch'; -import createKibanaIndex from './create_kibana_index'; import kibanaVersion from './kibana_version'; import { ensureEsVersion } from './ensure_es_version'; import { ensureNotTribe } from './ensure_not_tribe'; import { patchKibanaIndex } from './patch_kibana_index'; const NoConnections = elasticsearch.errors.NoConnections; -import util from 'util'; -const format = util.format; - -const NO_INDEX = 'no_index'; -const INITIALIZING = 'initializing'; -const READY = 'ready'; export default function (plugin, server) { const config = server.config(); @@ -24,62 +17,16 @@ export default function (plugin, server) { function waitForPong(callWithInternalUser, url) { return callWithInternalUser('ping').catch(function (err) { if (!(err instanceof NoConnections)) throw err; - plugin.status.red(format('Unable to connect to Elasticsearch at %s.', url)); + plugin.status.red(`Unable to connect to Elasticsearch at ${url}.`); return Promise.delay(REQUEST_DELAY).then(waitForPong.bind(null, callWithInternalUser, url)); }); } - // just figure out the current "health" of the es setup - function getHealth() { - return callAdminAsKibanaUser('cluster.health', { - timeout: '5s', // tells es to not sit around and wait forever - index: config.get('kibana.index'), - ignore: [408] - }) - .then(function (resp) { - // if "timed_out" === true then elasticsearch could not - // find any idices matching our filter within 5 seconds - if (!resp || resp.timed_out) { - return NO_INDEX; - } - - // If status === "red" that means that index(es) were found - // but the shards are not ready for queries - if (resp.status === 'red') { - return INITIALIZING; - } - - return READY; - }); - } - function waitUntilReady() { - return getHealth() - .then(function (health) { - if (health !== READY) { - return Promise.delay(REQUEST_DELAY).then(waitUntilReady); - } - - return new Promise((resolve) => { - plugin.status.once('green', resolve); - }); - }); - } - - function waitForShards() { - return getHealth() - .then(function (health) { - if (health === NO_INDEX) { - plugin.status.yellow('No existing Kibana index found'); - return createKibanaIndex(server); - } - - if (health === INITIALIZING) { - plugin.status.red('Elasticsearch is still initializing the kibana index.'); - return Promise.delay(REQUEST_DELAY).then(waitForShards); - } - }); + return new Promise((resolve) => { + plugin.status.once('green', resolve); + }); } function waitForEsVersion() { @@ -90,7 +37,7 @@ export default function (plugin, server) { } function setGreenStatus() { - return plugin.status.green('Kibana index ready'); + return plugin.status.green('Ready'); } function check() { @@ -98,7 +45,6 @@ export default function (plugin, server) { waitForPong(callAdminAsKibanaUser, config.get('elasticsearch.url')) .then(waitForEsVersion) .then(() => ensureNotTribe(callAdminAsKibanaUser)) - .then(waitForShards) .then(() => patchKibanaIndex({ callCluster: callAdminAsKibanaUser, log: (...args) => server.log(...args), diff --git a/src/core_plugins/elasticsearch/lib/patch_kibana_index.js b/src/core_plugins/elasticsearch/lib/patch_kibana_index.js index b95d55e01dc76..9f0d7d07caad6 100644 --- a/src/core_plugins/elasticsearch/lib/patch_kibana_index.js +++ b/src/core_plugins/elasticsearch/lib/patch_kibana_index.js @@ -25,6 +25,12 @@ export async function patchKibanaIndex(options) { const rootEsType = getRootType(kibanaIndexMappingsDsl); const currentMappingsDsl = await getCurrentMappings(callCluster, indexName, rootEsType); + + // patchKibanaIndex() should do nothing if there are no current mappings + if (!currentMappingsDsl) { + return; + } + const missingProperties = await getMissingRootProperties(currentMappingsDsl, kibanaIndexMappingsDsl); const missingPropertyNames = Object.keys(missingProperties); @@ -51,21 +57,26 @@ export async function patchKibanaIndex(options) { } /** - * Get the mappings dsl for the current Kibana index + * Get the mappings dsl for the current Kibana index if it exists * @param {Function} callCluster * @param {string} indexName * @param {string} rootEsType - * @return {EsMappingsDsl} + * @return {EsMappingsDsl|undefined} */ async function getCurrentMappings(callCluster, indexName, rootEsType) { - const index = await callCluster('indices.get', { + const response = await callCluster('indices.get', { index: indexName, - feature: '_mappings' + feature: '_mappings', + ignore: [404], }); + if (response.status === 404) { + return undefined; + } + // could be different if aliases were resolved by `indices.get` - const resolvedName = Object.keys(index)[0]; - const currentMappingsDsl = index[resolvedName].mappings; + const resolvedName = Object.keys(response)[0]; + const currentMappingsDsl = response[resolvedName].mappings; const currentTypes = getTypes(currentMappingsDsl); const isV5Index = currentTypes.length > 1 || currentTypes[0] !== rootEsType; diff --git a/src/server/config/__tests__/transform_deprecations.js b/src/server/config/__tests__/transform_deprecations.js index 94c97a31a78ff..ff8901ecd19f3 100644 --- a/src/server/config/__tests__/transform_deprecations.js +++ b/src/server/config/__tests__/transform_deprecations.js @@ -57,5 +57,45 @@ describe('server/config', function () { expect(log.called).to.be(false); }); }); + + describe('savedObjects.indexCheckTimeout', () => { + it('removes the indexCheckTimeout and savedObjects properties', () => { + const settings = { + savedObjects: { + indexCheckTimeout: 123 + } + }; + + expect(transformDeprecations(settings)).to.eql({}); + }); + + it('keeps the savedObjects property if it has other keys', () => { + const settings = { + savedObjects: { + indexCheckTimeout: 123, + foo: 'bar' + } + }; + + expect(transformDeprecations(settings)).to.eql({ + savedObjects: { + foo: 'bar' + } + }); + }); + + it('logs that the setting is no longer necessary', () => { + const settings = { + savedObjects: { + indexCheckTimeout: 123 + } + }; + + const log = sinon.spy(); + transformDeprecations(settings, log); + sinon.assert.calledOnce(log); + sinon.assert.calledWithExactly(log, sinon.match('savedObjects.indexCheckTimeout')); + }); + }); }); }); diff --git a/src/server/config/schema.js b/src/server/config/schema.js index 453808d601784..0c3d020e964f8 100644 --- a/src/server/config/schema.js +++ b/src/server/config/schema.js @@ -201,10 +201,6 @@ export default () => Joi.object({ })) }).default(), - savedObjects: Joi.object({ - indexCheckTimeout: Joi.number().default(5000) - }).default(), - i18n: Joi.object({ defaultLocale: Joi.string().default('en'), }).default(), diff --git a/src/server/config/transform_deprecations.js b/src/server/config/transform_deprecations.js index f6f4c6906c5e3..7548db30e7c97 100644 --- a/src/server/config/transform_deprecations.js +++ b/src/server/config/transform_deprecations.js @@ -13,12 +13,25 @@ const serverSslEnabled = (settings, log) => { } }; +const savedObjectsIndexCheckTimeout = (settings, log) => { + if (_.has(settings, 'savedObjects.indexCheckTimeout')) { + log('savedObjects.indexCheckTimeout is no longer necessary.'); + + if (Object.keys(settings.savedObjects).length > 1) { + delete settings.savedObjects.indexCheckTimeout; + } else { + delete settings.savedObjects; + } + } +}; + const deprecations = [ //server rename('server.ssl.cert', 'server.ssl.certificate'), unused('server.xsrf.token'), unused('uiSettings.enabled'), serverSslEnabled, + savedObjectsIndexCheckTimeout, ]; export const transformDeprecations = createTransform(deprecations); diff --git a/src/server/saved_objects/client/lib/__tests__/errors.js b/src/server/saved_objects/client/lib/__tests__/errors.js index e4b7802509d3b..9dc5ff5174f1a 100644 --- a/src/server/saved_objects/client/lib/__tests__/errors.js +++ b/src/server/saved_objects/client/lib/__tests__/errors.js @@ -15,6 +15,8 @@ import { decorateEsUnavailableError, isEsUnavailableError, decorateGeneralError, + isEsAutoCreateIndexError, + createEsAutoCreateIndexError, } from '../errors'; describe('savedObjectsClient/errorTypes', () => { @@ -285,4 +287,39 @@ describe('savedObjectsClient/errorTypes', () => { }); }); }); + + describe('EsAutoCreateIndex error', () => { + describe('createEsAutoCreateIndexError', () => { + it('does not take an error argument', () => { + const error = new Error(); + expect(createEsAutoCreateIndexError(error)).to.not.be(error); + }); + + it('returns a new Error', () => { + expect(createEsAutoCreateIndexError()).to.be.a(Error); + }); + + it('makes errors identifiable as EsAutoCreateIndex errors', () => { + expect(isEsAutoCreateIndexError(createEsAutoCreateIndexError())).to.be(true); + }); + + it('returns a boom error', () => { + const error = createEsAutoCreateIndexError(); + expect(error).to.have.property('isBoom', true); + expect(error.output).to.be.an('object'); + expect(error.output.statusCode).to.be(503); + }); + + describe('error.output', () => { + it('uses "Automatic index creation failed" message', () => { + const error = createEsAutoCreateIndexError(); + expect(error.output.payload).to.have.property('message', 'Automatic index creation failed'); + }); + it('sets statusCode to 503', () => { + const error = createEsAutoCreateIndexError(); + expect(error.output).to.have.property('statusCode', 503); + }); + }); + }); + }); }); diff --git a/src/server/saved_objects/client/lib/errors.js b/src/server/saved_objects/client/lib/errors.js index 2aa22c7db817f..74881392856d0 100644 --- a/src/server/saved_objects/client/lib/errors.js +++ b/src/server/saved_objects/client/lib/errors.js @@ -72,7 +72,7 @@ export function isConflictError(error) { } -// 500 - Es Unavailable +// 503 - Es Unavailable const CODE_ES_UNAVAILABLE = 'SavedObjectsClient/esUnavailable'; export function decorateEsUnavailableError(error, reason) { return decorate(error, CODE_ES_UNAVAILABLE, 503, reason); @@ -82,6 +82,19 @@ export function isEsUnavailableError(error) { } +// 503 - Unable to automatically create index because of action.auto_create_index setting +const CODE_ES_AUTO_CREATE_INDEX_ERROR = 'SavedObjectsClient/autoCreateIndex'; +export function createEsAutoCreateIndexError() { + const error = Boom.serverUnavailable('Automatic index creation failed'); + error.output.payload.code = 'ES_AUTO_CREATE_INDEX_ERROR'; + + return decorate(error, CODE_ES_AUTO_CREATE_INDEX_ERROR, 503); +} +export function isEsAutoCreateIndexError(error) { + return error && error[code] === CODE_ES_AUTO_CREATE_INDEX_ERROR; +} + + // 500 - General Error const CODE_GENERAL_ERROR = 'SavedObjectsClient/generalError'; export function decorateGeneralError(error, reason) { diff --git a/src/server/saved_objects/client/saved_objects_client.js b/src/server/saved_objects/client/saved_objects_client.js index b9e840069d1b1..79dc55540e039 100644 --- a/src/server/saved_objects/client/saved_objects_client.js +++ b/src/server/saved_objects/client/saved_objects_client.js @@ -1,5 +1,4 @@ import uuid from 'uuid'; -import Boom from 'boom'; import { getRootType } from '../../mappings'; @@ -81,6 +80,15 @@ export class SavedObjectsClient { * takes special care to ensure that 404 errors are generic and don't distinguish * between index missing or document missing. * + * ### 503s from missing index + * + * Unlike all other methods, create requests are supposed to succeed even when + * the Kibana index does not exist because it will be automatically created by + * elasticsearch. When that is not the case it is because Elasticsearch's + * `action.auto_create_index` setting prevents it from being created automatically + * so we throw a special 503 with the intention of informing the user that their + * Elasticsearch settings need to be updated. + * * @type {ErrorHelpers} see ./lib/errors */ static errors = errors @@ -126,12 +134,9 @@ export class SavedObjectsClient { attributes }; } catch (error) { - // if we get a 404 because the index is missing we should respond - // with a 503 instead, 404 on saved object create doesn't really - // make sense when we are trying not to leak the implementation - // details of the SavedObjects index if (errors.isNotFoundError(error)) { - throw errors.decorateEsUnavailableError(Boom.serverUnavailable()); + // See "503s from missing index" above + throw errors.createEsAutoCreateIndexError(); } throw error; diff --git a/src/server/saved_objects/saved_objects_mixin.js b/src/server/saved_objects/saved_objects_mixin.js index a755be54c69fe..8fb569efa3fe0 100644 --- a/src/server/saved_objects/saved_objects_mixin.js +++ b/src/server/saved_objects/saved_objects_mixin.js @@ -6,7 +6,7 @@ import { createDeleteRoute, createFindRoute, createGetRoute, - createUpdateRoute + createUpdateRoute, } from './routes'; export function savedObjectsMixin(kbnServer, server) { @@ -15,7 +15,7 @@ export function savedObjectsMixin(kbnServer, server) { assign: 'savedObjectsClient', method(req, reply) { reply(req.getSavedObjectsClient()); - } + }, }, }; @@ -30,30 +30,34 @@ export function savedObjectsMixin(kbnServer, server) { const adminCluster = server.plugins.elasticsearch.getCluster('admin'); try { - await adminCluster.callWithInternalUser('cluster.health', { - timeout: `${server.config().get('savedObjects.indexCheckTimeout')}ms`, - index: server.config().get('kibana.index'), - waitForStatus: 'yellow', + const index = server.config().get('kibana.index'); + await adminCluster.callWithInternalUser('indices.putTemplate', { + name: `kibana_index_template:${index}`, + body: { + template: index, + settings: { + number_of_shards: 1, + }, + mappings: server.getKibanaIndexMappingsDsl(), + }, }); } catch (error) { - // This check is designed to emulate our planned index template move until - // we get there, and once we do the plan is to just post the index template - // and attempt the request. - // - // Because of this we only throw NotFound() when the status is red AND - // there are no shards. All other red statuses indicate real problems that - // will be described in better detail when the write fails. - if ( - error && - error.body && - error.body.status === 'red' && - !error.body.unassigned_shards && - !error.body.initializing_shards && - !error.body.delayed_unassigned_shards - ) { - server.log(['debug', 'savedObjects'], `Attempted to write to the Kibana index when it didn't exist.`); - throw new adminCluster.errors.NotFound(); - } + server.log(['debug', 'savedObjects'], { + tmpl: 'Attempt to write indexTemplate for SavedObjects index failed: <%= err.message %>', + es: { + resp: error.body, + status: error.status, + }, + err: { + message: error.message, + stack: error.stack, + }, + }); + + // We reject with `es.ServiceUnavailable` because writing an index + // template is a very simple operation so if we get an error here + // then something must be very broken + throw new adminCluster.errors.ServiceUnavailable(); } } diff --git a/src/ui/public/courier/saved_object/saved_object_loader.js b/src/ui/public/courier/saved_object/saved_object_loader.js index 6e522da6b7a98..f0fa8d2214e39 100644 --- a/src/ui/public/courier/saved_object/saved_object_loader.js +++ b/src/ui/public/courier/saved_object/saved_object_loader.js @@ -21,7 +21,9 @@ export class SavedObjectLoader { nouns: `${ this.lowercaseType }s`, }; - this.savedObjectsClient = new SavedObjectsClient($http); + this.savedObjectsClient = new SavedObjectsClient({ + $http + }); } /** diff --git a/src/ui/public/error_auto_create_index/error_auto_create_index.html b/src/ui/public/error_auto_create_index/error_auto_create_index.html new file mode 100644 index 0000000000000..fbb9bbabb6ab7 --- /dev/null +++ b/src/ui/public/error_auto_create_index/error_auto_create_index.html @@ -0,0 +1,42 @@ +
+

+ Oh no! +

+ +

+ It looks like your Elasticsearch cluster's action.auto_create_index setting is preventing Kibana from automatically creating an index to store saved objects. Kibana uses this Elasticsearch feature because it is the best way to make sure that the saved object index uses the correct mappings/schema, and it allows Kibana to poll Elasticsearch less often. +

+ +

+ Unfortunately, until this issue is fixed you won't be able to save anything in Kibana. +

+ +

+ Ok, how do I fix this? +

+ +
    +
  1. Remove action.auto_create_index: false from your Elasticsearch configuration file
  2. +
  3. Restart elasticsearch.
  4. +
  5. Use the browser's back button to return to what you were doing.
  6. +
+ +
+
+ + + Note: + +
+ +
+
+ The action.auto_create_index can also define a whitelist of patterns where this feature should be enabled. We don't discuss how to use the setting that way here because it requires knowing all of the other plugins/interations that rely on the feature for the same reasons that Kibana does. +
+
+
+
diff --git a/src/ui/public/error_auto_create_index/error_auto_create_index.js b/src/ui/public/error_auto_create_index/error_auto_create_index.js new file mode 100644 index 0000000000000..9c7c10298a988 --- /dev/null +++ b/src/ui/public/error_auto_create_index/error_auto_create_index.js @@ -0,0 +1,28 @@ +import { get } from 'lodash'; + +import uiRoutes from 'ui/routes'; +import { KbnUrlProvider } from 'ui/url'; + +import './error_auto_create_index.less'; +import template from './error_auto_create_index.html'; + +uiRoutes + .when('/error/action.auto_create_index', { template }); + +export function ErrorAutoCreateIndexProvider(Private, Promise) { + const kbnUrl = Private(KbnUrlProvider); + + return new class ErrorAutoCreateIndex { + test(error) { + return ( + error.statusCode === 503 && + get(error, 'body.code') === 'ES_AUTO_CREATE_INDEX_ERROR' + ); + } + + takeover() { + kbnUrl.change('/error/action.auto_create_index'); + return Promise.halt(); + } + }; +} diff --git a/src/ui/public/error_auto_create_index/error_auto_create_index.less b/src/ui/public/error_auto_create_index/error_auto_create_index.less new file mode 100644 index 0000000000000..99617c157a9f6 --- /dev/null +++ b/src/ui/public/error_auto_create_index/error_auto_create_index.less @@ -0,0 +1,3 @@ +.error-auto-create-index { + padding: 25px; +} diff --git a/src/ui/public/error_auto_create_index/index.js b/src/ui/public/error_auto_create_index/index.js new file mode 100644 index 0000000000000..eafeda9c2e727 --- /dev/null +++ b/src/ui/public/error_auto_create_index/index.js @@ -0,0 +1 @@ +export { ErrorAutoCreateIndexProvider } from './error_auto_create_index'; diff --git a/src/ui/public/saved_objects/__tests__/saved_objects_client.js b/src/ui/public/saved_objects/__tests__/saved_objects_client.js index eaa0e04af351a..08690e7bd4025 100644 --- a/src/ui/public/saved_objects/__tests__/saved_objects_client.js +++ b/src/ui/public/saved_objects/__tests__/saved_objects_client.js @@ -18,7 +18,10 @@ describe('SavedObjectsClient', () => { beforeEach(() => { $http = sandbox.stub(); - savedObjectsClient = new SavedObjectsClient($http, basePath); + savedObjectsClient = new SavedObjectsClient({ + $http, + basePath + }); }); afterEach(() => { diff --git a/src/ui/public/saved_objects/saved_objects_client.js b/src/ui/public/saved_objects/saved_objects_client.js index c0f34c3c100cb..7ceedfd297217 100644 --- a/src/ui/public/saved_objects/saved_objects_client.js +++ b/src/ui/public/saved_objects/saved_objects_client.js @@ -16,10 +16,18 @@ const join = (...uriComponents) => ( const BATCH_INTERVAL = 100; export class SavedObjectsClient { - constructor($http, basePath = chrome.getBasePath(), PromiseCtor = Promise) { + constructor(options) { + const { + $http, + basePath = chrome.getBasePath(), + PromiseConstructor = Promise, + onCreateFailure = () => {}, + } = options; + this._$http = $http; this._apiBaseUrl = `${basePath}/api/saved_objects/`; - this._PromiseCtor = PromiseCtor; + this._PromiseCtor = PromiseConstructor; + this._onCreateFailure = onCreateFailure; this.batchQueue = []; } @@ -40,9 +48,9 @@ export class SavedObjectsClient { const url = this._getUrl([type, options.id], _.pick(options, ['overwrite'])); - return this._request('POST', url, { attributes }).then(resp => { - return this.createSavedObject(resp); - }); + return this._request('POST', url, { attributes }) + .catch(this._onCreateFailure) + .then(resp => this.createSavedObject(resp)); } /** diff --git a/src/ui/public/saved_objects/saved_objects_client_provider.js b/src/ui/public/saved_objects/saved_objects_client_provider.js index 2d335e5b2353d..4948781864f39 100644 --- a/src/ui/public/saved_objects/saved_objects_client_provider.js +++ b/src/ui/public/saved_objects/saved_objects_client_provider.js @@ -1,7 +1,19 @@ -import chrome from 'ui/chrome'; +import { ErrorAutoCreateIndexProvider } from 'ui/error_auto_create_index'; import { SavedObjectsClient } from './saved_objects_client'; -export function SavedObjectsClientProvider($http, $q) { - return new SavedObjectsClient($http, chrome.getBasePath(), $q); +export function SavedObjectsClientProvider($http, $q, Private) { + const errorAutoCreateIndex = Private(ErrorAutoCreateIndexProvider); + + return new SavedObjectsClient({ + $http, + PromiseConstructor: $q, + onCreateFailure(error) { + if (errorAutoCreateIndex.test(error)) { + return errorAutoCreateIndex.takeover(); + } + + throw error; + } + }); } diff --git a/src/ui/ui_settings/routes/__tests__/doc_exists.js b/src/ui/ui_settings/routes/__tests__/doc_exists.js index 13b445089a1d2..4a1ad6fb629a0 100644 --- a/src/ui/ui_settings/routes/__tests__/doc_exists.js +++ b/src/ui/ui_settings/routes/__tests__/doc_exists.js @@ -13,7 +13,26 @@ export function docExistsSuite() { initialSettings } = options; - const { kbnServer, uiSettings } = getServices(); + const { kbnServer, uiSettings, callCluster } = getServices(); + + // delete the kibana index to ensure we start fresh + await callCluster('indices.delete', { + index: kbnServer.config.get('kibana.index'), + ignore: [404] + }); + + // write a setting to create kibana index and savedConfig + await kbnServer.inject({ + method: 'POST', + url: '/api/kibana/settings/defaultIndex', + payload: { value: 'abc' } + }); + + // delete our defaultIndex setting to make doc empty + await kbnServer.inject({ + method: 'DELETE', + url: '/api/kibana/settings/defaultIndex', + }); if (initialSettings) { await uiSettings.setMany(initialSettings); diff --git a/src/ui/ui_settings/routes/__tests__/doc_missing.js b/src/ui/ui_settings/routes/__tests__/doc_missing.js index 0b90d67941447..a1aa547d9e2f0 100644 --- a/src/ui/ui_settings/routes/__tests__/doc_missing.js +++ b/src/ui/ui_settings/routes/__tests__/doc_missing.js @@ -5,17 +5,21 @@ import { getServices, chance, assertSinonMatch, - waitUntilNextHealthCheck, } from './lib'; export function docMissingSuite() { - // health check doesn't create config doc so we - // only have to wait once - before(waitUntilNextHealthCheck); - // ensure the kibana index has no documents beforeEach(async () => { const { kbnServer, callCluster } = getServices(); + + // write a setting to ensure kibana index is created + await kbnServer.inject({ + method: 'POST', + url: '/api/kibana/settings/defaultIndex', + payload: { value: 'abc' } + }); + + // delete all docs from kibana index to ensure savedConfig is not found await callCluster('deleteByQuery', { index: kbnServer.config.get('kibana.index'), body: { diff --git a/src/ui/ui_settings/routes/__tests__/index.js b/src/ui/ui_settings/routes/__tests__/index.js index c15a10753a8f3..0f5c4eb3ccdae 100644 --- a/src/ui/ui_settings/routes/__tests__/index.js +++ b/src/ui/ui_settings/routes/__tests__/index.js @@ -29,8 +29,8 @@ describe('uiSettings/routes', function () { this.timeout(10000); before(startServers); - describe('doc exists', docExistsSuite); - describe('doc missing', docMissingSuite); describe('index missing', indexMissingSuite); + describe('doc missing', docMissingSuite); + describe('doc exists', docExistsSuite); after(stopServers); }); diff --git a/src/ui/ui_settings/routes/__tests__/index_missing.js b/src/ui/ui_settings/routes/__tests__/index_missing.js index c2ab830e01933..994d70c657528 100644 --- a/src/ui/ui_settings/routes/__tests__/index_missing.js +++ b/src/ui/ui_settings/routes/__tests__/index_missing.js @@ -1,23 +1,21 @@ import expect from 'expect.js'; +import sinon from 'sinon'; import { getServices, chance, - assertServiceUnavailableResponse, - waitUntilNextHealthCheck, + assertSinonMatch, } from './lib'; export function indexMissingSuite() { - beforeEach(waitUntilNextHealthCheck); - async function setup() { const { callCluster, kbnServer } = getServices(); const indexName = kbnServer.config.get('kibana.index'); - // delete the kibana index and run the test, we have about 2 seconds - // before the healthCheck runs again, that SHOULD be enough time + // ensure the kibana index does not exist await callCluster('indices.delete', { index: indexName, + ignore: [404], }); return { @@ -25,24 +23,22 @@ export function indexMissingSuite() { // an incorrect number of shards is how we determine when the index was not created by Kibana, // but automatically by writing to es when index didn't exist - async assertNoKibanaIndex() { - const resp = await callCluster('indices.delete', { - index: indexName, - ignore: [404] + async assertValidKibanaIndex() { + const resp = await callCluster('indices.get', { + index: indexName }); - expect(resp).to.have.property('status', 404); + + expect(resp[indexName].mappings).to.have.property('doc'); + expect(resp[indexName].mappings.doc.properties).to.have.keys( + 'index-pattern', + 'visualization', + 'search', + 'dashboard' + ); } }; } - afterEach(async () => { - const { kbnServer, callCluster } = getServices(); - await callCluster('indices.delete', { - index: kbnServer.config.get('kibana.index'), - ignore: 404 - }); - }); - describe('get route', () => { it('returns a 200 and with empty values', async () => { const { kbnServer } = await setup(); @@ -58,49 +54,82 @@ export function indexMissingSuite() { }); describe('set route', () => { - it('returns a 503 and does not create the kibana index', async () => { - const { kbnServer, assertNoKibanaIndex } = await setup(); + it('returns a 200 and creates a valid kibana index', async () => { + const { kbnServer, assertValidKibanaIndex } = await setup(); - assertServiceUnavailableResponse(await kbnServer.inject({ + const defaultIndex = chance.word(); + const { statusCode, result } = await kbnServer.inject({ method: 'POST', url: '/api/kibana/settings/defaultIndex', payload: { - value: chance.word() + value: defaultIndex } - })); + }); + + expect(statusCode).to.be(200); + assertSinonMatch(result, { + settings: { + buildNum: { + userValue: sinon.match.number + }, + defaultIndex: { + userValue: defaultIndex + } + } + }); - await assertNoKibanaIndex(); + await assertValidKibanaIndex(); }); }); describe('setMany route', () => { - it('returns a 503 and does not create the kibana index', async () => { - const { kbnServer, assertNoKibanaIndex } = await setup(); + it('returns a 200 and creates a valid kibana index', async () => { + const { kbnServer, assertValidKibanaIndex } = await setup(); - assertServiceUnavailableResponse(await kbnServer.inject({ + const defaultIndex = chance.word(); + const { statusCode, result } = await kbnServer.inject({ method: 'POST', url: '/api/kibana/settings', payload: { - changes: { - defaultIndex: chance.word() + changes: { defaultIndex } + } + }); + + expect(statusCode).to.be(200); + assertSinonMatch(result, { + settings: { + buildNum: { + userValue: sinon.match.number + }, + defaultIndex: { + userValue: defaultIndex } } - })); + }); - await assertNoKibanaIndex(); + await assertValidKibanaIndex(); }); }); describe('delete route', () => { - it('returns a 503 and does not create the kibana index', async () => { - const { kbnServer, assertNoKibanaIndex } = await setup(); + it('returns a 200 and creates a valid kibana index', async () => { + const { kbnServer, assertValidKibanaIndex } = await setup(); - assertServiceUnavailableResponse(await kbnServer.inject({ + const { statusCode, result } = await kbnServer.inject({ method: 'DELETE', url: '/api/kibana/settings/defaultIndex' - })); + }); + + expect(statusCode).to.be(200); + assertSinonMatch(result, { + settings: { + buildNum: { + userValue: sinon.match.number + } + } + }); - await assertNoKibanaIndex(); + await assertValidKibanaIndex(); }); }); } diff --git a/src/ui/ui_settings/routes/__tests__/lib/assert.js b/src/ui/ui_settings/routes/__tests__/lib/assert.js index dfa28a480dcad..846c8b7708267 100644 --- a/src/ui/ui_settings/routes/__tests__/lib/assert.js +++ b/src/ui/ui_settings/routes/__tests__/lib/assert.js @@ -5,11 +5,3 @@ export function assertSinonMatch(value, match) { stub(value); sinon.assert.calledWithExactly(stub, match); } - -export function assertServiceUnavailableResponse({ result }) { - assertSinonMatch(result, { - statusCode: 503, - error: 'Service Unavailable', - message: 'Service Unavailable' - }); -} diff --git a/src/ui/ui_settings/routes/__tests__/lib/index.js b/src/ui/ui_settings/routes/__tests__/lib/index.js index e040da5057cbd..7cdb162c83a96 100644 --- a/src/ui/ui_settings/routes/__tests__/lib/index.js +++ b/src/ui/ui_settings/routes/__tests__/lib/index.js @@ -2,7 +2,6 @@ export { startServers, getServices, stopServers, - waitUntilNextHealthCheck, } from './servers'; export { @@ -11,5 +10,4 @@ export { export { assertSinonMatch, - assertServiceUnavailableResponse, } from './assert'; diff --git a/src/ui/ui_settings/routes/__tests__/lib/servers.js b/src/ui/ui_settings/routes/__tests__/lib/servers.js index 94dfdb88e56f4..2cc5c7a29c78d 100644 --- a/src/ui/ui_settings/routes/__tests__/lib/servers.js +++ b/src/ui/ui_settings/routes/__tests__/lib/servers.js @@ -11,29 +11,11 @@ export async function startServers() { this.timeout(es.getStartTimeout()); await es.start(); - kbnServer = kbnTestServer.createServerWithCorePlugins({ - // speed up the index check timeout so that the healthCheck doesn't - // have time to recreate the index before we get a response - savedObjects: { - indexCheckTimeout: 1000 - } - }); + kbnServer = kbnTestServer.createServerWithCorePlugins(); await kbnServer.ready(); await kbnServer.server.plugins.elasticsearch.waitUntilReady(); } -export async function waitUntilNextHealthCheck() { - // make sure the kibana index is still missing so that waitUntilReady() - // won't resolve until the next health check is complete - await es.getClient().indices.delete({ - index: kbnServer.config.get('kibana.index'), - ignore: [404], - }); - - // wait for healthcheck to recreate the kibana index - await kbnServer.server.plugins.elasticsearch.waitUntilReady(); -} - export function getServices() { if (services) { return services; diff --git a/tasks/config/simplemocha.js b/tasks/config/simplemocha.js index 25f2ec2184dc5..3b0f6c1c17cae 100644 --- a/tasks/config/simplemocha.js +++ b/tasks/config/simplemocha.js @@ -13,6 +13,7 @@ module.exports = { 'src/**/__tests__/**/*.js', 'tasks/**/__tests__/**/*.js', 'test/fixtures/__tests__/*.js', + '!**/__tests__/fixtures/**/*', '!src/**/public/**', '!**/_*.js' ] diff --git a/test/api_integration/apis/saved_objects/create.js b/test/api_integration/apis/saved_objects/create.js index 86d31da38653b..37f6ef7893a46 100644 --- a/test/api_integration/apis/saved_objects/create.js +++ b/test/api_integration/apis/saved_objects/create.js @@ -47,7 +47,7 @@ export default function ({ getService }) { }) )); - it('should return 503 and not create kibana index', async () => { + it('should return 200 and create kibana index', async () => { await supertest .post(`/api/saved_objects/visualization`) .send({ @@ -55,22 +55,27 @@ export default function ({ getService }) { title: 'My favorite vis' } }) - .expect(503) + .expect(200) .then(resp => { // loose uuid validation + expect(resp.body).to.have.property('id').match(/^[0-9a-f-]{36}$/); + + // loose ISO8601 UTC time with milliseconds validation + expect(resp.body).to.have.property('updated_at').match(/^[\d-]{10}T[\d:\.]{12}Z$/); + expect(resp.body).to.eql({ - error: 'Service Unavailable', - statusCode: 503, - message: 'Service Unavailable' + id: resp.body.id, + type: 'visualization', + updated_at: resp.body.updated_at, + version: 1, + attributes: { + title: 'My favorite vis' + } }); }); - const index = await es.indices.get({ - index: '.kibana', - ignore: [404] - }); - - expect(index).to.have.property('status', 404); + expect(await es.indices.exists({ index: '.kibana' })) + .to.be(true); }); }); }); diff --git a/test/api_integration/config.js b/test/api_integration/config.js index 4adac456114eb..5e80b19dc4631 100644 --- a/test/api_integration/config.js +++ b/test/api_integration/config.js @@ -13,7 +13,6 @@ export default async function ({ readConfigFile }) { services: { es: commonConfig.get('services.es'), esArchiver: commonConfig.get('services.esArchiver'), - kibanaIndex: commonConfig.get('services.kibanaIndex'), retry: commonConfig.get('services.retry'), supertest: SupertestProvider, chance: ChanceProvider, diff --git a/test/common/config.js b/test/common/config.js index a843201efa0a0..40973b7ad609e 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -1,6 +1,5 @@ import { KibanaServerProvider, - KibanaIndexProvider, EsProvider, EsArchiverProvider, RetryProvider, @@ -17,7 +16,6 @@ export default function () { }, services: { kibanaServer: KibanaServerProvider, - kibanaIndex: KibanaIndexProvider, retry: RetryProvider, es: EsProvider, esArchiver: EsArchiverProvider, diff --git a/test/common/services/index.js b/test/common/services/index.js index 941e1e5df2de7..42c80c4e1ed5e 100644 --- a/test/common/services/index.js +++ b/test/common/services/index.js @@ -1,5 +1,4 @@ export { KibanaServerProvider } from './kibana_server'; -export { KibanaIndexProvider } from './kibana_index'; export { EsProvider } from './es'; export { EsArchiverProvider } from './es_archiver'; export { RetryProvider } from './retry'; diff --git a/test/common/services/kibana_index.js b/test/common/services/kibana_index.js deleted file mode 100644 index 8f7061bc8eac9..0000000000000 --- a/test/common/services/kibana_index.js +++ /dev/null @@ -1,21 +0,0 @@ -export async function KibanaIndexProvider({ getService }) { - const retry = getService('retry'); - const es = getService('es'); - - const KIBANA_INDEX_NAME = '.kibana'; - const esIndex = await retry.try(async () => { - return await es.indices.get({ - index: KIBANA_INDEX_NAME - }); - }); - - return new class KibanaIndex { - getName() { - return KIBANA_INDEX_NAME; - } - - getMappingsDsl() { - return Object.values(esIndex)[0].mappings; - } - }; -} diff --git a/test/common/services/kibana_server/kibana_server.js b/test/common/services/kibana_server/kibana_server.js index 161d90cf23eb4..770aaa759ba64 100644 --- a/test/common/services/kibana_server/kibana_server.js +++ b/test/common/services/kibana_server/kibana_server.js @@ -7,15 +7,13 @@ import { KibanaServerVersion } from './version'; export async function KibanaServerProvider({ getService }) { const log = getService('log'); const config = getService('config'); - const es = getService('es'); - const kibanaIndex = await getService('kibanaIndex').init(); return new class KibanaServer { constructor() { const url = formatUrl(config.get('servers.kibana')); this.status = new KibanaServerStatus(url); this.version = new KibanaServerVersion(this.status); - this.uiSettings = new KibanaServerUiSettings(url, log, es, kibanaIndex, this.version); + this.uiSettings = new KibanaServerUiSettings(url, log, this.version); } }; } diff --git a/test/common/services/kibana_server/ui_settings.js b/test/common/services/kibana_server/ui_settings.js index 7c051b3919537..5d08b0291520d 100644 --- a/test/common/services/kibana_server/ui_settings.js +++ b/test/common/services/kibana_server/ui_settings.js @@ -5,7 +5,7 @@ const MINUTE = 60 * 1000; const HOUR = 60 * MINUTE; export class KibanaServerUiSettings { - constructor(url, log, es, kibanaIndex, kibanaVersion) { + constructor(url, log, kibanaVersion) { this._log = log; this._kibanaVersion = kibanaVersion; this._wreck = Wreck.defaults({ diff --git a/test/functional/config.js b/test/functional/config.js index 20f76629431aa..23c0423d32410 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -57,7 +57,6 @@ export default async function ({ readConfigFile }) { es: commonConfig.get('services.es'), esArchiver: commonConfig.get('services.esArchiver'), kibanaServer: commonConfig.get('services.kibanaServer'), - kibanaIndex: commonConfig.get('services.kibanaIndex'), retry: commonConfig.get('services.retry'), remote: RemoteProvider, filterBar: FilterBarProvider,