UNPKG

webgme-gridlabd

Version:

Metamodel, visualization, and model generators for gridlab-d in WebGME. Allows graphical model-driven development and simulation of power grids and power generation / transmission / distribution / storage systems.

650 lines (579 loc) 25.7 kB
/*globals define*/ /*jshint node:true, browser:true*/ /** * Generated by PluginGenerator 0.14.0 from webgme on Mon Jun 20 2016 10:43:18 GMT-0500 (Central Daylight Time). */ define([ 'plugin/PluginConfig', 'plugin/PluginBase', 'text!./metadata.json', 'common/util/ejs', // for ejs templates 'common/util/xmljsonconverter', // used to save model as json 'plugin/SimulateTESCluster/SimulateTESCluster/Templates/Templates', 'plugin/SimulateTESCluster/SimulateTESCluster/SimulateTESCluster.Parser', 'plugin/SimulateTESCluster/SimulateTESCluster/SimulateTESCluster.Plotter', 'text!./task_template.json.tpl', 'gridlabd/meta', 'gridlabd/modelLoader', 'gridlabd/remote_utils', 'gridlabd/renderer', 'q' ], function ( PluginConfig, PluginBase, pluginMetadata, ejs, Converter, TEMPLATES, Parser, Plotter, marathonTaskTemplate, MetaTypes, loader, utils, renderer, Q) { 'use strict'; pluginMetadata = JSON.parse(pluginMetadata); // fixed vars var marathonIP = "129.59.107.73"; var marathonUser = 'ubuntu'; var marathonKey = '/home/c2wt/.ssh/id_rsa_marathon'; var marathonUrl = "10.100.0.11"; var inputfilesServerHost = "10.100.0.11"; var inputfilesServerPort = 8081; var LATEST_DOCKER_TAG = { JAVA: 'v5', CPP: 'v2', OMNET: 'v3' } var logPathBase = "/mnt/nfs/demo-share/"; /** * Initializes a new instance of SimulateTESCluster. * @class * @augments {PluginBase} * @classdesc This class represents the plugin SimulateTESCluster. * @constructor */ var SimulateTESCluster = function () { // Call base class' constructor. PluginBase.call(this); this.pluginMetadata = pluginMetadata; this.metaTypes = MetaTypes; }; SimulateTESCluster.metadata = pluginMetadata; // Prototypal inheritance from PluginBase. SimulateTESCluster.prototype = Object.create(PluginBase.prototype); SimulateTESCluster.prototype.constructor = SimulateTESCluster; SimulateTESCluster.prototype.notify = function(level, msg) { var self = this; var prefix = self.projectId + '::' + self.projectName + '::' + level + '::'; var max_msg_len = 100; if (level=='error') self.logger.error(msg); else if (level=='debug') self.logger.debug(msg); else if (level=='info') self.logger.info(msg); else if (level=='warning') self.logger.warn(msg); self.createMessage(self.activeNode, msg, level); if (msg.length < max_msg_len) self.sendNotification(prefix+msg); else { var splitMsgs = utils.chunkString(msg, max_msg_len); splitMsgs.map(function(splitMsg) { self.sendNotification(prefix+splitMsg); }); } }; /** * 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 */ SimulateTESCluster.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, modelNode; self.result.success = false; if (typeof WebGMEGlobal !== 'undefined') { var msg = 'You must run this plugin on the server!'; self.notify('error', msg); callback(new Error(msg), self.result); } self.updateMETA(self.metaTypes); // What did the user select for our configuration? var currentConfig = self.getCurrentConfig(); self.simulationTime = currentConfig.simulationTime; self.delayMean = currentConfig.delayMean; self.delayStdDev = currentConfig.delayStdDev; self.community1 = currentConfig.community1; self.community2 = currentConfig.community2; self.generator1 = currentConfig.generator1; self.generator2 = currentConfig.generator2; self.returnZip = currentConfig.returnZip; var path = require('path'); var filendir = require('filendir'); self.root_dir = path.join(process.cwd(), 'generated', self.project.projectId, self.branchName, 'models'); // FEDERATION STUFF // should be unique! var timestamp = (new Date()).getTime(); var fedNumber = Math.floor(Math.random() * 250 + 1); var federateGroupName = "tesdemo2016-" + fedNumber; var weaveNet = "10.33."+ fedNumber + ".0"; var federateFolder = "tesdemo2016_"+fedNumber+"_"+timestamp; self.generationDir = path.join(self.root_dir, federateGroupName); self.remoteInputDir = "/home/ubuntu/demo-inputs/"+federateGroupName; self.remoteOutputDir = "/home/ubuntu/demo-outputs/"+federateGroupName + "/" + federateFolder; self.federateGroupName = federateGroupName; self.weaveNet = weaveNet; self.federateFolder = federateFolder; // END FEDERATION STUFF modelNode = self.activeNode; self.modelName = self.core.getAttribute(modelNode, 'name'); self.fileName = self.modelName + '.glm'; return loader.loadModel(self.core, modelNode, true, true) .then(function(powerModel) { self.powerModel = powerModel; }) .then(function() { return self.renderModel(); }) .then(function() { return self.writeInputs(); }) .then(function() { return self.copyInputs(); }) .then(function() { return self.runSimulation(); }) .then(function() { return self.copyArtifacts(); }) .then(function() { return self.plotLogs(); }) .then(function() { self.result.success = true; self.notify('info', 'Simulation Complete.'); callback(null, self.result); }) .catch(function(err) { self.notify('error', err); self.result.success = false; callback(err, self.result); }); }; SimulateTESCluster.prototype.renderModel = function() { var self = this; self.notify('info', 'Rendering GLM'); self.fileData = renderer.renderGLM(self.powerModel, self.core, self.META); }; SimulateTESCluster.prototype.getFederationManagerTaskData = function() { var Mustache = require('mustache'); var path = require('path'); var federationManagerData = { federateGroupName: this.federateGroupName, federateName: "federation-manager", dockerExecuteScript: "/root/Projects/c2wt/generated/TES2016Demo/scripts/docker-scripts/baseline-Dep/docker-execute--FederationManager.sh", cpu: 0.6, mem: 1024, dockerImageName: "c2wtng/tesdemo2016_java", dockerImageTag: LATEST_DOCKER_TAG.JAVA, dockerHostName: "fedmgr.net."+this.federateGroupName, marathonUrl: marathonUrl, inputfilesServerHost: inputfilesServerHost, inputfilesServerPort: inputfilesServerPort, weaveNet: this.weaveNet, inputfilesList: "inputs/"+this.federateGroupName+"/script.xml", dockerVolumeLogPath: logPathBase+this.federateGroupName+"/"+this.federateFolder+"/fedmgr" }; var taskData = Mustache.render(marathonTaskTemplate, federationManagerData); return taskData; }; SimulateTESCluster.prototype.getCommunityDemandControllerTaskData = function(idx) { var Mustache = require('mustache'); var path = require('path'); var communityDemandControllerData = { federateGroupName: this.federateGroupName, federateName: "community"+idx+"-demandcontroller", dockerExecuteScript: "/root/Projects/c2wt/generated/TES2016Demo/scripts/docker-scripts/baseline-Dep/docker-execute--Community"+idx+"DemandController.sh && sleep 20", cpu: 0.4, mem: 512, dockerImageName: "c2wtng/tesdemo2016_cpp", dockerImageTag: LATEST_DOCKER_TAG.CPP, dockerHostName: "c"+idx+"demandctrl.net."+this.federateGroupName, marathonUrl: marathonUrl, inputfilesServerHost: inputfilesServerHost, inputfilesServerPort: inputfilesServerPort, weaveNet: this.weaveNet, inputfilesList: "inputs/"+this.federateGroupName+"/Community"+idx+"DemandController.config", dockerVolumeLogPath: logPathBase+this.federateGroupName+"/"+this.federateFolder+"/communitydemandcontroller"+idx }; var taskData = Mustache.render(marathonTaskTemplate, communityDemandControllerData); return taskData; }; SimulateTESCluster.prototype.getGeneratorPriceControllerTaskData = function(idx) { var Mustache = require('mustache'); var path = require('path'); var generatorPriceControllerData = { federateGroupName: this.federateGroupName, federateName: "generator"+idx+"-pricecontroller", dockerExecuteScript: "/root/Projects/c2wt/generated/TES2016Demo/scripts/docker-scripts/baseline-Dep/docker-execute--Generator"+idx+"PriceController.sh && sleep 20", cpu: 0.4, mem: 512, dockerImageName: "c2wtng/tesdemo2016_cpp", dockerImageTag: LATEST_DOCKER_TAG.CPP, dockerHostName: "gpc"+idx+".net."+this.federateGroupName, marathonUrl: marathonUrl, inputfilesServerHost: inputfilesServerHost, inputfilesServerPort: inputfilesServerPort, weaveNet: this.weaveNet, inputfilesList: "inputs/"+this.federateGroupName+"/Generator"+idx+"PriceController.config", dockerVolumeLogPath: logPathBase+this.federateGroupName+"/"+this.federateFolder+"/generatorpricecontroller"+idx }; var taskData = Mustache.render(marathonTaskTemplate, generatorPriceControllerData); return taskData; }; SimulateTESCluster.prototype.getGridlabdTaskData = function() { var Mustache = require('mustache'); var path = require('path'); var gridlabdData = { federateGroupName: this.federateGroupName, federateName: "gridlabd", dockerExecuteScript: "/root/Projects/c2wt/generated/TES2016Demo/scripts/docker-scripts/baseline-Dep/docker-execute--GridlabDFederate.sh && sleep 20", cpu: 0.6, mem: 768, dockerImageName: "c2wtng/tesdemo2016_cpp", dockerImageTag: LATEST_DOCKER_TAG.CPP, dockerHostName: "gridlabd.net."+this.federateGroupName, marathonUrl: marathonUrl, inputfilesServerHost: inputfilesServerHost, inputfilesServerPort: inputfilesServerPort, weaveNet: this.weaveNet, inputfilesList: "inputs/"+this.federateGroupName+"/model.glm", dockerVolumeLogPath: logPathBase+this.federateGroupName+"/"+this.federateFolder+"/gridlabd" }; var taskData = Mustache.render(marathonTaskTemplate, gridlabdData); return taskData; }; SimulateTESCluster.prototype.getMapperTaskData = function() { var Mustache = require('mustache'); var mapperData = { federateGroupName: this.federateGroupName, federateName: "mapper", dockerExecuteScript: "/root/Projects/c2wt/generated/TES2016Demo/scripts/docker-scripts/baseline-Dep/docker-execute--Mapper.sh && sleep 20", cpu: 0.6, mem: 1024, dockerImageName: "c2wtng/tesdemo2016_java", dockerImageTag: LATEST_DOCKER_TAG.JAVA, dockerHostName: "mapper.net."+this.federateGroupName, marathonUrl: marathonUrl, inputfilesServerHost: inputfilesServerHost, inputfilesServerPort: inputfilesServerPort, weaveNet: this.weaveNet, inputfilesList: "", dockerVolumeLogPath: logPathBase+this.federateGroupName+"/"+this.federateFolder+"/mapper" }; var taskData = Mustache.render(marathonTaskTemplate, mapperData); return taskData; }; SimulateTESCluster.prototype.getOmnetTaskData = function() { var Mustache = require('mustache'); var omnetData = { federateGroupName: this.federateGroupName, federateName: "omnet", dockerExecuteScript: "/root/Projects/c2wt/generated/TES2016Demo/scripts/docker-scripts/baseline-Dep/docker-execute--OmnetFederate.sh && sleep 20", cpu: 0.6, mem: 1024, dockerImageName: "c2wtng/tesdemo2016_omnet", dockerImageTag: LATEST_DOCKER_TAG.OMNET, dockerHostName: "omnet.net."+this.federateGroupName, marathonUrl: marathonUrl, inputfilesServerHost: inputfilesServerHost, inputfilesServerPort: inputfilesServerPort, weaveNet: this.weaveNet, inputfilesList: "", dockerVolumeLogPath: logPathBase+this.federateGroupName+"/"+this.federateFolder+"/omnet" }; var taskData = Mustache.render(marathonTaskTemplate, omnetData); return taskData; }; SimulateTESCluster.prototype.writeInputs = function() { var self = this, basePath = self.generationDir, inputFiles = { "model.glm": self.fileData, "Community1DemandController.config": JSON.stringify({ "Threshold": +self.community1 }, null, 2), "Community2DemandController.config": JSON.stringify({ "Threshold": +self.community2 }, null, 2), "Generator1PriceController.config": JSON.stringify({ "Threshold": +self.generator1 }, null, 2), "Generator2PriceController.config": JSON.stringify({ "Threshold": +self.generator2 }, null, 2), "script.xml": ejs.render( TEMPLATES['script.xml.ejs'], { simEnd: self.simulationTime, delayMean: self.delayMean, delayStdDev: self.delayStdDev }) }, fs = require('fs'), path = require('path'), filendir = require('filendir'); self.notify('info', 'Creating input files'); var fileNames = Object.keys(inputFiles); var tasks = fileNames.map((fileName) => { var deferred = Q.defer(); var data = inputFiles[fileName]; filendir.writeFile(path.join(basePath, fileName), data, (err) => { if (err) { deferred.reject('Couldnt write ' + fileName + ': ' + err); } else { deferred.resolve(); } }); return deferred.promise; }); return Q.all(tasks) .then(function() { self.notify('info', 'Generated artifacts.'); }); }; SimulateTESCluster.prototype.copyInputs = function() { var self = this, srcDir = self.generationDir, dstDir = self.remoteInputDir, host = marathonIP, user = marathonUser, key = marathonKey; self.notify('info', 'Copying inputs to marathon'); // scp the inputs over into the host return utils.mkdirRemote(dstDir, host, user, key) .then(function () { return utils.copyToHost(srcDir, dstDir, host, user, key); }); }; SimulateTESCluster.prototype.runSimulation = function() { var self = this; self.notify('info', 'Starting Simulation'); return self.startFederates() .then(function() { return self.monitorContainers(); }); }; SimulateTESCluster.prototype.startFederates = function() { // run-cpp-feds.sh var self = this; var federationManagerTaskJSON = self.getFederationManagerTaskData(); var community1DemandControllerTaskJSON = self.getCommunityDemandControllerTaskData(1); var community2DemandControllerTaskJSON = self.getCommunityDemandControllerTaskData(2); var generator1PriceControllerTaskJSON = self.getGeneratorPriceControllerTaskData(1); var generator2PriceControllerTaskJSON = self.getGeneratorPriceControllerTaskData(2); var gridlabdTaskJSON = self.getGridlabdTaskData(); var mapperTaskJSON = self.getMapperTaskData(); var omnetTaskJSON = self.getOmnetTaskData(); /* self.logger.info(federationManagerTaskJSON); self.logger.info(community1DemandControllerTaskJSON); self.logger.info(community2DemandControllerTaskJSON); self.logger.info(generator1PriceControllerTaskJSON); self.logger.info(generator2PriceControllerTaskJSON); self.logger.info(gridlabdTaskJSON); */ self.notify('info', 'POSTing config data to marathon'); var sleep = function(seconds) { var deferred = Q.defer(); //self.notify('info', 'sleeping for ' + seconds + ' seconds'); setTimeout(function() { deferred.resolve(); }, seconds*1000); return deferred.promise; } var host = marathonIP; var port = 8080; var path = '/v2/apps'; self.notify('info', 'POSTing FedMgr data'); return utils.POST(host, port, path, federationManagerTaskJSON) .then(function(body) { //self.notify('info', 'Response: ' + body); return sleep(1); }) .then(function() { self.notify('info', 'POSTing Community1DemandController data'); return utils.POST(host, port, path, community1DemandControllerTaskJSON); }) .then(function(body) { //self.notify('info', 'Response: ' + body); return sleep(2); }) .then(function() { self.notify('info', 'POSTing Community2DemandController data'); return utils.POST(host, port, path, community2DemandControllerTaskJSON); }) .then(function(body) { //self.notify('info', 'Response: ' + body); return sleep(2); }) .then(function() { self.notify('info', 'POSTing Generator1PriceController data'); return utils.POST(host, port, path, generator1PriceControllerTaskJSON); }) .then(function(body) { //self.notify('info', 'Response: ' + body); return sleep(2); }) .then(function() { self.notify('info', 'POSTing Generator2PriceController data'); return utils.POST(host, port, path, generator2PriceControllerTaskJSON); }) .then(function(body) { //self.notify('info', 'Response: ' + body); return sleep(2); }) .then(function() { self.notify('info', 'POSTing GridlabD data'); return utils.POST(host, port, path, gridlabdTaskJSON); }) .then(function(body) { //self.notify('info', 'Response: ' + body); return sleep(2); }) .then(function() { self.notify('info', 'POSTing Mapper data'); return utils.POST(host, port, path, mapperTaskJSON); }) .then(function(body) { //self.notify('info', 'Response: ' + body); return sleep(2); }) .then(function() { self.notify('info', 'POSTing Omnet data'); return utils.POST(host, port, path, omnetTaskJSON); }); }; SimulateTESCluster.prototype.monitorContainers = function() { var self = this; var host = marathonIP; var port = 8080; var path = '/v2/groups/' + this.federateGroupName; // curl http://demo-c2wt-master:8080/v2/groups/{{ // federateGroupName }} -- if you're polling this, it'll give // you results back while running. when it's done, should give // back a reply like this: {"message":"Group '/tesdemo2016' // does not exist"}% var deferred = Q.defer(); var queryFunc = function() { utils.GET(host, port, path) .then(function(result) { if (result.indexOf('does not exist') == -1) { setTimeout(queryFunc, 1000); } else { deferred.resolve(); } }); }; self.notify('info', 'Monitoring ' + this.federateGroupName); queryFunc(); return deferred.promise; }; SimulateTESCluster.prototype.copyArtifacts = function() { var self = this; var path = require('path'); var localPath = path.join(self.generationDir,'outputs'); var remotePath = self.remoteOutputDir + '/*'; var host = marathonIP; var user = marathonUser; var key = marathonKey; self.notify('info', 'Copying output.'); return utils.copyFromHost(remotePath, localPath, host, user, key) .then(function() { return new Promise(function(resolve, reject) { var zlib = require('zlib'), tar = require('tar'), fstream = require('fstream'), input = localPath; 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() { var buf = Buffer.concat(bufs); self.blobClient.putFile('output.tar.gz',buf) .then(function (hash) { self.result.addArtifact(hash); 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.notify('info', 'Created archive.'); }); }; SimulateTESCluster.prototype.plotLogs = function() { var self = this; var path = require('path'); var fs = require('fs'); var basePath = path.join(self.generationDir, 'outputs'); var controllers = [ "Community1DemandController", "Community2DemandController", "Generator1PriceController", "Generator2PriceController" ]; self.notify('info', 'Plotting logs.'); var tasks = controllers.map((controller) => { var fileName = path.join(basePath, controller + '.log'); var deferred = Q.defer(); // load the file fs.readFile(fileName, (err, data) => { if (err) { deferred.reject('Couldnt open ' + fileName + ': ' + err); return; } var logData = Parser.getDataFromLog(data); Plotter.logger = self.logger; Plotter.plotData(logData) .then((svgHtml) => { var resultFileName = controller + '.svg'; self.blobClient.putFile(resultFileName, svgHtml) .then((hash) => { self.result.addArtifact(hash); var resultUrl = '/rest/blob/download/' + hash + '/' + resultFileName; self.createMessage(self.activeNode, controller + ' log plot:' + svgHtml, 'info'); deferred.resolve(); }) .catch((err) => { deferred.reject('Couldnt add ' + resultFileName +' to blob'); }); }); }); return deferred.promise; }); return Q.all(tasks); }; return SimulateTESCluster; });