From 2670ca36a01f994438f322111dbc5e11ec1ed08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Mon, 10 Jul 2017 23:14:36 +0300 Subject: [PATCH] MXS-1300: Send requests to multiple hosts The host and port options were replace with one hosts option which combines the two. This allows a single command to be executed on multiple maxscale instances. Added accompanying code for checking responsiveness of all maxscale instances and handling synchronization of the check results. The code uses ECMAScript 2016 native promises to do this. Added a configurable timeout to all requests. This should allow users to interact with MaxScale over slow and laggy networks. Before each executed command, all hosts given as parameters are pinged to make sure they are alive. This should reduce the possibility of partial execution of commands due to failed MaxScales. The results of requests are grouped by the values of the hosts option. This allows users to compare results of multiple servers with relative ease. Fixed reversion of the default credentials from mariadb:admin to admin:mariadb. --- client/maxctrl/common.js | 88 ++++++++++++++++++++++++++------------- client/maxctrl/maxctrl.js | 28 +++++++------ 2 files changed, 75 insertions(+), 41 deletions(-) diff --git a/client/maxctrl/common.js b/client/maxctrl/common.js index ad09e67ef..96068a42c 100644 --- a/client/maxctrl/common.js +++ b/client/maxctrl/common.js @@ -114,44 +114,52 @@ module.exports = function() { } // Helper for converting endpoints to acutal URLs - this.getUri = function(endpoint, options) { - var base = 'http://'; + this.getUri = function(host, endpoint) { + var base = 'http://' var argv = this.program.argv if (argv.secure) { - base = 'https://'; + base = 'https://' } - return base + argv.user + ':' + argv.password + '@' + - argv.host + ':' + argv.port + '/v1/' + endpoint; + return base + argv.user + ':' + argv.password + '@' + host + '/v1/' + endpoint } // Helper for executing requests and handling their responses this.doRequest = function(resource, cb, obj) { + pingCluster() + .then(function() { + var argv = this.program.argv + argv.hosts.forEach(function(host) { + args = obj || {} + args.uri = getUri(host, resource) + args.json = true + args.timeout = argv.timeout - args = obj || {} - args.uri = getUri(resource), - args.json = true - - request(args, function(err, resp, res) { - if (err) { - // Failed to request - logError(JSON.stringify(err, null, 4)) - } else if (resp.statusCode == 200 && cb) { - // Request OK, returns data - cb(res) - } else if (resp.statusCode == 204) { - // Request OK, no data - console.log(colors.green('OK')) - } else { - // Unexpected return code, probably an error - var errstr = resp.statusCode + ' ' + resp.statusMessage - if (res) { - errstr += ' ' + JSON.stringify(res, null, 4) - } - logError(errstr) - } - }) + request(args, function(err, resp, res) { + if (err) { + // Failed to request + console.log(colors.yellow(host) + ':') + logError(JSON.stringify(err, null, 4)) + } else if (resp.statusCode == 200 && cb) { + // Request OK, returns data + console.log(colors.yellow(host) + ':') + cb(res) + } else if (resp.statusCode == 204) { + // Request OK, no data + console.log(colors.yellow(host) + ': ' + colors.green('OK')) + } else { + // Unexpected return code, probably an error + var errstr = resp.statusCode + ' ' + resp.statusMessage + if (res) { + errstr += ' ' + JSON.stringify(res, null, 4) + } + console.log(colors.yellow(host) + ':') + logError(errstr) + } + }) + }) + }) } this.updateValue = function(resource, key, value) { @@ -180,3 +188,27 @@ function getTable(headobj) { head: headobj }) } + +function pingMaxScale(host) { + return new Promise(function(resolve, reject) { + request('http://' + host + '/v1', function(err, resp, res) { + if (err) { + reject(err) + } else if (resp.statusCode != 200) { + reject(resp.statusCode + ' ' + resp.statusMessage) + } else { + resolve() + } + }) + }) +} + +function pingCluster() { + var promises = [] + + this.program.argv.hosts.forEach(function(i) { + promises.push(pingMaxScale(i)) + }) + + return Promise.all(promises) +} diff --git a/client/maxctrl/maxctrl.js b/client/maxctrl/maxctrl.js index 483a4b309..7c8751927 100644 --- a/client/maxctrl/maxctrl.js +++ b/client/maxctrl/maxctrl.js @@ -18,31 +18,26 @@ const maxctrl_version = '1.0.0'; program .version(maxctrl_version) - .group(['u', 'p', 'h', 'p', 'P', 's'], 'Global Options:') + .group(['u', 'p', 'h', 's', 't'], 'Global Options:') .option('u', { alias:'user', global: true, - default: 'mariadb', + default: 'admin', describe: 'Username to use', type: 'string' }) .option('p', { alias: 'password', describe: 'Password for the user', - default: 'admin', + default: 'mariadb', type: 'string' }) .option('h', { - alias: 'host', - describe: 'MaxScale hostname', - default: 'localhost', - type: 'string' - }) - .option('P', { - alias: 'port', - describe: 'MaxScale REST API port', - default: 8989, - type: 'number' + alias: 'hosts', + describe: 'List of MaxScale hosts. The hosts must be in ' + + ': format and each host must be separated by spaces.', + default: 'localhost:8989', + type: 'array' }) .option('s', { alias: 'secure', @@ -50,6 +45,13 @@ program default: 'false', type: 'boolean' }) + .option('t', { + alias: 'timeout', + describe: 'Request timeout in milliseconds', + default: '10000', + type: 'number' + }) + .command(require('./lib/list.js')) .command(require('./lib/show.js')) .command(require('./lib/set.js'))