UNPKG

shync

Version:

Simple parallel server cluster management tool using ssh and scp

375 lines (296 loc) 11.3 kB
var assert = require('chai').assert, sinon = require('sinon'), util = require('util'), Shync = require('../lib/shync.js').Shync; var EventEmitter = require('events').EventEmitter; suite('Shync', function(){ setup(function(){ this.opts = { domains:['abc.com', '123.456.789'], user: 'ubuntu', keyLoc: '/foo/id_rsa.pub', bypassFingerprint: false }; this.singleCmdOpts = { domain:this.opts.domains[0], user: this.opts.user, keyLoc: this.opts.keyLoc, }; this.LIVEopts = { domains:['ec2-174-129-171-245.compute-1.amazonaws.com', 'ec2-54-225-9-79.compute-1.amazonaws.com'], user: 'ubuntu', keyLoc: '/Users/davemckenna/.ec2/ec22.pem', bypassFingerprint: true, stdout: function(o) { console.log(o); }, stderr: function(e) { console.log(e); } }; }); teardown(function(){ }); suite('run()', function(){ test('should add the cb to the parent object', function(){ function cb (){} var ssh = new Shync(this.opts); ssh._runCmd = sinon.stub(); ssh.run('date', cb); assert.strictEqual(cb, ssh.cb); }); test('should reset object state, for running with a clean slate', function(){ function cb (){} var ssh = new Shync(this.opts); ssh._runCmd = sinon.stub(); ssh.cbCalled = true; ssh.domains = {foo:'bar'}; ssh.procs = {bar:'baz'}; ssh.run('date', cb); assert.isFalse(ssh.cbCalled); assert.deepEqual({}, ssh.domains); assert.deepEqual({}, ssh.procs); }); test('should call _runCmd() w/ opts and the command, iterating over domains', function(){ var ssh = new Shync(this.opts); ssh._runCmd = sinon.spy(); ssh.run('date', function(){}); assert.ok(ssh._runCmd.calledTwice); assert.ok(ssh._runCmd.getCall(0).calledWith( {domain: this.opts.domains[0], user: this.opts.user, keyLoc: this.opts.keyLoc }, 'date' )); assert.ok(ssh._runCmd.getCall(1).calledWith( {domain: this.opts.domains[1], user: this.opts.user, keyLoc: this.opts.keyLoc }, 'date' )); }); }); suite('_runCmd()', function(){ test('should add an object representing command state to Shync.domains', function(){ var ssh = new Shync(this.opts); sinon.stub(ssh, '_spawn', function(){ return { addListener: sinon.stub() } }); ssh._runCmd(this.singleCmdOpts, 'date'); assert.ok(ssh.domains.hasOwnProperty(this.singleCmdOpts.domain)); assert.isFalse(ssh.domains[this.singleCmdOpts.domain].cmdComplete); }); test('should add RSA fingerprint bypass args if bypassFingerprint is true', function(){ var ssh = new Shync(this.opts); sinon.stub(ssh, '_spawn', function(){ return { addListener: sinon.stub() } }); var opts = this.singleCmdOpts; ssh.opts.bypassFingerprint = true; ssh._runCmd(opts, 'date'); var sshParams = []; sshParams.push('-oUserKnownHostsFile=/dev/null'); sshParams.push('-oStrictHostKeyChecking=no'); sshParams.push('-oNumberOfPasswordPrompts=0'); sshParams.push('-i' + opts.keyLoc); sshParams.push('-l' + opts.user); sshParams.push(opts.domain); sshParams.push('date'); assert.ok(ssh._spawn.calledOnce); assert.ok(ssh._spawn.calledWith('ssh', sshParams)); }); test('should call Shync._spawn with the ssh or scp cmd', function(){ var ssh = new Shync(this.opts); sinon.stub(ssh, '_spawn', function(){ return { addListener: sinon.stub() } }); var opts = this.singleCmdOpts; ssh._runCmd(opts, 'date'); var sshParams = []; sshParams.push('-oNumberOfPasswordPrompts=0'); sshParams.push('-i' + opts.keyLoc); sshParams.push('-l' + opts.user); sshParams.push(opts.domain); sshParams.push('date'); assert.ok(ssh._spawn.calledOnce); assert.ok(ssh._spawn.calledWith('ssh', sshParams)); ssh = new Shync(this.opts); sinon.stub(ssh, '_spawn', function(){ return { addListener: sinon.stub() } }); opts = this.singleCmdOpts; ssh._runCmd(opts, ['/foo/bar', '/bar/baz']); var scpParams = []; scpParams.push('-oNumberOfPasswordPrompts=0'); scpParams.push('-i' + opts.keyLoc); scpParams.push('/foo/bar'); scpParams.push(opts.user+'@'+opts.domain+':'+'/bar/baz'); assert.ok(ssh._spawn.calledOnce); assert.ok(ssh._spawn.calledWith('scp', scpParams)); }); test('should add a process to Shync.procs', function(){ var ssh = new Shync(this.opts); sinon.stub(ssh, '_spawn', function(){ return { addListener: sinon.stub() } }); var opts = this.singleCmdOpts; ssh._runCmd(opts, 'date'); assert.ok(ssh.procs.hasOwnProperty(opts.domain)); }); test('should call Shync._spawn().addListener with "exit" and a cb', function(){ var ssh = new Shync(this.opts); sinon.stub(ssh, '_spawn', function(){ return { addListener: sinon.spy() } }); ssh._runCmd(this.singleCmdOpts, 'date'); var proc = ssh.procs[this.singleCmdOpts.domain]; assert.ok(proc.addListener.calledOnce); assert.ok(proc.addListener.calledWith('exit')); assert.isFunction(proc.addListener.getCall(0).args[1]); }); test('should call Shync._cmdCb with ret code + domain', function(){ var ssh = new Shync(this.opts); ssh._cmdCb = sinon.spy(); sinon.stub(ssh, '_spawn', function(){ return new EventEmitter(); }); this.singleCmdOpts.domain = 'hithere.com'; ssh._runCmd(this.singleCmdOpts, 'date'); ssh.procs['hithere.com'].emit('exit', 0); assert.ok(ssh._cmdCb.calledOnce); assert.ok(ssh._cmdCb.calledWith(0, 'hithere.com')); }); test('should call Shync._cmdCb as commands complete', function(done){ var ssh = new Shync(this.opts); sinon.stub(ssh, '_spawn', function(){ return new EventEmitter(); }); var calls = 0 ssh._cmdCb = function(){ calls += 1; if (calls == 2) { assert.ok(true); done(); } } var self = this; this.singleCmdOpts.domain = 'hithere.com'; ssh._runCmd(this.singleCmdOpts, 'date'); setTimeout(function(){ ssh.procs['hithere.com'].emit('exit', 0); }, 250); this.singleCmdOpts.domain = 'hellotoyou.com'; ssh._runCmd(this.singleCmdOpts, 'date'); setTimeout(function(){ ssh.procs['hellotoyou.com'].emit('exit', 0); }, 500); }); }); suite('_cmdCb()', function(){ test('should update Shync.domains state object', function(){ var ssh = new Shync(this.opts); ssh.cb = sinon.stub(); ssh.domains['google.com'] = {cmdComplete: false}; ssh._cmdCb(0, 'google.com'); assert.isTrue(ssh.domains['google.com'].cmdComplete); }); test('should call the user-provided callback with null if all commands have completed with a 0', function(){ var ssh = new Shync(this.opts); ssh.cb = sinon.spy(); ssh.domains['google.com'] = {cmdComplete: false}; ssh.domains['maps.google.com'] = {cmdComplete: false}; ssh._cmdCb(0, 'google.com'); assert.ok(!ssh.cb.called); ssh._cmdCb(0, 'maps.google.com'); assert.ok(ssh.cb.calledOnce); assert.isNull(ssh.cb.getCall(0).args[0]); }); test('should call the user cb immediately with the ret code if the ret code is not 0', function(){ var ssh = new Shync(this.opts); ssh.cb = sinon.spy(); ssh.domains['google.com'] = {cmdComplete: false}; ssh.domains['maps.google.com'] = {cmdComplete: false}; ssh.procs['google.com'] = {kill:sinon.stub()}; ssh.procs['maps.google.com'] = {kill:sinon.stub()}; ssh._cmdCb(1928, 'google.com'); assert.ok(ssh.cb.calledOnce); assert.ok(ssh.cb.calledWith('Error: google.com: Return Code 1928')); assert.isTrue(ssh.domains['google.com'].cmdComplete); assert.isFalse(ssh.domains['maps.google.com'].cmdComplete); }); test('should kill all outstanding processes as soon as we get a non 0 ret code from a process', function(){ var ssh = new Shync(this.opts); ssh.cb = sinon.spy(); ssh.domains['mail.google.com'] = {cmdComplete: false}; ssh.domains['google.com'] = {cmdComplete: false}; ssh.domains['maps.google.com'] = {cmdComplete: false}; ssh.domains['docs.google.com'] = {cmdComplete: false}; ssh.procs['mail.google.com'] = {kill:sinon.spy()}; ssh.procs['google.com'] = {kill:sinon.spy()}; ssh.procs['maps.google.com'] = {kill:sinon.spy()}; ssh.procs['docs.google.com'] = {kill:sinon.spy()}; ssh._cmdCb(0, 'mail.google.com'); ssh._cmdCb(1928, 'google.com'); assert.ok(!ssh.procs['mail.google.com'].kill.called); assert.ok(!ssh.procs['google.com'].kill.called); assert.ok(ssh.procs['maps.google.com'].kill.calledOnce); assert.ok(ssh.procs['docs.google.com'].kill.calledOnce); }); test('should only call the user cb once', function(){ var ssh = new Shync(this.opts); ssh.cb = sinon.spy(); ssh.domains['google.com'] = {cmdComplete: false}; ssh.domains['maps.google.com'] = {cmdComplete: false}; ssh.procs['google.com'] = {kill:sinon.stub()}; ssh.procs['maps.google.com'] = {kill:sinon.stub()}; ssh._cmdCb(1928, 'google.com'); assert.ok(ssh.cb.called); ssh._cmdCb(0, 'maps.google.com'); assert.ok(ssh.cb.calledOnce); ssh = new Shync(this.opts); ssh.cb = sinon.spy(); ssh.domains['google.com'] = {cmdComplete: false}; ssh.domains['maps.google.com'] = {cmdComplete: false}; ssh.procs['google.com'] = {kill:sinon.stub()}; ssh.procs['maps.google.com'] = {kill:sinon.stub()}; ssh._cmdCb(0, 'google.com'); assert.ok(!ssh.cb.called); ssh._cmdCb(0, 'maps.google.com'); assert.ok(ssh.cb.calledOnce); }); }); // suite('playground', function(){ // test('do stuff', function(done){ // var cluster = new Shync(this.LIVEopts); // // cluster.run('echo "console.log(\'yoohoo\')" > yoohoo.js', function(err){ // // if (err) return console.log(err); // // cluster.run('cat yoohoo.js', function(err){ // // if (err) return console.log(err); // // console.log('you\'ve done something important on many machines!'); // // }); // // }); // cluster.run('~/ascript.js', '~/ascript.js', function(err){ // if (err) return console.log(err); // // cluster.run('cat ~/ascript.js', function(err){ // // if (err) return console.log(err); // // console.log('you\'ve done something important on many machines!'); // // }); // }); // }); // }); });