diff --git a/test/FileSystemBehaviour/Append.js b/test/FileSystemBehaviour/Append.js new file mode 100644 index 0000000..3deb2ab --- /dev/null +++ b/test/FileSystemBehaviour/Append.js @@ -0,0 +1,16 @@ +var GFS = require('../../lib/things.js').addons.gfs('mongodb://localhost:27017/things-js-test-fs'); + +var args = process.argv.slice(2); +var identifier = args[0]; +var fpath = args[1]; + +console.log('Process ' + identifier + ' starting'); +GFS.appendFile(fpath, ' hello ' + identifier, function(err, res){ + if(err){ + console.log('Error appending on process ' + identifier); + } + process.exit(); +}); + + + diff --git a/test/FileSystemBehaviour/Filesystem-forked-append.js b/test/FileSystemBehaviour/Filesystem-forked-append.js new file mode 100644 index 0000000..593ecbe --- /dev/null +++ b/test/FileSystemBehaviour/Filesystem-forked-append.js @@ -0,0 +1,57 @@ +var GFS = require('../../lib/things.js').addons.gfs('mongodb://localhost:27017/things-js-test-fs'); +var spawn = require('child_process').spawn + +var fname = '/appendtest.txt'; +var spawnFile = './Append.js'; +var numProcs = Number(process.argv.slice(2)[0]) || 3; + +function init(){ + return new Promise(function(resolve){ + var procs = []; + GFS.writeFile(fname, '', function(err){ + if(err){ + console.log('Could not successfully create the test file. Aborting...'); + process.exit(); + } + for(var i = 0; i < numProcs; i++){ + procs.push(spawnChild(i)); + } + Promise.all(procs).then(resolve); + }); + }); +} + +function spawnChild(id){ + return new Promise(function(resolve){ + var child = spawn('node', [spawnFile, id, fname]); + child.on('exit', function(){ + resolve(); + }); + child.stdout.on('data', function(data){ + console.log(data.toString()); + }); + }) +} + +function multiAppend(){ + init().then(function(){ + GFS.readFile(fname, function(err, data){ + if(err){ + console.log('Error occurred: ' + err); + } + else{ + console.log('Here is the result of the file:\n' + data); + GFS.deleteFile(fname, function(err){ + if(err){ + console.log(err); + } + process.exit(); + }); + } + }); + }); +} + +multiAppend(); + + diff --git a/test/FileSystemBehaviour/Filesystem-multi-write.js b/test/FileSystemBehaviour/Filesystem-multi-write.js new file mode 100644 index 0000000..d69af57 --- /dev/null +++ b/test/FileSystemBehaviour/Filesystem-multi-write.js @@ -0,0 +1,47 @@ +var assert = require('assert'); +var thingsjs = require('../lib/things.js'); +var GFS = require('../lib/things.js').addons.gfs('mongodb://localhost:27017/things-js-test-fs'); +var mqtt = require('mqtt'); +var mosca = require('mosca'); +var sinon = require('sinon'); + +var fname = '/test_read.txt'; + + +function timeWrite(writeValue){ + return new Promise(function(resolve){ + var start = Date.now(); + GFS.writeFile(fname, 'hello world', function(err, data){ + var end = Date.now(); + if(err){ + console.log(err); + resolve([err, writeValue+1]); + } + else{ + var elapsed = (end - start) / 1000; + resolve([elapsed, writeValue+1]); + } + }); + }); +} + +function multiWrite(instances){ + var reads = []; + for(var i = 0; i < instances; i++){ + var promise = timeWrite(i).then(function(res){ + var time = (res[0] instanceof Error) ? ('could not calculate due to error\n') : ( res[0] + ' seconds\n'); + console.log('Time it took for write ' + res[1] + ' : ' + time); + }); + reads.push(promise); + } + + Promise.all(reads).then(function(){ + GFS.deleteFile(fname, function(err){ + console.log('Tests completed'); + process.exit(); + }); + }); +} + +multiWrite(10); + diff --git a/test/Filesystem-REST-test.js b/test/Filesystem-REST-test.js new file mode 100644 index 0000000..1cb5f0b --- /dev/null +++ b/test/Filesystem-REST-test.js @@ -0,0 +1,211 @@ +var assert = require('assert'); +var expect = require('chai').expect; +var should = require('chai').should(); +var sinon = require('sinon'); + +var http = require('http'); +var express = require('express'); +var helmet = require('helmet'); +var FSServer = require('../lib/things.js').addons.FSServer; +var mongoclient = require('mongodb').MongoClient; + +describe('REST API', function(){ + var self = this; + this.mongourl = 'mongodb://localhost:27017/things-js-test-fs'; + this.port = 3030; + this.fsurl = 'localhost'; + this.fspath = '/fs'; + this.folders = ['folder1', 'folder2', 'folder3']; + this.files = { + 'file1': 'hello world', + 'file2': '12345' + } + + // initialize the test server + before(function(done){ + this.timeout(10000); + var app = express(); + var router = express.Router(); + + app.use(express.json()); + app.use(helmet()); + app.use(express.urlencoded({ extended: true })); + + var gfs = new FSServer(self.mongourl, router); + app.use(self.fspath, router); + + self.server = http.createServer(app).listen(self.port, done); + }); + + function makehttp(method, path, body){ + console.log(self.fsurl, self.port, path); + var options = { + method: method, + host: self.fsurl, + port: self.port, + path: self.fspath + path, + headers: { + 'Content-Type': 'application/json' + } + } + return new Promise(function(resolve, reject){ + var req = http.request(options, function(res){ + var reply = ''; + res.on('data', function(c){ + reply += c; + }); + res.on('end', function(){ + resolve(JSON.parse(reply)); + }); + }) + .on('error', function(){ + throw new Error(); + }); + if(body){ + req.write(JSON.stringify(body)); + } + req.end(); + }); + } + + it('Fetch the root directory', function(){ + this.timeout(5000); + + return makehttp('GET', '/').then(function(res){ + expect(res.abs_path).to.eql('/'); + }); + }); + + it('Create a folder in the root directory', function(){ + this.timeout(5000); + + var body = { + type: 'directory', + name: self.folders[0] + } + + return makehttp('POST', '/', body).then(function(res){ + expect(res).to.exist; + }); + }); + + it('Fetch the newly created folder', function(){ + this.timeout(5000); + + return makehttp('GET', '/'+self.folders[0]).then(function(res){ + expect(res.name).to.eql(self.folders[0]); + expect(Object.keys(res.children).length).to.eql(0); + }); + }); + + it('Create a file in the root directory', function(){ + this.timeout(5000); + + var fname = Object.keys(self.files)[0]; + var body = { + type: 'file', + name: fname, + content: self.files[fname] + } + + return makehttp('POST', '/', body).then(function(res){ + expect(res).to.exist; + }); + }); + + it('Fetch the newly created file', function(){ + this.timeout(5000); + var fname = Object.keys(self.files)[0]; + + return makehttp('GET', '/'+fname).then(function(res){ + expect(res.name).to.eql(fname); + expect(res.content).to.eql(self.files[fname]); + }); + }); + + it('Create a file in a subdirectory', function(){ + this.timeout(5000); + + var fname = Object.keys(self.files)[1]; + var body = { + type: 'file', + name: fname, + content: self.files[fname] + } + + return makehttp('POST', '/'+self.folders[0], body).then(function(res){ + expect(res).to.exist; + }); + }); + + it('Fetch the file within the subdirectory', function(){ + this.timeout(5000); + var fname = Object.keys(self.files)[1]; + var fpath = '/' + self.folders[0] + '/' + fname; + + return makehttp('GET', fpath).then(function(res){ + expect(res.name).to.eql(fname); + expect(res.content).to.eql(self.files[fname]); + }); + }); + + it('Check that the subdirectory has reference to the new file', function(){ + this.timeout(5000); + var fname = Object.keys(self.files)[1]; + + return makehttp('GET', '/'+self.folders[0]).then(function(res){ + expect(res.children[fname]).to.exist; + }); + }); + + it('Delete a file', function(){ + this.timeout(5000); + var fname = Object.keys(self.files)[0]; + return makehttp('GET', '/'+fname).then(function(res){ + return makehttp('DELETE', '/?ids='+res._id).then(function(data){ + expect(data).to.exist; + }); + }); + }); + + it('Try accessing a deleted file', function(){ + this.timeout(5000); + var fname = Object.keys(self.files)[0]; + + return makehttp('GET', '/'+fname).then(function(res){ + expect(res.error).to.exist; + }); + }); + + it('Delete a non-empty folder', function(){ + this.timeout(5000); + return makehttp('GET', '/'+self.folders[0]).then(function(res){ + return makehttp('DELETE', '/?ids='+res._id).then(function(data){ + expect(data).to.exist; + }); + }); + }); + + it('Try to access a file from within the deleted folder', function(){ + this.timeout(5000); + var fpath = '/' + self.folders[0] + Object.keys(self.files)[1]; + return makehttp('GET', fpath).then(function(res){ + expect(res.error).to.exist; + }); + }); + + + after(function(){ + // drop the entire test db + mongoclient.connect(self.mongourl, { useNewParser: true }, function(err, db){ + db.dropCollection('fsobjects', function(){ + db.close(); + }); + }); + self.server.close(); + }); + +}); + + + diff --git a/test/Filesystem-test.js b/test/Filesystem-test.js new file mode 100644 index 0000000..30392d5 --- /dev/null +++ b/test/Filesystem-test.js @@ -0,0 +1,159 @@ +var assert = require('assert'); +var GFS = require('../lib/things.js').addons.gfs; +var helpers = require('../lib/helpers.js'); +var expect = require('chai').expect; +var should = require('chai').should(); +var fs = require('fs'); +var mqtt = require('mqtt'); +var mosca = require('mosca'); +var sinon = require('sinon'); + + +describe('API tests', function(){ + var self = this; + this.mongourl = 'mongodb://localhost:27017/things-js-fs-test'; + this.fpaths = ['/test.txt', '/append.txt']; + + // initialize the gfs + before(function(done){ + self.gfs = GFS(self.mongourl); + setTimeout(done, 1000); + }); + + /** + * R/W + */ + describe('R/W tests', function(){ + var content = 'hello world'; + + it('Write a new file', function(){ + var fpath = self.fpaths[0]; + + return new Promise(function(resolve){ + self.gfs.writeFile(fpath, Date.now(), function(err){ + resolve(err); + }); + }).then(function(res){ + // no error was thrown + expect(res).to.eql(null); + }) + }); + + it('Read an existing file', function(){ + var fpath = self.fpaths[0]; + + return new Promise(function(resolve){ + self.gfs.readFile(fpath, function(err, data){ + expect(err).to.eql(null); + resolve(data); + }); + }).then(function(res){ + expect(isNaN(res.toString())).to.eql(false); + }); + }); + + it('Read a nonexistent file', function(){ + + return new Promise(function(resolve){ + self.gfs.readFile('NONEXISTENTPATH', function(err, data){ + resolve(err); + }); + }).then(function(res){ + expect(res).to.be.instanceOf(Error); + }) + + }); + + it('Write to an existing file', function(){ + var fpath = self.fpaths[0]; + + return new Promise(function(resolve){ + self.gfs.writeFile(fpath, content, function(err){ + resolve(err); + }); + }).then(function(res){ + expect(res).to.eql(null); + }); + }); + + it('Read back a write update', function(){ + var fpath = self.fpaths[0]; + + return new Promise(function(resolve){ + self.gfs.readFile(fpath, function(err, data){ + expect(err).to.eql(null); + resolve(data); + }); + }).then(function(res){ + expect(res.toString()).to.eql(content); + }); + }); + + }); + + describe('Append tests', function(){ + var str = 'hello*'; + var numAppends = 5; + var fpath = self.fpaths[1]; + + function append(count){ + function readBack(cb){ + self.gfs.readFile(fpath, function(err, data){ + expect(err).to.eql(null); + cb(data.toString()); + }); + } + + it('Append count: ' + count, function(){ + return new Promise(function(resolve){ + self.gfs.appendFile(fpath, str, function(err){ + expect(err).to.eql(null); + readBack(resolve); + }); + }).then(function(res){ + var tokens = res.split('*'); + expect(tokens.length).to.eql(count + 2); + }); + }); + } + + for(j = 0; j < numAppends; j++){ + append(j); + } + }); + + describe('Delete tests', function(){ + + it('Delete a nonexistent file', function(){ + this.timeout(10000); + + return new Promise(function(resolve){ + self.gfs.deleteFile('DNE', function(err){ + resolve(err); + }) + }).then(function(res){ + expect(res).to.be.instanceOf(Error); + }); + }); + + function deleteFile(file){ + function readBack(cb){ + return self.gfs.readFile(file, function(err, data){ + return expect(err).to.be.instanceOf(Error); + }); + } + + it('Delete ' + file, function(){ + return self.gfs.deleteFile(file, function(err){ + expect(err).to.eql(null); + return readBack(); + }); + }); + } + + self.fpaths.forEach(function(filepath){ + deleteFile(filepath); + }); + }); + +}); diff --git a/test/Scheduler-test.js b/test/Scheduler-test.js new file mode 100644 index 0000000..acc7d06 --- /dev/null +++ b/test/Scheduler-test.js @@ -0,0 +1,576 @@ +var assert = require('assert'); +var things = require('../lib/things.js'); +var helpers = require('../lib/helpers.js'); +var expect = require('chai').expect; +var should = require('chai').should(); +var fs = require('fs'); +var mqtt = require('mqtt'); +var mosca = require('mosca'); +var sinon = require('sinon'); + +describe('API methods', function(){ + var self = this; + self.SCHEDULING_INTERVAL = 10000; + + before(function(){ + this.timeout(10000); + + return new Promise(function(resolve){ + self.server = mosca.Server({ port: 1883 }); + self.server.on('ready', function(){ + self.pubsub = new things.Pubsub('mqtt://localhost'); + self.pubsub.on('ready', resolve); + }); + }); + }); + + describe('First-fit scheduling algorithm', function(){ + + it('Base case: devices = [], tasks = [], mapping = {}', function(){ + var new_mapping = things.Scheduler.Algorithms['first_fit']([], [], {}); + expect(new_mapping).to.eql({}); + }); + + it('1 device, 0 tasks', function(){ + var devices = [{ id: 'pi0', available_memory: 150 }]; + var tasks = []; + + var new_mapping = things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + expect(Object.keys(new_mapping['pi0']['processes']).length).to.eql(0); + }) + + it('0 devices, 1 task', function(){ + var devices = []; + var tasks = [{ id: 'A', required_memory: 100 }]; + + var new_mapping = things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + expect(Object.keys(new_mapping).length).to.eql(0); + }); + + it('1 device, 1 task with enough memory', function(){ + var devices = [{ id: 'pi0', available_memory: 150 }]; + var tasks = [{ id: 'A', required_memory: 100 }]; + + var new_mapping = things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + expect(new_mapping['pi0']['processes']['A']).to.exist; + }); + + it('1 device, 1 task with not enough memory', function(){ + var devices = [{ id: 'pi0', avaible_memory: 0 }]; + var tasks = [{ id: 'A', required_memory: 1 }]; + + expect(function(){ + things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + }).to.throw(Error); + }); + + it('1 device, 1 task with memory required_memory very close to available_memory', function(){ + var devices = [{ id: 'pi0', available_memory: 100 }]; + var tasks = [{ id: 'A', required_memory: 99.99 }]; + + var new_mapping = things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + expect(new_mapping['pi0']['processes']['A']).to.exist; + }); + + it('1 device, 1 task with available_memory = required_memory', function(){ + var devices = [{ id: 'pi0', available_memory: 100 }]; + var tasks = [{ id: 'A', required_memory: 100 }]; + + expect(function(){ + things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + }).to.throw(Error); + }); + + it('multi-device scheduling with 1 task', function(){ + var device_ids = ['pi0', 'pi1', 'pi2', 'pi3']; + var devices = []; + var available_memory = 2; + var tasks = [{ id: 'A', required_memory: available_memory -1 }]; + + device_ids.forEach(function(id){ + devices.push({ id: id, available_memory: available_memory }); + available_memory++; + }); + + var new_mapping = things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + expect(new_mapping['pi3']['processes']['A']).to.exist; + + device_ids.forEach(function(id){ + if(id === 'pi3'){ return; } + expect(Object.keys(new_mapping[id]['processes']).length).to.eql(0); + }); + }); + + it('multi-device scheduling with 2 tasks allocated to 1 device', function(){ + var devices = [{ id: 'pi0', available_memory: 100 }, { id: 'pi1', available_memory: 10 }]; + var tasks = [{ id: 'A', required_memory: 50 }, { id: 'B', required_memory: 40 }]; + + var new_mapping = things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + expect(Object.keys(new_mapping['pi0']['processes'])).to.include('A', 'B') + expect(Object.keys(new_mapping['pi1']['processes']).length).to.eql(0); + }); + + it('multi-device scheduling with 1 task per device', function(){ + var devices = [{ id: 'pi0', available_memory: 10}, { id: 'pi1', available_memory: 15 }]; + var tasks = [{ id: 'A', required_memory: 10 }, { id: 'B', required_memory: 5}]; + + var new_mapping = things.Scheduler.Algorithms['first_fit'](devices, tasks, {}); + expect(Object.keys(new_mapping['pi0']['processes'])).to.eql(['B']); + expect(Object.keys(new_mapping['pi1']['processes'])).to.eql(['A']); + }); + }); + + describe('Test for compute actions', function(){ + + it('Base case: current mapping = {}, desired mapping = {}', function(){ + var actions = things.Scheduler.computeActions({}, {}); + expect(actions).to.eql([]); + }); + + it('run tasks', function(){ + var processes = ['A', 'B', 'C']; + var tested_processes = []; + var desired_mapping = { 'pi0': { processes: {} } }; + + processes.forEach(function(id){ + desired_mapping['pi0']['processes'][id] = { code_name: id }; + tested_processes.push(id); + }); + + var actions = things.Scheduler.computeActions({}, desired_mapping); + console.log(actions); + + var programs_to_run = actions.reduce(function(arr, act){ + expect(act.type).to.eql('run'); + arr.push(act.args[1]); // code_name is second arg + return arr; + }, []); + + expect(actions.length).to.eql(tested_processes.length); + expect(programs_to_run).to.include.members(processes); + }); + + it('stop tasks', function(){ + var processes = ['A', 'B', 'C']; + var tested_processes = []; + var current_mapping = { 'pi0': { processes: {} } }; + var to_kill = []; + + processes.forEach(function(id){ + current_mapping['pi0']['processes'][id] = { code_name: id }; + }); + var desired_mapping = JSON.parse(JSON.stringify(current_mapping)); + + processes.forEach(function(killId){ + to_kill.push(killId); + delete desired_mapping['pi0']['processes'][killId]; + + actions = things.Scheduler.computeActions(current_mapping, desired_mapping); + + var programs_to_kill = actions.reduce(function(arr, act){ + expect(act.type).to.eql('kill'); + arr.push(act.args[0]); // code_name is first arg + return arr; + }, []); + + expect(actions.length).to.eql(to_kill.length); + expect(programs_to_kill).to.include.members(to_kill); + }); + }); + + it('migrate tasks', function(){ + var processes = ['A', 'B', 'C']; + var current_mapping = { 'pi0': { processes: {} }, 'pi1': { processes: {} } }; + var to_migrate = []; + + processes.forEach(function(id){ + current_mapping['pi0']['processes'][id] = { code_name: id }; + }); + var desired_mapping = JSON.parse(JSON.stringify(current_mapping)); + + processes.forEach(function(migrateId){ + to_migrate.push(migrateId); + desired_mapping['pi1']['processes'][migrateId] = { code_name: migrateId }; + + actions = things.Scheduler.computeActions(current_mapping, desired_mapping); + + var programs_to_migrate = actions.reduce(function(arr, act){ + expect(act.type).to.eql('migrate'); + arr.push(act.args[2]); // code_name is third arg + return arr; + }, []); + + expect(actions.length).to.eql(to_migrate.length); + expect(programs_to_migrate).to.include.members(to_migrate); + }); + }); + + it('run, kill, and migrate tasks', function(){ + var running = ['A', 'B', 'C']; + var to_kill = 'B'; + var to_migrate = 'C'; + var to_run = 'D'; + + var current_mapping = { 'pi0': { processes: {} }, 'pi1': { processes: {} } }; + running.forEach(function(id){ + current_mapping['pi0']['processes'][id] = { code_name: id }; + }); + var desired_mapping = JSON.parse(JSON.stringify(current_mapping)); + + desired_mapping['pi0']['processes'][to_run] = { code_name: to_run }; + delete desired_mapping['pi0']['processes'][to_kill]; + desired_mapping['pi1']['processes'][to_migrate] = current_mapping['pi0']['processes'][to_migrate]; + delete desired_mapping['pi0']['processes'][to_migrate]; + + actions = things.Scheduler.computeActions(current_mapping, desired_mapping); + + actions.forEach(function(act){ + var args = act.args; + switch(act.type){ + case 'run': + expect(args[0]).to.eql('pi0'); + expect(args[1]).to.eql(to_run); + break; + case 'kill': + expect(args[0]).to.eql(to_kill); + break; + case 'migrate': + expect(args[0]).to.eql('pi0'); + expect(args[1]).to.eql('pi1'); + expect(args[2]).to.eql(to_migrate); + break; + default: + console.log('unexpected arg type: ' + act.type); + } + }); + }); + }); + + describe('Initialization', function(){ + before(function(){ + self.identity = 'TEST-SCHEDULER'; + self.scheduler = new things.Scheduler({ id: self.identity }); + return new Promise(function(resolve){ + self.scheduler.on('ready', resolve); + }); + }); + + it('Has correct configurations', function(){ + expect(self.scheduler.id).to.eql(self.identity); + }); + + it('Boot event logged', function(){ + expect(self.scheduler.history.length).to.be.above(0); + expect(self.scheduler.history[0].type).to.eql('scheduler-event'); + expect(self.scheduler.history[0].data).to.eql({ phase: 'boot' }); + }); + + it('Queue is empty', function(){ + expect(self.scheduler.queue).to.eql([]); + }); + + }); + + describe('Process detection', function(){ + it('Ignore rogue processes', function(){ + this.timeout(10000); + var pubsub = new things.Pubsub(); + var code = things.Code.fromString(pubsub, 'test-rogue', 'console.log(\"test\")\;'); + code.run().then(function(instance){ + instance.on('finished', function(){ + setTimeout(code.kill, 1000); + process.exit(); + }); + }); + + return new Promise(function(resolve){ + self.scheduler._assess().then(function(data){ + resolve(data); + }); + }).then(function(res){ + console.log(res); + expect(res).to.exist; + }); + }); + }); + + describe('Correct view of the network', function(){ + var ENGINE_REPORT = 2000; + var id = 'THIS_ENGINE' + + it('Detects when an engine is dead', function(){ + this.timeout(15000); + + return new Promise(function(resolve){ + var eng = new things.CodeEngine({ id: id }); + eng.on('ready', function(){ + setTimeout(function(){ + eng.kill(); + setTimeout(function(){ + self.scheduler._assess().then(function(data){ + resolve(data); + }); + }, 2*ENGINE_REPORT); + }, ENGINE_REPORT); + }); + }).then(function(data){ + expect(data.engines.length).to.eql(0); + expect(data.mapping).to.not.have.all.keys(id); + }); + }) + }); + + describe('Node failures', function(){ + var engines = []; + var ready = []; + var num_engines = 3; + + before(function(){ + for(var i = 0; i < num_engines; i++){ + var device = new things.CodeEngine({ id: i.toString() }); + engines.push(device); + var is_ready = function(){ + return new Promise(function(resolve){ + device.on('ready', resolve); + }); + } + ready.push(is_ready); + } + return new Promise(function(resolve){ + Promise.all(ready).then(resolve); + }); + }); + + it('Test scheduler does not fail when engines leave the network', function(){ + this.timeout(20000); + return self.scheduler._assess() + .then(function(data){ + expect(Object.keys(data.mapping).length).to.eql(0); + }); + }); + + after(function(){ + engines.forEach(function(device){ + device.kill(); + }); + }) + }); + + describe('Scheduler cmds', function(){ + var engine; + + before(function(done){ + this.timeout(5000); + engine = new things.CodeEngine(); + engine.on('ready', function(){ + setTimeout(done, 2000); + }); + }); + + var counter = "var count = 0\; setInterval(++count, 1000)"; + + var actions = ['pause_application', 'resume_application', 'kill_application']; + + function schedule(ctrl, kwargs){ + var reqId = helpers.randKey(); + + var req = { + ctrl: ctrl, + kwargs: kwargs, + request_id: reqId, + reply_to: reqId, + + } + self.pubsub.publish(self.identity + '/cmd', req); + return reqId; + } + + it('Schedule an application', function(){ + this.timeout(10000); + var app = { + components: { + 'comp0': { count: 1, source: counter.toString(), required_memory: 1 } + } + } + return new Promise(function(resolve){ + var listen = schedule('run_application', app); + self.pubsub.subscribe(listen, function(res){ + self.pubsub.unsubscribe(listen); + resolve(res); + }) + }).then(function(data){ + expect(data.payload.token).to.exist; + self.token = data.payload.token; + }); + }); + + function makeAppRequest(action){ + it(action, function(){ + this.timeout(10000); + + if(!self.token){ + this.skip(); + } + var req = { + token: self.token + } + return new Promise(function(resolve){ + var listen = schedule(action, req); + self.pubsub.subscribe(listen, function(res){ + self.pubsub.unsubscribe(listen); + resolve(res); + }); + }).then(function(data){ + expect(data.payload).to.exist; + }); + }); + } + + actions.forEach(function(appCtrl){ + makeAppRequest(appCtrl); + }); + + after(function(){ + engine.kill(); + }); + + }); + + describe('Test scheduling an application', function(){ + + var simple_function = 'console.log(\"Testing\")'; + + function generate_app(app_conf){ + var request_token = Math.ceil(Math.random() * 100).toString(); + var app_request = { + ctrl: 'run_application', + kwargs: app_conf, + reply_to: request_token, + request_id: request_token + } + return app_request; + } + + it('Schedule an application that should fail (no engines)', function(){ + this.timeout(20000); + var callback = sinon.fake(); + var app = { components: { '1': { source: simple_function, count: 1 } } }; + var request = generate_app(app); + self.pubsub.subscribe(request.reply_to, callback); + + /* currently assume that the application fails from the + * client-side if there is no response after x time + */ + return new Promise(function(resolve){ + self.pubsub.publish(self.identity + '/cmd', request); + setTimeout(resolve, 2000); + }).then(function(){ + expect(callback.called).to.eql(false); + }); + }) + + it('Schedule an empty application', function(){ + this.timeout(20000); + var callback = sinon.fake(); + var engine; + var app = { components: {} }; + var request = generate_app(app); + self.pubsub.subscribe(request.reply_to, callback); + + return new Promise(function(resolve){ + engine = new things.CodeEngine({ id: 'TEST-EMPTY' }); + engine.on('ready', function(){ + setTimeout(function(){ + self.pubsub.publish(self.identity + '/cmd', request); + setTimeout(resolve, 2000); + }, 2000); + }); + }).then(function(){ + engine.kill(); + expect(callback.called).to.eql(true); + }); + }); + + + it('Schedule an application', function(){ + this.timeout(20000); + var engine; + var callback = sinon.fake(); + + return new Promise(function(resolve){ + var app = { + components: { + '0': { source: simple_function, count: 1 } + } + }; + var request = generate_app(app); + engine = new things.CodeEngine({ id: 'TEST-ENGINE '}); + engine.on('ready', function(){ + setTimeout(function(){ + self.pubsub.publish(self.identity + '/cmd', request); + setTimeout(resolve, 5000); + }, 2000); + }); + self.pubsub.subscribe(request.reply_to, callback); + + }).then(function(){ + engine.kill(); + expect(callback.called).to.eql(true); + }); + }); + + it('Test migration', function(){ + this.skip(); + this.timeout(30000); + + var callback = sinon.fake(); + var engines = []; + var dev_one = 'DEV1'; + var dev_two = 'DEV2'; + var app = { + components: { + 'bar': { source: simple_function, count: 1 }, + 'foo': { source: simple_function, count: 1 } + } + }; + var request = generate_app(app); + + return new Promise(function(resolve){ + var init = function(){ + second_device = new things.CodeEngine({ id: dev_two }); + engines.push(second_device); + second_device.on('ready', function(){ + setTimeout(function(){ + self.scheduler._assess().then(function(data){ + resolve(data); + }); + }, self.SCHEDULING_INTERVAL); + }); + } + + first_device = new things.CodeEngine({ id: dev_one }); + engines.push(first_device); + self.pubsub.subscribe(request.reply_to, init); + first_device.on('ready', function(){ + setTimeout(function(){ + self.pubsub.publish(self.identity + '/cmd', request); + }, 2000); + }); + + }).then(function(res){ + expect(Object.keys(res.mapping[dev_one]['processes']).length).to.eql(1); + expect(Object.keys(res.mapping[dev_two]['processes']).length).to.eql(1); + }); + }); + + after(function(){ + self.engine = undefined; + }); + }); + + after(function(){ + self.pubsub.kill(); + self.server.close(); + }); + +}); \ No newline at end of file