scalra
Version:
node.js framework to prototype and scale rapidly
321 lines (250 loc) • 7.73 kB
JavaScript
/*
//
// REST_web.js
//
// a RESTful handler for request handling in monitor
//
supported_handles:
getPort
report
run
*/
var l_handles = exports.REST_handles = {};
//
// local private variables to keep track of created data
//
// list of ports already assigned
var l_ports = {};
//
// helper code
//
// send back response to client
var l_reply = function (res, res_obj) {
// return response if exist, otherwise response might be returned
// AFTER some callback is done handling (i.e., response will be returned within the handler)
if (typeof res_obj === 'string') {
LOG.sys('replying a string: ' + res_obj);
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(res_obj);
}
else {
LOG.sys('replying a JSON: ' + JSON.stringify(res_obj));
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(res_obj));
}
}
// recycle an app port
var l_recyclePort = function (ports) {
var port = ports[0];
var list = '';
for (var i=0; i < ports.length; i++) {
list += (ports[i] + ', ');
delete l_ports[ports[i]];
}
LOG.warn('release ports: ' + list, 'SR.Monitor');
// remove all other ports associated with this port
for (var i in l_ports) {
if (l_ports[i] === port) {
LOG.warn('release port: ' + i, 'SR.Monitor');
delete l_ports[i];
}
}
}
// record an existing port
// 'parent_port' indicats the which port, if released, will also release the current port
var l_recordPort = function (port, parent_port) {
// check if the port wasn't reported
if (l_ports.hasOwnProperty(port) === false) {
LOG.warn('recording used port [' + port + ']...', 'SR.Monitor');
l_ports[port] = parent_port || port;
}
}
// assign a unique application port
var l_assignPort = function (size) {
size = size || 1;
LOG.warn('trying to assign ' + size + ' new ports...', 'SR.Monitor');
// find first available port
var port = SR.Settings.PORT_APP_RANGE_START;
var last_port = SR.Settings.PORT_APP_RANGE_END;
// first port found
var first_port = undefined;
var results = [];
while (port <= last_port) {
if (l_ports.hasOwnProperty(port) === false || l_ports[port] === null) {
// found a unique port
if (!first_port)
first_port = port;
// mark the port as assigned
l_ports[port] = first_port;
results.push(port);
LOG.warn('assigning port: ' + port, 'SR.Monitor');
if (--size === 0)
break;
}
port++;
}
// no available ports found, or not enough ports found
if (port > last_port)
return 0;
if (results.length > 1)
return results;
else
return results[0];
}
//
// internal functions
//
// periodic checking liveness of app servers
var l_checkAlive = function () {
var currTime = new Date();
var overtime = (SR.Settings.INTERVAL_STAT_REPORT * 2);
// remove servers no longer reporting
for (var serverID in SR.Report.servers) {
var stat = SR.Report.servers[serverID];
if (!stat || !stat.reportedTime)
continue;
// server considered dead
if (currTime - stat.reportedTime > overtime) {
var ip_port = stat.server.IP + ':' + stat.server.port;
LOG.sys(ip_port + ' overtime: ' + overtime + ' last: ' + stat.reportedTime);
LOG.error('server: ' + ip_port + ' (' + stat.server.type + ') not responding after ' + overtime + ' ms, mark dead...', 'SR.Monitor');
SR.Report.removeStat(serverID);
// recycle port if it's from a local server
if (stat.server.IP === SR.Settings.SERVER_INFO.IP) {
LOG.warn('server is a local server, re-cycle its ports...', 'SR.Monitor');
l_recyclePort(stat.ports);
}
}
}
}
//
// REST handlers
//
// custom request handlers
l_handles.getPort = function (path_array, res, para, req) {
var size = 1;
if (para && typeof para['size'] !== 'undefined') {
var n = parseInt(para['size']);
if (typeof n === 'number')
size = n;
}
LOG.warn('request ' + size + ' new ports...', 'SR.Monitor');
var ports = l_assignPort(size);
// NOTE: return value may be a single number (if requesting only one) or an array of ports
var res_obj = {
port: ports
};
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(res_obj));
}
/*
// process server stat report
l_handles.report = function (path_array, res, para, req) {
if (typeof para === 'undefined') {
LOG.error('POST data is empty', 'SR.Monitor');
}
else {
var stat_array = para;
for (var i=0; i < stat_array.length; i++) {
var stat = stat_array[i];
// check if the reporting server is locally on the same machine, if so,
// mark its port as 'already used'
// TODO: this only works if the reporting server & monitor are at the same physical host
// a better approach?
if (stat.server && stat.server.IP === SR.Settings.SERVER_INFO.IP &&
stat.ports instanceof Array &&
stat.ports.length > 0) {
for (var i=0; i < stat.ports.length; i++)
l_recordPort(stat.ports[i], stat.ports[0]);
}
// store stat
// NOTE: if the stat is a shutdown, port may be recycled afterwards
// so SR.Report.storeStat should be called AFTER _reportPort is checked
SR.Report.storeStat(stat);
// notify other nodes in DHT ring about update
//UTIL.contactMonitor('update', {id: stat.server.id});
}
}
res.end();
}
// new server added to DHT ring
l_handles.update = function (path_array, res, para, req) {
LOG.warn('update received, update server list:', 'SR.Monitor');
LOG.warn(para);
SR.Report.updateServer(para.id);
res.end();
}
*/
// redirect to the URI of a particular app
l_handles.run = function (path_array, res, para, req) {
var app_name = path_array[2];
var app = SR.Report.redirects[app_name];
// check if app is registered
if (app === undefined) {
var msg = '[' + app_name + '] not reported, cannot redirect to server...';
LOG.warn(msg);
l_reply(res, msg);
return false;
}
var alive = false;
var redirect_uri = undefined;
if (app.hasOwnProperty('live_url') === false) {
LOG.error('no live_url found for [' + app_name + ']');
}
// get live or dead URL
else if (app.alive) {
alive = true;
redirect_uri = app.live_url;
}
else
redirect_uri = app.dead_url;
// check liveness and redirection URL
LOG.warn('app [' + app_name + '] liveness: ' + alive + ', redirect to: ' + redirect_uri);
res.writeHead(302, {
'Location': redirect_uri
});
res.end();
return true;
}
//-----------------------------------------
// Start/Stop procedures
//
//-----------------------------------------
var l_timer = undefined;
SR.Callback.onStart(function () {
/*
var list = undefined;
LOG.warn('IP_MONITOR:', 'SR.Monitor');
LOG.warn(SR.Settings.IP_MONITOR, 'SR.Monitor');
if (SR.Settings.IP_MONITOR instanceof Array) {
list = SR.Settings.IP_MONITOR;
}
else
list = [SR.Settings.IP_MONITOR];
// init DHT
SR.Report.init({hosts: list}, function () {
LOG.warn('DHT init done, monitor ring joined...', 'SR.Monitor');
});
*/
// start periodic probing for app servers
//l_timer = setInterval(l_checkAlive, SR.Settings.INTERVAL_LIVENESS_CHECK);
/*
// register callback to recycle port when server shutdown
// TODO: better way to do this than put here?
SR.Report.onShutdown(function (stat) {
// recycle port if it's from a local server
if (stat.server.IP === SR.Settings.SERVER_INFO.IP) {
LOG.warn('server is a local server, re-cycle its ports...', 'SR.Monitor');
l_recyclePort(stat.ports);
}
});
*/
});
// stop check-alive timer when server stops
SR.Callback.onStop(function () {
if (l_timer) {
clearInterval(l_timer);
l_timer = undefined;
}
});
SR.REST.addHandler(l_handles);