mirror of
https://github.com/bluxmit/alnoda-workspaces.git
synced 2024-09-20 03:37:11 +12:00
346 lines
11 KiB
JavaScript
Executable file
346 lines
11 KiB
JavaScript
Executable file
// Cronicle API Layer - Administrative
|
|
// Copyright (c) 2015 Joseph Huckaby
|
|
// Released under the MIT License
|
|
|
|
var fs = require('fs');
|
|
var assert = require("assert");
|
|
var async = require('async');
|
|
|
|
var Class = require("pixl-class");
|
|
var Tools = require("pixl-tools");
|
|
|
|
module.exports = Class.create({
|
|
|
|
//
|
|
// Servers / Master Control
|
|
//
|
|
|
|
api_check_add_server: function(args, callback) {
|
|
// check if it is okay to manually add this server to a remote cluster
|
|
// (This is a server-to-server API, sent from master to a potential remote slave)
|
|
var self = this;
|
|
var params = args.params;
|
|
|
|
if (!this.requireParams(params, {
|
|
master: /\S/,
|
|
now: /^\d+$/,
|
|
token: /\S/
|
|
}, callback)) return;
|
|
|
|
if (params.token != Tools.digestHex( params.master + params.now + this.server.config.get('secret_key') )) {
|
|
return this.doError('server', "Secret keys do not match. Please synchronize your config files.", callback);
|
|
}
|
|
if (this.multi.master) {
|
|
return this.doError('server', "Server is already a master server, controlling its own cluster.", callback);
|
|
}
|
|
if (this.multi.masterHostname && (this.multi.masterHostname != params.master)) {
|
|
return this.doError('server', "Server is already a member of a cluster (Primary: " + this.multi.masterHostname + ")", callback);
|
|
}
|
|
|
|
callback({ code: 0, hostname: this.server.hostname, ip: this.server.ip });
|
|
},
|
|
|
|
api_add_server: function(args, callback) {
|
|
// add any arbitrary server to cluster (i.e. outside of UDP broadcast range)
|
|
var self = this;
|
|
var params = args.params;
|
|
if (!this.requireMaster(args, callback)) return;
|
|
|
|
if (!this.requireParams(params, {
|
|
hostname: /\S/
|
|
}, callback)) return;
|
|
|
|
var hostname = params.hostname.toLowerCase();
|
|
|
|
this.loadSession(args, function(err, session, user) {
|
|
if (err) return self.doError('session', err.message, callback);
|
|
if (!self.requireAdmin(session, user, callback)) return;
|
|
|
|
args.user = user;
|
|
args.session = session;
|
|
|
|
// make sure server isn't already added
|
|
if (self.slaves[hostname]) {
|
|
return self.doError('server', "Server is already in cluster: " + hostname, callback);
|
|
}
|
|
|
|
// send HTTP request to server, to make sure we can reach it
|
|
var api_url = self.getServerBaseAPIURL( hostname ) + '/app/check_add_server';
|
|
var now = Tools.timeNow(true);
|
|
var api_args = {
|
|
master: self.server.hostname,
|
|
now: now,
|
|
token: Tools.digestHex( self.server.hostname + now + self.server.config.get('secret_key') )
|
|
};
|
|
self.logDebug(9, "Sending API request to remote server: " + api_url);
|
|
|
|
// send request
|
|
self.request.json( api_url, api_args, { timeout: 8 * 1000 }, function(err, resp, data) {
|
|
if (err) {
|
|
return self.doError('server', "Failed to contact server: " + err.message, callback );
|
|
}
|
|
if (resp.statusCode != 200) {
|
|
return self.doError('server', "Failed to contact server: " + hostname + ": HTTP " + resp.statusCode + " " + resp.statusMessage, callback);
|
|
}
|
|
if (data.code != 0) {
|
|
return self.doError('server', "Failed to add server to cluster: " + hostname + ": " + data.description, callback);
|
|
}
|
|
|
|
// replace user-entered hostname with one returned from server (check_add_server api response)
|
|
// just in case user entered an IP, or some CNAME
|
|
hostname = data.hostname;
|
|
|
|
// re-check this, for sanity
|
|
if (self.slaves[hostname]) {
|
|
return self.doError('server', "Server is already in cluster: " + hostname, callback);
|
|
}
|
|
|
|
// one more sanity check, with the IP this time
|
|
for (var key in self.slaves) {
|
|
var slave = self.slaves[key];
|
|
if (slave.ip == data.ip) {
|
|
return self.doError('server', "Server is already in cluster: " + hostname + " (" + data.ip + ")", callback);
|
|
}
|
|
}
|
|
|
|
// okay to add
|
|
var stub = { hostname: hostname, ip: data.ip };
|
|
self.logDebug(4, "Adding remote slave server to cluster: " + hostname, stub);
|
|
self.addServer(stub, args);
|
|
|
|
// add to global/servers list
|
|
self.storage.listFind( 'global/servers', { hostname: hostname }, function(err, item) {
|
|
if (item) {
|
|
// server is already in list, just ignore and go
|
|
return callback({ code: 0 });
|
|
}
|
|
|
|
// okay to add
|
|
self.storage.listPush( 'global/servers', stub, function(err) {
|
|
if (err) {
|
|
// should never happen
|
|
self.logError('server', "Failed to add server to storage: " + hostname + ": " + err);
|
|
}
|
|
|
|
// success
|
|
callback({ code: 0 });
|
|
} ); // listPush
|
|
} ); // listFind
|
|
} ); // http request
|
|
} ); // load session
|
|
},
|
|
|
|
api_remove_server: function(args, callback) {
|
|
// remove any manually-added server from cluster
|
|
var self = this;
|
|
var params = args.params;
|
|
if (!this.requireMaster(args, callback)) return;
|
|
|
|
if (!this.requireParams(params, {
|
|
hostname: /\S/
|
|
}, callback)) return;
|
|
|
|
var hostname = params.hostname.toLowerCase();
|
|
|
|
this.loadSession(args, function(err, session, user) {
|
|
if (err) return self.doError('session', err.message, callback);
|
|
if (!self.requireAdmin(session, user, callback)) return;
|
|
|
|
args.user = user;
|
|
args.session = session;
|
|
|
|
// do not allow removal of current master
|
|
if (hostname == self.server.hostname) {
|
|
return self.doError('server', "Cannot remove current master server: " + hostname, callback);
|
|
}
|
|
|
|
var slave = self.slaves[hostname];
|
|
if (!slave) {
|
|
return self.doError('server', "Server not found in cluster: " + hostname, callback);
|
|
}
|
|
|
|
// Do not allow removing server if it has any active jobs
|
|
var all_jobs = self.getAllActiveJobs(true);
|
|
for (var key in all_jobs) {
|
|
var job = all_jobs[key];
|
|
if (job.hostname == hostname) {
|
|
var err = "Still has running jobs";
|
|
return self.doError('server', "Failed to remove server: " + err, callback);
|
|
} // matches server
|
|
} // foreach job
|
|
|
|
// okay to remove
|
|
self.logDebug(4, "Removing remote slave server from cluster: " + hostname);
|
|
self.removeServer({ hostname: hostname }, args);
|
|
|
|
// delete from global/servers list
|
|
self.storage.listFindDelete( 'global/servers', { hostname: hostname }, function(err) {
|
|
if (err) {
|
|
// should never happen
|
|
self.logError('server', "Failed to remove server from storage: " + hostname + ": " + err);
|
|
}
|
|
|
|
// success
|
|
callback({ code: 0 });
|
|
} ); // listFindDelete
|
|
} ); // load session
|
|
},
|
|
|
|
api_restart_server: function(args, callback) {
|
|
// restart any server in cluster
|
|
var self = this;
|
|
var params = args.params;
|
|
if (!this.requireMaster(args, callback)) return;
|
|
|
|
if (!this.requireParams(params, {
|
|
hostname: /\S/
|
|
}, callback)) return;
|
|
|
|
this.loadSession(args, function(err, session, user) {
|
|
if (err) return self.doError('session', err.message, callback);
|
|
if (!self.requireAdmin(session, user, callback)) return;
|
|
|
|
args.user = user;
|
|
args.session = session;
|
|
|
|
self.logTransaction('server_restart', '', self.getClientInfo(args, params));
|
|
self.logActivity('server_restart', params, args);
|
|
|
|
var reason = "User request by: " + user.username;
|
|
|
|
if (params.hostname == self.server.hostname) {
|
|
// restart this server
|
|
self.restartLocalServer({ reason: reason });
|
|
callback({ code: 0 });
|
|
}
|
|
else {
|
|
// restart another server in the cluster
|
|
var slave = self.slaves[ params.hostname ];
|
|
if (slave && slave.socket) {
|
|
self.logDebug(6, "Sending remote restart command to: " + slave.hostname);
|
|
slave.socket.emit( 'restart_server', { reason: reason } );
|
|
callback({ code: 0 });
|
|
}
|
|
else {
|
|
callback({ code: 1, description: "Could not locate server: " + params.hostname });
|
|
}
|
|
}
|
|
|
|
} );
|
|
},
|
|
|
|
api_shutdown_server: function(args, callback) {
|
|
// shutdown any server in cluster
|
|
var self = this;
|
|
var params = args.params;
|
|
if (!this.requireMaster(args, callback)) return;
|
|
|
|
if (!this.requireParams(params, {
|
|
hostname: /\S/
|
|
}, callback)) return;
|
|
|
|
this.loadSession(args, function(err, session, user) {
|
|
if (err) return self.doError('session', err.message, callback);
|
|
if (!self.requireAdmin(session, user, callback)) return;
|
|
|
|
args.user = user;
|
|
args.session = session;
|
|
|
|
self.logTransaction('server_shutdown', '', self.getClientInfo(args, params));
|
|
self.logActivity('server_shutdown', params, args);
|
|
|
|
var reason = "User request by: " + user.username;
|
|
|
|
if (params.hostname == self.server.hostname) {
|
|
// shutdown this server
|
|
self.shutdownLocalServer({ reason: reason });
|
|
callback({ code: 0 });
|
|
}
|
|
else {
|
|
// shutdown another server in the cluster
|
|
var slave = self.slaves[ params.hostname ];
|
|
if (slave && slave.socket) {
|
|
self.logDebug(6, "Sending remote shutdown command to: " + slave.hostname);
|
|
slave.socket.emit( 'shutdown_server', { reason: reason } );
|
|
callback({ code: 0 });
|
|
}
|
|
else {
|
|
callback({ code: 1, description: "Could not locate server: " + params.hostname });
|
|
}
|
|
}
|
|
|
|
} );
|
|
},
|
|
|
|
api_update_master_state: function(args, callback) {
|
|
// update master state (i.e. scheduler enabled)
|
|
var self = this;
|
|
var params = args.params;
|
|
if (!this.requireMaster(args, callback)) return;
|
|
|
|
this.loadSession(args, function(err, session, user) {
|
|
if (err) return self.doError('session', err.message, callback);
|
|
if (!self.requireValidUser(session, user, callback)) return;
|
|
if (!self.requirePrivilege(user, "state_update", callback)) return;
|
|
|
|
args.user = user;
|
|
args.session = session;
|
|
|
|
// import params into state
|
|
self.logDebug(4, "Updating master state:", params);
|
|
self.logTransaction('state_update', '', self.getClientInfo(args, params));
|
|
self.logActivity('state_update', params, args);
|
|
|
|
if (params.enabled) {
|
|
// need to re-initialize schedule if being enabled
|
|
var now = Tools.normalizeTime( Tools.timeNow(), { sec: 0 } );
|
|
var cursors = self.state.cursors;
|
|
|
|
self.storage.listGet( 'global/schedule', 0, 0, function(err, items) {
|
|
// got all schedule items
|
|
for (var idx = 0, len = items.length; idx < len; idx++) {
|
|
var item = items[idx];
|
|
|
|
// reset cursor to now if event is NOT set to catch up
|
|
if (!item.catch_up) {
|
|
cursors[ item.id ] = now;
|
|
}
|
|
} // foreach item
|
|
|
|
// now it's safe to enable
|
|
Tools.mergeHashInto( self.state, params );
|
|
self.authSocketEmit( 'update', { state: self.state } );
|
|
} ); // loaded schedule
|
|
} // params.enabled
|
|
else {
|
|
// not enabling scheduler, so merge right away
|
|
Tools.mergeHashInto( self.state, params );
|
|
self.authSocketEmit( 'update', { state: self.state } );
|
|
}
|
|
|
|
callback({ code: 0 });
|
|
} );
|
|
},
|
|
|
|
api_get_activity: function(args, callback) {
|
|
// get rows from activity log (with pagination)
|
|
var self = this;
|
|
var params = args.params;
|
|
|
|
this.loadSession(args, function(err, session, user) {
|
|
if (err) return self.doError('session', err.message, callback);
|
|
if (!self.requireAdmin(session, user, callback)) return;
|
|
|
|
self.storage.listGet( 'logs/activity', parseInt(params.offset || 0), parseInt(params.limit || 50), function(err, items, list) {
|
|
if (err) {
|
|
// no rows found, not an error for this API
|
|
return callback({ code: 0, rows: [], list: { length: 0 } });
|
|
}
|
|
|
|
// success, return rows and list header
|
|
callback({ code: 0, rows: items, list: list });
|
|
} ); // got data
|
|
} ); // loaded session
|
|
}
|
|
|
|
} );
|