UNPKG

vortex

Version:
710 lines (659 loc) 23.7 kB
(function() { var async, download, fs, logsmith, path_extra, portchecker, shell_quote, url, vboxmanage; fs = require('fs'); url = require('url'); async = require('async'); logsmith = require('logsmith'); path_extra = require('path-extra'); vboxmanage = require('vboxmanage'); portchecker = require('portchecker'); shell_quote = require('shell-quote'); download = require('./download'); exports.Provider = (function() { /* This class exposes VirtualBox as a provider to Vortex. */ function _Class(manifest) { this.manifest = manifest; /* The provider accepts a manifest as a parameter by specification. */ } _Class.prototype.get_node = function(node_name) { /* This method returns a node by looking up its name. It throws an error if the node is not found. */ if ((this.manifest.nodes != null) && (this.manifest.nodes[node_name] != null)) { return this.manifest.nodes[node_name]; } throw new Error("node " + node_name + " does not exist"); }; _Class.prototype.extract_property = function(property_name, node_name) { /* Extracts a property by looking into a node and upper layers of the manifest. */ var e, node, _ref, _ref1; try { node = this.get_node(node_name); } catch (_error) { e = _error; node = null; } if ((node != null ? (_ref = node.virtualbox) != null ? _ref[property_name] : void 0 : void 0) != null) { return node.virtualbox[property_name]; } if (((_ref1 = this.manifest.virtualbox) != null ? _ref1[property_name] : void 0) != null) { return this.manifest.virtualbox[property_name]; } return null; }; _Class.prototype.extract_vm_id = function(node_name) { return this.extract_property('vmId', node_name); }; _Class.prototype.extract_vm_url = function(node_name) { return this.extract_property('vmUrl', node_name); }; _Class.prototype.extract_username = function(node_name) { return this.extract_property('username', node_name); }; _Class.prototype.extract_password = function(node_name) { return this.extract_property('password', node_name); }; _Class.prototype.extract_private_key = function(node_name) { return this.extract_property('privateKey', node_name); }; _Class.prototype.extract_passphrase = function(node_name) { return this.extract_property('passphrase', node_name); }; _Class.prototype.extract_ssh_port = function(node_name) { return this.extract_property('sshPort', node_name); }; _Class.prototype.extract_namespace = function(node_name) { /* Extracts a namespace by looking it up in the node itself and upper layers of the manifest */ var node; try { node = this.get_node(node_name); } catch (_error) { node = null; } if ((node != null ? node.namespace : void 0) != null) { return node.namespace; } if (this.manifest.namespace != null) { return this.manifest.namespace; } }; _Class.prototype.get_node_handle = function(node_name) { /* Creates a VirtualBox friendlier name out of a node name. The method take into account the namespace. */ var namespace; namespace = this.extract_namespace(node_name); return (namespace ? namespace + ':' : '') + node_name; }; _Class.prototype.get_share_handle = function(share_name) { /* Creates a VirtualBox friendlier name out of a share name. */ return share_name.replace(/[^\w]+/, '_').replace(/_+/, '_'); }; _Class.prototype.schedule_import = function(vm_url, vm_id, callback) { /* Schedules import operation. The function will check if the vm_id exists before execution. */ var task, _this = this; if (this.import_queue == null) { this.import_queue = async.queue(function(task, callback) { return vboxmanage.machine.info(task.vm_id, function(err, info) { if (!err) { return callback(null); } return _this.perform_import(task.vm_url, task.vm_id, callback); }); }); } task = { vm_url: vm_url, vm_id: vm_id }; return this.import_queue.push(task, callback); }; _Class.prototype.perform_import = function(vm_url, vm_id, callback) { /* Performs import operation. */ var local_name, local_path, spec, _ref; logsmith.debug("import " + vm_url + " into " + vm_id); try { spec = url.parse(vm_url); } catch (_error) { return callback(new Error("cannot parse url " + vm_url)); } if ((_ref = spec.protocol) !== 'file:' && _ref !== 'http:' && _ref !== 'https:') { return callback(new Error("unsupported scheme for url " + vm_url)); } if (spec.protocol === 'file') { if (!spec.host) { local_path = spec.pathname; } else { local_path = path_extra.resolve(path_extra.dirname(this.manifest.meta.location), path_extra.join(spec.host, spec.pathname)); } return vboxmanage.machine["import"](local_path, vm_id, callback); } else { local_name = (new Date()).getTime() + '-' + path_extra.basename(spec.pathname); local_path = path_extra.join(path_extra.tempdir(), local_name); return download.get(vm_url, local_path, function(err) { if (err) { fs.unlink(local_path, function(err) { if (err) { return logsmith.exception(err); } }); return callback(err); } return vboxmanage.machine["import"](local_path, vm_id, function(err) { fs.unlink(local_path, function(err) { if (err) { return logmisth.exception(err); } }); if (err) { return callback(err); } return callback(null); }); }); } }; _Class.prototype.bootstrap = function(node_name, callback) { /* Provider-specific method for bootstrapping a node. */ var commands, node_handle, prepare_exposed, run_commands, verify_status, _this = this; commands = ['sudo mkdir -p /etc/vortex/flags/', 'sudo chmod a+rx /etc/vortex/flags/', '[ ! -f /etc/vortex/flags/network_ready ] && sudo ifconfig eth1 0.0.0.0 0.0.0.0', '[ ! -f /etc/vortex/flags/network_ready ] && sudo ifconfig eth2 0.0.0.0 0.0.0.0', '[ ! -f /etc/vortex/flags/network_ready ] && sudo dhclient -r eth1 eth2', '[ ! -f /etc/vortex/flags/network_ready ] && sudo dhclient eth1 eth2', '[ ! -f /etc/vortex/flags/network_ready ] && sudo touch /etc/vortex/flags/network_ready']; node_handle = this.get_node_handle(node_name); verify_status = function(callback) { return _this.status(node_name, function(err, state, address) { if (err) { return callback(err); } if (state !== 'running') { return callback(new Error("node " + node_name + " is not ready")); } return callback(null); }); }; prepare_exposed = function(callback) { var dst, e, handle_exposure, node, src; try { node = _this.get_node(node_name); } catch (_error) { e = _error; node = null; } if ((node != null ? node.expose : void 0) == null) { return callback(null); } handle_exposure = function(exposure, callback) { var source_path; source_path = path_extra.resolve(path_extra.dirname(_this.manifest.meta.location), exposure.src); return fs.stat(source_path, function(err, stats) { var share_handle; if (err) { return callback(new Error("cannot expose " + exposure.src + " because it does not exist")); } if (stats.isDirectory()) { share_handle = _this.get_share_handle(exposure.dst); commands.push(shell_quote.quote(['sudo', 'mkdir', '-p', exposure.dst])); commands.push(shell_quote.quote(['sudo', 'mount.vboxsf', share_handle, exposure.dst, '-o', 'rw'])); return callback(null); } else { return vboxmanage.instance.copy_from(source_path, exposure.dst, callback); } }); }; return async.eachSeries((function() { var _ref, _results; _ref = node.expose; _results = []; for (src in _ref) { dst = _ref[src]; _results.push({ src: src, dst: dst }); } return _results; })(), handle_exposure, callback); }; run_commands = function(callback) { var run_command; run_command = function(command, callback) { return vboxmanage.instance.exec(node_handle, 'vortex', 'vortex', '/bin/sh', '-c', command, function(err, output) { var _ref; if (err) { return callback(err); } if ((_ref = logsmith.level) === 'verbose' || _ref === 'debug' || _ref === 'silly') { process.stdout.write(output); } return callback(null); }); }; return async.eachSeries(commands, run_command, callback); }; return async.waterfall([verify_status, prepare_exposed, run_commands], function(err, state, address) { if (err) { return callback(err); } return callback(null); }); }; _Class.prototype.status = function(node_name, callback) { /* Provider-specific method for checking the status of a node. */ var node_handle, obtain_machine_address, obtain_machine_state; node_handle = this.get_node_handle(node_name); obtain_machine_state = function(callback) { return vboxmanage.machine.info(node_handle, function(err, info) { var state; if (err) { return callback(null, 'stopped'); } state = info.VMState.toLowerCase(); switch (state) { case 'saved': state = 'paused'; break; case 'paused': state = 'paused'; break; case 'running': state = 'running'; break; case 'starting': state = 'booting'; break; case 'powered off': state = 'stopped'; break; case 'guru meditation': state = 'paused'; } return callback(null, state); }); }; obtain_machine_address = function(state, callback) { return vboxmanage.adaptors.list(node_handle, function(err, adaptors) { var address, e; if (err) { return callback(null, 'stopped', address); } try { address = adaptors['Adaptor 1'].V4.IP; } catch (_error) { e = _error; address = null; state = 'booting'; } return callback(null, state, address); }); }; return async.waterfall([obtain_machine_state, obtain_machine_address], function(err, state, address) { if (err) { return callback(err); } return callback(null, state, address); }); }; _Class.prototype.boot = function(node_name, callback) { /* Provider-specific method for booting a node. */ var attemp_to_remove_vm, clone_vm, ensure_networking, ensure_vm_id, node_handle, setup_vm, start_vm, verify_status, vm_id, _this = this; vm_id = this.extract_vm_id(node_name); if (!vm_id) { return callback(new Error('no virtualbox "vmId" paramter specified for node')); } node_handle = this.get_node_handle(node_name); verify_status = function(callback) { return _this.status(node_name, function(err, state, address) { if (err) { return callback(err); } if (state === 'booting') { return callback(new Error("node " + node_name + " is already booting")); } if (state === 'running') { return callback(new Error("node " + node_name + " is already running")); } if (state === 'halting') { return callback(new Error("node " + node_name + " is halting")); } if (state === 'paused') { return callback(new Error("node " + node_name + " is paused")); } return callback(null); }); }; attemp_to_remove_vm = function(callback) { return vboxmanage.machine.remove(node_handle, function(err) { if (err) { logsmith.exception(err); } return callback(null); }); }; ensure_vm_id = function(callback) { return vboxmanage.machine.info(vm_id, function(err, info) { var vm_url; if (!err) { return callback(null); } vm_url = _this.extract_vm_url(node_name); if (vm_url == null) { return callback(new Error('no virtualbox "vmUrl" paramter specified for node')); } return _this.schedule_import(vm_url, vm_id, callback); }); }; clone_vm = function(callback) { return vboxmanage.machine.clone(vm_id, node_handle, callback); }; ensure_networking = function(callback) { var config; config = { network: { hostonly: { vboxnet5: { ip: '10.100.100.1', netmask: '255.255.255.0', dhcp: { lower_ip: '10.100.100.101', upper_ip: '10.100.100.254' } } }, internal: { vortex: { ip: '10.200.200.1', netmask: '255.255.255.0', dhcp: { lower_ip: '10.200.200.101', upper_ip: '10.200.200.254' } } } } }; return vboxmanage.setup.system(config, callback); }; setup_vm = function(callback) { var config, dst, e, node, share_handle, src, _ref; config = { network: { adaptors: [ { type: 'hostonly', network: 'vboxnet5' }, { type: 'internal', network: 'vortex' }, { type: 'nat' } ] }, shares: {} }; try { node = _this.get_node(node_name); } catch (_error) { e = _error; return callback(e); } if (node.expose != null) { _ref = node.expose; for (src in _ref) { dst = _ref[src]; src = path_extra.resolve(path_extra.dirname(_this.manifest.meta.location), src); share_handle = _this.get_share_handle(dst); config.shares[share_handle] = src; } } return vboxmanage.setup.machine(node_handle, config, callback); }; start_vm = function(callback) { return vboxmanage.instance.start(node_handle, callback); }; return async.waterfall([verify_status, attemp_to_remove_vm, ensure_vm_id, clone_vm, ensure_networking, setup_vm, start_vm], function(err) { if (err) { return callback(err); } return _this.status(node_name, callback); }); }; _Class.prototype.halt = function(node_name, callback) { /* Provider-specific method for halting a node. */ var attempt_to_remove_vm, attempt_to_stop_vm, node_handle, verify_status, _this = this; node_handle = this.get_node_handle(node_name); verify_status = function(callback) { return _this.status(node_name, function(err, state, address) { if (err) { return callback(err); } if (state === 'halting') { return callback(new Error("" + node_name + " is already halting")); } if (state === 'stopped') { return callback(new Error("" + node_name + " is already stopped")); } return callback(null); }); }; attempt_to_stop_vm = function(callback) { return vboxmanage.instance.stop(node_handle, function(err) { if (err) { logsmith.exception(err); } return callback(null); }); }; attempt_to_remove_vm = function(callback) { return vboxmanage.machine.remove(node_handle, function(err) { if (err) { logsmith.exception(err); } return callback(null); }); }; return async.waterfall([verify_status, attempt_to_stop_vm, attempt_to_remove_vm], function(err) { if (err) { return callback(err); } return _this.status(node_name, callback); }); }; _Class.prototype.pause = function(node_name, callback) { /* Provider-specific method for pausing a machine. */ var node_handle, pause_vm, verify_status, _this = this; node_handle = this.get_node_handle(node_name); verify_status = function(callback) { return _this.status(node_name, function(err, state, address) { if (err) { return callback(err); } if (state === 'paused') { return callback(new Error("" + node_name + " is already paused")); } if (state === 'halting') { return callback(new Error("" + node_name + " is halting")); } if (state === 'stopped') { return callback(new Error("" + node_name + " is stopped")); } return callback(null); }); }; pause_vm = function(callback) { return vboxmanage.instance.save(node_handle, callback); }; return async.waterfall([verify_status, pause_vm], function(err) { if (err) { return callback(err); } return _this.status(node_name, callback); }); }; _Class.prototype.resume = function(node_name, callback) { /* Provider-specific method for resuming a machine. */ var attempt_resume_vm, attempt_start_vm, node_handle, verify_status, _this = this; node_handle = this.get_node_handle(node_name); verify_status = function(callback) { return _this.status(node_name, function(err, state, address) { if (err) { return callback(err); } if (state === 'booting') { return callback(new Error("" + node_name + " is already booting")); } if (state === 'running') { return callback(new Error("" + node_name + " is already running")); } if (state === 'halting') { return callback(new Error("" + node_name + " is halting")); } if (state === 'stopped') { return callback(new Error("" + node_name + " is stopped")); } return callback(null); }); }; attempt_start_vm = function(callback) { return vboxmanage.instance.start(node_handle, function(err) { if (err) { logsmith.exception(err); } return callback(null); }); }; attempt_resume_vm = function(callback) { return vboxmanage.instance.resume(node_handle, function(err) { if (err) { logsmith.exception(err); } return callback(null); }); }; return async.waterfall([verify_status, attempt_start_vm, attempt_resume_vm], function(err) { if (err) { return callback(err); } return _this.status(node_name, callback); }); }; _Class.prototype.shell_spec = function(node_name, callback) { /* Provider-specific method for obtaining a shell spec from a node. */ var build_spec, ensure_port, obtain_status, passphrase, password, private_key, ssh_port, username, _this = this; password = this.extract_password(node_name); private_key = this.extract_private_key(node_name); if (!password && !private_key) { return callback(new Error("no password or privateKey provided for node " + node_name)); } ssh_port = this.extract_ssh_port(node_name); if (ssh_port) { ssh_port = parseInt(ssh_port, 10); if (isNaN(ssh_port || ssh_port < 1)) { return callback(new Error("ssh port for node " + node_name + " is incorrect")); } } else { ssh_port = 22; } username = this.extract_username(node_name); if (!username) { username = 'vortex'; } passphrase = this.extract_passphrase(node_name); obtain_status = function(callback) { return _this.status(node_name, function(err, state, address) { if (err) { return callback(err); } if (state === 'halting') { return callback(new Error("node " + node_name + " is halting")); } if (state === 'stopped') { return callback(new Error("node " + node_name + " is stopped")); } if (!address) { return callback(new Error("cannot find network address for node " + node_name)); } return callback(null, address); }); }; ensure_port = function(address, callback) { return portchecker.isOpen(ssh_port, address, function(is_open) { var callee, milliseconds, timeout; if (is_open) { return callback(null, address); } callee = arguments.callee; milliseconds = 10000; timeout = function() { return portchecker.isOpen(ssh_port, address, callee); }; logsmith.debug("repeat check for ssh port open for node " + node_name + " in " + milliseconds + " milliseconds"); return setTimeout(timeout, milliseconds); }); }; build_spec = function(address, callback) { var parts, spec, spec_options; parts = []; parts.push('ssh://'); parts.push(encodeURIComponent(username)); if (password) { parts.push(':' + encodeURIComponent(password)); } parts.push('@'); parts.push(address); parts.push(':' + ssh_port); if (private_key) { parts.push(';privateKey=' + encodeURIComponent(private_key)); } if (passphrase) { parts.push(';passphrase=' + encodeURIComponent(passphrase)); } spec = parts.join(''); spec_options = { username: username, password: password, host: address, port: ssh_port, privateKey: private_key, passphrase: passphrase }; return callback(null, spec, spec_options); }; return async.waterfall([obtain_status, ensure_port, build_spec], callback); }; return _Class; })(); }).call(this);