UNPKG

pomelo-admin1312

Version:

`pomelo-admin1312` is an admin console library for [pomelo](https://github.com/NetEase/pomelo). It provides the a series of utilities to monitor the `pomelo` server clusters.

475 lines (425 loc) 11.7 kB
var logger = require('pomelo-logger').getLogger('pomelo-admin', 'ConsoleService'); var MonitorAgent = require('./monitor/monitorAgent'); var EventEmitter = require('events').EventEmitter; var MasterAgent = require('./master/masterAgent'); var schedule = require('pomelo-scheduler'); var protocol = require('./util/protocol'); var utils = require('./util/utils'); var util = require('util'); var MS_OF_SECOND = 1000; /** * ConsoleService Constructor * * @class ConsoleService * @constructor * @param {Object} opts construct parameter * opts.type {String} server type, 'master', 'connector', etc. * opts.id {String} server id * opts.host {String} (monitor only) master server host * opts.port {String | Number} listen port for master or master port for monitor * opts.master {Boolean} current service is master or monitor * opts.info {Object} more server info for current server, {id, serverType, host, port} * @api public */ var ConsoleService = function(opts) { EventEmitter.call(this); this.port = opts.port; this.env = opts.env; this.values = {}; this.master = opts.master; this.modules = {}; this.commands = { 'list': listCommand, 'enable': enableCommand, 'disable': disableCommand }; if (this.master) { this.authUser = opts.authUser || utils.defaultAuthUser; this.authServer = opts.authServer || utils.defaultAuthServerMaster; this.agent = new MasterAgent(this, opts); } else { this.type = opts.type; this.id = opts.id; this.host = opts.host; this.authServer = opts.authServer || utils.defaultAuthServerMonitor; this.agent = new MonitorAgent({ consoleService: this, id: this.id, type: this.type, info: opts.info }); } }; util.inherits(ConsoleService, EventEmitter); /** * start master or monitor * * @param {Function} cb callback function * @api public */ ConsoleService.prototype.start = function(cb) { if (this.master) { var self = this; this.agent.listen(this.port, function(err) { if (!!err) { utils.invokeCallback(cb, err); return; } exportEvent(self, self.agent, 'register'); exportEvent(self, self.agent, 'disconnect'); exportEvent(self, self.agent, 'reconnect'); process.nextTick(function() { utils.invokeCallback(cb); }); }); } else { logger.info('try to connect master: %j, %j, %j', this.type, this.host, this.port); this.agent.connect(this.port, this.host, cb); exportEvent(this, this.agent, 'close'); } exportEvent(this, this.agent, 'error'); for (var mid in this.modules) { this.enable(mid); } }; /** * stop console modules and stop master server * * @api public */ ConsoleService.prototype.stop = function() { for (var mid in this.modules) { this.disable(mid); } this.agent.close(); }; /** * register a new adminConsole module * * @param {String} moduleId adminConsole id/name * @param {Object} module module object * @api public */ ConsoleService.prototype.register = function(moduleId, module) { this.modules[moduleId] = registerRecord(this, moduleId, module); }; /** * enable adminConsole module * * @param {String} moduleId adminConsole id/name * @api public */ ConsoleService.prototype.enable = function(moduleId) { var record = this.modules[moduleId]; if (record && !record.enable) { record.enable = true; addToSchedule(this, record); return true; } return false; }; /** * disable adminConsole module * * @param {String} moduleId adminConsole id/name * @api public */ ConsoleService.prototype.disable = function(moduleId) { var record = this.modules[moduleId]; if (record && record.enable) { record.enable = false; if (record.schedule && record.jobId) { schedule.cancelJob(record.jobId); schedule.jobId = null; } return true; } return false; }; /** * call concrete module and handler(monitorHandler,masterHandler,clientHandler) * * @param {String} moduleId adminConsole id/name * @param {String} method handler * @param {Object} msg message * @param {Function} cb callback function * @api public */ ConsoleService.prototype.execute = function(moduleId, method, msg, cb) { var self = this; var m = this.modules[moduleId]; if (!m) { logger.error('unknown module: %j.', moduleId); cb('unknown moduleId:' + moduleId); return; } if (!m.enable) { logger.error('module %j is disable.', moduleId); cb('module ' + moduleId + ' is disable'); return; } var module = m.module; if (!module || typeof module[method] !== 'function') { logger.error('module %j dose not have a method called %j.', moduleId, method); cb('module ' + moduleId + ' dose not have a method called ' + method); return; } var log = { action: 'execute', moduleId: moduleId, method: method, msg: msg } var aclMsg = aclControl(self.agent, 'execute', method, moduleId, msg); if (aclMsg !== 0 && aclMsg !== 1) { log['error'] = aclMsg; self.emit('admin-log', log, aclMsg); cb(new Error(aclMsg), null); return; } if (method === 'clientHandler') { self.emit('admin-log', log); } module[method](this.agent, msg, cb); }; ConsoleService.prototype.command = function(command, moduleId, msg, cb) { var self = this; var fun = this.commands[command]; if (!fun || typeof fun !== 'function') { cb('unknown command:' + command); return; } var log = { action: 'command', moduleId: moduleId, msg: msg } var aclMsg = aclControl(self.agent, 'command', null, moduleId, msg); if (aclMsg !== 0 && aclMsg !== 1) { log['error'] = aclMsg; self.emit('admin-log', log, aclMsg); cb(new Error(aclMsg), null); return; } self.emit('admin-log', log); fun(this, moduleId, msg, cb); } /** * set module data to a map * * @param {String} moduleId adminConsole id/name * @param {Object} value module data * @api public */ ConsoleService.prototype.set = function(moduleId, value) { this.values[moduleId] = value; }; /** * get module data from map * * @param {String} moduleId adminConsole id/name * @api public */ ConsoleService.prototype.get = function(moduleId) { return this.values[moduleId]; }; /** * register a module service * * @param {Object} service consoleService object * @param {String} moduleId adminConsole id/name * @param {Object} module module object * @api private */ var registerRecord = function(service, moduleId, module) { var record = { moduleId: moduleId, module: module, enable: false }; if (module.type && module.interval) { if (!service.master && record.module.type === 'push' || service.master && record.module.type !== 'push') { // push for monitor or pull for master(default) record.delay = module.delay || 0; record.interval = module.interval || 1; // normalize the arguments if (record.delay < 0) { record.delay = 0; } if (record.interval < 0) { record.interval = 1; } record.interval = Math.ceil(record.interval); record.delay *= MS_OF_SECOND; record.interval *= MS_OF_SECOND; record.schedule = true; } } return record; }; /** * schedule console module * * @param {Object} service consoleService object * @param {Object} record module object * @api private */ var addToSchedule = function(service, record) { if (record && record.schedule) { record.jobId = schedule.scheduleJob({ start: Date.now() + record.delay, period: record.interval }, doScheduleJob, { service: service, record: record }); } }; /** * run schedule job * * @param {Object} args argments * @api private */ var doScheduleJob = function(args) { var service = args.service; var record = args.record; if (!service || !record || !record.module || !record.enable) { return; } if (service.master) { record.module.masterHandler(service.agent, null, function(err) { logger.error('interval push should not have a callback.'); }); } else { record.module.monitorHandler(service.agent, null, function(err) { logger.error('interval push should not have a callback.'); }); } }; /** * export closure function out * * @param {Function} outer outer function * @param {Function} inner inner function * @param {object} event * @api private */ var exportEvent = function(outer, inner, event) { inner.on(event, function() { var args = Array.prototype.slice.call(arguments, 0); args.unshift(event); outer.emit.apply(outer, args); }); }; /** * List current modules */ var listCommand = function(consoleService, moduleId, msg, cb) { var modules = consoleService.modules; var result = []; for (var moduleId in modules) { if (/^__\w+__$/.test(moduleId)) { continue; } result.push(moduleId); } cb(null, { modules: result }); }; /** * enable module in current server */ var enableCommand = function(consoleService, moduleId, msg, cb) { if (!moduleId) { logger.error('fail to enable admin module for ' + moduleId); cb('empty moduleId'); return; } var modules = consoleService.modules; if (!modules[moduleId]) { cb(null, protocol.PRO_FAIL); return; } if (consoleService.master) { consoleService.enable(moduleId); consoleService.agent.notifyCommand("enable", moduleId, msg); cb(null, protocol.PRO_OK); } else { consoleService.enable(moduleId); cb(null, protocol.PRO_OK); } }; /** * disable module in current server */ var disableCommand = function(consoleService, moduleId, msg, cb) { if (!moduleId) { logger.error('fail to enable admin module for ' + moduleId); cb('empty moduleId'); return; } var modules = consoleService.modules; if (!modules[moduleId]) { cb(null, protocol.PRO_FAIL); return; } if (consoleService.master) { consoleService.disable(moduleId); consoleService.agent.notifyCommand("disable", moduleId, msg); cb(null, protocol.PRO_OK); } else { consoleService.disable(moduleId); cb(null, protocol.PRO_OK); } }; var aclControl = function(agent, action, method, moduleId, msg) { if (action === 'execute') { if (method !== 'clientHandler' || moduleId !== '__console__') { return 0; } var signal = msg.signal; if (!signal || !(signal === 'stop' || signal === 'add' || signal === 'kill')) { return 0; } } var clientId = msg.clientId; if (!clientId) { return 'Unknow clientId'; } var _client = agent.getClientById(clientId); if (_client && _client.info && _client.info.level) { var level = _client.info.level; if (level > 1) { return 'Command permission denied'; } } else { return 'Client info error'; } return 1; } /** * Create master ConsoleService * * @param {Object} opts construct parameter * opts.port {String | Number} listen port for master console */ module.exports.createMasterConsole = function(opts) { opts = opts || {}; opts.master = true; return new ConsoleService(opts); }; /** * Create monitor ConsoleService * * @param {Object} opts construct parameter * opts.type {String} server type, 'master', 'connector', etc. * opts.id {String} server id * opts.host {String} master server host * opts.port {String | Number} master port */ module.exports.createMonitorConsole = function(opts) { return new ConsoleService(opts); };