Skip to content

Commit

Permalink
#192 Add ExecutorPlugin
Browse files Browse the repository at this point in the history
  • Loading branch information
pmeijer committed Feb 26, 2015
1 parent a7751c7 commit b72f467
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/*globals define*/
/*jshint node: true, browser: true*/

/**
* Simple plugin illustrating how to configure a configuration for the plugin and how to generate
* and return artifacts from a plugin.
*
* @author pmeijer /
* @author pmeijer / https://github.com/pmeijer
*/

define(['plugin/PluginConfig',
Expand Down
259 changes: 259 additions & 0 deletions src/plugin/coreplugins/ExecutorPlugin/ExecutorPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/*globals define*/
/*jshint node:true, browser:true*/

/**
*
* @author pmeijer / https://github.com/pmeijer
*/

define(['plugin/PluginConfig',
'plugin/PluginBase',
'executor/ExecutorClient',
'ejs',
'plugin/ExecutorPlugin/ExecutorPlugin/Templates/Templates'],
function (PluginConfig, PluginBase, ExecutorClient, ejs, TEMPLATES) {
'use strict';

/**
* Initializes a new instance of ExecutorPlugin.
* @class
* @augments {PluginBase}
* @classdesc This class represents the plugin ExecutorPlugin.
* @constructor
*/
var ExecutorPlugin = function () {
// Call base class' constructor.
PluginBase.call(this);
};

// Prototypal inheritance from PluginBase.
ExecutorPlugin.prototype = Object.create(PluginBase.prototype);
ExecutorPlugin.prototype.constructor = ExecutorPlugin;

/**
* Gets the name of the ExecutorPlugin.
* @returns {string} The name of the plugin.
* @public
*/
ExecutorPlugin.prototype.getName = function () {
return 'Executor Plugin';
};

/**
* Gets the semantic version (semver.org) of the ExecutorPlugin.
* @returns {string} The version of the plugin.
* @public
*/
ExecutorPlugin.prototype.getVersion = function () {
return '0.1.0';
};

/**
* Gets the description of the ExecutorPlugin.
* @returns {string} The description of the plugin.
* @public
*/
ExecutorPlugin.prototype.getDescription = function () {
return 'Plugin using the Executor Client';
};

ExecutorPlugin.prototype.getConfigStructure = function () {
return [
{
'name': 'pythonCmd',
'displayName': 'Python path',
'description': 'How Python is executed',
'value': 'C:/Python27/python.exe',
'valueType': 'string',
'readOnly': false
},
{
'name': 'update',
'displayName': 'Write back to model',
'description': 'If false no need to provide active node.',
'value': true,
'valueType': 'boolean',
'readOnly': false
},
{
'name': 'success',
'displayName': 'Should succeed',
'description': 'Should the execution exit with 0?',
'value': true,
'valueType': 'boolean',
'readOnly': false
},
{
'name': 'time',
'displayName': 'Execution time [s]',
'description': 'How long should the script run?',
'value': 1,
'valueType': 'number',
'minValue': 0.1,
'maxValue': 10000,
'readOnly': false
}
];
};

/**
* 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
*/
ExecutorPlugin.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,
config = self.getCurrentConfig(),
exitCode = config.success ? 0 : 1,
executorConfig = {
cmd: config.pythonCmd + ' generate_name.py ', // This is the command that will be executed on the worker
resultArtifacts: [ // These are the results that will be returned
{
name: 'all',
resultPatterns: []
},
{
name: 'logs',
resultPatterns: ['log/**/*']
},
{
name: 'resultFile',
resultPatterns: ['new_name.json']
}
]
},
filesToAdd,
artifact;

// This is just to track the current node path in the result from the execution.
if (config.update) {
if (!self.activeNode) {
callback('No activeNode specified! Set update to false or invoke from a node.', self.result);
return;
}
executorConfig.cmd += self.core.getPath(self.activeNode);
} else {
executorConfig.cmd += 'dummy';
}

// The hash of the artifact with these files will define the job. N.B. executor_config.json must exist.
filesToAdd = {
'executor_config.json': JSON.stringify(executorConfig, null, 4),
'generate_name.py': ejs.render(TEMPLATES['generate_name.py.ejs'], {
exitCode: exitCode,
sleepTime: config.time
})
};

artifact = self.blobClient.createArtifact('executionFiles');

artifact.addFiles(filesToAdd, function (err) {
if (err) {
callback(err, self.result);
return;
}
artifact.save(function (err, hash) {
var executorClient;
if (err) {
callback(err, self.result);
return;
}
self.result.addArtifact(hash);
executorClient = new ExecutorClient();
// Here the hash of the artifact is passed to the new job.
executorClient.createJob({hash: hash}, function (err, jobInfo) {
var intervalID,
atSucceedJob;
if (err) {
callback('Creating job failed: ' + err.toString(), self.result);
return;
}
self.logger.debug(jobInfo);
// This will be called after a succeed job

intervalID = setInterval(function () {
// Get the job-info at intervals and check for a non-CREATED/RUNNING status.
executorClient.getInfo(hash, function (err, jInfo) {
var key;
self.logger.info(JSON.stringify(jInfo, null, 4));
if (jInfo.status === 'CREATED' || jInfo.status === 'RUNNING') {
// The job is still running..
return;
}

clearInterval(intervalID);
if (jInfo.status === 'SUCCESS') {
self.atSucceedJob(jInfo, callback);
} else {
//Add the resultHashes even though job failed (for user to debug).
for (key in jInfo.resultHashes) {
if (jInfo.resultHashes.hasOwnProperty(key)) {
self.result.addArtifact(jInfo.resultHashes[key]);
}
}
callback('Job execution failed', self.result);
}
});
}, 400);
});
});
});
};

ExecutorPlugin.prototype.atSucceedJob = function (jobInfo, mainCallback) {
var self = this;
//After the job has been executed jobInfo will contain the result hashes.
self.blobClient.getMetadata(jobInfo.resultHashes.resultFile, function (err, metaData) {
var newNameJsonHash;
if (err) {
mainCallback('Getting results metadata failed: ' + err.toString(), self.result);
return;
}
newNameJsonHash = metaData.content['new_name.json'].content;
self.blobClient.getObject(newNameJsonHash, function (err, newName) {
var key;
if (err) {
mainCallback('Getting content failed: ' + err.toString(), self.result);
return;
}
if (self.getCurrentConfig().update) {
for (key in newName) {
if (newName.hasOwnProperty(key)) {
self.core.setAttribute(self.activeNode, 'name', newName[key]);
}
}
for (key in jobInfo.resultHashes) {
if (jobInfo.resultHashes.hasOwnProperty(key)) {
self.result.addArtifact(jobInfo.resultHashes[key]);
}
}

self.save('Updated new name from execution', function (err) {
if (err) {
mainCallback(err, self.result);
return;
}
self.result.setSuccess(true);
mainCallback(null, self.result);
});
} else {
for (key in jobInfo.resultHashes) {
if (jobInfo.resultHashes.hasOwnProperty(key)) {
self.result.addArtifact(jobInfo.resultHashes[key]);
}
}
self.result.setSuccess(true);
mainCallback(null, self.result);
}
});
});
};

return ExecutorPlugin;
});
5 changes: 5 additions & 0 deletions src/plugin/coreplugins/ExecutorPlugin/Templates/Templates.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (C) 2014 Vanderbilt University, All rights reserved.
*
* Author: Zsolt Lattmann, Patrik Meijer
*
* This script will combine all ejs files in the current directory (recursively)
* into one Templates.js file. By importing this file as TEMPLATE you can retrieve the
* content of each original ejs file through TEMPLATES['plugin.js.ejs'].
*
* Usage: Run this script in the directory with the ejs-templates, e.g. '%YourPlugin%/Templates'.
*/

var main = function () {
'use strict';
var fs = require('fs'),
isEjsFile = function (str) {
var ending = '.ejs',
lastIndex = str.lastIndexOf(ending);
return (lastIndex !== -1) && (lastIndex + ending.length === str.length);
},
walk = function (dir, done) {
var results = [];
fs.readdir(dir, function (err, list) {
if (err) {
return done(err);
}
var i = 0;
(function next() {
var file = list[i];
if (!file) {
return done(null, results);
}
i += 1;
file = dir + '/' + file;
fs.stat(file, function (err, stat) {
if (stat && stat.isDirectory()) {
walk(file, function (err, res) {
results = results.concat(res);
next();
});
} else {
results.push(file);
next();
}
});
})();
});
},
content = {},
fileName,
i,
templateContent;

walk('.', function (err, results) {
if (err) {
throw err;
}

for (i = 0; i < results.length; i += 1) {
fileName = results[i];
console.info(fileName);
if (isEjsFile(fileName)) {
console.info('Was ejs -> added!');
content[fileName.substring(2)] = fs.readFileSync(fileName, {'encoding': 'utf-8'});
}
}

console.info(content);
templateContent = '';
templateContent += '/* Generated file based on ejs templates */\r\n';
templateContent += 'define([], function() {\r\n';
templateContent += ' return ' + JSON.stringify(content, null, 4);
templateContent += '});';

fs.writeFileSync('Templates.js', templateContent);
console.info('Created Templates.js');
});
};

if (require.main === module) {
main();
}
Loading

0 comments on commit b72f467

Please sign in to comment.