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:
parent
fad1b4e3e0
commit
8fb4532468
@ -29,59 +29,78 @@ function getDifference(a, b) {
|
||||
return _.differenceWith(a, b, sameResource)
|
||||
}
|
||||
|
||||
// Return a list of objects that differ from each other
|
||||
function getChangedObjects(a, b) {
|
||||
var ours = _.intersectionWith(a, b, sameResource)
|
||||
var theirs = _.intersectionWith(b, a, sameResource)
|
||||
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.desc = 'Cluster objects'
|
||||
exports.handler = function() {}
|
||||
exports.builder = function(yargs) {
|
||||
yargs
|
||||
.command('diff <a> <b>', 'Show differences of <a> when compared with <b>. ' +
|
||||
'Values 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]
|
||||
.command('diff <target>', 'Show difference between host servers and <target>. ' +
|
||||
'Value must be in HOST:PORT format', {}, function(argv) {
|
||||
|
||||
maxctrl(argv, function(host) {
|
||||
const collections = [
|
||||
'servers',
|
||||
'monitors',
|
||||
'services',
|
||||
'users'
|
||||
]
|
||||
const endpoints = [
|
||||
'maxscale',
|
||||
'maxscale/logs'
|
||||
]
|
||||
return getDiffs(host, argv.target)
|
||||
.then(function(diffs) {
|
||||
var src = diffs[0]
|
||||
var dest = diffs[1]
|
||||
|
||||
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) {
|
||||
var newObj = getDifference(src[i].data, dest[i].data)
|
||||
var oldObj = getDifference(dest[i].data, src[i].data)
|
||||
@ -101,9 +120,8 @@ exports.builder = function(yargs) {
|
||||
}
|
||||
})
|
||||
endpoints.forEach(function(i) {
|
||||
// Treating the resource endpoints as arrays allows
|
||||
// the same functions to be used to compare
|
||||
// individual resources and resource collections
|
||||
// Treating the resource endpoints as arrays allows the same functions to be used
|
||||
// to compare individual resources and resource collections
|
||||
var changed = getChangedObjects([src[i].data], [dest[i].data])
|
||||
if (changed.length) {
|
||||
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>')
|
||||
.help()
|
||||
.command('*', 'the default command', {}, function(argv) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user