UNPKG

webgme-rosmod

Version:

This repository contains ROSMOD developed for WebGME. ROSMOD is a web-based, collaborative, modeling and execution environment for distributed embedded applications built using ROS http://rosmod.rcps.isis.vanderbilt.edu

507 lines (451 loc) 16.5 kB
/*globals define*/ /*jshint node:true, browser:true*/ /** * Generated by PluginGenerator 0.14.0 from webgme on Thu Mar 24 2016 07:15:53 GMT-0700 (PDT). */ define([ 'plugin/PluginConfig', 'plugin/PluginBase', 'text!./metadata.json', 'remote-utils/remote-utils', 'q' ], function ( PluginConfig, PluginBase, pluginMetadata, utils, Q) { 'use strict'; pluginMetadata = JSON.parse(pluginMetadata); /** * Initializes a new instance of StopExperiment. * @class * @augments {PluginBase} * @classdesc This class represents the plugin StopExperiment. * @constructor */ var StopExperiment = function () { // Call base class' constructor. PluginBase.call(this); this.pluginMetadata = pluginMetadata; }; StopExperiment.metadata = pluginMetadata; // Prototypal inheritance from PluginBase. StopExperiment.prototype = Object.create(PluginBase.prototype); StopExperiment.prototype.constructor = StopExperiment; StopExperiment.prototype.notify = function(level, msg) { var self = this; var prefix = self.projectId + '::' + self.projectName + '::' + level + '::'; var lines = msg.split('\n'); lines.map(function(line) { if (level=='error') self.logger.error(line); else if (level=='debug') self.logger.debug(line); else if (level=='info') self.logger.info(line); else if (level=='warning') self.logger.warn(line); self.createMessage(self.activeNode, line, level); self.sendNotification(prefix+line); }); }; /** * Main function for the plugin to execute. This will perform the execution. * Notes: * - Always log with the provided logger.[error,warning,info,debug]. * - Do NOT put any user interaction logic UI, etc. inside this method. * - callback always has to be called even if error happened. * * @param {function(string, plugin.PluginResult)} callback - the result callback */ StopExperiment.prototype.main = function (callback) { // Use self to access core, project, result, logger etc from PluginBase. // These are all instantiated at this point. var self = this; // Default fails self.result.success = false; if (typeof WebGMEGlobal !== 'undefined') { callback(new Error('Client-side execution is not supported'), self.result); return; } self.updateMETA({}); // What did the user select for our configuration? var currentConfig = self.getCurrentConfig(); self.returnZip = currentConfig.returnZip; // will be filled out by the plugin self.activeHosts = []; self.experiment = []; self.rosCorePort = Math.floor((Math.random() * (65535-1024) + 1024)); self.rosCoreIp = ''; self.rosBridgePort =''; self.rosBridgeURL =''; self.rosMCTExperimentName = ''; utils.notify = function(level, msg) {self.notify(level, msg);} // the active node for this plugin is experiment -> experiments -> project var projectNode = self.core.getParent(self.core.getParent(self.activeNode)); var projectName = self.core.getAttribute(projectNode, 'name'); self.experimentName = self.core.getAttribute(self.activeNode, 'name'); var path = require('path'); self.root_dir = path.join(process.cwd(), 'generated', self.project.projectId, self.branchName, projectName); self.exp_dir = path.join(self.root_dir, 'experiments', self.experimentName); self.resultsName = 'Results-'+(new Date()).toUTCString().replace(/:/gm, '-'); self.results_dir = path.join(self.exp_dir, self.resultsName); self.notify('info', 'loading project: ' + projectName); return self.getActiveHosts() .then(function (ah) { self.activeHosts = ah; return self.killAllActiveHosts(); }) .then(function() { return self.copyLogs(); }) .then(function() { return self.createResults(); }) .then(function() { return self.cleanupExperiment(); }) .then(function() { return self.createZip(); }) .then(function() { // This will save the changes. If you don't want to save; self.notify('info', 'saving updates to model'); return self.save('StopExperiment updated model.'); }) .then(function (err) { if (err.status != 'SYNCED') { callback(err, self.result); return; } self.result.setSuccess(true); callback(null, self.result); }) .catch(function(err) { self.logger.error(err); self.createMessage(self.activeNode, err, 'error'); self.result.setSuccess(false); callback(err, self.result); }) .done(); }; StopExperiment.prototype.getActiveHosts = function() { var self = this; return self.core.loadSubTree(self.activeNode) .then(function (nodes) { var ah = []; for (var i=0; i<nodes.length; i++) { var node = nodes[i]; if (self.core.isTypeOf(node, self.META.Host)) { var artifacts = JSON.parse(self.core.getAttribute(node, 'Artifacts')), // old style user = self.core.getAttribute(node, 'User'), intf = self.core.getAttribute(node, 'Interface'), // new style directory = self.core.getAttribute(node, 'Directory'), ip = self.core.getAttribute(node, 'IP'), key = self.core.getAttribute(node, 'Key'), RunningRoscore = self.core.getAttribute(node, 'RunningRoscore'), // external node PIDS PIDs = self.core.getAttribute(node, 'PIDs'), // ROS Bridge ROSBridgePID = self.core.getAttribute(node, 'ROS Bridge PID'), ROSBridgePort = self.core.getAttribute(node, 'ROS Bridge Port'), ROSBridgeUrl = self.core.getAttribute(node, 'ROS Bridge URL'), rosMCTExperimentName = self.core.getAttribute(node, 'Experiment Name'); if (ROSBridgePID) { self.ROSBridgePID = ROSBridgePID; self.rosBridgePort =ROSBridgePort; self.rosBridgeURL =ROSBridgeUrl; self.rosMCTExperimentName = rosMCTExperimentName; } if (PIDs) { PIDs = JSON.parse(PIDs); } if (ip && key && directory) { // if new style then package them user = { name: user, Directory: directory, Key: key }; intf = { IP: ip }; } ah.push({ user: user, intf: intf, artifacts: artifacts, RunningRoscore: RunningRoscore, PIDs: PIDs }); self.core.deleteNode(node); } else if (self.core.isTypeOf(node, self.META.Container)) { self.core.deleteNode(node); } else if (self.core.isTypeOf(node, self.META.FCO) && !self.core.isTypeOf(node, self.META.Experiment) && !self.core.isTypeOf(node, self.META.Documentation) && !self.core.isTypeOf(node, self.META.Results)) { self.core.deleteNode(node); // delete connections } } return ah; }); }; StopExperiment.prototype.killAllActiveHosts = function() { var self = this; if (self.activeHosts.length == 0) throw new String('No actively deployed experiment!'); var tasks = self.activeHosts.map(function(host) { var ip = host.intf.IP; var user = host.user; var host_commands = []; if (host.RunningRoscore) { self.notify('info', 'stopping roscore'); host_commands.push('pkill roscore'); } if (host.PIDs && host.PIDs.length) { var pids = host.PIDs.join(' '); self.notify('info', 'stopping node processes: ' + pids); host.PIDs.map((pid) => { host_commands.push("if ps -p "+pid+" > /dev/null; then kill -s SIGINT -$( ps opgid= "+pid+" | tr -d ' ' ) > /dev/null 2>&1; fi"); }); } host_commands.push('sleep 5'); host_commands.push('pkill rosmod_actor'); host_commands.push('if hash rc_kill 2>/dev/null; then rc_kill; fi'); self.notify('info', 'stopping processes on: '+ user.name + '@' + ip); return utils.executeOnHost(host_commands, ip, user); }); return Q.all(tasks); }; StopExperiment.prototype.copyRosLogs = function(host) { var self = this; var ip = host.intf.IP; var user = host.user; var path = require('path'); var mkdirp = require('mkdirp'); var localDir = self.results_dir; // copy the ros logs back over var rosLogDir = '~/.ros/log/latest'; self.notify('info', 'Copying ROS logs from ' + ip); return utils.copyFromHost(rosLogDir, localDir + '/.', ip, user) .then(function() { // now that we have the logs, we need to zip them up and remove the dir var child_process = require('child_process'); child_process.execSync('tar -zcvf rosLogs.tar.gz latest', {cwd: localDir}); child_process.execSync('rm -rf latest', {cwd: localDir}); }) .catch(function(err) { self.notify('warning', 'ROS logs not found on ' + ip); }); }; StopExperiment.prototype.copyLogs = function() { var self = this; var path = require('path'); var mkdirp = require('mkdirp'); var localDir = self.results_dir; // create the new results dir mkdirp.sync(localDir); var tasks = self.activeHosts.map(function(host) { var ip = host.intf.IP; var user = host.user; var remoteDir = path.join(user.Directory, 'experiments', self.experimentName); self.notify('info', 'Copying experiment data from ' + ip); var hostArtifacts = host.artifacts; hostArtifacts.push("*.log"); hostArtifacts.push("*.config"); var artTasks = hostArtifacts.map(function(artifact) { return utils.copyFromHost(remoteDir + '/' + artifact, localDir + '/.', ip, user) .catch(function(err) { self.notify('warning', artifact + ' not found on ' + ip); }); }); return Q.all(artTasks) .then(function() { if (host.RunningRoscore) { return self.copyRosLogs(host); } }); }); return Q.all(tasks) .then(function() { self.notify( 'info', 'Logs and Configs placed in ' + self.exp_dir); }); }; StopExperiment.prototype.createResults = function() { var self = this; var path = require('path'); var fs = require('fs'); var resultsNode = self.META['Results']; var localDir = self.results_dir; var logs = fs.readdirSync(localDir); var rn = self.core.createNode({parent: self.activeNode, base: resultsNode}); self.core.setRegistry(rn, 'position', {x: 100, y:50}); var validExts = ['log', 'config']; var tasks = logs.map(function(log) { var fname = log.split('/').slice(-1)[0]; var ext = fname.split('.').slice(-1)[0]; if (validExts.indexOf(ext) == -1) return; var logName = fname.replace(/\./g, '_'); self.notify('info', 'Adding ' + logName + ' to Results.'); //self.logger.info('setting meta attr for '+logName); self.core.setAttributeMeta(rn, logName, {'type':'asset'}); //self.logger.info('set meta attr for '+logName); return self.blobClient.putFile(log, fs.readFileSync(localDir + '/' + log)) .then(function (hash) { //self.logger.info('got hash for '+log+': ' + hash); self.core.setAttribute(rn, logName, hash); }); }); return Q.all(tasks) .then(() => { self.core.setAttribute(rn, 'name', self.resultsName); }); }; StopExperiment.prototype.stopRosBridge = function() { var self = this; const { execSync } = require('child_process'); const { exec } = require('child_process'); var find_child_commands=[ 'ps h --ppid '+self.ROSBridgePID+' -o pid' ].join('\n'); var options = { shell: '/bin/bash', }; var deferred = Q.defer(); exec(find_child_commands, options, function(error, stdout, stderr) { if (error) { self.notify('warning', 'ROS Bridge couldnt stop, perhaps it died already?'); self.notify('warning', new String(error)); deferred.resolve(); } var commands=[ 'kill -9 ' + self.ROSBridgePID, ] var rosBridgeChildren = stdout.split('\n').map(function(l) { var childPID = parseInt(l); if (childPID) commands.push( 'kill -9 '+childPID ); }); commands = commands.join('\n'); try { execSync(commands, options); self.notify('info', 'ROS Bridge with PID: '+self.ROSBridgePID + ' stopped'); } catch (err) { self.notify('info', 'ROS Bridge with PID: '+self.ROSBridgePID + ' couldnt be stopped'); self.notify('info', new String(err)); } self.informRosMCT() .then(function() { deferred.resolve(); }); }); return deferred.promise; }; StopExperiment.prototype.informRosMCT = function() { var self = this; const WebSocket = require('ws'); var deferred = Q.defer(); const ws = new WebSocket('ws://localhost:8085/realtime/notify'); var info = { name: self.rosMCTExperimentName, rosbridgeurl: 'localhost', rosbridgeport: ""+self.rosBridgePort, status: 'CLOSE' }; ws.on('error', function(err) { self.notify('warning', "Couldn't connect to ROSMCT server, is it running?"); self.notify('warning', new String(err)); deferred.resolve(); }); ws.on('open', function open() { ws.send(JSON.stringify(info)); self.notify('info', 'Informed ROSMCT about experiment'); deferred.resolve(); }); return deferred.promise; }; StopExperiment.prototype.cleanupExperiment = function() { var self = this; var path = require('path'); var tasks = self.activeHosts.map(function(host) { var ip = host.intf.IP; var user = host.user; var remoteDir = path.join(user.Directory, 'experiments'); self.notify('info', 'Removing experiment data on ' + ip); return utils.executeOnHost(['rm -rf ' + remoteDir], ip, user); }); return Q.all(tasks) .then(function() { // now stop ROS Bridge if it was started if (self.ROSBridgePID) { return self.stopRosBridge(); } }); }; StopExperiment.prototype.createZip = function() { var self = this; if (!self.returnZip) { self.notify('info','Skipping compression.'); return; } return new Promise(function(resolve, reject) { var zlib = require('zlib'), tar = require('tar'), fstream = require('fstream'), input = self.results_dir; self.notify('info', 'zipping ' + input); var bufs = []; var packer = tar.Pack() .on('error', function(e) { reject(e); }); var gzipper = zlib.Gzip() .on('error', function(e) { reject(e); }) .on('data', function(d) { bufs.push(d); }) .on('end', function() { //self.logger.debug('gzip ended.'); var buf = Buffer.concat(bufs); var name = self.projectName + '+' + self.experimentName + '+Results'; self.blobClient.putFile(name+'.tar.gz',buf) .then(function (hash) { self.result.addArtifact(hash); self.notify('info', 'compression complete'); resolve(); }) .catch(function(err) { reject(err); }) .done(); }); var reader = fstream.Reader({ 'path': input, 'type': 'Directory' }) .on('error', function(e) { reject(e); }); reader .pipe(packer) .pipe(gzipper); }) .then(function() { self.createMessage(self.activeNode, 'Created archive.'); }); }; return StopExperiment; });