UNPKG

occaecatidicta

Version:
559 lines (495 loc) 16.3 kB
import { getLogger } from 'omelox-logger'; import {MonitorAgent} from './monitor/monitorAgent'; import { EventEmitter } from 'events'; import { MasterAgent, MasterAgentOptions } from './master/masterAgent'; import * as schedule from 'omelox-scheduler'; import * as protocol from './util/protocol'; import * as utils from './util/utils'; import * as util from 'util'; import { AdminServerInfo, ServerInfo, AdminUserInfo, Callback } from './util/constants'; import * as path from 'path'; let logger = getLogger('omelox-admin', path.basename(__filename)); let MS_OF_SECOND = 1000; export enum ModuleType { push = 'push', pull = 'pull', Normal = '' } export type MasterCallback = (err?: string , data?: any) => void; export type MonitorCallback = (err?: Error , data?: any) => void; export interface IModule { moduleId ?: string; type ?: ModuleType; interval ?: number; delay ?: number; start?: (cb: (err?: Error) => void) => void; masterHandler ?: (agent: MasterAgent, msg: any, cb: MasterCallback) => void; monitorHandler ?: (agent: MonitorAgent, msg: any, cb: MonitorCallback) => void; } export interface ModuleRecord { moduleId: string; module: any; enable: boolean; delay ?: number; interval ?: number; schedule ?: boolean; jobId?: number; } export interface IModuleFactory { new (opts: any , consoleService ?: ConsoleService): IModule; moduleId: string; } export interface MasterConsoleServiceOpts extends MasterAgentOptions { master?: boolean; port?: number; env?: string; authUser ?: typeof utils.defaultAuthUser; authServer ?: typeof utils.defaultAuthServerMaster; } export interface MonitorConsoleServiceOpts { type?: string; id?: string; host?: string; info?: ServerInfo; port?: number; env?: string; authServer ?: typeof utils.defaultAuthServerMaster; } export type ConsoleServiceOpts = MasterConsoleServiceOpts | MonitorConsoleServiceOpts; export interface AdminLogInfo { action: string; moduleId: string; method?: string; msg: any; error?: Error | string | number; } /** * 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 */ export class ConsoleService extends EventEmitter { port: number; env: string; values: {[key: string]: any}; master: boolean; modules: {[key: string]: ModuleRecord}; commands: {[key: string]: (consoleService: ConsoleService, moduleId: string, msg: any, cb: Callback) => void}; authUser: typeof utils.defaultAuthUser; authServer: typeof utils.defaultAuthServerMonitor; agent: MasterAgent | MonitorAgent; id: string; host: string; type: string; constructor(_opts: ConsoleServiceOpts) { super(); this.port = _opts.port; this.env = _opts.env; this.values = {}; let masterOpts = _opts as MasterConsoleServiceOpts; let monitorOpts = _opts as MonitorConsoleServiceOpts; this.master = masterOpts.master; this.modules = {}; this.commands = { 'list': listCommand, 'enable': enableCommand, 'disable': disableCommand }; if (this.master) { this.authUser = masterOpts.authUser || utils.defaultAuthUser; this.authServer = masterOpts.authServer || utils.defaultAuthServerMaster; this.agent = new MasterAgent(this, masterOpts); } else { this.type = monitorOpts.type; this.id = monitorOpts.id; this.host = monitorOpts.host; this.authServer = monitorOpts.authServer || utils.defaultAuthServerMonitor; this.agent = new MonitorAgent({ consoleService: this, id: this.id, type: this.type, info: monitorOpts.info }); } } /** * start master or monitor * * @param {Function} cb callback function * @api public */ start(cb: (err?: Error) => void) { if (this.master) { let self = this; (this.agent as MasterAgent).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 as MonitorAgent).connect(this.port, this.host, cb); exportEvent(this, this.agent, 'close'); } exportEvent(this, this.agent, 'error'); for (let mid in this.modules) { this.enable(mid); } } /** * stop console modules and stop master server * * @api public */ stop() { for (let 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 */ register(moduleId: string, module: IModule) { this.modules[moduleId] = registerRecord(this, moduleId, module); } /** * enable adminConsole module * * @param {String} moduleId adminConsole id/name * @api public */ enable(moduleId: string) { let 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 */ disable(moduleId: string) { let record = this.modules[moduleId]; if (record && record.enable) { record.enable = false; if (record.schedule && record.jobId) { schedule.cancelJob(record.jobId); record.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 */ execute(moduleId: string, method: string, msg: any, cb: (err ?: Error|string , msg?: any) => void) { let self = this; let 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; } let 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; } let log: AdminLogInfo = { action: 'execute', moduleId: moduleId, method: method, msg: msg }; let aclMsg = aclControl(self.agent as MasterAgent, 'execute', method, moduleId, msg); if (aclMsg !== 0 && aclMsg !== 1) { log['error'] = aclMsg; self.emit('admin-log', log, aclMsg); cb(new Error(aclMsg.toString()), null); return; } if (method === 'clientHandler') { self.emit('admin-log', log); } module[method](this.agent, msg, cb); } command(command: string, moduleId: string, msg: any, cb: (err ?: Error|string , msg?: any) => void) { let self = this; let fun = this.commands[command]; if (!fun || typeof fun !== 'function') { cb('unknown command:' + command); return; } let log: AdminLogInfo = { action: 'command', moduleId: moduleId, msg: msg }; let aclMsg = aclControl(self.agent as MasterAgent, 'command', null, moduleId, msg); if (aclMsg !== 0 && aclMsg !== 1) { log['error'] = aclMsg; self.emit('admin-log', log, aclMsg); cb(new Error(aclMsg.toString()), 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 */ set(moduleId: string, value: any) { this.values[moduleId] = value; } /** * get module data from map * * @param {String} moduleId adminConsole id/name * @api public */ get(moduleId: string) { 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 */ let registerRecord = function (service: ConsoleService, moduleId: string, module: IModule) { let record: any = { 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 */ let addToSchedule = function (service: ConsoleService, record: ModuleRecord) { 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 */ let doScheduleJob = function (args: {service: ConsoleService , record: ModuleRecord}) { let service: ConsoleService = args.service; let record = args.record; if (!service || !record || !record.module || !record.enable) { return; } if (service.master) { record.module.masterHandler(service.agent as MasterAgent, null, function (err: Error) { logger.error('interval push should not have a callback.'); }); } else { record.module.monitorHandler(service.agent as MonitorAgent, null, function (err: Error) { 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 */ let exportEvent = function (outer: ConsoleService, inner: MasterAgent | MonitorAgent, event: string) { inner.on(event, function () { let args = Array.prototype.slice.call(arguments, 0); args.unshift(event); outer.emit.apply(outer, args); }); }; /** * List current modules */ let listCommand = function (consoleService: ConsoleService, moduleId: string, msg: any, cb: Callback) { let modules = consoleService.modules; let result = []; for (let moduleId in modules) { if (/^__\w+__$/.test(moduleId)) { continue; } result.push(moduleId); } cb(null, { modules: result }); }; /** * enable module in current server */ let enableCommand = function (consoleService: ConsoleService, moduleId: string, msg: any, cb: Callback) { if (!moduleId) { logger.error('fail to enable admin module for ' + moduleId); cb('empty moduleId'); return; } let modules = consoleService.modules; if (!modules[moduleId]) { cb(null, protocol.PRO_FAIL); return; } if (consoleService.master) { consoleService.enable(moduleId); (consoleService.agent as MasterAgent).notifyCommand('enable', moduleId, msg); cb(null, protocol.PRO_OK); } else { consoleService.enable(moduleId); cb(null, protocol.PRO_OK); } }; /** * disable module in current server */ let disableCommand = function (consoleService: ConsoleService, moduleId: string, msg: any, cb: Callback) { if (!moduleId) { logger.error('fail to enable admin module for ' + moduleId); cb('empty moduleId'); return; } let modules = consoleService.modules; if (!modules[moduleId]) { cb(null, protocol.PRO_FAIL); return; } if (consoleService.master) { consoleService.disable(moduleId); (consoleService.agent as MasterAgent).notifyCommand('disable', moduleId, msg); cb(null, protocol.PRO_OK); } else { consoleService.disable(moduleId); cb(null, protocol.PRO_OK); } }; let aclControl = function (agent: MasterAgent, action: string, method: string, moduleId: string, msg: any) { if (action === 'execute') { if (method !== 'clientHandler' || moduleId !== '__console__') { return 0; } let signal = msg.signal; if (!signal || !(signal === 'stop' || signal === 'add' || signal === 'kill')) { return 0; } } let clientId = msg.clientId; if (!clientId) { return 'Unknow clientId'; } let _client = agent.getClientById(clientId); if (_client && _client.info && (_client.info as AdminUserInfo).level) { let level = (_client.info as AdminUserInfo).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 */ export function createMasterConsole(opts: ConsoleServiceOpts) { (opts as MasterConsoleServiceOpts).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 */ export function createMonitorConsole(opts: ConsoleServiceOpts) { return new ConsoleService(opts); }