isnodeonline updates
- rename command to isonline
- accept multiple nodes
- support json output
- support for silent mode which is useful if used programatically
diff --git a/doc/api/couchadmin-is-node-online.md b/doc/api/couchadmin-is-node-online.md
deleted file mode 100644
index 8d6ce94..0000000
--- a/doc/api/couchadmin-is-node-online.md
+++ /dev/null
@@ -1,18 +0,0 @@
-couchadmin-is-node-online(3) - check if a cluster node is online
-================================================================
-
-### SYNOPSIS
-
- couchadmin.commands.isnodeonline(url)
-
-
-### DESCRIPTION
-
-Check if a given node is online / available on the current network.
-
-The command takes one argument: `url` - the url of the node to check as
-a `String`.
-
-The command returns a promise which will return a `Boolean` once the
-promise is resolved after making the request to the node. `true`
-indicates an online, available node.
diff --git a/doc/api/couchadmin-is-online.md b/doc/api/couchadmin-is-online.md
new file mode 100644
index 0000000..a97bc68
--- /dev/null
+++ b/doc/api/couchadmin-is-online.md
@@ -0,0 +1,30 @@
+couchadmin-is-online(3) - check if a cluster node is online
+===========================================================
+
+### SYNOPSIS
+
+ couchadmin.commands.isonline(urls, opts)
+
+
+### DESCRIPTION
+
+Check if nodes are online / available on the current network.
+
+The command takes two arguments: `urls` - the urls of the nodes to
+check as an `Array`. The second argument must be an `Object` providing
+options
+
+The command returns a promise which will return an `Object` where the
+keys are the provided urls and the values have the type `Boolean`.
+`true` indicates an online, available node.
+
+### Options
+
+#### json
+
+If `json` is `true` the output is logged to stdout console as json
+output.
+
+#### silent
+
+If the option `silent` is `true`, no output will be logged to stdout.
diff --git a/doc/cli/couchadmin-is-online.md b/doc/cli/couchadmin-is-online.md
new file mode 100644
index 0000000..cf363d4
--- /dev/null
+++ b/doc/cli/couchadmin-is-online.md
@@ -0,0 +1,15 @@
+couchadmin-is-online(1) - check if a cluster node is online
+===========================================================
+
+### SYNOPSIS
+
+ couchadmin isonline <url> [<url>, <url> ...] [--json]
+ couchadmin on <url> [<url>, <url> ...] [--json]
+
+
+### DESCRIPTION
+
+Check if one or several nodes are currently online or available.
+
+It will print the result as colored output. JSON output is also
+supported by passing the `--json` flag.
diff --git a/src/api.js b/src/api.js
deleted file mode 100644
index c60db9b..0000000
--- a/src/api.js
+++ /dev/null
@@ -1,37 +0,0 @@
-'use strict';
-
-import * as utils from './utils.js';
-import log from 'npmlog';
-import request from 'request';
-import Promise from 'bluebird';
-
-export const isnodeonline = function isNodeOnline (url) {
- return new Promise(function (resolve, reject) {
- const er = utils.checkUrl(url);
- if (er) {
- return reject(er);
- }
-
- log.http('request', 'GET', url);
-
- request(url, (err, res, body) => {
- if (err && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND')) {
- log.info('nodestatus', 'NOT OK', 'node is probably offline');
- return resolve(false);
- }
-
- if (err) {
- return reject(err);
- }
-
- const online = res.statusCode < 300;
-
- if (online) {
- log.info('nodestatus', 'OK', 'node seems to be online');
- return resolve(online);
- }
- log.info('nodestatus', 'NOT OK', 'node is probably offline');
- resolve(online);
- });
- });
-}
diff --git a/src/isonline.js b/src/isonline.js
new file mode 100644
index 0000000..71b110a
--- /dev/null
+++ b/src/isonline.js
@@ -0,0 +1,76 @@
+import * as utils from './utils.js';
+import log from 'npmlog';
+import request from 'request';
+import Promise from 'bluebird';
+import assert from 'assert';
+
+export default isonline;
+
+function isonline (urls, opts = {silent: false, json: false}) {
+ return new Promise((resolve, reject) => {
+ if (!Array.isArray(urls)) {
+ urls = [urls];
+ }
+
+ const urlPromises = urls.map((url) => {
+ return isNodeOnline(url);
+ });
+
+ Promise.all(urlPromises).then((results) => {
+ results = results.reduce((acc, el) => {
+ const key = Object.keys(el)[0];
+ acc[key] = el[key];
+ return acc;
+ }, {});
+
+ if (opts.silent && opts.json) {
+ let msg = 'silent and json are not supported at the same time';
+ const err = new Error(msg);
+ return reject(err);
+ }
+
+ if (opts.silent) {
+ return resolve(results);
+ }
+
+ if (opts.json) {
+ console.log(results);
+ return resolve(results);
+ }
+
+ Object.keys(results).forEach((entry) => {
+ let msg;
+ if (results[entry]) {
+ msg = 'seems to be online';
+ }
+ msg = 'seems to be offline';
+ log.info('status', entry, msg);
+ });
+ return resolve(results);
+ }, reject);
+ });
+};
+
+function isNodeOnline (url) {
+ return new Promise((resolve, reject) => {
+ const er = utils.checkUrl(url);
+ if (er) {
+ return reject(er);
+ }
+
+ log.http('request', 'GET', url);
+
+ request(url, (err, res, body) => {
+ if (err && (err.code === 'ECONNREFUSED'
+ || err.code === 'ENOTFOUND')) {
+ return resolve({[url]: false});
+ }
+
+ if (err) {
+ return reject(err);
+ }
+
+ resolve({[url]: res.statusCode < 300});
+ });
+ });
+}
diff --git a/src/utils.js b/src/utils.js
index f88ce08..a806d21 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -22,7 +22,7 @@
}
export function sendJsonToNode (url, json) {
- return new Promise(function (resolve, reject) {
+ return new Promise((resolve, reject) => {
assert.equal(typeof json, 'object', 'argument must be an object');
log.http('request', 'POST', url);
diff --git a/test/api.js b/test/api.js
deleted file mode 100644
index 9132467..0000000
--- a/test/api.js
+++ /dev/null
@@ -1,80 +0,0 @@
-'use strict';
-
-import assert from 'assert';
-import Lab from 'lab';
-
-import * as api from '../src/api.js';
-import * as common from './common.js';
-
-
-export let lab = Lab.script();
-
-lab.experiment('isnodeonline', () => {
-
- let server;
-
- lab.beforeEach((done) => {
- server = common.createTestServer(done);
- });
-
- lab.afterEach((done) => {
- server.close(done);
- });
-
-
- lab.test('rejects the promise for connection errors', (done) => {
- api
- .isnodeonline(common.NODE + '/socketclose')
- .catch((err) => {
- assert.ok(err instanceof Error);
- done();
- });
- });
-
-
- lab.test('returns error on no value provided', (done) => {
- api
- .isnodeonline()
- .catch((err) => {
- assert.ok(err instanceof Error);
- done();
- });
- });
-
- lab.test('returns error for all other errors', (done) => {
- api
- .isnodeonline({})
- .catch((err) => {
- assert.ok(err instanceof Error);
- done();
- });
- });
-
- lab.test('returns false for down site', (done) => {
- api
- .isnodeonline('http://127.0.0.1:65516')
- .then((res) => {
- assert.equal(res, false);
- done();
- });
- });
-
- lab.test('returns false for down site with bad DNS', (done) => {
- api
- .isnodeonline('http://example.neverexists')
- .then((res) => {
- assert.equal(res, false);
- done();
- });
- });
-
- lab.test('returns true for online site', (done) => {
- api
- .isnodeonline(common.NODE)
- .then((res) => {
- assert.equal(res, true);
- done();
- });
- });
-
-});
diff --git a/test/common.js b/test/common.js
index c10926c..95974cd 100644
--- a/test/common.js
+++ b/test/common.js
@@ -1,17 +1,39 @@
import http from 'http';
+import Promise from 'bluebird';
export const PORT = 1337;
+export const PORT_TWO = 1338;
+
export const NODE = 'http://127.0.0.1:' + PORT;
+export const NODE_TWO = 'http://127.0.0.1:' + PORT_TWO;
-export function createTestServer (done) {
- return http.createServer((req, res) => {
- if (req.url === '/socketclose') {
- res.write('a');
- }
+export function createTestServers () {
+ const promises = [PORT, PORT_TWO].map((port) => {
+ return createTestServer(port);
+ });
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.end('Hello World\n');
- }).listen(PORT, '127.0.0.1', done);
+ return Promise.all(promises);
+}
+
+export function stopTestServers (servers) {
+ const servers = servers.map((s) => {
+ const close = Promise.promisify(s.close).bind(s);
+ return close();
+ });
+ return Promise.all(servers);
+}
+
+export function createTestServer (port) {
+ return new Promise((resolve) => {
+ const s = http.createServer((req, res) => {
+ if (req.url === '/socketclose') {
+ res.write('a');
+ }
+
+ res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.end('Hello World\n');
+ }).listen(port, '127.0.0.1', () => { resolve(s); });
+ });
}
diff --git a/test/isonline.js b/test/isonline.js
new file mode 100644
index 0000000..b12db94
--- /dev/null
+++ b/test/isonline.js
@@ -0,0 +1,170 @@
+import assert from 'assert';
+import Lab from 'lab';
+
+import isonline from '../src/isonline.js';
+import * as common from './common.js';
+import log from 'npmlog';
+
+
+export let lab = Lab.script();
+
+lab.experiment('isonline', () => {
+
+ let servers;
+
+ lab.beforeEach((done) => {
+ common.createTestServers().done((s) => {
+ servers = s;
+ done();
+ });
+ });
+
+ lab.afterEach((done) => {
+ common.stopTestServers(servers).then((res) => {
+ done();
+ });
+ });
+
+
+ lab.test('rejects the promise for connection errors', (done) => {
+ isonline(common.NODE + '/socketclose')
+ .catch((err) => {
+ assert.ok(err instanceof Error);
+ done();
+ });
+ });
+
+ lab.test('returns error on no value provided', (done) => {
+ isonline()
+ .catch((err) => {
+ assert.ok(err instanceof Error);
+ done();
+ });
+ });
+
+ lab.test('returns error for all other errors', (done) => {
+ isonline({})
+ .catch((err) => {
+ assert.ok(err instanceof Error);
+ done();
+ });
+ });
+
+ lab.test('returns false for down site', (done) => {
+ isonline('http://127.0.0.1:65516')
+ .then((res) => {
+ assert.deepEqual(res, {'http://127.0.0.1:65516': false});
+ done();
+ });
+ });
+
+ lab.test('returns false for down site with bad DNS', (done) => {
+ isonline('http://exampleneverexists')
+ .then((res) => {
+ assert.deepEqual(res, {'http://exampleneverexists': false});
+ done();
+ });
+ });
+
+ lab.test('returns true for online site', (done) => {
+ isonline(common.NODE)
+ .then((res) => {
+ assert.deepEqual(res, {[common.NODE]: true});
+ done();
+ });
+ });
+
+ lab.test('accepts multiple sites', (done) => {
+ isonline([common.NODE, common.NODE_TWO])
+ .then((res) => {
+ assert.deepEqual(res, {[common.NODE]: true, [common.NODE_TWO]: true});
+ done();
+ });
+ });
+
+ lab.test('it logs per default', (done) => {
+ let msgs = '';
+ log.once('log', ({message: message}) => {
+ msgs += message;
+ });
+ isonline(common.NODE)
+ .then((res) => {
+ assert.ok(msgs);
+ done();
+ });
+ });
+
+ lab.test('silent does not output', (done) => {
+
+ const oldConsole = console.log;
+ console.log = (...args) => {
+ throw new Error('not silent');
+ };
+
+ isonline(common.NODE, {silent: true, json: false})
+ .then((res) => {
+ console.log = oldConsole;
+ done();
+ });
+ });
+
+ lab.test('can output json', (done) => {
+
+ const oldConsole = console.log;
+ console.log = (...args) => {
+ assert.deepEqual({ 'http://127.0.0.1:1337': true }, args[0]);
+ return oldConsole.apply(oldConsole, args);
+ };
+
+ isonline(common.NODE, {silent: false, json: true})
+ .then((res) => {
+ console.log = oldConsole;
+ done();
+ });
+ });
+
+ lab.test('if json output is selected, colored output is not provided', (done) => {
+
+ let msgs = '';
+ log.once('log', ({message: message}) => {
+ msgs += message;
+ });
+
+ isonline(common.NODE, {silent: false, json: true})
+ .then((res) => {
+ assert.ok(!(/seems to be/).test(msgs));
+ done();
+ });
+ });
+
+ lab.test('if silent output is selected, colored output is not provided', (done) => {
+
+ const oldConsole = console.log;
+ console.log = (...args) => {
+ throw new Error('fail');
+ return oldConsole.apply(oldConsole, args);
+ };
+
+ let msgs = '';
+ log.once('log', ({message: message}) => {
+ msgs += message;
+ });
+
+ isonline(common.NODE, {silent: true, json: false})
+ .then((res) => {
+ assert.ok(!(/seems to be/).test(msgs));
+ assert.deepEqual(res, {[common.NODE]: true});
+ done();
+ });
+ });
+
+ lab.test('silent & json are not compatible', (done) => {
+
+ isonline(common.NODE, {silent: true, json: true})
+ .catch((err) => {
+ assert.ok(err instanceof Error);
+ done();
+ });
+ });
+
+});