Skip to content

Commit

Permalink
Support changes that require reindex too
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisronline committed Nov 30, 2017
1 parent 577a67b commit 583d4d5
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 110 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { getRootProperties } from '../../../../server/mappings';
import { KibanaIndexChange } from './kibana_index_change';

export class ChangeMistypedTypeField extends KibanaIndexChange {
getNewMappings() {
const properties = getRootProperties(this.currentMappingsDsl);
const typeProperty = properties.type;
if (typeProperty.type !== 'keyword') {
properties.type.type = 'keyword';
return properties;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
export class KibanaIndexChange {
constructor(options) {
this.log = options.log;
this.indexName = options.indexName;
this.kibanaIndexMappingsDsl = options.kibanaIndexMappingsDsl;
this.currentMappingsDsl = options.currentMappingsDsl;
this.rootEsType = options.rootEsType;
this.callCluster = options.callCluster;
}

async getNewMappings() { }

getNewIndex() {
return `${this.indexName}.1`;
}

getBackupIndex() {
return `${this.indexName}.backup`;
}

async rollback() {
// Delete index
const indexName = this.getNewIndex();

await this.deleteIndex(indexName);
}

async applyChange() {
const newIndexName = this.getNewIndex();
const backupIndexName = this.getBackupIndex();
const newMappings = await this.getNewMappings();

// If there are no mapping changes, bail
if (!newMappings) {
return;
}

// Create the new index
try {
await this.createNewIndex(newIndexName, newMappings);
}
catch (e) {
this.log(['error', 'elasticsearch'], {
tmpl: `Unable to create new index ${newIndexName}`,
});
return;
}

// Perform a backup for safety!
try {
await this.reindex(this.indexName, backupIndexName);
}
catch (e) {
this.log(['error', 'elasticsearch'], {
tmpl: `Unable to reindex to ${backupIndexName}`,
});
await this.rollback();
return;
}

// Reindex into the new index
try {
await this.reindex(this.indexName, newIndexName);
}
catch (e) {
this.log(['error', 'elasticsearch'], {
tmpl: `Unable to reindex to ${newIndexName}`,
});
await this.rollback();
return;
}

// Delete existing .kibana index so we can alias into it
try {
await this.deleteIndex(this.indexName);
}
catch (e) {
this.log(['error', 'elasticsearch'], {
tmpl: `Unable to delete ${this.indexName}`,
});
await this.rollback();
return;
}

try {
await this.createAlias(newIndexName);
}
catch (e) {
this.log(['error', 'elasticsearch'], {
tmpl: `Unable to alias to ${newIndexName}`,
});
await this.rollback();
return;
}
}

async createAlias(newIndexName) {
// log about new properties
this.log(['info', 'elasticsearch'], {
tmpl: `Aliasing ${newIndexName} to ${this.indexName}`,
});

// add the new properties to the index mapping
await this.callCluster('indices.putAlias', {
index: newIndexName,
name: this.indexName,
});
}

async reindex(source, dest) {
// log about new properties
this.log(['info', 'elasticsearch'], {
tmpl: `Reindexing from ${source} to ${dest}`,
});

// add the new properties to the index mapping
await this.callCluster('reindex', {
body: {
source: {
index: source,
},
dest: {
index: dest,
}
},
});
}

async createNewIndex(indexName, newMappings) {
// log about new properties
this.log(['info', 'elasticsearch'], {
tmpl: `Creating new .kibana index, named ${indexName}`,
});

// add the new properties to the index mapping
await this.callCluster('indices.create', {
index: indexName,
body: {
mappings: {
doc: {
properties: newMappings,
}
},
},
});
}

async deleteIndex(indexName) {
// log about new properties
this.log(['info', 'elasticsearch'], {
tmpl: `Deleting index ${indexName}`,
});

// add the new properties to the index mapping
await this.callCluster('indices.delete', {
index: indexName
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export class KibanaIndexPatch {
constructor(options) {
this.log = options.log;
this.indexName = options.indexName;
this.kibanaIndexMappingsDsl = options.kibanaIndexMappingsDsl;
this.currentMappingsDsl = options.currentMappingsDsl;
this.rootEsType = options.rootEsType;
this.callCluster = options.callCluster;
}

async applyPatch() {
const patchMappings = await this.getUpdatedPatchMappings();
if (!patchMappings) {
return;
}

try {
await this.putMappings(patchMappings);
}
catch (e) {
this.log(['error', 'elasticsearch'], {
tmpl: `Unable to patch mappings for "<%= cls %>"`,
cls: this.constructor.name,
});
return;
}

try {
await this.applyChanges(patchMappings);
}
catch (e) {
this.log(['error', 'elasticsearch'], {
tmpl: `Unable to apply patch changes for "<%= cls %>"`,
cls: this.constructor.name,
});
}
}

getUpdatedPatchMappings() {}
applyChanges() {}

async putMappings(patchMappings) {
// log about new properties
this.log(['info', 'elasticsearch'], {
tmpl: `Adding mappings to kibana index for SavedObject types "<%= names.join('", "') %>"`,
names: Object.keys(patchMappings),
});

// add the new properties to the index mapping
await this.callCluster('indices.putMapping', {
index: this.indexName,
type: this.rootEsType,
body: {
properties: patchMappings,
},
update_all_types: true
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getRootProperties } from '../../../../server/mappings';
import { KibanaIndexPatch } from './kibana_index_patch';

export class PatchMissingProperties extends KibanaIndexPatch {
getUpdatedPatchMappings() {
const expectedProps = getRootProperties(this.kibanaIndexMappingsDsl);
const existingProps = getRootProperties(this.currentMappingsDsl);

return Object.keys(expectedProps)
.reduce((acc, prop) => {
if (existingProps[prop]) {
return acc;
} else {
return { ...acc || {}, [prop]: expectedProps[prop] };
}
}, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { getRootProperties } from '../../../../server/mappings';
import { get } from 'lodash';
import { KibanaIndexPatch } from './kibana_index_patch';

const propertiesWithTitles = [
'index-pattern',
'dashboard',
'visualization',
'search',
];

export class PatchMissingTitleKeywordFields extends KibanaIndexPatch {
getUpdatedPatchMappings() {
const properties = getRootProperties(this.currentMappingsDsl);
const mappings = {};

for (const property of propertiesWithTitles) {
const hasKeyword = !!get(properties, `${property}.properties.title.fields.keyword`);
if (hasKeyword) {
continue;
}

const titleMapping = get(properties, `${property}.properties.title`);
mappings[property] = {
properties: {
title: {
...titleMapping,
fields: {
keyword: {
type: 'keyword',
}
}
}
}
};
}

// Make sure we return a falsy value
if (!Object.keys(mappings).length) {
return;
}

return mappings;
}

async applyChanges(patchMappings) {
const properties = Object.keys(patchMappings);
const types = properties.map(type => ({ match: { type } }));

this.log(['info', 'elasticsearch'], {
tmpl: `Updating by query for Saved Object types "<%= names.join('", "') %>"`,
names: properties,
});

await this.callCluster('updateByQuery', {
conflicts: 'proceed',
index: this.indexName,
type: this.rootEsType,
body: {
query: {
bool: {
should: types,
},
},
},
});
}
}
Loading

0 comments on commit 583d4d5

Please sign in to comment.