mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[uiSettings] auto create/upgrade saved config (#14164)
* [uiSettings] auto upgrade savedConfig doc when missing * naming tweaks * fix comments * ensure that rcVersions are not found within a version * add some tests for non-single digit versions/rcs/betas * return the condition, rather than using an if() * assert that getUpgradeableConfig() is always called once * [uiSettingsService] remove excess space * [savedObjectsClient] only attempt to createOrUpgradeSavedConfig once * [uiSettings/routes/tests] remove unused assert helper * [functional/console] correct test title * [ftr/kibanaServer/uiSettings] fix disableToastAutohide timeout
This commit is contained in:
parent
330665b706
commit
6998f07454
38 changed files with 717 additions and 755 deletions
|
@ -15,7 +15,6 @@ export default class ClusterManager {
|
|||
const serverArgv = [];
|
||||
const optimizerArgv = [
|
||||
'--plugins.initialize=false',
|
||||
'--uiSettings.enabled=false',
|
||||
'--server.autoListen=false',
|
||||
];
|
||||
|
||||
|
|
|
@ -4,3 +4,5 @@ logging:
|
|||
json: true
|
||||
optimize:
|
||||
enabled: false
|
||||
plugins:
|
||||
initialize: false
|
||||
|
|
|
@ -72,7 +72,7 @@ describe(`Server logging configuration`, function () {
|
|||
}
|
||||
|
||||
function switchToPlainTextLog() {
|
||||
json = 3; // ignore both "reloading" messages + ui settings status message
|
||||
json = 2; // ignore both "reloading" messages
|
||||
setLoggingJson(false);
|
||||
child.kill(`SIGHUP`); // reload logging config
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import healthCheck from '../health_check';
|
|||
import kibanaVersion from '../kibana_version';
|
||||
import { esTestConfig } from '../../../../test_utils/es';
|
||||
import * as patchKibanaIndexNS from '../patch_kibana_index';
|
||||
import * as migrateConfigNS from '../migrate_config';
|
||||
|
||||
const esPort = esTestConfig.getPort();
|
||||
const esUrl = esTestConfig.getUrl();
|
||||
|
@ -34,7 +33,6 @@ describe('plugins/elasticsearch', () => {
|
|||
// Stub the Kibana version instead of drawing from package.json.
|
||||
sandbox.stub(kibanaVersion, 'get').returns(COMPATIBLE_VERSION_NUMBER);
|
||||
sandbox.stub(patchKibanaIndexNS, 'patchKibanaIndex');
|
||||
sandbox.stub(migrateConfigNS, 'migrateConfig');
|
||||
|
||||
// setup the plugin stub
|
||||
plugin = {
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import expect from 'expect.js';
|
||||
|
||||
import isUpgradeable from '../is_upgradeable';
|
||||
import { pkg } from '../../../../utils';
|
||||
let version = pkg.version;
|
||||
|
||||
describe('plugins/elasticsearch', function () {
|
||||
describe('lib/is_upgradeable', function () {
|
||||
const server = {
|
||||
config: _.constant({
|
||||
get: function (key) {
|
||||
switch (key) {
|
||||
case 'pkg.version': return version;
|
||||
default: throw new Error(`no stub for config key ${key}`);
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function upgradeDoc(id, _version, bool) {
|
||||
describe('', function () {
|
||||
before(function () { version = _version; });
|
||||
|
||||
it(`should return ${bool} for ${id} <= ${version}`, function () {
|
||||
expect(isUpgradeable(server, { id: id })).to.be(bool);
|
||||
});
|
||||
|
||||
after(function () { version = pkg.version; });
|
||||
});
|
||||
}
|
||||
|
||||
upgradeDoc('1.0.0-beta1', pkg.version, false);
|
||||
upgradeDoc(pkg.version, pkg.version, false);
|
||||
upgradeDoc('4.0.0-RC1', '4.0.0-RC2', true);
|
||||
upgradeDoc('4.0.0-rc2', '4.0.0-rc1', false);
|
||||
upgradeDoc('4.0.0-rc2', '4.0.0', true);
|
||||
upgradeDoc('4.0.0-rc2', '4.0.2', true);
|
||||
upgradeDoc('4.0.1', '4.1.0-rc', true);
|
||||
upgradeDoc('4.0.0-rc1', '4.0.0', true);
|
||||
upgradeDoc('4.0.0-rc1-SNAPSHOT', '4.0.0', false);
|
||||
upgradeDoc('4.1.0-rc1-SNAPSHOT', '4.1.0-rc1', false);
|
||||
upgradeDoc('5.0.0-alpha1', '5.0.0', false);
|
||||
|
||||
it('should handle missing id field', function () {
|
||||
const configSavedObject = {
|
||||
'type': 'config',
|
||||
'attributes': {
|
||||
'buildNum': 1.7976931348623157e+308,
|
||||
'defaultIndex': '[logstash-]YYYY.MM.DD'
|
||||
}
|
||||
};
|
||||
|
||||
expect(isUpgradeable(server, configSavedObject)).to.be(false);
|
||||
});
|
||||
|
||||
it('should handle id of @@version', function () {
|
||||
const configSavedObject = {
|
||||
'type': 'config',
|
||||
'id': '@@version',
|
||||
'attributes': {
|
||||
'buildNum': 1.7976931348623157e+308,
|
||||
'defaultIndex': '[logstash-]YYYY.MM.DD'
|
||||
}
|
||||
};
|
||||
expect(isUpgradeable(server, configSavedObject)).to.be(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
|
@ -1,157 +0,0 @@
|
|||
import Promise from 'bluebird';
|
||||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
|
||||
import upgradeConfig from '../upgrade_config';
|
||||
|
||||
describe('plugins/elasticsearch', function () {
|
||||
describe('lib/upgrade_config', function () {
|
||||
let get;
|
||||
let server;
|
||||
let savedObjectsClient;
|
||||
let upgrade;
|
||||
|
||||
beforeEach(function () {
|
||||
get = sinon.stub();
|
||||
get.withArgs('kibana.index').returns('.my-kibana');
|
||||
get.withArgs('pkg.version').returns('4.0.1');
|
||||
get.withArgs('pkg.buildNum').returns(Math.random());
|
||||
|
||||
savedObjectsClient = {
|
||||
create: sinon.stub()
|
||||
};
|
||||
|
||||
server = {
|
||||
log: sinon.stub(),
|
||||
config: function () {
|
||||
return {
|
||||
get: get
|
||||
};
|
||||
},
|
||||
};
|
||||
upgrade = upgradeConfig(server, savedObjectsClient);
|
||||
});
|
||||
|
||||
describe('nothing is found', function () {
|
||||
const configSavedObjects = { hits: { hits:[] } };
|
||||
|
||||
beforeEach(function () {
|
||||
savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 }));
|
||||
});
|
||||
|
||||
describe('production', function () {
|
||||
beforeEach(function () {
|
||||
get.withArgs('env.name').returns('production');
|
||||
get.withArgs('env.prod').returns(true);
|
||||
get.withArgs('env.dev').returns(false);
|
||||
});
|
||||
|
||||
it('should resolve buildNum to pkg.buildNum config', function () {
|
||||
return upgrade(configSavedObjects).then(function () {
|
||||
sinon.assert.calledOnce(savedObjectsClient.create);
|
||||
const attributes = savedObjectsClient.create.args[0][1];
|
||||
expect(attributes).to.have.property('buildNum', get('pkg.buildNum'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve version to pkg.version config', function () {
|
||||
return upgrade(configSavedObjects).then(function () {
|
||||
const options = savedObjectsClient.create.args[0][2];
|
||||
expect(options).to.have.property('id', get('pkg.version'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('development', function () {
|
||||
beforeEach(function () {
|
||||
get.withArgs('env.name').returns('development');
|
||||
get.withArgs('env.prod').returns(false);
|
||||
get.withArgs('env.dev').returns(true);
|
||||
});
|
||||
|
||||
it('should resolve buildNum to pkg.buildNum config', function () {
|
||||
return upgrade(configSavedObjects).then(function () {
|
||||
const attributes = savedObjectsClient.create.args[0][1];
|
||||
expect(attributes).to.have.property('buildNum', get('pkg.buildNum'));
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve version to pkg.version config', function () {
|
||||
return upgrade(configSavedObjects).then(function () {
|
||||
const options = savedObjectsClient.create.args[0][2];
|
||||
expect(options).to.have.property('id', get('pkg.version'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should resolve with undefined if the current version is found', function () {
|
||||
const configSavedObjects = [ { id: '4.0.1' } ];
|
||||
return upgrade(configSavedObjects).then(function (resp) {
|
||||
expect(resp).to.be(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create new config if the nothing is upgradeable', function () {
|
||||
get.withArgs('pkg.buildNum').returns(9833);
|
||||
savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 }));
|
||||
|
||||
const configSavedObjects = [ { id: '4.0.1-alpha3' }, { id: '4.0.1-beta1' }, { id: '4.0.0-SNAPSHOT1' } ];
|
||||
return upgrade(configSavedObjects).then(function () {
|
||||
sinon.assert.calledOnce(savedObjectsClient.create);
|
||||
const savedObjectType = savedObjectsClient.create.args[0][0];
|
||||
expect(savedObjectType).to.eql('config');
|
||||
const attributes = savedObjectsClient.create.args[0][1];
|
||||
expect(attributes).to.have.property('buildNum', 9833);
|
||||
const options = savedObjectsClient.create.args[0][2];
|
||||
expect(options).to.have.property('id', '4.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update the build number on the new config', function () {
|
||||
get.withArgs('pkg.buildNum').returns(5801);
|
||||
savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 }));
|
||||
|
||||
const configSavedObjects = [ { id: '4.0.0', attributes: { buildNum: 1 } } ];
|
||||
|
||||
return upgrade(configSavedObjects).then(function () {
|
||||
sinon.assert.calledOnce(savedObjectsClient.create);
|
||||
const attributes = savedObjectsClient.create.args[0][1];
|
||||
expect(attributes).to.have.property('buildNum', 5801);
|
||||
const savedObjectType = savedObjectsClient.create.args[0][0];
|
||||
expect(savedObjectType).to.eql('config');
|
||||
const options = savedObjectsClient.create.args[0][2];
|
||||
expect(options).to.have.property('id', '4.0.1');
|
||||
});
|
||||
});
|
||||
|
||||
it('should log a message for upgrades', function () {
|
||||
get.withArgs('pkg.buildNum').returns(5801);
|
||||
savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 }));
|
||||
|
||||
const configSavedObjects = [ { id: '4.0.0', attributes: { buildNum: 1 } } ];
|
||||
|
||||
return upgrade(configSavedObjects).then(function () {
|
||||
sinon.assert.calledOnce(server.log);
|
||||
expect(server.log.args[0][0]).to.eql(['plugin', 'elasticsearch']);
|
||||
const msg = server.log.args[0][1];
|
||||
expect(msg).to.have.property('prevVersion', '4.0.0');
|
||||
expect(msg).to.have.property('newVersion', '4.0.1');
|
||||
expect(msg.tmpl).to.contain('Upgrade');
|
||||
});
|
||||
});
|
||||
|
||||
it('should copy attributes from old config', function () {
|
||||
get.withArgs('pkg.buildNum').returns(5801);
|
||||
savedObjectsClient.create.returns(Promise.resolve({ id: 1, version: 1 }));
|
||||
|
||||
const configSavedObjects = [ { id: '4.0.0', attributes: { buildNum: 1, defaultIndex: 'logstash-*' } } ];
|
||||
|
||||
return upgrade(configSavedObjects).then(function () {
|
||||
sinon.assert.calledOnce(savedObjectsClient.create);
|
||||
const attributes = savedObjectsClient.create.args[0][1];
|
||||
expect(attributes).to.have.property('defaultIndex', 'logstash-*');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
import Promise from 'bluebird';
|
||||
import elasticsearch from 'elasticsearch';
|
||||
import { migrateConfig } from './migrate_config';
|
||||
import createKibanaIndex from './create_kibana_index';
|
||||
import kibanaVersion from './kibana_version';
|
||||
import { ensureEsVersion } from './ensure_es_version';
|
||||
|
@ -107,7 +105,6 @@ export default function (plugin, server) {
|
|||
indexName: config.get('kibana.index'),
|
||||
kibanaIndexMappingsDsl: server.getKibanaIndexMappingsDsl()
|
||||
}))
|
||||
.then(_.partial(migrateConfig, server))
|
||||
.then(() => {
|
||||
const tribeUrl = config.get('elasticsearch.tribe.url');
|
||||
if (tribeUrl) {
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import semver from 'semver';
|
||||
const rcVersionRegex = /(\d+\.\d+\.\d+)\-rc(\d+)/i;
|
||||
|
||||
export default function (server, configSavedObject) {
|
||||
const config = server.config();
|
||||
if (/alpha|beta|snapshot/i.test(configSavedObject.id)) return false;
|
||||
if (!configSavedObject.id) return false;
|
||||
if (configSavedObject.id === config.get('pkg.version')) return false;
|
||||
|
||||
let packageRcRelease = Infinity;
|
||||
let rcRelease = Infinity;
|
||||
let packageVersion = config.get('pkg.version');
|
||||
let version = configSavedObject.id;
|
||||
const matches = configSavedObject.id.match(rcVersionRegex);
|
||||
const packageMatches = config.get('pkg.version').match(rcVersionRegex);
|
||||
|
||||
if (matches) {
|
||||
version = matches[1];
|
||||
rcRelease = parseInt(matches[2], 10);
|
||||
}
|
||||
|
||||
if (packageMatches) {
|
||||
packageVersion = packageMatches[1];
|
||||
packageRcRelease = parseInt(packageMatches[2], 10);
|
||||
}
|
||||
|
||||
try {
|
||||
if (semver.gte(version, packageVersion) && rcRelease >= packageRcRelease) return false;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import upgrade from './upgrade_config';
|
||||
|
||||
export async function migrateConfig(server) {
|
||||
const savedObjectsClient = server.savedObjectsClientFactory({
|
||||
callCluster: server.plugins.elasticsearch.getCluster('admin').callWithInternalUser
|
||||
});
|
||||
|
||||
const { saved_objects: configSavedObjects } = await savedObjectsClient.find({
|
||||
type: 'config',
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
sortField: 'buildNum',
|
||||
sortOrder: 'desc'
|
||||
});
|
||||
|
||||
return await upgrade(server, savedObjectsClient)(configSavedObjects);
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
import Promise from 'bluebird';
|
||||
import isUpgradeable from './is_upgradeable';
|
||||
import _ from 'lodash';
|
||||
|
||||
export default function (server, savedObjectsClient) {
|
||||
const config = server.config();
|
||||
|
||||
function createNewConfig() {
|
||||
return savedObjectsClient.create('config', {
|
||||
buildNum: config.get('pkg.buildNum')
|
||||
}, {
|
||||
id: config.get('pkg.version')
|
||||
});
|
||||
}
|
||||
|
||||
return function (configSavedObjects) {
|
||||
// Check to see if there are any doc. If not then we set the build number and id
|
||||
if (configSavedObjects.length === 0) {
|
||||
return createNewConfig();
|
||||
}
|
||||
|
||||
// if we already have a the current version in the index then we need to stop
|
||||
const devConfig = _.find(configSavedObjects, function currentVersion(configSavedObject) {
|
||||
return configSavedObject.id !== '@@version' && configSavedObject.id === config.get('pkg.version');
|
||||
});
|
||||
|
||||
if (devConfig) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Look for upgradeable configs. If none of them are upgradeable
|
||||
// then create a new one.
|
||||
const configSavedObject = _.find(configSavedObjects, isUpgradeable.bind(null, server));
|
||||
if (!configSavedObject) {
|
||||
return createNewConfig();
|
||||
}
|
||||
|
||||
// if the build number is still the template string (which it wil be in development)
|
||||
// then we need to set it to the max interger. Otherwise we will set it to the build num
|
||||
configSavedObject.attributes.buildNum = config.get('pkg.buildNum');
|
||||
|
||||
server.log(['plugin', 'elasticsearch'], {
|
||||
tmpl: 'Upgrade config from <%= prevVersion %> to <%= newVersion %>',
|
||||
prevVersion: configSavedObject.id,
|
||||
newVersion: config.get('pkg.version')
|
||||
});
|
||||
|
||||
return savedObjectsClient.create('config', configSavedObject.attributes, {
|
||||
id: config.get('pkg.version')
|
||||
});
|
||||
};
|
||||
}
|
|
@ -202,14 +202,6 @@ export default () => Joi.object({
|
|||
indexCheckTimeout: Joi.number().default(5000)
|
||||
}).default(),
|
||||
|
||||
uiSettings: Joi.object({
|
||||
// this is used to prevent the uiSettings from initializing. Since they
|
||||
// require the elasticsearch plugin in order to function we need to turn
|
||||
// them off when we turn off the elasticsearch plugin (like we do in the
|
||||
// optimizer half of the dev server)
|
||||
enabled: Joi.boolean().default(true)
|
||||
}).default(),
|
||||
|
||||
i18n: Joi.object({
|
||||
defaultLocale: Joi.string().default('en'),
|
||||
}).default(),
|
||||
|
|
|
@ -17,6 +17,7 @@ const deprecations = [
|
|||
//server
|
||||
rename('server.ssl.cert', 'server.ssl.certificate'),
|
||||
unused('server.xsrf.token'),
|
||||
unused('uiSettings.enabled'),
|
||||
serverSslEnabled,
|
||||
];
|
||||
|
||||
|
|
|
@ -55,12 +55,13 @@ export default class ServerStatus {
|
|||
}
|
||||
|
||||
overall() {
|
||||
const state = _(this._created)
|
||||
.map(function (status) {
|
||||
return states.get(status.state);
|
||||
})
|
||||
.sortBy('severity')
|
||||
.pop();
|
||||
const state = Object
|
||||
// take all created status objects
|
||||
.values(this._created)
|
||||
// get the state descriptor for each status
|
||||
.map(status => states.get(status.state))
|
||||
// reduce to the state with the highest severity, defaulting to green
|
||||
.reduce((a, b) => a.severity > b.severity ? a : b, states.get('green'));
|
||||
|
||||
const statuses = _.where(this._created, { state: state.id });
|
||||
const since = _.get(_.sortBy(statuses, 'since'), [0, 'since']);
|
||||
|
|
|
@ -32,17 +32,24 @@ describe('UiExports', function () {
|
|||
server: { port: 0 }, // pick a random open port
|
||||
logging: { silent: true }, // no logs
|
||||
optimize: { enabled: false },
|
||||
uiSettings: { enabled: false },
|
||||
plugins: {
|
||||
paths: [resolve(__dirname, './fixtures/test_app')] // inject an app so we can hit /app/{id}
|
||||
},
|
||||
});
|
||||
|
||||
await kbnServer.ready();
|
||||
kbnServer.status.get('ui settings').state = 'green';
|
||||
kbnServer.server.decorate('request', 'getUiSettingsService', () => {
|
||||
return { getDefaults: noop, getUserProvided: noop };
|
||||
});
|
||||
|
||||
// TODO: hopefully we can add better support for something
|
||||
// like this in the new platform
|
||||
kbnServer.server._requestor._decorations.getUiSettingsService = {
|
||||
apply: undefined,
|
||||
method() {
|
||||
return {
|
||||
getDefaults: noop,
|
||||
getUserProvided: noop
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
|
|
@ -6,8 +6,9 @@ export const savedObjectsClientErrors = SavedObjectsClient.errors;
|
|||
|
||||
export function createObjectsClientStub(type, id, esDocSource = {}) {
|
||||
const savedObjectsClient = {
|
||||
update: sinon.stub().returns(Promise.resolve()),
|
||||
update: sinon.stub(),
|
||||
get: sinon.stub().returns({ attributes: esDocSource }),
|
||||
create: sinon.stub(),
|
||||
errors: savedObjectsClientErrors
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
import Chance from 'chance';
|
||||
|
||||
import ServerStatus from '../../../server/status/server_status';
|
||||
import Config from '../../../server/config/config';
|
||||
|
||||
/* eslint-disable import/no-duplicates */
|
||||
|
@ -14,8 +12,6 @@ import { getUiSettingsServiceForRequest } from '../ui_settings_service_for_reque
|
|||
|
||||
import { uiSettingsMixin } from '../ui_settings_mixin';
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
describe('uiSettingsMixin()', () => {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
|
||||
|
@ -58,7 +54,6 @@ describe('uiSettingsMixin()', () => {
|
|||
server,
|
||||
config,
|
||||
uiExports: { addConsumer: sinon.stub() },
|
||||
status: new ServerStatus(server),
|
||||
ready: sinon.stub().returns(readyPromise),
|
||||
};
|
||||
|
||||
|
@ -69,60 +64,11 @@ describe('uiSettingsMixin()', () => {
|
|||
server,
|
||||
decorations,
|
||||
readyPromise,
|
||||
status: kbnServer.status.get('ui settings'),
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => sandbox.restore());
|
||||
|
||||
describe('status', () => {
|
||||
it('creates a "ui settings" status', () => {
|
||||
const { status } = setup();
|
||||
expect(status).to.have.property('state', 'uninitialized');
|
||||
});
|
||||
|
||||
describe('disabled', () => {
|
||||
it('disables if uiSettings.enabled config is false', () => {
|
||||
const { status } = setup({ enabled: false });
|
||||
expect(status).to.have.property('state', 'disabled');
|
||||
});
|
||||
|
||||
it('does not register a handler for kbnServer.ready()', () => {
|
||||
const { readyPromise } = setup({ enabled: false });
|
||||
sinon.assert.notCalled(readyPromise.then);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enabled', () => {
|
||||
it('registers a handler for kbnServer.ready()', () => {
|
||||
const { readyPromise } = setup();
|
||||
sinon.assert.calledOnce(readyPromise.then);
|
||||
});
|
||||
|
||||
it('mirrors the elasticsearch plugin status once kibanaServer.ready() resolves', () => {
|
||||
const { kbnServer, readyPromise, status } = setup();
|
||||
const esStatus = kbnServer.status.createForPlugin({
|
||||
id: 'elasticsearch',
|
||||
version: 'kibana',
|
||||
});
|
||||
|
||||
esStatus.green();
|
||||
expect(status).to.have.property('state', 'uninitialized');
|
||||
const readyPromiseHandler = readyPromise.then.firstCall.args[0];
|
||||
readyPromiseHandler();
|
||||
expect(status).to.have.property('state', 'green');
|
||||
|
||||
|
||||
const states = chance.shuffle(['red', 'green', 'yellow']);
|
||||
states.forEach((state) => {
|
||||
esStatus[state]();
|
||||
expect(esStatus).to.have.property('state', state);
|
||||
expect(status).to.have.property('state', state);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('server.uiSettingsServiceFactory()', () => {
|
||||
it('decorates server with "uiSettingsServiceFactory"', () => {
|
||||
const { decorations } = setup();
|
||||
|
@ -172,32 +118,6 @@ describe('uiSettingsMixin()', () => {
|
|||
decorations.request.getUiSettingsService.call(request);
|
||||
sinon.assert.calledWith(getUiSettingsServiceForRequest, server, request);
|
||||
});
|
||||
|
||||
it('defines read interceptor that intercepts when status is not green', () => {
|
||||
const { status, decorations } = setup();
|
||||
expect(decorations.request).to.have.property('getUiSettingsService').a('function');
|
||||
|
||||
sandbox.stub(getUiSettingsServiceForRequestNS, 'getUiSettingsServiceForRequest');
|
||||
decorations.request.getUiSettingsService();
|
||||
|
||||
const options = getUiSettingsServiceForRequest.firstCall.args[2];
|
||||
expect(options).to.have.property('readInterceptor');
|
||||
|
||||
const { readInterceptor } = options;
|
||||
expect(readInterceptor).to.be.a('function');
|
||||
|
||||
status.green();
|
||||
expect(readInterceptor()).to.be(undefined);
|
||||
|
||||
status.yellow();
|
||||
expect(readInterceptor()).to.eql({});
|
||||
|
||||
status.red();
|
||||
expect(readInterceptor()).to.eql({});
|
||||
|
||||
status.green();
|
||||
expect(readInterceptor()).to.eql(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('server.uiSettings()', () => {
|
||||
|
|
|
@ -2,9 +2,10 @@ import { isEqual } from 'lodash';
|
|||
import expect from 'expect.js';
|
||||
import { errors as esErrors } from 'elasticsearch';
|
||||
import Chance from 'chance';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { UiSettingsService } from '../ui_settings_service';
|
||||
|
||||
import * as createOrUpgradeSavedConfigNS from '../create_or_upgrade_saved_config/create_or_upgrade_saved_config';
|
||||
import {
|
||||
createObjectsClientStub,
|
||||
savedObjectsClientErrors,
|
||||
|
@ -12,33 +13,41 @@ import {
|
|||
|
||||
const TYPE = 'config';
|
||||
const ID = 'kibana-version';
|
||||
const BUILD_NUM = 1234;
|
||||
const chance = new Chance();
|
||||
|
||||
function setup(options = {}) {
|
||||
const {
|
||||
readInterceptor,
|
||||
getDefaults,
|
||||
defaults = {},
|
||||
esDocSource = {},
|
||||
savedObjectsClient = createObjectsClientStub(TYPE, ID, esDocSource)
|
||||
} = options;
|
||||
|
||||
const uiSettings = new UiSettingsService({
|
||||
type: TYPE,
|
||||
id: ID,
|
||||
getDefaults: getDefaults || (() => defaults),
|
||||
readInterceptor,
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
return {
|
||||
uiSettings,
|
||||
assertGetQuery: savedObjectsClient.assertGetQuery,
|
||||
assertUpdateQuery: savedObjectsClient.assertUpdateQuery,
|
||||
};
|
||||
}
|
||||
|
||||
describe('ui settings', () => {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
|
||||
function setup(options = {}) {
|
||||
const {
|
||||
getDefaults,
|
||||
defaults = {},
|
||||
esDocSource = {},
|
||||
savedObjectsClient = createObjectsClientStub(TYPE, ID, esDocSource)
|
||||
} = options;
|
||||
|
||||
const uiSettings = new UiSettingsService({
|
||||
type: TYPE,
|
||||
id: ID,
|
||||
buildNum: BUILD_NUM,
|
||||
getDefaults: getDefaults || (() => defaults),
|
||||
savedObjectsClient,
|
||||
});
|
||||
|
||||
const createOrUpgradeSavedConfig = sandbox.stub(createOrUpgradeSavedConfigNS, 'createOrUpgradeSavedConfig');
|
||||
|
||||
return {
|
||||
uiSettings,
|
||||
savedObjectsClient,
|
||||
createOrUpgradeSavedConfig,
|
||||
assertGetQuery: savedObjectsClient.assertGetQuery,
|
||||
assertUpdateQuery: savedObjectsClient.assertUpdateQuery,
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => sandbox.restore());
|
||||
|
||||
describe('overview', () => {
|
||||
it('has expected api surface', () => {
|
||||
const { uiSettings } = setup();
|
||||
|
@ -61,15 +70,43 @@ describe('ui settings', () => {
|
|||
});
|
||||
|
||||
it('updates a single value in one operation', async () => {
|
||||
const { uiSettings, assertUpdateQuery } = setup();
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
await uiSettings.setMany({ one: 'value' });
|
||||
assertUpdateQuery({ one: 'value' });
|
||||
savedObjectsClient.assertUpdateQuery({ one: 'value' });
|
||||
});
|
||||
|
||||
it('updates several values in one operation', async () => {
|
||||
const { uiSettings, assertUpdateQuery } = setup();
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
await uiSettings.setMany({ one: 'value', another: 'val' });
|
||||
assertUpdateQuery({ one: 'value', another: 'val' });
|
||||
savedObjectsClient.assertUpdateQuery({ one: 'value', another: 'val' });
|
||||
});
|
||||
|
||||
it('automatically creates the savedConfig if it is missing', async () => {
|
||||
const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup();
|
||||
savedObjectsClient.update
|
||||
.onFirstCall()
|
||||
.throws(savedObjectsClientErrors.createGenericNotFoundError())
|
||||
.onSecondCall()
|
||||
.returns({});
|
||||
|
||||
await uiSettings.setMany({ foo: 'bar' });
|
||||
sinon.assert.calledTwice(savedObjectsClient.update);
|
||||
sinon.assert.calledOnce(createOrUpgradeSavedConfig);
|
||||
});
|
||||
|
||||
it('only tried to auto create once and throws NotFound', async () => {
|
||||
const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup();
|
||||
savedObjectsClient.update.throws(savedObjectsClientErrors.createGenericNotFoundError());
|
||||
|
||||
try {
|
||||
await uiSettings.setMany({ foo: 'bar' });
|
||||
throw new Error('expected setMany to throw a NotFound error');
|
||||
} catch (error) {
|
||||
expect(savedObjectsClientErrors.isNotFoundError(error)).to.be(true);
|
||||
}
|
||||
|
||||
sinon.assert.calledTwice(savedObjectsClient.update);
|
||||
sinon.assert.calledOnce(createOrUpgradeSavedConfig);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -80,9 +117,9 @@ describe('ui settings', () => {
|
|||
});
|
||||
|
||||
it('updates single values by (key, value)', async () => {
|
||||
const { uiSettings, assertUpdateQuery } = setup();
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
await uiSettings.set('one', 'value');
|
||||
assertUpdateQuery({ one: 'value' });
|
||||
savedObjectsClient.assertUpdateQuery({ one: 'value' });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -93,9 +130,9 @@ describe('ui settings', () => {
|
|||
});
|
||||
|
||||
it('removes single values by key', async () => {
|
||||
const { uiSettings, assertUpdateQuery } = setup();
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
await uiSettings.remove('one');
|
||||
assertUpdateQuery({ one: null });
|
||||
savedObjectsClient.assertUpdateQuery({ one: null });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -106,15 +143,15 @@ describe('ui settings', () => {
|
|||
});
|
||||
|
||||
it('removes a single value', async () => {
|
||||
const { uiSettings, assertUpdateQuery } = setup();
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
await uiSettings.removeMany(['one']);
|
||||
assertUpdateQuery({ one: null });
|
||||
savedObjectsClient.assertUpdateQuery({ one: null });
|
||||
});
|
||||
|
||||
it('updates several values in one operation', async () => {
|
||||
const { uiSettings, assertUpdateQuery } = setup();
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
await uiSettings.removeMany(['one', 'two', 'three']);
|
||||
assertUpdateQuery({ one: null, two: null, three: null });
|
||||
savedObjectsClient.assertUpdateQuery({ one: null, two: null, three: null });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -145,9 +182,9 @@ describe('ui settings', () => {
|
|||
|
||||
describe('#getUserProvided()', () => {
|
||||
it('pulls user configuration from ES', async () => {
|
||||
const { uiSettings, assertGetQuery } = setup();
|
||||
const { uiSettings, savedObjectsClient } = setup();
|
||||
await uiSettings.getUserProvided();
|
||||
assertGetQuery();
|
||||
savedObjectsClient.assertGetQuery();
|
||||
});
|
||||
|
||||
it('returns user configuration', async () => {
|
||||
|
@ -240,9 +277,9 @@ describe('ui settings', () => {
|
|||
describe('#getRaw()', () => {
|
||||
it('pulls user configuration from ES', async () => {
|
||||
const esDocSource = {};
|
||||
const { uiSettings, assertGetQuery } = setup({ esDocSource });
|
||||
const { uiSettings, savedObjectsClient } = setup({ esDocSource });
|
||||
await uiSettings.getRaw();
|
||||
assertGetQuery();
|
||||
savedObjectsClient.assertGetQuery();
|
||||
});
|
||||
|
||||
it(`without user configuration it's equal to the defaults`, async () => {
|
||||
|
@ -273,9 +310,9 @@ describe('ui settings', () => {
|
|||
describe('#getAll()', () => {
|
||||
it('pulls user configuration from ES', async () => {
|
||||
const esDocSource = {};
|
||||
const { uiSettings, assertGetQuery } = setup({ esDocSource });
|
||||
const { uiSettings, savedObjectsClient } = setup({ esDocSource });
|
||||
await uiSettings.getAll();
|
||||
assertGetQuery();
|
||||
savedObjectsClient.assertGetQuery();
|
||||
});
|
||||
|
||||
it(`returns defaults when es doc is empty`, async () => {
|
||||
|
@ -310,9 +347,9 @@ describe('ui settings', () => {
|
|||
describe('#get()', () => {
|
||||
it('pulls user configuration from ES', async () => {
|
||||
const esDocSource = {};
|
||||
const { uiSettings, assertGetQuery } = setup({ esDocSource });
|
||||
const { uiSettings, savedObjectsClient } = setup({ esDocSource });
|
||||
await uiSettings.get();
|
||||
assertGetQuery();
|
||||
savedObjectsClient.assertGetQuery();
|
||||
});
|
||||
|
||||
it(`returns the promised value for a key`, async () => {
|
||||
|
@ -337,47 +374,4 @@ describe('ui settings', () => {
|
|||
expect(result).to.equal('YYYY-MM-DD');
|
||||
});
|
||||
});
|
||||
|
||||
describe('readInterceptor() argument', () => {
|
||||
describe('#getUserProvided()', () => {
|
||||
it('returns a promise when interceptValue doesn\'t', () => {
|
||||
const { uiSettings } = setup({ readInterceptor: () => ({}) });
|
||||
expect(uiSettings.getUserProvided()).to.be.a(Promise);
|
||||
});
|
||||
|
||||
it('returns intercept values', async () => {
|
||||
const { uiSettings } = setup({
|
||||
readInterceptor: () => ({
|
||||
foo: 'bar'
|
||||
})
|
||||
});
|
||||
|
||||
expect(await uiSettings.getUserProvided()).to.eql({
|
||||
foo: {
|
||||
userValue: 'bar'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAll()', () => {
|
||||
it('merges intercept value with defaults', async () => {
|
||||
const { uiSettings } = setup({
|
||||
defaults: {
|
||||
foo: { value: 'foo' },
|
||||
bar: { value: 'bar' },
|
||||
},
|
||||
|
||||
readInterceptor: () => ({
|
||||
foo: 'not foo'
|
||||
}),
|
||||
});
|
||||
|
||||
expect(await uiSettings.getAll()).to.eql({
|
||||
foo: 'not foo',
|
||||
bar: 'bar'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
|
||||
import { createEsTestCluster } from '../../../../test_utils/es';
|
||||
import { createServerWithCorePlugins } from '../../../../test_utils/kbn_server';
|
||||
import { createToolingLog } from '../../../../utils';
|
||||
import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config';
|
||||
|
||||
describe('createOrUpgradeSavedConfig()', () => {
|
||||
let savedObjectsClient;
|
||||
let kbnServer;
|
||||
const cleanup = [];
|
||||
|
||||
before(async function () {
|
||||
const log = createToolingLog('debug');
|
||||
log.pipe(process.stdout);
|
||||
log.indent(6);
|
||||
|
||||
const es = createEsTestCluster({
|
||||
log: msg => log.debug(msg),
|
||||
name: 'savedObjects/healthCheck/integration',
|
||||
});
|
||||
|
||||
this.timeout(es.getStartTimeout());
|
||||
log.info('starting elasticsearch');
|
||||
log.indent(2);
|
||||
await es.start();
|
||||
log.indent(-2);
|
||||
cleanup.push(() => es.stop());
|
||||
|
||||
kbnServer = createServerWithCorePlugins();
|
||||
await kbnServer.ready();
|
||||
cleanup.push(async () => {
|
||||
await kbnServer.close();
|
||||
kbnServer = null;
|
||||
savedObjectsClient = null;
|
||||
});
|
||||
|
||||
await kbnServer.server.plugins.elasticsearch.waitUntilReady();
|
||||
|
||||
savedObjectsClient = kbnServer.server.savedObjectsClientFactory({
|
||||
callCluster: es.getCallCluster(),
|
||||
});
|
||||
|
||||
await savedObjectsClient.bulkCreate([
|
||||
{
|
||||
id: '5.4.0-SNAPSHOT',
|
||||
type: 'config',
|
||||
attributes: {
|
||||
buildNum: 54090,
|
||||
'5.4.0-SNAPSHOT': true
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '5.4.0-rc1',
|
||||
type: 'config',
|
||||
attributes: {
|
||||
buildNum: 54010,
|
||||
'5.4.0-rc1': true
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '@@version',
|
||||
type: 'config',
|
||||
attributes: {
|
||||
buildNum: 99999,
|
||||
'@@version': true
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await Promise.all(cleanup.map(fn => fn()));
|
||||
cleanup.length = 0;
|
||||
});
|
||||
|
||||
it('upgrades the previous version on each increment', async function () {
|
||||
this.timeout(30000);
|
||||
|
||||
// ------------------------------------
|
||||
// upgrade to 5.4.0
|
||||
await createOrUpgradeSavedConfig({
|
||||
savedObjectsClient,
|
||||
version: '5.4.0',
|
||||
buildNum: 54099,
|
||||
log: sinon.stub()
|
||||
});
|
||||
|
||||
const config540 = await savedObjectsClient.get('config', '5.4.0');
|
||||
expect(config540).to.have.property('attributes').eql({
|
||||
// should have the new build number
|
||||
buildNum: 54099,
|
||||
|
||||
// 5.4.0-SNAPSHOT and @@version were ignored so we only have the
|
||||
// attributes from 5.4.0-rc1, even though the other build nums are greater
|
||||
'5.4.0-rc1': true,
|
||||
});
|
||||
|
||||
// add the 5.4.0 flag to the 5.4.0 savedConfig
|
||||
await savedObjectsClient.update('config', '5.4.0', {
|
||||
'5.4.0': true,
|
||||
});
|
||||
|
||||
// ------------------------------------
|
||||
// upgrade to 5.4.1
|
||||
await createOrUpgradeSavedConfig({
|
||||
savedObjectsClient,
|
||||
version: '5.4.1',
|
||||
buildNum: 54199,
|
||||
log: sinon.stub()
|
||||
});
|
||||
|
||||
const config541 = await savedObjectsClient.get('config', '5.4.1');
|
||||
expect(config541).to.have.property('attributes').eql({
|
||||
// should have the new build number
|
||||
buildNum: 54199,
|
||||
|
||||
// should also include properties from 5.4.0 and 5.4.0-rc1
|
||||
'5.4.0': true,
|
||||
'5.4.0-rc1': true,
|
||||
});
|
||||
|
||||
// add the 5.4.1 flag to the 5.4.1 savedConfig
|
||||
await savedObjectsClient.update('config', '5.4.1', {
|
||||
'5.4.1': true,
|
||||
});
|
||||
|
||||
// ------------------------------------
|
||||
// upgrade to 7.0.0-rc1
|
||||
await createOrUpgradeSavedConfig({
|
||||
savedObjectsClient,
|
||||
version: '7.0.0-rc1',
|
||||
buildNum: 70010,
|
||||
log: sinon.stub()
|
||||
});
|
||||
|
||||
const config700rc1 = await savedObjectsClient.get('config', '7.0.0-rc1');
|
||||
expect(config700rc1).to.have.property('attributes').eql({
|
||||
// should have the new build number
|
||||
buildNum: 70010,
|
||||
|
||||
// should also include properties from 5.4.1, 5.4.0 and 5.4.0-rc1
|
||||
'5.4.1': true,
|
||||
'5.4.0': true,
|
||||
'5.4.0-rc1': true,
|
||||
});
|
||||
|
||||
// tag the 7.0.0-rc1 doc
|
||||
await savedObjectsClient.update('config', '7.0.0-rc1', {
|
||||
'7.0.0-rc1': true,
|
||||
});
|
||||
|
||||
// ------------------------------------
|
||||
// upgrade to 7.0.0
|
||||
await createOrUpgradeSavedConfig({
|
||||
savedObjectsClient,
|
||||
version: '7.0.0',
|
||||
buildNum: 70099,
|
||||
log: sinon.stub()
|
||||
});
|
||||
|
||||
const config700 = await savedObjectsClient.get('config', '7.0.0');
|
||||
expect(config700).to.have.property('attributes').eql({
|
||||
// should have the new build number
|
||||
buildNum: 70099,
|
||||
|
||||
// should also include properties from ancestors, including 7.0.0-rc1
|
||||
'7.0.0-rc1': true,
|
||||
'5.4.1': true,
|
||||
'5.4.0': true,
|
||||
'5.4.0-rc1': true,
|
||||
});
|
||||
|
||||
// tag the 7.0.0 doc
|
||||
await savedObjectsClient.update('config', '7.0.0', {
|
||||
'7.0.0': true,
|
||||
});
|
||||
|
||||
// ------------------------------------
|
||||
// "downgrade" to 6.2.3-rc1
|
||||
await createOrUpgradeSavedConfig({
|
||||
savedObjectsClient,
|
||||
version: '6.2.3-rc1',
|
||||
buildNum: 62310,
|
||||
log: sinon.stub()
|
||||
});
|
||||
|
||||
const config623rc1 = await savedObjectsClient.get('config', '6.2.3-rc1');
|
||||
expect(config623rc1).to.have.property('attributes').eql({
|
||||
// should have the new build number
|
||||
buildNum: 62310,
|
||||
|
||||
// should also include properties from ancestors, but not 7.0.0-rc1 or 7.0.0
|
||||
'5.4.1': true,
|
||||
'5.4.0': true,
|
||||
'5.4.0-rc1': true,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,119 @@
|
|||
import sinon from 'sinon';
|
||||
import Chance from 'chance';
|
||||
|
||||
import * as getUpgradeableConfigNS from '../get_upgradeable_config';
|
||||
import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config';
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
describe('uiSettings/createOrUpgradeSavedConfig', function () {
|
||||
const sandbox = sinon.sandbox.create();
|
||||
afterEach(() => sandbox.restore());
|
||||
|
||||
const version = '4.0.1';
|
||||
const prevVersion = '4.0.0';
|
||||
const buildNum = chance.integer({ min: 1000, max: 5000 });
|
||||
|
||||
function setup() {
|
||||
const log = sinon.stub();
|
||||
const getUpgradeableConfig = sandbox.stub(getUpgradeableConfigNS, 'getUpgradeableConfig');
|
||||
const savedObjectsClient = {
|
||||
create: sinon.spy(async (type, attributes, options = {}) => ({
|
||||
type,
|
||||
id: options.id,
|
||||
version: 1,
|
||||
}))
|
||||
};
|
||||
|
||||
async function run() {
|
||||
const resp = await createOrUpgradeSavedConfig({
|
||||
savedObjectsClient,
|
||||
version,
|
||||
buildNum,
|
||||
log,
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(getUpgradeableConfig);
|
||||
sinon.assert.alwaysCalledWith(getUpgradeableConfig, { savedObjectsClient, version });
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
return {
|
||||
buildNum,
|
||||
log,
|
||||
run,
|
||||
version,
|
||||
savedObjectsClient,
|
||||
getUpgradeableConfig,
|
||||
};
|
||||
}
|
||||
|
||||
describe('nothing is upgradeable', function () {
|
||||
it('should create config with current version and buildNum', async () => {
|
||||
const { run, savedObjectsClient } = setup();
|
||||
|
||||
await run();
|
||||
|
||||
sinon.assert.calledOnce(savedObjectsClient.create);
|
||||
sinon.assert.calledWithExactly(savedObjectsClient.create, 'config', {
|
||||
buildNum,
|
||||
}, {
|
||||
id: version
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('something is upgradeable', () => {
|
||||
it('should merge upgraded attributes with current build number in new config', async () => {
|
||||
const {
|
||||
run,
|
||||
getUpgradeableConfig,
|
||||
savedObjectsClient
|
||||
} = setup();
|
||||
|
||||
const savedAttributes = {
|
||||
buildNum: buildNum - 100,
|
||||
[chance.word()]: chance.sentence(),
|
||||
[chance.word()]: chance.sentence(),
|
||||
[chance.word()]: chance.sentence()
|
||||
};
|
||||
|
||||
getUpgradeableConfig
|
||||
.returns({ id: prevVersion, attributes: savedAttributes });
|
||||
|
||||
await run();
|
||||
|
||||
sinon.assert.calledOnce(getUpgradeableConfig);
|
||||
sinon.assert.calledOnce(savedObjectsClient.create);
|
||||
sinon.assert.calledWithExactly(savedObjectsClient.create,
|
||||
'config',
|
||||
{
|
||||
...savedAttributes,
|
||||
buildNum,
|
||||
},
|
||||
{
|
||||
id: version,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should log a message for upgrades', async () => {
|
||||
const { getUpgradeableConfig, log, run } = setup();
|
||||
|
||||
getUpgradeableConfig
|
||||
.returns({ id: prevVersion, attributes: { buildNum: buildNum - 100 } });
|
||||
|
||||
await run();
|
||||
sinon.assert.calledOnce(log);
|
||||
sinon.assert.calledWithExactly(log,
|
||||
['plugin', 'elasticsearch'],
|
||||
sinon.match({
|
||||
tmpl: sinon.match('Upgrade'),
|
||||
prevVersion,
|
||||
newVersion: version,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
import expect from 'expect.js';
|
||||
|
||||
import { isConfigVersionUpgradeable } from '../is_config_version_upgradeable';
|
||||
import { pkg } from '../../../../utils';
|
||||
|
||||
describe('savedObjects/health_check/isConfigVersionUpgradeable', function () {
|
||||
function isUpgradableTest(savedVersion, kibanaVersion, expected) {
|
||||
it(`should return ${expected} for config version ${savedVersion} and kibana version ${kibanaVersion}`, () => {
|
||||
expect(isConfigVersionUpgradeable(savedVersion, kibanaVersion)).to.be(expected);
|
||||
});
|
||||
}
|
||||
|
||||
isUpgradableTest('1.0.0-beta1', pkg.version, false);
|
||||
isUpgradableTest('1.0.0-beta256', pkg.version, false);
|
||||
isUpgradableTest('10.100.1000-beta256', '10.100.1000-beta257', false);
|
||||
isUpgradableTest(pkg.version, pkg.version, false);
|
||||
isUpgradableTest('4.0.0-RC1', '4.0.0-RC2', true);
|
||||
isUpgradableTest('10.100.1000-rc256', '10.100.1000-RC257', true);
|
||||
isUpgradableTest('4.0.0-rc2', '4.0.0-rc1', false);
|
||||
isUpgradableTest('4.0.0-rc2', '4.0.0', true);
|
||||
isUpgradableTest('4.0.0-rc2', '4.0.2', true);
|
||||
isUpgradableTest('4.0.1', '4.1.0-rc', true);
|
||||
isUpgradableTest('4.0.0-rc1', '4.0.0', true);
|
||||
isUpgradableTest('50.0.9-rc150', '50.0.9', true);
|
||||
isUpgradableTest('50.0.9', '50.0.9-rc150', false);
|
||||
isUpgradableTest('50.0.9', '50.0.10-rc150', true);
|
||||
isUpgradableTest('4.0.0-rc1-SNAPSHOT', '4.0.0', false);
|
||||
isUpgradableTest('4.1.0-rc1-SNAPSHOT', '4.1.0-rc1', false);
|
||||
isUpgradableTest('5.0.0-alpha11', '5.0.0', false);
|
||||
isUpgradableTest('50.0.10-rc150-SNAPSHOT', '50.0.9', false);
|
||||
isUpgradableTest(undefined, pkg.version, false);
|
||||
isUpgradableTest('@@version', pkg.version, false);
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
import { defaults } from 'lodash';
|
||||
|
||||
import { getUpgradeableConfig } from './get_upgradeable_config';
|
||||
|
||||
export async function createOrUpgradeSavedConfig(options) {
|
||||
const {
|
||||
savedObjectsClient,
|
||||
version,
|
||||
buildNum,
|
||||
log,
|
||||
} = options;
|
||||
|
||||
// try to find an older config we can upgrade
|
||||
const upgradeableConfig = await getUpgradeableConfig({
|
||||
savedObjectsClient,
|
||||
version
|
||||
});
|
||||
|
||||
if (upgradeableConfig) {
|
||||
log(['plugin', 'elasticsearch'], {
|
||||
tmpl: 'Upgrade config from <%= prevVersion %> to <%= newVersion %>',
|
||||
prevVersion: upgradeableConfig.id,
|
||||
newVersion: version
|
||||
});
|
||||
}
|
||||
|
||||
// default to the attributes of the upgradeableConfig if available
|
||||
const attributes = defaults(
|
||||
{ buildNum },
|
||||
upgradeableConfig ? upgradeableConfig.attributes : {}
|
||||
);
|
||||
|
||||
// create the new SavedConfig
|
||||
await savedObjectsClient.create(
|
||||
'config',
|
||||
attributes,
|
||||
{ id: version }
|
||||
);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { isConfigVersionUpgradeable } from './is_config_version_upgradeable';
|
||||
|
||||
/**
|
||||
* Find the most recent SavedConfig that is upgradeable to the specified version
|
||||
* @param {Object} options
|
||||
* @property {SavedObjectsClient} savedObjectsClient
|
||||
* @property {string} version
|
||||
* @return {Promise<SavedConfig|undefined>}
|
||||
*/
|
||||
export async function getUpgradeableConfig({ savedObjectsClient, version }) {
|
||||
// attempt to find a config we can upgrade
|
||||
const { saved_objects: savedConfigs } = await savedObjectsClient.find({
|
||||
type: 'config',
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
sortField: 'buildNum',
|
||||
sortOrder: 'desc'
|
||||
});
|
||||
|
||||
// try to find a config that we can upgrade
|
||||
return savedConfigs.find(savedConfig => (
|
||||
isConfigVersionUpgradeable(savedConfig.id, version)
|
||||
));
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
|
|
@ -0,0 +1,35 @@
|
|||
import semver from 'semver';
|
||||
const rcVersionRegex = /^(\d+\.\d+\.\d+)\-rc(\d+)$/i;
|
||||
|
||||
function extractRcNumber(version) {
|
||||
const match = version.match(rcVersionRegex);
|
||||
return match
|
||||
? [match[1], parseInt(match[2], 10)]
|
||||
: [version, Infinity];
|
||||
}
|
||||
|
||||
export function isConfigVersionUpgradeable(savedVersion, kibanaVersion) {
|
||||
if (
|
||||
typeof savedVersion !== 'string' ||
|
||||
typeof kibanaVersion !== 'string' ||
|
||||
savedVersion === kibanaVersion ||
|
||||
/alpha|beta|snapshot/i.test(savedVersion)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [savedReleaseVersion, savedRcNumber] = extractRcNumber(savedVersion);
|
||||
const [kibanaReleaseVersion, kibanaRcNumber] = extractRcNumber(kibanaVersion);
|
||||
|
||||
// ensure that both release versions are valid, if not then abort
|
||||
if (!semver.valid(savedReleaseVersion) || !semver.valid(kibanaReleaseVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ultimately if the saved config is from a previous kibana version
|
||||
// or from an earlier rc of the same version, then we can upgrade
|
||||
const savedIsLessThanKibana = semver.lt(savedReleaseVersion, kibanaReleaseVersion);
|
||||
const savedIsSameAsKibana = semver.eq(savedReleaseVersion, kibanaReleaseVersion);
|
||||
const savedRcIsLessThanKibana = savedRcNumber < kibanaRcNumber;
|
||||
return savedIsLessThanKibana || (savedIsSameAsKibana && savedRcIsLessThanKibana);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
export function mirrorStatus(status, esStatus) {
|
||||
if (!esStatus) {
|
||||
status.red('UI Settings requires the elasticsearch plugin');
|
||||
return;
|
||||
}
|
||||
|
||||
const copyEsStatus = () => {
|
||||
const { state } = esStatus;
|
||||
const statusMessage = state === 'green' ? 'Ready' : `Elasticsearch plugin is ${state}`;
|
||||
status[state](statusMessage);
|
||||
};
|
||||
|
||||
copyEsStatus();
|
||||
esStatus.on('change', copyEsStatus);
|
||||
}
|
|
@ -1,31 +1,32 @@
|
|||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import {
|
||||
getServices,
|
||||
chance,
|
||||
assertDocMissingResponse,
|
||||
assertSinonMatch,
|
||||
waitUntilNextHealthCheck,
|
||||
} from './lib';
|
||||
|
||||
export function docMissingSuite() {
|
||||
beforeEach(waitUntilNextHealthCheck);
|
||||
// health check doesn't create config doc so we
|
||||
// only have to wait once
|
||||
before(waitUntilNextHealthCheck);
|
||||
|
||||
async function setup() {
|
||||
const { kbnServer, savedObjectsClient } = getServices();
|
||||
|
||||
// delete all config docs
|
||||
const { saved_objects: objs } = await savedObjectsClient.find({ type: 'config' });
|
||||
|
||||
for (const obj of objs) {
|
||||
await savedObjectsClient.delete(obj.type, obj.id);
|
||||
}
|
||||
|
||||
return { kbnServer };
|
||||
}
|
||||
// ensure the kibana index has no documents
|
||||
beforeEach(async () => {
|
||||
const { kbnServer, callCluster } = getServices();
|
||||
await callCluster('deleteByQuery', {
|
||||
index: kbnServer.config.get('kibana.index'),
|
||||
body: {
|
||||
query: { match_all: {} }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('get route', () => {
|
||||
it('returns a 200 with empty values', async () => {
|
||||
const { kbnServer } = await setup();
|
||||
it('creates doc, returns a 200 with no settings', async () => {
|
||||
const { kbnServer } = getServices();
|
||||
|
||||
const { statusCode, result } = await kbnServer.inject({
|
||||
method: 'GET',
|
||||
|
@ -33,48 +34,81 @@ export function docMissingSuite() {
|
|||
});
|
||||
|
||||
expect(statusCode).to.be(200);
|
||||
expect(result).to.eql({ settings: {} });
|
||||
assertSinonMatch(result, {
|
||||
settings: {}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('set route', () => {
|
||||
it('returns a 404', async () => {
|
||||
const { kbnServer } = await setup();
|
||||
it('creates doc, returns a 200 with value set', async () => {
|
||||
const { kbnServer } = getServices();
|
||||
|
||||
assertDocMissingResponse(await kbnServer.inject({
|
||||
const defaultIndex = chance.word();
|
||||
const { statusCode, result } = await kbnServer.inject({
|
||||
method: 'POST',
|
||||
url: '/api/kibana/settings/defaultIndex',
|
||||
payload: {
|
||||
value: chance.word()
|
||||
payload: { value: defaultIndex }
|
||||
});
|
||||
|
||||
expect(statusCode).to.be(200);
|
||||
assertSinonMatch(result, {
|
||||
settings: {
|
||||
buildNum: {
|
||||
userValue: sinon.match.number
|
||||
},
|
||||
defaultIndex: {
|
||||
userValue: defaultIndex
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setMany route', () => {
|
||||
it('returns a 404', async () => {
|
||||
const { kbnServer } = await setup();
|
||||
it('creates doc, returns 200 with updated values', async () => {
|
||||
const { kbnServer } = getServices();
|
||||
|
||||
assertDocMissingResponse(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
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete route', () => {
|
||||
it('returns a 404', async () => {
|
||||
const { kbnServer } = await setup();
|
||||
it('creates doc, returns a 200 with just buildNum', async () => {
|
||||
const { kbnServer } = getServices();
|
||||
|
||||
assertDocMissingResponse(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
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import expect from 'expect.js';
|
|||
import {
|
||||
getServices,
|
||||
chance,
|
||||
assertGeneric404Response,
|
||||
assertServiceUnavailableResponse,
|
||||
waitUntilNextHealthCheck,
|
||||
} from './lib';
|
||||
|
||||
|
@ -58,10 +58,10 @@ export function indexMissingSuite() {
|
|||
});
|
||||
|
||||
describe('set route', () => {
|
||||
it('returns a generic 404 and does not create the kibana index', async () => {
|
||||
it('returns a 503 and does not create the kibana index', async () => {
|
||||
const { kbnServer, assertNoKibanaIndex } = await setup();
|
||||
|
||||
assertGeneric404Response(await kbnServer.inject({
|
||||
assertServiceUnavailableResponse(await kbnServer.inject({
|
||||
method: 'POST',
|
||||
url: '/api/kibana/settings/defaultIndex',
|
||||
payload: {
|
||||
|
@ -74,10 +74,10 @@ export function indexMissingSuite() {
|
|||
});
|
||||
|
||||
describe('setMany route', () => {
|
||||
it('returns a generic 404 and does not create the kibana index', async () => {
|
||||
it('returns a 503 and does not create the kibana index', async () => {
|
||||
const { kbnServer, assertNoKibanaIndex } = await setup();
|
||||
|
||||
assertGeneric404Response(await kbnServer.inject({
|
||||
assertServiceUnavailableResponse(await kbnServer.inject({
|
||||
method: 'POST',
|
||||
url: '/api/kibana/settings',
|
||||
payload: {
|
||||
|
@ -92,10 +92,10 @@ export function indexMissingSuite() {
|
|||
});
|
||||
|
||||
describe('delete route', () => {
|
||||
it('returns a generic 404 and does not create the kibana index', async () => {
|
||||
it('returns a 503 and does not create the kibana index', async () => {
|
||||
const { kbnServer, assertNoKibanaIndex } = await setup();
|
||||
|
||||
assertGeneric404Response(await kbnServer.inject({
|
||||
assertServiceUnavailableResponse(await kbnServer.inject({
|
||||
method: 'DELETE',
|
||||
url: '/api/kibana/settings/defaultIndex'
|
||||
}));
|
||||
|
|
|
@ -6,18 +6,10 @@ export function assertSinonMatch(value, match) {
|
|||
sinon.assert.calledWithExactly(stub, match);
|
||||
}
|
||||
|
||||
export function assertGeneric404Response({ result }) {
|
||||
export function assertServiceUnavailableResponse({ result }) {
|
||||
assertSinonMatch(result, {
|
||||
statusCode: 404,
|
||||
error: 'Not Found',
|
||||
message: sinon.match.same('Not Found')
|
||||
});
|
||||
}
|
||||
|
||||
export function assertDocMissingResponse({ result }) {
|
||||
assertSinonMatch(result, {
|
||||
statusCode: 404,
|
||||
error: 'Not Found',
|
||||
message: 'Not Found'
|
||||
statusCode: 503,
|
||||
error: 'Service Unavailable',
|
||||
message: 'Service Unavailable'
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,6 +11,5 @@ export {
|
|||
|
||||
export {
|
||||
assertSinonMatch,
|
||||
assertDocMissingResponse,
|
||||
assertGeneric404Response,
|
||||
assertServiceUnavailableResponse,
|
||||
} from './assert';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { uiSettingsServiceFactory } from './ui_settings_service_factory';
|
||||
import { getUiSettingsServiceForRequest } from './ui_settings_service_for_request';
|
||||
import { mirrorStatus } from './mirror_status';
|
||||
import { UiExportsConsumer } from './ui_exports_consumer';
|
||||
import {
|
||||
deleteRoute,
|
||||
|
@ -9,40 +8,15 @@ import {
|
|||
setRoute,
|
||||
} from './routes';
|
||||
|
||||
export function uiSettingsMixin(kbnServer, server, config) {
|
||||
const status = kbnServer.status.create('ui settings');
|
||||
|
||||
export function uiSettingsMixin(kbnServer, server) {
|
||||
// reads the "uiSettingDefaults" from uiExports
|
||||
const uiExportsConsumer = new UiExportsConsumer();
|
||||
kbnServer.uiExports.addConsumer(uiExportsConsumer);
|
||||
|
||||
if (!config.get('uiSettings.enabled')) {
|
||||
status.disabled('uiSettings.enabled config is set to `false`');
|
||||
return;
|
||||
}
|
||||
|
||||
// Passed to the UiSettingsService.
|
||||
// UiSettingsService calls the function before trying to read data from
|
||||
// elasticsearch, giving us a chance to prevent it from happening.
|
||||
//
|
||||
// If the ui settings status isn't green we shouldn't be attempting to get
|
||||
// user settings, since we can't be sure that all the necessary conditions
|
||||
// (e.g. elasticsearch being available) are met.
|
||||
const readInterceptor = () => {
|
||||
if (status.state !== 'green') {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const getDefaults = () => (
|
||||
uiExportsConsumer.getUiSettingDefaults()
|
||||
);
|
||||
|
||||
// don't return, just let it happen when the plugins are ready
|
||||
kbnServer.ready().then(() => {
|
||||
mirrorStatus(status, kbnServer.status.getForPluginId('elasticsearch'));
|
||||
});
|
||||
|
||||
server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => {
|
||||
return uiSettingsServiceFactory(server, {
|
||||
getDefaults,
|
||||
|
@ -53,7 +27,6 @@ export function uiSettingsMixin(kbnServer, server, config) {
|
|||
server.addMemoizedFactoryToRequest('getUiSettingsService', request => {
|
||||
return getUiSettingsServiceForRequest(server, request, {
|
||||
getDefaults,
|
||||
readInterceptor,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { defaultsDeep, noop } from 'lodash';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';
|
||||
|
||||
function hydrateUserSettings(userSettings) {
|
||||
return Object.keys(userSettings)
|
||||
|
@ -9,35 +10,38 @@ function hydrateUserSettings(userSettings) {
|
|||
|
||||
/**
|
||||
* Service that provides access to the UiSettings stored in elasticsearch.
|
||||
*
|
||||
* @class UiSettingsService
|
||||
* @param {Object} options
|
||||
* @property {string} options.index Elasticsearch index name where settings are stored
|
||||
* @property {string} options.type type of ui settings Elasticsearch doc
|
||||
* @property {string} options.id id of ui settings Elasticsearch doc
|
||||
* @property {AsyncFunction} options.callCluster function that accepts a method name and
|
||||
* param object which causes a request via some elasticsearch client
|
||||
* @property {AsyncFunction} [options.readInterceptor] async function that is called when the
|
||||
* UiSettingsService does a read() an has an oportunity to intercept the
|
||||
* request and return an alternate `_source` value to use.
|
||||
*/
|
||||
export class UiSettingsService {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object} options
|
||||
* @property {string} options.type type of SavedConfig object
|
||||
* @property {string} options.id id of SavedConfig object
|
||||
* @property {number} options.buildNum
|
||||
* @property {SavedObjectsClient} options.savedObjectsClient
|
||||
* @property {Function} [options.getDefaults]
|
||||
* @property {Function} [options.log]
|
||||
*/
|
||||
constructor(options) {
|
||||
const {
|
||||
type,
|
||||
id,
|
||||
buildNum,
|
||||
savedObjectsClient,
|
||||
readInterceptor = noop,
|
||||
// we use a function for getDefaults() so that defaults can be different in
|
||||
// different scenarios, and so they can change over time
|
||||
getDefaults = () => ({}),
|
||||
// function that accepts log messages in the same format as server.log
|
||||
log = () => {},
|
||||
} = options;
|
||||
|
||||
this._savedObjectsClient = savedObjectsClient;
|
||||
this._getDefaults = getDefaults;
|
||||
this._readInterceptor = readInterceptor;
|
||||
this._type = type;
|
||||
this._id = id;
|
||||
this._buildNum = buildNum;
|
||||
this._savedObjectsClient = savedObjectsClient;
|
||||
this._getDefaults = getDefaults;
|
||||
this._log = log;
|
||||
}
|
||||
|
||||
async getDefaults() {
|
||||
|
@ -72,7 +76,7 @@ export class UiSettingsService {
|
|||
}
|
||||
|
||||
async setMany(changes) {
|
||||
await this._write(changes);
|
||||
await this._write({ changes });
|
||||
}
|
||||
|
||||
async set(key, value) {
|
||||
|
@ -91,16 +95,30 @@ export class UiSettingsService {
|
|||
await this.setMany(changes);
|
||||
}
|
||||
|
||||
async _write(changes) {
|
||||
await this._savedObjectsClient.update(this._type, this._id, changes);
|
||||
async _write({ changes, autoCreateOrUpgradeIfMissing = true }) {
|
||||
try {
|
||||
await this._savedObjectsClient.update(this._type, this._id, changes);
|
||||
} catch (error) {
|
||||
const { isNotFoundError } = this._savedObjectsClient.errors;
|
||||
if (!isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
await createOrUpgradeSavedConfig({
|
||||
savedObjectsClient: this._savedObjectsClient,
|
||||
version: this._id,
|
||||
buildNum: this._buildNum,
|
||||
log: this._log,
|
||||
});
|
||||
|
||||
await this._write({
|
||||
changes,
|
||||
autoCreateOrUpgradeIfMissing: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async _read(options = {}) {
|
||||
const interceptValue = await this._readInterceptor(options);
|
||||
if (interceptValue != null) {
|
||||
return interceptValue;
|
||||
}
|
||||
|
||||
const {
|
||||
ignore401Errors = false
|
||||
} = options;
|
||||
|
|
|
@ -10,9 +10,6 @@ import { UiSettingsService } from './ui_settings_service';
|
|||
* param object which causes a request via some elasticsearch client
|
||||
* @property {AsyncFunction} [options.getDefaults] async function that returns defaults/details about
|
||||
* the uiSettings.
|
||||
* @property {AsyncFunction} [options.readInterceptor] async function that is called when the
|
||||
* UiSettingsService does a read() an has an oportunity to intercept the
|
||||
* request and return an alternate `_source` value to use.
|
||||
* @return {UiSettingsService}
|
||||
*/
|
||||
export function uiSettingsServiceFactory(server, options) {
|
||||
|
@ -20,15 +17,15 @@ export function uiSettingsServiceFactory(server, options) {
|
|||
|
||||
const {
|
||||
savedObjectsClient,
|
||||
readInterceptor,
|
||||
getDefaults,
|
||||
} = options;
|
||||
|
||||
return new UiSettingsService({
|
||||
type: 'config',
|
||||
id: config.get('pkg.version'),
|
||||
buildNum: config.get('pkg.buildNum'),
|
||||
savedObjectsClient,
|
||||
readInterceptor,
|
||||
getDefaults,
|
||||
log: (...args) => server.log(...args),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,19 +11,14 @@ import { uiSettingsServiceFactory } from './ui_settings_service_factory';
|
|||
* @param {Object} [options={}]
|
||||
* @property {AsyncFunction} [options.getDefaults] async function that returns defaults/details about
|
||||
* the uiSettings.
|
||||
* @property {AsyncFunction} [options.readInterceptor] async function that is called when the
|
||||
* UiSettingsService does a read() and has an oportunity to intercept the
|
||||
* request and return an alternate `_source` value to use.
|
||||
* @return {UiSettingsService}
|
||||
*/
|
||||
export function getUiSettingsServiceForRequest(server, request, options = {}) {
|
||||
const {
|
||||
readInterceptor,
|
||||
getDefaults
|
||||
} = options;
|
||||
|
||||
const uiSettingsService = uiSettingsServiceFactory(server, {
|
||||
readInterceptor,
|
||||
getDefaults,
|
||||
savedObjectsClient: request.getSavedObjectsClient()
|
||||
});
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { format as formatUrl } from 'url';
|
||||
|
||||
import { delay } from 'bluebird';
|
||||
|
||||
import { KibanaServerStatus } from './status';
|
||||
import { KibanaServerUiSettings } from './ui_settings';
|
||||
import { KibanaServerVersion } from './version';
|
||||
|
@ -9,7 +7,6 @@ import { KibanaServerVersion } from './version';
|
|||
export async function KibanaServerProvider({ getService }) {
|
||||
const log = getService('log');
|
||||
const config = getService('config');
|
||||
const lifecycle = getService('lifecycle');
|
||||
const es = getService('es');
|
||||
const kibanaIndex = await getService('kibanaIndex').init();
|
||||
|
||||
|
@ -18,51 +15,7 @@ export async function KibanaServerProvider({ getService }) {
|
|||
const url = formatUrl(config.get('servers.kibana'));
|
||||
this.status = new KibanaServerStatus(url);
|
||||
this.version = new KibanaServerVersion(this.status);
|
||||
this.uiSettings = new KibanaServerUiSettings(log, es, kibanaIndex, this.version);
|
||||
|
||||
lifecycle.on('beforeEachTest', async () => {
|
||||
await this.waitForStabilization();
|
||||
});
|
||||
}
|
||||
|
||||
async waitForStabilization() {
|
||||
const { status, uiSettings } = this;
|
||||
|
||||
let firstCheck = true;
|
||||
const pingInterval = 500; // ping every 500 ms for an update
|
||||
const startMs = Date.now();
|
||||
const timeout = config.get('timeouts.kibanaStabilize');
|
||||
|
||||
let exists;
|
||||
let state;
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
exists = await uiSettings.existInEs();
|
||||
state = await status.getOverallState();
|
||||
|
||||
if (exists && state === 'green') {
|
||||
log.debug(`Kibana uiSettings are in elasticsearch and the server is reporting a green status`);
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
log.warning(`Failed to check for kibana stabilization: ${err.stack}`);
|
||||
}
|
||||
|
||||
if (Date.now() - startMs >= timeout) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (firstCheck) {
|
||||
// we only log once, and only if we failed the first check
|
||||
firstCheck = false;
|
||||
log.debug(`waiting up to ${timeout}ms for kibana to stabilize...`);
|
||||
}
|
||||
await delay(pingInterval);
|
||||
}
|
||||
|
||||
const docState = exists ? 'exists' : `doesn't exist`;
|
||||
throw new Error(`Kibana never stabilized: config doc ${docState} and status is ${state}`);
|
||||
this.uiSettings = new KibanaServerUiSettings(url, log, es, kibanaIndex, this.version);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,42 +1,27 @@
|
|||
import { createCallCluster } from '../../../../src/test_utils/es';
|
||||
import { SavedObjectsClient } from '../../../../src/server/saved_objects';
|
||||
import Wreck from 'wreck';
|
||||
import { get } from 'lodash';
|
||||
|
||||
const MINUTE = 60 * 1000;
|
||||
const HOUR = 60 * MINUTE;
|
||||
|
||||
export class KibanaServerUiSettings {
|
||||
constructor(log, es, kibanaIndex, kibanaVersion) {
|
||||
constructor(url, log, es, kibanaIndex, kibanaVersion) {
|
||||
this._log = log;
|
||||
this._kibanaVersion = kibanaVersion;
|
||||
|
||||
this._savedObjectsClient = new SavedObjectsClient({
|
||||
index: kibanaIndex.getName(),
|
||||
mappings: kibanaIndex.getMappingsDsl(),
|
||||
callCluster: createCallCluster(es),
|
||||
async onBeforeWrite() {
|
||||
await es.cluster.health({
|
||||
timeout: '5s',
|
||||
index: kibanaIndex.getName(),
|
||||
waitForStatus: 'yellow',
|
||||
});
|
||||
}
|
||||
this._wreck = Wreck.defaults({
|
||||
headers: { 'kbn-xsrf': 'ftr/services/uiSettings' },
|
||||
baseUrl: url,
|
||||
json: true,
|
||||
redirects: 3,
|
||||
});
|
||||
}
|
||||
|
||||
async _id() {
|
||||
return await this._kibanaVersion.get();
|
||||
}
|
||||
|
||||
async existInEs() {
|
||||
return !!(await this._read());
|
||||
}
|
||||
|
||||
/*
|
||||
** Gets defaultIndex from the config doc.
|
||||
*/
|
||||
async getDefaultIndex() {
|
||||
const doc = await this._read();
|
||||
if (!doc) {
|
||||
throw new TypeError('Failed to fetch kibana config doc');
|
||||
}
|
||||
const defaultIndex = doc.attributes.defaultIndex;
|
||||
const { payload } = await this._wreck.get('/api/kibana/settings');
|
||||
const defaultIndex = get(payload, 'settings.defaultIndex.userValue');
|
||||
this._log.verbose('uiSettings.defaultIndex: %j', defaultIndex);
|
||||
return defaultIndex;
|
||||
}
|
||||
|
@ -51,18 +36,26 @@ export class KibanaServerUiSettings {
|
|||
*/
|
||||
async disableToastAutohide() {
|
||||
await this.update({
|
||||
'notifications:lifetime:banner': 360000,
|
||||
'notifications:lifetime:error': 360000,
|
||||
'notifications:lifetime:warning': 360000,
|
||||
'notifications:lifetime:info': 360000,
|
||||
'notifications:lifetime:banner': HOUR,
|
||||
'notifications:lifetime:error': HOUR,
|
||||
'notifications:lifetime:warning': HOUR,
|
||||
'notifications:lifetime:info': HOUR,
|
||||
});
|
||||
}
|
||||
|
||||
async replace(doc) {
|
||||
const { payload } = await this._wreck.get('/api/kibana/settings');
|
||||
|
||||
for (const key of Object.keys(payload.settings)) {
|
||||
await this._wreck.delete(`/api/kibana/settings/${key}`);
|
||||
}
|
||||
|
||||
this._log.debug('replacing kibana config doc: %j', doc);
|
||||
await this._savedObjectsClient.create('config', doc, {
|
||||
id: await this._id(),
|
||||
overwrite: true,
|
||||
|
||||
await this._wreck.post('/api/kibana/settings', {
|
||||
payload: {
|
||||
changes: doc
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -72,17 +65,10 @@ export class KibanaServerUiSettings {
|
|||
*/
|
||||
async update(updates) {
|
||||
this._log.debug('applying update to kibana config: %j', updates);
|
||||
await this._savedObjectsClient.update('config', await this._id(), updates);
|
||||
}
|
||||
|
||||
async _read() {
|
||||
try {
|
||||
const doc = await this._savedObjectsClient.get('config', await this._id());
|
||||
this._log.verbose('Fetched kibana config doc', doc);
|
||||
return doc;
|
||||
} catch (err) {
|
||||
this._log.debug('Failed to fetch kibana config doc', err.message);
|
||||
return;
|
||||
}
|
||||
await this._wreck.post('/api/kibana/settings', {
|
||||
payload: {
|
||||
changes: updates
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,8 +35,8 @@ export default function ({ getService, getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
it('default request response should contain .kibana' , function () {
|
||||
const expectedResponseContains = '"_index": ".kibana",';
|
||||
it('default request response should include `"timed_out": false`' , function () {
|
||||
const expectedResponseContains = '"timed_out": false,';
|
||||
|
||||
return PageObjects.console.clickPlay()
|
||||
.then(function () {
|
||||
|
|
|
@ -13,7 +13,6 @@ export default function ({ getService, getPageObjects }) {
|
|||
|
||||
await esArchiver.loadIfNeeded('logstash_functional');
|
||||
await esArchiver.load('discover');
|
||||
await kibanaServer.waitForStabilization();
|
||||
// delete .kibana index and update configDoc
|
||||
await kibanaServer.uiSettings.replace({
|
||||
'dateFormat:tz': 'UTC',
|
||||
|
|
|
@ -10,7 +10,6 @@ export default function ({ getService, loadTestFile }) {
|
|||
remote.setWindowSize(1280, 800);
|
||||
await esArchiver.loadIfNeeded('logstash_functional');
|
||||
await esArchiver.load('visualize');
|
||||
await kibanaServer.waitForStabilization();
|
||||
await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'UTC', 'defaultIndex': 'logstash-*' });
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue