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
JavaScript
/*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;
});