Files
MaxScale/client/maxctrl/common.js
Markus Mäkelä 2670ca36a0 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.
2017-07-18 11:37:18 +03:00

215 lines
6.2 KiB
JavaScript

/*
* Copyright (c) 2016 MariaDB Corporation Ab
*
* Use of this software is governed by the Business Source License included
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
*
* Change Date: 2020-01-01
*
* On the date above, in accordance with the Business Source License, use
* of this software will be governed by version 2 or later of the General
* Public License.
*/
var request = require('request');
var colors = require('colors/safe');
var Table = require('cli-table');
module.exports = function() {
this._ = require('lodash-getpath')
// Common options for all commands
this.program = require('yargs');
// Request a resource collection and format it as a table
this.getCollection = function (resource, fields) {
doRequest(resource, function(res) {
var header = []
fields.forEach(function(i) {
header.push(Object.keys(i))
})
var table = getTable(header)
res.data.forEach(function(i) {
row = []
fields.forEach(function(p) {
var v = _.getPath(i, p[Object.keys(p)[0]], '')
if (Array.isArray(v)) {
v = v.join(', ')
}
row.push(v)
})
table.push(row)
})
console.log(table.toString())
})
}
// Request a part of a resource as a collection
this.getSubCollection = function (resource, subres, fields) {
doRequest(resource, function(res) {
var header = []
fields.forEach(function(i) {
header.push(Object.keys(i))
})
var table = getTable(header)
_.getPath(res.data, subres, []).forEach(function(i) {
row = []
fields.forEach(function(p) {
var v = _.getPath(i, p[Object.keys(p)[0]], '')
if (Array.isArray(v) && typeof(v[0]) != 'object') {
v = v.join(', ')
} else if (typeof(v) == 'object') {
v = JSON.stringify(v, null, 4)
}
row.push(v)
})
table.push(row)
})
console.log(table.toString())
})
}
// Request a single resource and format it as a key-value list
this.getResource = function (resource, fields) {
doRequest(resource, function(res) {
var table = getList()
fields.forEach(function(i) {
var k = Object.keys(i)[0]
var path = i[k]
var v = _.getPath(res.data, path, '')
if (Array.isArray(v) && typeof(v[0]) != 'object') {
v = v.join(', ')
} else if (typeof(v) == 'object') {
v = JSON.stringify(v, null, 4)
}
var o = {}
o[k] = v
table.push(o)
})
console.log(table.toString())
})
}
// Helper for converting endpoints to acutal URLs
this.getUri = function(host, endpoint) {
var base = 'http://'
var argv = this.program.argv
if (argv.secure) {
base = 'https://'
}
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
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) {
var body = {}
_.set(body, key, value)
doRequest(resource, null, { method: 'PATCH', body: body })
}
this.logError = function(err) {
console.log(colors.red('Error:'), err)
}
}
function getList() {
return new Table({ style: { head: ['cyan'] } })
}
// Creates a table-like array for output. The parameter is an array of header names
function getTable(headobj) {
for (i = 0; i < headobj.length; i++) {
headobj[i] = colors.cyan(headobj[i])
}
return new Table({
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)
}