MXS-1300: Add cluster sync
command
The `cluster sync` command synchronizes a MaxScale cluster with one server. This allows new MaxScale instances to be synchronized with an old instance by using the same configuration file and performing a cluster sync.
This commit is contained in:
@ -29,59 +29,78 @@ function getDifference(a, b) {
|
|||||||
return _.differenceWith(a, b, sameResource)
|
return _.differenceWith(a, b, sameResource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return a list of objects that differ from each other
|
||||||
function getChangedObjects(a, b) {
|
function getChangedObjects(a, b) {
|
||||||
var ours = _.intersectionWith(a, b, sameResource)
|
var ours = _.intersectionWith(a, b, sameResource)
|
||||||
var theirs = _.intersectionWith(b, a, sameResource)
|
var theirs = _.intersectionWith(b, a, sameResource)
|
||||||
return _.differenceWith(ours, theirs, equalResources)
|
return _.differenceWith(ours, theirs, equalResources)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resource collections
|
||||||
|
const collections = [
|
||||||
|
'servers',
|
||||||
|
'monitors',
|
||||||
|
'services',
|
||||||
|
'users'
|
||||||
|
]
|
||||||
|
|
||||||
|
// Individual resources
|
||||||
|
const endpoints = [
|
||||||
|
'maxscale',
|
||||||
|
'maxscale/logs'
|
||||||
|
]
|
||||||
|
|
||||||
|
// Calculate a diff between two MaxScale servers
|
||||||
|
function getDiffs(a, b) {
|
||||||
|
var src = {}
|
||||||
|
var dest = {}
|
||||||
|
var promises = []
|
||||||
|
|
||||||
|
collections.forEach(function(i) {
|
||||||
|
promises.push(doAsyncRequest(b, i, function(res) {
|
||||||
|
dest[i] = res
|
||||||
|
}))
|
||||||
|
promises.push(doAsyncRequest(a, i, function(res) {
|
||||||
|
src[i] = res
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
endpoints.forEach(function(i) {
|
||||||
|
promises.push(doAsyncRequest(b, i, function(res) {
|
||||||
|
dest[i] = res
|
||||||
|
}))
|
||||||
|
promises.push(doAsyncRequest(a, i, function(res) {
|
||||||
|
src[i] = res
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(function() {
|
||||||
|
return Promise.resolve([src, dest])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the diffs add or delete services
|
||||||
|
function haveExtraServices(src, dest) {
|
||||||
|
var newObj = getDifference(src.services.data, dest.services.data)
|
||||||
|
var oldObj = getDifference(dest.services.data, src.services.data)
|
||||||
|
return newObj.length > 0 || oldObj.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
exports.command = 'cluster <command>'
|
exports.command = 'cluster <command>'
|
||||||
exports.desc = 'Cluster objects'
|
exports.desc = 'Cluster objects'
|
||||||
exports.handler = function() {}
|
exports.handler = function() {}
|
||||||
exports.builder = function(yargs) {
|
exports.builder = function(yargs) {
|
||||||
yargs
|
yargs
|
||||||
.command('diff <a> <b>', 'Show differences of <a> when compared with <b>. ' +
|
.command('diff <target>', 'Show difference between host servers and <target>. ' +
|
||||||
'Values must be in HOST:PORT format', {}, function(argv) {
|
'Value must be in HOST:PORT format', {}, function(argv) {
|
||||||
|
|
||||||
// Sort of a hack-ish way to force only one iteration of this command
|
|
||||||
argv.hosts = [argv.a]
|
|
||||||
|
|
||||||
maxctrl(argv, function(host) {
|
maxctrl(argv, function(host) {
|
||||||
const collections = [
|
return getDiffs(host, argv.target)
|
||||||
'servers',
|
.then(function(diffs) {
|
||||||
'monitors',
|
var src = diffs[0]
|
||||||
'services',
|
var dest = diffs[1]
|
||||||
'users'
|
|
||||||
]
|
|
||||||
const endpoints = [
|
|
||||||
'maxscale',
|
|
||||||
'maxscale/logs'
|
|
||||||
]
|
|
||||||
|
|
||||||
var src = {}
|
|
||||||
var dest = {}
|
|
||||||
var promises = []
|
|
||||||
|
|
||||||
collections.forEach(function(i) {
|
|
||||||
promises.push(doAsyncRequest(argv.b, i, function(res) {
|
|
||||||
dest[i] = res
|
|
||||||
}))
|
|
||||||
promises.push(doAsyncRequest(argv.a, i, function(res) {
|
|
||||||
src[i] = res
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
endpoints.forEach(function(i) {
|
|
||||||
promises.push(doAsyncRequest(argv.b, i, function(res) {
|
|
||||||
dest[i] = res
|
|
||||||
}))
|
|
||||||
promises.push(doAsyncRequest(argv.a, i, function(res) {
|
|
||||||
src[i] = res
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
return Promise.all(promises)
|
|
||||||
.then(function() {
|
|
||||||
collections.forEach(function(i) {
|
collections.forEach(function(i) {
|
||||||
var newObj = getDifference(src[i].data, dest[i].data)
|
var newObj = getDifference(src[i].data, dest[i].data)
|
||||||
var oldObj = getDifference(dest[i].data, src[i].data)
|
var oldObj = getDifference(dest[i].data, src[i].data)
|
||||||
@ -101,9 +120,8 @@ exports.builder = function(yargs) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
endpoints.forEach(function(i) {
|
endpoints.forEach(function(i) {
|
||||||
// Treating the resource endpoints as arrays allows
|
// Treating the resource endpoints as arrays allows the same functions to be used
|
||||||
// the same functions to be used to compare
|
// to compare individual resources and resource collections
|
||||||
// individual resources and resource collections
|
|
||||||
var changed = getChangedObjects([src[i].data], [dest[i].data])
|
var changed = getChangedObjects([src[i].data], [dest[i].data])
|
||||||
if (changed.length) {
|
if (changed.length) {
|
||||||
logger.log("Changed:", i)
|
logger.log("Changed:", i)
|
||||||
@ -113,6 +131,79 @@ exports.builder = function(yargs) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.command('sync <target>', 'Synchronize the cluster with target MaxScale server.', {}, function(argv) {
|
||||||
|
maxctrl(argv, function(host) {
|
||||||
|
// TODO: Create new listeners
|
||||||
|
return getDiffs(argv.target, host)
|
||||||
|
.then(function(diffs) {
|
||||||
|
var promises = []
|
||||||
|
var src = diffs[0]
|
||||||
|
var dest = diffs[1]
|
||||||
|
|
||||||
|
if (haveExtraServices(src, dest)) {
|
||||||
|
return error('Cannot synchronize host `' + host + '` with target `' +
|
||||||
|
argv.target + '`: New or deleted services on target host, ' +
|
||||||
|
'both hosts must have the same services.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old servers
|
||||||
|
getDifference(dest.servers.data, src.servers.data).forEach(function(i) {
|
||||||
|
// First unlink the servers from all services and monitors
|
||||||
|
promises.push(
|
||||||
|
doAsyncRequest(host, 'servers/' + i.id, null, {method: 'PATCH', body: _.set({}, 'data.relationships', {})})
|
||||||
|
.then(function() {
|
||||||
|
return doAsyncRequest(host, 'servers/' + i.id, null, {method: 'DELETE'})
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add new servers
|
||||||
|
getDifference(src.servers.data, dest.servers.data).forEach(function(i) {
|
||||||
|
// Create the servers without relationships, those are generated when services and
|
||||||
|
// monitors are updated
|
||||||
|
delete i.relationships
|
||||||
|
promises.push(doAsyncRequest(host, 'servers', null, {method: 'POST', body: {data: i}}))
|
||||||
|
})
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(function() {
|
||||||
|
var promises = []
|
||||||
|
// Delete old monitors
|
||||||
|
getDifference(dest.monitors.data, src.monitors.data).forEach(function(i) {
|
||||||
|
promises.push(
|
||||||
|
doAsyncRequest(host, 'monitors/' + i.id, null, {
|
||||||
|
method: 'PATCH', body: _.set({}, 'data.relationships', {})
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
return doAsyncRequest(host, 'monitors/' + i.id, null, {method: 'DELETE'})
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
return Promise.all(promises)
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
var promises = []
|
||||||
|
// Add new monitors
|
||||||
|
getDifference(src.monitors.data, dest.monitors.data).forEach(function(i) {
|
||||||
|
promises.push(doAsyncRequest(host, 'servers', null, {method: 'POST', body: {data: i}}))
|
||||||
|
})
|
||||||
|
return Promise.all(promises)
|
||||||
|
})
|
||||||
|
.then(function() {
|
||||||
|
var promises = []
|
||||||
|
// PATCH all remaining resource collections in src from dest apart from the
|
||||||
|
// user resource as it requires passwords to be entered
|
||||||
|
_.difference(collections, ['users']).forEach(function(i) {
|
||||||
|
src[i].data.forEach(function(j) {
|
||||||
|
promises.push(doAsyncRequest(host, i + '/' + j.id, null, {method: 'PATCH', body: {data: j}}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Do the same for individual resources
|
||||||
|
endpoints.forEach(function(i) {
|
||||||
|
promises.push(doAsyncRequest(host, i, null, {method: 'PATCH', body: dest[i]}))
|
||||||
|
})
|
||||||
|
return Promise.all(promises)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
.usage('Usage: cluster <command>')
|
.usage('Usage: cluster <command>')
|
||||||
.help()
|
.help()
|
||||||
.command('*', 'the default command', {}, function(argv) {
|
.command('*', 'the default command', {}, function(argv) {
|
||||||
|
Reference in New Issue
Block a user