occaecatidicta
Version:
645 lines (574 loc) • 20 kB
text/typescript
import { getLogger } from 'omelox-logger';
import { MqttServer, MqttSocket } from '../protocol/mqtt/mqttServer';
import { EventEmitter } from 'events';
import { MasterSocket } from './masterSocket';
import * as protocol from '../util/protocol';
import * as utils from '../util/utils';
import * as Util from 'util';
import { ConsoleService } from '../consoleService';
import { ServerInfo, AdminUserInfo, AdminServerInfo, Callback } from '../util/constants';
import * as path from 'path';
import { MqttConnection } from '../protocol/mqtt/mqttConnectorDefine';
let logger = getLogger('omelox-admin', path.basename(__filename));
let ST_INITED = 1;
let ST_STARTED = 2;
let ST_CLOSED = 3;
export type WhiteList = string[];
export interface MasterAgentOptions { whitelist?: WhiteList; }
export interface AgentClient {
id: string;
type: string;
pid: string;
info: AdminUserInfo | ServerInfo;
socket: MqttSocket;
}
export interface AuthUserRequest { username: string; password: string; md5: string; id: string; type: string; }
export interface AuthServerRequest { id: string; serverType: string; token: string; pid: string; info: ServerInfo; }
/**
* MasterAgent Constructor
*
* @class MasterAgent
* @constructor
* @param {Object} opts construct parameter
* opts.consoleService {Object} consoleService
* opts.id {String} server id
* opts.type {String} server type, 'master', 'connector', etc.
* opts.socket {Object} socket-io object
* opts.reqId {Number} reqId add by 1
* opts.callbacks {Object} callbacks
* opts.state {Number} MasterAgent state
* @api public
*/
export class MasterAgent extends EventEmitter {
reqId = 1;
idMap: { [serverId: string]: AgentClient } = {};
msgMap: {
[serverId: string]: {
[reqId: number]: {
moduleId: string,
msg: any
}
}
} = {};
typeMap: { [type: string]: AgentClient[] } = {};
clients: { [id: string]: AgentClient } = {};
sockets: { [id: string]: MqttConnection } = {};
slaveMap: { [serverId: string]: AgentClient[] } = {};
server: MqttServer = null;
callbacks: { [reqId: number]: Callback } = {};
state = ST_INITED;
whitelist: WhiteList;
consoleService: ConsoleService;
constructor(consoleService: ConsoleService, opts: MasterAgentOptions) {
super();
this.whitelist = opts.whitelist;
this.consoleService = consoleService;
}
/**
* master listen to a port and handle register and request
*
* @param {String} port
* @api public
*/
listen(port: number, cb: (err?: Error) => void) {
if (this.state > ST_INITED) {
logger.error('master agent has started or closed.');
return;
}
this.state = ST_STARTED;
this.server = new MqttServer();
this.server.listen(port);
// this.server = sio.listen(port);
// this.server.set('log level', 0);
cb = cb || function () { };
let self = this;
this.server.on('error', function (err) {
self.emit('error', err);
cb(err);
});
this.server.once('listening', function () {
setImmediate(function () {
cb();
});
});
this.server.on('connection', function (socket) {
// let id, type, info, registered, username;
let masterSocket = new MasterSocket();
masterSocket['agent'] = self;
masterSocket['socket'] = socket;
self.sockets[socket.id] = socket;
socket.on('register', function (msg: any) {
// register a new connection
masterSocket.onRegister(msg);
}); // end of on 'register'
// message from monitor
socket.on('monitor', function (msg: any) {
masterSocket.onMonitor(msg);
}); // end of on 'monitor'
// message from client
socket.on('client', function (msg: any) {
masterSocket.onClient(msg);
}); // end of on 'client'
socket.on('reconnect', function (msg: any) {
masterSocket.onReconnect(msg);
});
socket.on('disconnect', function () {
masterSocket.onDisconnect();
});
socket.on('close', function () {
masterSocket.onDisconnect();
});
socket.on('error', function (err: Error) {
masterSocket.onError(err);
});
}); // end of on 'connection'
} // end of listen
/**
* close master agent
*
* @api public
*/
close() {
if (this.state > ST_STARTED) {
return;
}
this.state = ST_CLOSED;
this.server.close();
}
/**
* set module
*
* @param {String} moduleId module id/name
* @param {Object} value module object
* @api public
*/
set(moduleId: string, value: any) {
this.consoleService.set(moduleId, value);
}
/**
* get module
*
* @param {String} moduleId module id/name
* @api public
*/
get(moduleId: string) {
return this.consoleService.get(moduleId);
}
/**
* getClientById
*
* @param {String} clientId
* @api public
*/
getClientById(clientId: string) {
return this.clients[clientId];
}
/**
* request monitor{master node} data from monitor
*
* @param {String} serverId
* @param {String} moduleId module id/name
* @param {Object} msg
* @param {Function} callback function
* @api public
*/
request(serverId: string, moduleId: string, msg: any, cb: (errOrResult?: Error | any, body?: any) => void) {
if (this.state > ST_STARTED) {
return false;
}
cb = cb || function () { };
let curId = this.reqId++;
this.callbacks[curId] = cb;
if (!this.msgMap[serverId]) {
this.msgMap[serverId] = {};
}
this.msgMap[serverId][curId] = {
moduleId: moduleId,
msg: msg
};
let record = this.idMap[serverId];
if (!record) {
cb(new Error('unknown server id:' + serverId));
return false;
}
this.sendToMonitor(record.socket, curId, moduleId, msg);
return true;
}
/**
* request server data from monitor by serverInfo{host:port}
*
* @param {String} serverId
* @param {Object} serverInfo
* @param {String} moduleId module id/name
* @param {Object} msg
* @param {Function} callback function
* @api public
*/
requestServer(serverId: string, serverInfo: ServerInfo, moduleId: string, msg: any, cb: Callback) {
if (this.state > ST_STARTED) {
return false;
}
let record = this.idMap[serverId];
if (!record) {
utils.invokeCallback(cb, new Error('unknown server id:' + serverId));
return false;
}
let curId = this.reqId++;
this.callbacks[curId] = cb;
if (utils.compareServer(record.info as ServerInfo, serverInfo)) {
this.sendToMonitor(record.socket, curId, moduleId, msg);
} else {
let slaves = this.slaveMap[serverId];
for (let i = 0, l = slaves.length; i < l; i++) {
if (utils.compareServer(slaves[i].info as ServerInfo, serverInfo)) {
this.sendToMonitor(slaves[i].socket, curId, moduleId, msg);
break;
}
}
}
return true;
}
/**
* notify a monitor{master node} by id without callback
*
* @param {String} serverId
* @param {String} moduleId module id/name
* @param {Object} msg
* @api public
*/
notifyById(serverId: string, moduleId: string, msg: any) {
if (this.state > ST_STARTED) {
return false;
}
let record = this.idMap[serverId];
if (!record) {
logger.error('fail to notifyById for unknown server id:' + serverId);
return false;
}
this.sendToMonitor(record.socket, null, moduleId, msg);
return true;
}
/**
* notify a monitor by server{host:port} without callback
*
* @param {String} serverId
* @param {Object} serverInfo{host:port}
* @param {String} moduleId module id/name
* @param {Object} msg
* @api public
*/
notifyByServer(serverId: string, serverInfo: ServerInfo, moduleId: string, msg: any) {
if (this.state > ST_STARTED) {
return false;
}
let record = this.idMap[serverId];
if (!record) {
logger.error('fail to notifyByServer for unknown server id:' + serverId);
return false;
}
if (utils.compareServer(record.info as ServerInfo, serverInfo)) {
this.sendToMonitor(record.socket, null, moduleId, msg);
} else {
let slaves = this.slaveMap[serverId];
for (let i = 0, l = slaves.length; i < l; i++) {
if (utils.compareServer(slaves[i].info as ServerInfo, serverInfo)) {
this.sendToMonitor(slaves[i].socket, null, moduleId, msg);
break;
}
}
}
return true;
}
/**
* notify slaves by id without callback
*
* @param {String} serverId
* @param {String} moduleId module id/name
* @param {Object} msg
* @api public
*/
notifySlavesById(serverId: string, moduleId: string, msg: any) {
if (this.state > ST_STARTED) {
return false;
}
let slaves = this.slaveMap[serverId];
if (!slaves || slaves.length === 0) {
logger.error('fail to notifySlavesById for unknown server id:' + serverId);
return false;
}
this.broadcastMonitors(slaves, moduleId, msg);
return true;
}
/**
* notify monitors by type without callback
*
* @param {String} type serverType
* @param {String} moduleId module id/name
* @param {Object} msg
* @api public
*/
notifyByType(type: string, moduleId: string, msg: any) {
if (this.state > ST_STARTED) {
return false;
}
let list = this.typeMap[type];
if (!list || list.length === 0) {
logger.error('fail to notifyByType for unknown server type:' + type);
return false;
}
this.broadcastMonitors(list, moduleId, msg);
return true;
}
/**
* notify all the monitors without callback
*
* @param {String} moduleId module id/name
* @param {Object} msg
* @api public
*/
notifyAll(moduleId: string, msg?: any) {
if (this.state > ST_STARTED) {
return false;
}
this.broadcastMonitors(this.idMap, moduleId, msg);
return true;
}
/**
* notify a client by id without callback
*
* @param {String} clientId
* @param {String} moduleId module id/name
* @param {Object} msg
* @api public
*/
notifyClient(clientId: string, moduleId: string, msg: any) {
if (this.state > ST_STARTED) {
return false;
}
let record = this.clients[clientId];
if (!record) {
logger.error('fail to notifyClient for unknown client id:' + clientId);
return false;
}
this.sendToClient(record.socket, null, moduleId, msg);
}
notifyCommand(command: string, moduleId: string, msg: any) {
if (this.state > ST_STARTED) {
return false;
}
this.broadcastCommand(this.idMap, command, moduleId, msg);
return true;
}
doAuthUser(msg: AuthUserRequest, socket: MqttSocket, cb: Callback) {
if (!msg.id) {
// client should has a client id
return cb(new Error('client should has a client id'));
}
let self = this;
let username = msg.username;
if (!username) {
// client should auth with username
this.doSend(socket, 'register', {
code: protocol.PRO_FAIL,
msg: 'client should auth with username'
});
return cb(new Error('client should auth with username'));
}
let authUser = self.consoleService.authUser;
let env = self.consoleService.env;
authUser(msg, env, (user) => {
if (!user) {
// client should auth with username
this.doSend(socket, 'register', {
code: protocol.PRO_FAIL,
msg: 'client auth failed with username or password error'
});
return cb(new Error('client auth failed with username or password error'));
}
if (self.clients[msg.id]) {
this.doSend(socket, 'register', {
code: protocol.PRO_FAIL,
msg: 'id has been registered. id:' + msg.id
});
return cb(new Error('id has been registered. id:' + msg.id));
}
logger.info('client user : ' + username + ' login to master');
this.addConnection(msg.id, msg.type, null, user, socket);
this.doSend(socket, 'register', {
code: protocol.PRO_OK,
msg: 'ok'
});
cb();
});
}
doAuthServer(msg: AuthServerRequest, socket: MqttSocket, cb: Callback) {
let self = this;
let authServer = self.consoleService.authServer;
let env = self.consoleService.env;
authServer(msg, env, (status) => {
if (status !== 'ok') {
this.doSend(socket, 'register', {
code: protocol.PRO_FAIL,
msg: 'server auth failed,check config `adminServer`.'
});
cb(new Error('server auth failed,check config `adminServer`.'));
return;
}
let record = this.addConnection(msg.id, msg.serverType, msg.pid, msg.info, socket);
this.doSend(socket, 'register', {
code: protocol.PRO_OK,
msg: 'ok'
});
msg.info = msg.info;
msg.info.pid = msg.pid;
self.emit('register', msg.info);
cb(null);
});
}
/**
* add monitor,client to connection -- idMap
*
* @param {Object} agent agent object
* @param {String} id
* @param {String} type serverType
* @param {Object} socket socket-io object
* @api private
*/
addConnection(id: string, type: string, pid: string, info: AdminUserInfo | ServerInfo, socket: MqttSocket) {
let record: AgentClient = {
id: id,
type: type,
pid: pid,
info: info,
socket: socket
};
if (type === 'client') {
this.clients[id] = record;
} else {
if (!this.idMap[id]) {
this.idMap[id] = record;
let list = this.typeMap[type] = this.typeMap[type] || [];
list.push(record);
} else {
let slaves = this.slaveMap[id] = this.slaveMap[id] || [];
slaves.push(record);
}
}
return record;
}
/**
* remove monitor,client connection -- idMap
*
* @param {Object} agent agent object
* @param {String} id
* @param {String} type serverType
* @api private
*/
removeConnection(id: string, type: string, info: ServerInfo) {
if (type === 'client') {
delete this.clients[id];
} else {
// remove master node in idMap and typeMap
let record = this.idMap[id];
if (!record) {
return;
}
let _info = record['info']; // info {host, port}
if (utils.compareServer(_info as ServerInfo, info)) {
delete this.idMap[id];
let list = this.typeMap[type];
if (list) {
for (let i = 0, l = list.length; i < l; i++) {
if (list[i].id === id) {
list.splice(i, 1);
break;
}
}
if (list.length === 0) {
delete this.typeMap[type];
}
}
} else {
// remove slave node in slaveMap
let slaves = this.slaveMap[id];
if (slaves) {
for (let i = 0, l = slaves.length; i < l; i++) {
if (utils.compareServer(slaves[i]['info'] as ServerInfo, info)) {
slaves.splice(i, 1);
break;
}
}
if (slaves.length === 0) {
delete this.slaveMap[id];
}
}
}
}
}
/**
* send msg to monitor
*
* @param {Object} socket socket-io object
* @param {Number} reqId request id
* @param {String} moduleId module id/name
* @param {Object} msg message
* @api private
*/
sendToMonitor(socket: MqttSocket, reqId: number, moduleId: string, msg: any) {
this.doSend(socket, 'monitor', protocol.composeRequest(reqId, moduleId, msg));
}
/**
* send msg to client
*
* @param {Object} socket socket-io object
* @param {Number} reqId request id
* @param {String} moduleId module id/name
* @param {Object} msg message
* @api private
*/
sendToClient(socket: MqttSocket, reqId: number, moduleId: string, msg: any) {
this.doSend(socket, 'client', protocol.composeRequest(reqId, moduleId, msg));
}
doSend(socket: MqttSocket, topic: string, msg: any) {
socket.send(topic, msg);
}
/**
* broadcast msg to monitor
*
* @param {Object} record registered modules
* @param {String} moduleId module id/name
* @param {Object} msg message
* @api private
*/
broadcastMonitors(records: { [serverId: string]: AgentClient } | AgentClient[] , moduleId: string, msg: any) {
msg = protocol.composeRequest(null, moduleId, msg);
if (records instanceof Array) {
for (let i = 0, l = records.length; i < l; i++) {
let socket = records[i].socket;
this.doSend(socket, 'monitor', msg);
}
} else {
for (let id in records) {
let record = (records as { [id: string]: AgentClient })[id];
let socket = record.socket;
this.doSend(socket, 'monitor', msg);
}
}
}
broadcastCommand(records: AgentClient[] | { [id: string]: AgentClient }, command: string, moduleId: string, msg: any) {
msg = protocol.composeCommand(null, command, moduleId, msg);
if (records instanceof Array) {
for (let i = 0, l = records.length; i < l; i++) {
let socket = records[i].socket;
this.doSend(socket, 'monitor', msg);
}
}
else {
for (let id in records) {
let record = (records as { [id: string]: AgentClient })[id];
let socket = record.socket;
this.doSend(socket, 'monitor', msg);
}
}
}
}