- new consequent error and callback handling starts to manifest in the code, still a lot of work required!

- unit testing seems a bit more complex because of the dependencies, this has to be started before solving above point because it will give hints to better loose coupling
This commit is contained in:
Dominic Bosch 2013-11-13 23:31:43 +01:00
parent d01b40d5a2
commit b743a880e4
7 changed files with 284 additions and 196 deletions

View file

@ -54,29 +54,29 @@ Create the doc *(to be accessed via the webserver, e.g.: localhost:8125/doc/)*:
_
## TODO
* [ ] Redis queue
* [ ] user handling (personal credentials)
* [ ] security in terms of users (private key, login)
* [ ] vm for modules, only give few libraries (no fs!)
* [ ] rules generator (provide webpage that is used to create rules dependent on the existing modues)
* [ ] geo location module, test on smartphone.
TODO
----
* Redis queue
* user handling (personal credentials)
* security in terms of users (private key, login)
* vm for modules, only give few libraries (no fs!)
* rules generator (provide webpage that is used to create rules dependent on the existing modues)
* geo location module, test on smartphone.
_
## TODO per module
TODO per module
---------------
Testing | clean documentation | Clean error handling (Especially in loading of modules and their credentials):
* [ ] DB Interface
* [ ] Engine
* [ ] Event Poller
* [ ] HTTP Listener
* [ ] Logging
* [ ] Module Loader
* [ ] Module Manager
* [ ] Server
* DB Interface
* Engine
* Event Poller
* HTTP Listener
* Logging
* Module Loader
* Module Manager
* Server

78
js/config.js Normal file
View file

@ -0,0 +1,78 @@
'use strict';
/**
* command standard config file loading and pass certain property back to the callback
* @param {String} prop
* @param {function} cb
*/
function fetchProp(prop, cb) {
if(typeof cb === 'function') {
exports.getConfig(null, function(err, data) {
if(!err) cb(null, data[prop]);
else cb(err);
});
}
}
/**
*
* @param {String[]} relPath
* @param {Object} cb
*/
exports.getConfig = function(relPath, cb) {
var fs = require('fs'), path = require('path'), log = require('./logging');
if(!relPath) relPath = path.join('config', 'config.json');
fs.readFile(
path.resolve(__dirname, '..', relPath),
'utf8',
function (err, data) {
if (err) {
err.addInfo = 'config file loading';
if(typeof cb === 'function') cb(err);
else log.error('CF', err);
} else {
try {
var config = JSON.parse(data);
if(!config.http_port || !config.db_port || !config.crypto_key) {
var e = new Error('Missing property, requires:\n'
+ ' - http_port\n'
+ ' - db_port\n'
+ ' - crypto_key');
if(typeof cb === 'function') cb(e);
else log.error('CF', e);
} else {
if(typeof cb === 'function') cb(null, config);
else log.print('CF', 'config file loaded successfully but pointless since no callback defined...');
}
} catch(e) {
e.addInfo = 'config file parsing';
log.error('CF', e);
}
}
}
);
};
/**
* Command config file loading and retrieve the http port via the callback.
* @param {function} cb
*/
exports.getHttpPort = function(cb) {
fetchProp('http_port', cb);
};
/**
* Command config file loading and retrieve the DB port via the callback.
* @param {function} cb
*/
exports.getDBPort = function(cb) {
fetchProp('db_port', cb);
};
/**
* Command config file loading and retrieve the crypto key via the callback.
* @param {function} cb
*/
exports.getCryptoKey = function(cb) {
fetchProp('crypto_key', cb);
};

View file

@ -17,24 +17,26 @@ var redis = require('redis'),
crypto_key, db;
// @function init()
/*
* Initializes the DB connection. Requires a port where the DB listens to requests
* and a key that is used for encryptions.
* @param {int} db_port
*/
exports.init = function(db_port, key, cbDone){
if(!db_port || !key) {
log.error('DB', 'No DB port or cipher key defined!');
return null;
}
crypto_key = key;
db = redis.createClient(db_port);
db.on("error", function (err) {
log.error('DB', ' Message from DB: ' + err);
exports.init = function(cb){
require('./config').getConfig(null, function(err, data) {
if(!err) {
crypto_key = data.crypto_key;
db = redis.createClient(data.db_port);
db.on("error", function (err) {
err.addInfo = 'message from DB';
log.error('DB', err);
});
if(typeof cb === 'function') cb();
} else {
err.addInfo = 'fetching db_port and crypto_key';
if(typeof cb === 'function') cb(err);
}
});
if(cbDone) cbDone();
};
/**

View file

@ -17,10 +17,10 @@ var regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X
* @param {String} db_port the db port
* @param {String} crypto_key the key to be used for encryption on the db, max legnth 256
*/
function init(db_link, db_port, crypto_key) {
function init(db_link) {
db = db_link;
loadActions();
poller = cp.fork(path.resolve(__dirname, 'eventpoller'), [db_port, crypto_key]);
poller = cp.fork(path.resolve(__dirname, 'eventpoller'));
poller.on('message', function(evt) {
if(evt.event === 'ep_finished_loading') {
eventsLoaded = true;

View file

@ -2,145 +2,153 @@
'use strict';
if(process.argv.length < 3) {
log.error('EP', 'No DB port defined! Not starting poller...');
} else {
(function() {
var fs = require('fs'),
path = require('path'),
log = require('./logging'),
db = require('./db_interface'),
ml = require('./module_loader'),
listMessageActions = {},
listAdminCommands = {},
listEventModules = {},
listPoll = {}, //TODO this will change in the future because it could have
//several parameterized (user-specific) instances of each event module
isRunning = true,
eId = 0;
//TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution)
db.init(process.argv[2], process.argv[3]);
//TODO eventpoller will not load event modules from filesystem, this will be done by
// the moduel manager and the eventpoller receives messages about new/updated active rules
db.getEventModules(function(err, obj) {
if(err) log.error('EP', 'retrieving Event Modules from DB!');
else {
if(!obj) {
log.print('EP', 'No Event Modules found in DB!');
process.send({ event: 'ep_finished_loading' });
} else {
var m, semaphore = 0;
for(var el in obj) {
semaphore++;
m = ml.requireFromString(obj[el], el);
db.getEventModuleAuth(el, function(mod) {
return function(err, obj) {
if(--semaphore === 0) process.send({ event: 'ep_finished_loading' });
if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj));
};
}(m));
log.print('EP', 'Loading Event Module: ' + el);
listEventModules[el] = m;
}
}
}
});
listMessageActions['event'] = function(args) {
var prop = args[1], arrModule = prop.split('->');
// var arrModule = obj.module.split('->');
if(arrModule.length > 1){
var module = listEventModules[arrModule[0]];
for(var i = 1; i < arrModule.length; i++) {
if(module) module = module[arrModule[i]];
}
if(module) {
log.print('EP', 'Found active event module "' + prop + '", adding it to polling list');
//FIXME change this to [module][prop] = module; because like this identical properties get overwritten
// also add some on a per user basis information because this should go into a user context for the users
// that sat up this rule!
listPoll[prop] = module;
} else {
log.print('EP', 'No property "' + prop + '" found');
}
}
};
listAdminCommands['loadevent'] = function(args) {
ml.loadModule('mod_events', args[2], loadEventCallback);
};
listAdminCommands['loadevents'] = function(args) {
ml.loadModules('mod_events', loadEventCallback);
};
listAdminCommands['shutdown'] = function(args) {
log.print('EP', 'Shutting down DB Link');
isRunning = false;
db.shutDown();
};
//TODO this goes into module_manager, this will receive notification about
// new loaded/stored event modules and fetch them from the db
listMessageActions['cmd'] = function(args) {
var func = listAdminCommands[args[1]];
if(typeof(func) === 'function') func(args);
};
process.on('message', function(strProps) {
var arrProps = strProps.split('|');
if(arrProps.length < 2) log.error('EP', 'too few parameter in message!');
else {
var func = listMessageActions[arrProps[0]];
if(func) func(arrProps);
}
});
function loadEventCallback(name, data, mod, auth) {
db.storeEventModule(name, data); // store module in db
if(auth) db.storeEventModuleAuth(name, auth);
listEventModules[name] = mod; // store compiled module for polling
}
function checkRemotes() {
for(var prop in listPoll) {
try {
listPoll[prop](
/*
* define and immediately call anonymous function with param prop.
* This places the value of prop into the context of the callback
* and thus doesn't change when the for loop keeps iterating over listPoll
*/
(function(p) {
return function(err, obj) {
if(err) {
err.additionalInfo = 'module: ' + p;
log.error('EP', err);
} else {
process.send({
event: p,
eventid: 'polled_' + eId++,
data: obj
});
}
};
})(prop)
);
} catch (e) {
log.error('EP', e);
}
}
}
function pollLoop() {
if(isRunning) {
checkRemotes();
setTimeout(pollLoop, 10000);
}
}
var fs = require('fs'),
path = require('path'),
log = require('./logging'),
db = require('./db_interface'),
ml = require('./module_loader'),
listMessageActions = {},
listAdminCommands = {},
listEventModules = {},
listPoll = {}, //TODO this will change in the future because it could have
//several parameterized (user-specific) instances of each event module
isRunning = true,
eId = 0;
//TODO allow different polling intervals (a wrapper together with settimeout per to be polled could be an easy and solution)
pollLoop();
})();
}
function init() {
db.init(function(err) {
if(!err) {
//TODO eventpoller will not load event modules from filesystem, this will be done by
// the moduel manager and the eventpoller receives messages about new/updated active rules
db.getEventModules(function(err, obj) {
if(err) log.error('EP', 'retrieving Event Modules from DB!');
else {
if(!obj) {
log.print('EP', 'No Event Modules found in DB!');
process.send({ event: 'ep_finished_loading' });
} else {
var m, semaphore = 0;
for(var el in obj) {
semaphore++;
m = ml.requireFromString(obj[el], el);
db.getEventModuleAuth(el, function(mod) {
return function(err, obj) {
if(--semaphore === 0) process.send({ event: 'ep_finished_loading' });
if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj));
};
}(m));
log.print('EP', 'Loading Event Module: ' + el);
listEventModules[el] = m;
}
}
initAdminCommands();
initMessageActions();
}
});
} else {
err.addInfo = 'eventpoller init failed';
log.error('EP', err);
}
});
}
function initMessageActions() {
listMessageActions['event'] = function(args) {
var prop = args[1], arrModule = prop.split('->');
// var arrModule = obj.module.split('->');
if(arrModule.length > 1){
var module = listEventModules[arrModule[0]];
for(var i = 1; i < arrModule.length; i++) {
if(module) module = module[arrModule[i]];
}
if(module) {
log.print('EP', 'Found active event module "' + prop + '", adding it to polling list');
//FIXME change this to [module][prop] = module; because like this identical properties get overwritten
// also add some on a per user basis information because this should go into a user context for the users
// that sat up this rule!
listPoll[prop] = module;
} else {
log.print('EP', 'No property "' + prop + '" found');
}
}
};
//TODO this goes into module_manager, this will receive notification about
// new loaded/stored event modules and fetch them from the db
listMessageActions['cmd'] = function(args) {
var func = listAdminCommands[args[1]];
if(typeof(func) === 'function') func(args);
};
process.on('message', function(strProps) {
var arrProps = strProps.split('|');
if(arrProps.length < 2) log.error('EP', 'too few parameter in message!');
else {
var func = listMessageActions[arrProps[0]];
if(func) func(arrProps);
}
});
}
function initAdminCommands() {
listAdminCommands['loadevent'] = function(args) {
ml.loadModule('mod_events', args[2], loadEventCallback);
};
listAdminCommands['loadevents'] = function(args) {
ml.loadModules('mod_events', loadEventCallback);
};
listAdminCommands['shutdown'] = function(args) {
log.print('EP', 'Shutting down DB Link');
isRunning = false;
db.shutDown();
};
}
function loadEventCallback(name, data, mod, auth) {
db.storeEventModule(name, data); // store module in db
if(auth) db.storeEventModuleAuth(name, auth);
listEventModules[name] = mod; // store compiled module for polling
}
function checkRemotes() {
for(var prop in listPoll) {
try {
listPoll[prop](
/*
* define and immediately call anonymous function with param prop.
* This places the value of prop into the context of the callback
* and thus doesn't change when the for loop keeps iterating over listPoll
*/
(function(p) {
return function(err, obj) {
if(err) {
err.additionalInfo = 'module: ' + p;
log.error('EP', err);
} else {
process.send({
event: p,
eventid: 'polled_' + eId++,
data: obj
});
}
};
})(prop)
);
} catch (e) {
log.error('EP', e);
}
}
}
function pollLoop() {
if(isRunning) {
checkRemotes();
setTimeout(pollLoop, 10000);
}
}
init();
pollLoop();

View file

@ -31,7 +31,7 @@ exports.error = function(module, err) {
} else if(err) {
if(err.addInfo) ai = ' (' + err.addInfo + ')';
if(!err.message) err.message = 'UNKNOWN REASON!\n' + err.stack;
if(module) console.error(ts + module + ' | ERROR'+ai+': ' + err.message);
if(module) console.error(ts + module + ' | ERROR'+ai+': ' + err.message + '\n' + err.stack);
else console.error(ts + '!N/A! | ERROR AND NO MODULE NAME'+ai+': ' + err.message + '\n' + err.stack);
} else {
var e = new Error('Unexpected error');

View file

@ -67,29 +67,29 @@ function shutDown(args, answHandler) {
if(http_listener) http_listener.shutDown();
}
fs.readFile(path.resolve(__dirname, '..', 'config', 'config.json'), 'utf8', function (err, data) {
if (err) {
console.log(err);
log.error('RS', 'Loading config file');
return;
}
var config = JSON.parse(data);
if(!config.http_port || !config.db_port || !config.crypto_key) {
log.error('RS', new Error('you forgot to define either http_port, db_port, crypto_key, or even all of them!'));
} else {
log.print('RS', 'Initialzing DB');
db.init(config.db_port, config.crypto_key, function() {
engine.init(db, config.db_port, config.crypto_key);
});
log.print('RS', 'Initialzing http listener');
http_listener.init(config.http_port, handleAdminCommands, engine.pushEvent);
log.print('RS', 'Initialzing module manager');
mm.init(db, engine.loadActionModule, engine.loadRule);
}
});
// Send message
process.on('message', function(cmd) {
if(typeof procCmds[cmd] === 'function') procCmds[cmd]();
else console.error('err with command');
});
log.print('RS', 'STARTING SERVER');
log.print('RS', 'Initialzing DB');
//FIXME initialization of all modules should depend on one after the other
// in a transaction style manner
db.init(function(err) {
if(!err) {
engine.init(db);
log.print('RS', 'Initialzing http listener');
//FIXME http_port shouldn't be passed here we can load it inside the listener via the new config.js module
http_listener.init(null/*config.http_port*/, handleAdminCommands, engine.pushEvent);
log.print('RS', 'Initialzing module manager');
mm.init(db, engine.loadActionModule, engine.loadRule);
}
else {
err.addInfo = err.message;
err.message = 'Not Starting engine!';
log.error(err);
}
});