occaecatidicta
Version:
499 lines (461 loc) • 16.9 kB
text/typescript
/*!
* Omelox -- consoleModule serverStop stop/kill
* Copyright(c) 2012 fantasyni <fantasyni@163.com>
* MIT Licensed
*/
import { getLogger } from 'omelox-logger';
import * as countDownLatch from '../util/countDownLatch';
import * as utils from '../util/utils';
import * as Constants from '../util/constants';
import * as starter from '../master/starter';
import { exec } from 'child_process';
import { Application } from '../application';
import { IModule, MonitorCallback, MasterAgent, MasterCallback } from 'omelox-admin';
import { MonitorAgent } from 'omelox-admin';
import { ServerInfo } from '../util/constants';
import * as path from 'path';
import * as os from 'os';
let logger = getLogger('omelox', path.basename(__filename));
export interface ConsoleModuleOptions {
app?: Application;
}
export class ConsoleModule implements IModule {
app: Application;
static moduleId = '__console__';
constructor(opts: ConsoleModuleOptions) {
opts = opts || {};
this.app = opts.app;
}
monitorHandler(agent: MonitorAgent, msg: any, cb: MonitorCallback) {
let serverId = agent.id;
switch (msg.signal) {
case 'stop':
if (agent.type === Constants.RESERVED.MASTER) {
return;
}
this.app.stop(true);
break;
case 'list':
let serverType = agent.type;
let pid = process.pid;
let heapUsed = (process.memoryUsage().heapUsed / (1024 * 1024)).toFixed(2);
let rss = (process.memoryUsage().rss / (1024 * 1024)).toFixed(2);
let heapTotal = (process.memoryUsage().heapTotal / (1024 * 1024)).toFixed(2);
let uptime = (process.uptime() / 60).toFixed(2);
utils.invokeCallback(cb, {
serverId: serverId,
body: {
serverId: serverId,
serverType: serverType,
pid: pid,
rss: rss,
heapTotal: heapTotal,
heapUsed: heapUsed,
uptime: uptime
}
});
break;
case 'kill':
utils.invokeCallback(cb, serverId);
if (agent.type !== 'master') {
setTimeout(function () {
process.exit(-1);
}, Constants.TIME.TIME_WAIT_MONITOR_KILL);
}
break;
case 'addCron':
this.app.addCrons([msg.cron]);
break;
case 'removeCron':
this.app.removeCrons([msg.cron]);
break;
case 'blacklist':
if (this.app.isFrontend()) {
let connector = this.app.components.__connector__;
connector.blacklist = connector.blacklist.concat(msg.blacklist);
}
break;
case 'restart':
if (agent.type === Constants.RESERVED.MASTER) {
return;
}
let self = this;
let server = this.app.get(Constants.RESERVED.CURRENT_SERVER);
utils.invokeCallback(cb, server);
process.nextTick(function () {
self.app.stop(true);
});
break;
default:
logger.error('receive error signal: %j', msg);
break;
}
}
clientHandler(agent: MasterAgent, msg: any, cb: MasterCallback) {
let app = this.app;
switch (msg.signal) {
case 'kill':
kill(app, agent, msg, cb);
break;
case 'stop':
stop(app, agent, msg, cb);
break;
case 'list':
list(app, agent, msg, cb);
break;
case 'add':
add(app, agent, msg, cb);
break;
case 'addCron':
addCron(app, agent, msg, cb);
break;
case 'removeCron':
removeCron(app, agent, msg, cb);
break;
case 'blacklist':
blacklist(app, agent, msg, cb);
break;
case 'restart':
restart(app, agent, msg, cb);
break;
default:
utils.invokeCallback(cb, new Error('The command cannot be recognized, please check.'), null);
break;
}
}
}
let kill = function (app: Application, agent: MasterAgent, msg: any, cb: MasterCallback) {
let sid, record;
let serverIds: string[] = [];
let count = Object.keys(agent.idMap).length;
let latch = countDownLatch.createCountDownLatch(count, { timeout: Constants.TIME.TIME_WAIT_MASTER_KILL }, function (isTimeout) {
if (!isTimeout) {
utils.invokeCallback(cb, null, { code: 'ok' });
} else {
utils.invokeCallback(cb, null, { code: 'remained', serverIds: serverIds });
}
setTimeout(function () {
process.exit(-1);
}, Constants.TIME.TIME_WAIT_MONITOR_KILL);
});
let agentRequestCallback = function (msg: string) {
for (let i = 0; i < serverIds.length; ++i) {
if (serverIds[i] === msg) {
serverIds.splice(i, 1);
latch.done();
break;
}
}
};
for (sid in agent.idMap) {
record = agent.idMap[sid];
serverIds.push(record.id);
agent.request(record.id, ConsoleModule.moduleId, { signal: msg.signal }, agentRequestCallback);
}
};
let stop = function (app: Application, agent: MasterAgent, msg: any, cb: MasterCallback) {
let serverIds = msg.ids;
if (!!serverIds.length) {
let servers = app.getServers();
app.set(Constants.RESERVED.STOP_SERVERS, serverIds);
for (let i = 0; i < serverIds.length; i++) {
let serverId = serverIds[i];
if (!servers[serverId]) {
utils.invokeCallback(cb, new Error('Cannot find the server to stop.'), null);
} else {
agent.notifyById(serverId, ConsoleModule.moduleId, { signal: msg.signal });
}
}
utils.invokeCallback(cb, null, { status: 'part' });
} else {
let servers = app.getServers();
let serverIds: any = [];
for (let key in servers) {
serverIds.push(key);
}
app.set(Constants.RESERVED.STOP_SERVERS, serverIds);
agent.notifyAll(ConsoleModule.moduleId, { signal: msg.signal });
setTimeout(function () {
app.stop(true);
utils.invokeCallback(cb, null, { status: 'all' });
}, Constants.TIME.TIME_WAIT_STOP);
}
};
let restart = function (app: Application, agent: MasterAgent, msg: any, cb: MasterCallback) {
let successFlag: boolean;
let successIds: string[] = [];
let serverIds = msg.ids;
let type = msg.type;
let servers;
if (!serverIds.length && !!type) {
servers = app.getServersByType(type);
if (!servers) {
utils.invokeCallback(cb, new Error('restart servers with unknown server type: ' + type));
return;
}
for (let i = 0; i < servers.length; i++) {
serverIds.push(servers[i].id);
}
} else if (!serverIds.length) {
servers = app.getServers();
for (let key in servers) {
serverIds.push(key);
}
}
let count = serverIds.length;
let latch = countDownLatch.createCountDownLatch(count, { timeout: Constants.TIME.TIME_WAIT_COUNTDOWN }, function () {
if (!successFlag) {
utils.invokeCallback(cb, new Error('all servers start failed.'));
return;
}
utils.invokeCallback(cb, null, utils.arrayDiff(serverIds, successIds));
});
let request = function (id: string) {
return (function () {
agent.request(id, ConsoleModule.moduleId, { signal: msg.signal }, function (msg) {
if (msg && !Object.keys(msg).length) {
latch.done();
return;
}
setTimeout(function () {
runServer(app, msg, function (err, status) {
if (!!err) {
logger.error('restart ' + id + ' failed.', err.message, 'status:', status);
} else {
successIds.push(id);
successFlag = true;
}
latch.done();
});
}, Constants.TIME.TIME_WAIT_RESTART);
});
})();
};
for (let j = 0; j < serverIds.length; j++) {
request(serverIds[j]);
}
};
let list = function (app: Application, agent: MasterAgent, msg: any, cb: MasterCallback) {
let sid, record;
let serverInfo: any = {};
let count = Object.keys(agent.idMap).length;
let latch = countDownLatch.createCountDownLatch(count, { timeout: Constants.TIME.TIME_WAIT_COUNTDOWN }, function () {
utils.invokeCallback(cb, null, { msg: serverInfo });
});
let callback = function (msg: { serverId: string, body: any }) {
serverInfo[msg.serverId] = msg.body;
latch.done();
};
for (sid in agent.idMap) {
record = agent.idMap[sid];
agent.request(record.id, ConsoleModule.moduleId, { signal: msg.signal }, callback);
}
};
let add = function (app: Application, agent: MasterAgent, msg: any, cb: MasterCallback) {
if (checkCluster(msg)) {
startCluster(app, msg, cb);
} else {
startServer(app, msg, cb);
}
reset(ServerInfo);
};
let addCron = function (app: Application, agent: MasterAgent, msg: any, cb: MasterCallback) {
let cron = parseArgs(msg, CronInfo, cb);
sendCronInfo(cron, agent, msg, CronInfo, cb);
};
let removeCron = function (app: Application, agent: MasterAgent, msg: any, cb: MasterCallback) {
let cron = parseArgs(msg, RemoveCron, cb);
sendCronInfo(cron, agent, msg, RemoveCron, cb);
};
let blacklist = function (app: Application, agent: MasterAgent, msg: any, cb: MasterCallback) {
let ips = msg.args;
for (let i = 0; i < ips.length; i++) {
if (!(new RegExp(/(\d+)\.(\d+)\.(\d+)\.(\d+)/g).test(ips[i]))) {
utils.invokeCallback(cb, new Error('blacklist ip: ' + ips[i] + ' is error format.'), null);
return;
}
}
agent.notifyAll(ConsoleModule.moduleId, { signal: msg.signal, blacklist: msg.args });
process.nextTick(function () {
cb(null, { status: 'ok' });
});
};
let checkPort = function (server: ServerInfo, cb: MasterCallback) {
if (!server.port && !server.clientPort) {
utils.invokeCallback(cb, 'leisure');
return;
}
let p = server.port || server.clientPort;
let host = server.host;
// let cmd = 'netstat -tln | grep ';
let cmd = os.type() === 'Windows_NT' ?
`netstat -ano | %windir%\\system32\\find.exe ` : `netstat -tln | grep `;
if (!utils.isLocal(host)) {
cmd = 'ssh ' + host + ' ' + cmd;
}
p = os.type() === 'Windows_NT' ? `"${p}"` as any : p
exec(cmd + p, function (err, stdout, stderr) {
if (stdout || stderr) {
logger.debug('checkport has error?', cmd + p, 'stdout:', stdout, 'stderr', stderr)
utils.invokeCallback(cb, 'busy');
} else {
p = server.clientPort;
p = os.type() === 'Windows_NT' ? `"${p}"` as any : p
exec(cmd + p, function (err, stdout, stderr) {
if (stdout || stderr) {
utils.invokeCallback(cb, 'busy');
} else {
utils.invokeCallback(cb, 'leisure');
}
});
}
});
};
let parseArgs = function (msg: any, info: any, cb: (err?: string | Error, data?: any) => void) {
let rs: { [key: string]: string } = {};
let args = msg.args;
for (let i = 0; i < args.length; i++) {
if (args[i].indexOf('=') < 0) {
cb(new Error('Error server parameters format.'), null);
return;
}
let pairs = args[i].split('=');
let key = pairs[0];
if (!!info[key]) {
info[key] = 1;
}
rs[pairs[0]] = pairs[1];
}
return rs;
};
let sendCronInfo = function (cron: any, agent: MasterAgent, msg: any, info: any, cb: Function) {
if (isReady(info) && (cron.serverId || cron.serverType)) {
if (!!cron.serverId) {
agent.notifyById(cron.serverId, ConsoleModule.moduleId, { signal: msg.signal, cron: cron });
} else {
agent.notifyByType(cron.serverType, ConsoleModule.moduleId, { signal: msg.signal, cron: cron });
}
process.nextTick(function () {
cb(null, { status: 'ok' });
});
} else {
cb(new Error('Miss necessary server parameters.'), null);
}
reset(info);
};
let startServer = function (app: Application, msg: any, cb: (err?: Error | string, result?: any) => void) {
let server = parseArgs(msg, ServerInfo, cb);
if (isReady(ServerInfo)) {
runServer(app, server as any, cb);
} else {
cb(new Error('Miss necessary server parameters.'), null);
}
};
let runServer = function (app: Application, server: ServerInfo, cb: (err?: Error, result?: any) => void) {
checkPort(server, function (status) {
if (status === 'busy') {
utils.invokeCallback(cb, new Error('Port occupied already, check your server to add.'));
} else {
starter.run(app, server, function (err) {
if (err) {
err = String(err)
const checkErrCorrect = 'https://nodejs.org/en/docs/inspector'
const idx = err.indexOf(checkErrCorrect)
if (idx === -1 || idx + checkErrCorrect.length + 10 < err.length) {
utils.invokeCallback(cb, new Error(err), null);
return;
}
}
});
process.nextTick(function () {
utils.invokeCallback(cb, null, { status: 'ok' });
});
}
});
};
let startCluster = function (app: Application, msg: any, cb: MasterCallback) {
let serverMap = {};
let fails: ServerInfo[] = [];
let successFlag: boolean;
let serverInfo = parseArgs(msg, ClusterInfo, cb) as any;
utils.loadCluster(app, serverInfo, serverMap);
let count = Object.keys(serverMap).length;
let latch = countDownLatch.createCountDownLatch(count, () => {
if (!successFlag) {
utils.invokeCallback(cb, new Error('all servers start failed.'));
return;
}
utils.invokeCallback(cb, null, fails);
});
let start = function (server: ServerInfo) {
return (function () {
checkPort(server, function (status) {
if (status === 'busy') {
fails.push(server);
latch.done();
} else {
starter.run(app, server, function (err) {
if (err) {
fails.push(server);
if(latch.count) {
latch.done();
}
}
});
process.nextTick(function () {
successFlag = true;
if(latch.count) {
latch.done();
}
});
}
});
})();
};
for (let key in serverMap) {
let server = (serverMap as any)[key];
start(server);
}
};
let checkCluster = function (msg: any) {
let flag = false;
let args = msg.args;
for (let i = 0; i < args.length; i++) {
if (utils.startsWith(args[i], Constants.RESERVED.CLUSTER_COUNT)) {
flag = true;
}
}
return flag;
};
let isReady = function (info: any) {
for (let key in info) {
if (info[key]) {
return false;
}
}
return true;
};
let reset = function (info: any) {
for (let key in info) {
info[key] = 0;
}
};
let ServerInfo = {
host: 0,
port: 0,
id: 0,
serverType: 0
};
let CronInfo = {
id: 0,
action: 0,
time: 0
};
let RemoveCron = {
id: 0
};
let ClusterInfo = {
host: 0,
port: 0,
clusterCount: 0
};