pomelo-upgrade
Version:
627 lines (585 loc) • 16.9 kB
JavaScript
;
const logger = require('pomelo-logger-upgrade').getLogger('pomelo', __filename);
const countDownLatch = require('../util/countDownLatch');
const utils = require('../util/utils');
const Constants = require('../util/constants');
const starter = require('../master/starter');
const exec = require('child_process').exec;
class ConsoleModules
{
constructor(opts)
{
opts = opts || {};
this.app = opts.app;
this.starter = opts.starter;
}
monitorHandler(agent, msg, cb)
{
const serverId = agent.id;
switch (msg.signal)
{
case 'stop':
if (agent.type === Constants.RESERVED.MASTER)
{
return;
}
this.app.stop(true);
break;
case 'list':
{
const serverType = agent.type;
const pid = process.pid;
const heapUsed = (process.memoryUsage().heapUsed / (1024 * 1024)).toFixed(2);
const rss = (process.memoryUsage().rss / (1024 * 1024)).toFixed(2);
const heapTotal = (process.memoryUsage().heapTotal / (1024 * 1024)).toFixed(2);
utils.invokeCallback(cb, {
serverId : serverId,
body : {
serverId : serverId,
serverType : serverType,
pid : pid,
rss : rss,
heapTotal : heapTotal,
heapUsed : heapUsed,
uptime : (process.uptime() / 60).toFixed(2)
}
});
}
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.addCron([msg.cron]);
break;
case 'removeCron':
this.app.removeCron([msg.cron]);
break;
case 'updateCron':
this.app.updateCron([msg.cron]);
break;
case 'blacklist':
if (this.app.isFrontend())
{
const command = msg.command || 'add';
const connector = this.app.components.__connector__;
switch (command)
{
case 'add':
connector.blacklist = connector.blacklist.concat(msg.blacklist);
connector.blacklist = Array.from(new Set(connector.blacklist));
break;
case 'del':
if (!Array.isArray(msg.blacklist))
msg.blacklist = [msg.blacklist];
connector.blacklist = connector.blacklist.filter(v => {return !msg.blacklist.includes(v);});
break;
}
}
break;
case 'restart':
if (agent.type === Constants.RESERVED.MASTER)
{
return;
}
utils.invokeCallback(cb, this.app.get(Constants.RESERVED.CURRENT_SERVER));
process.nextTick(() =>
{
this.app.stop(true);
});
break;
default:
logger.error('receive error signal: %j', msg);
break;
}
}
clientHandler(agent, msg, cb)
{
const 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(agent, msg, cb);
break;
case 'add':
add(app, msg, cb);
break;
case 'addCron':
addCron(app, agent, msg, cb);
break;
case 'removeCron':
removeCron(app, agent, msg, cb);
break;
case 'updateCron':
updateCron(app, agent, msg, cb);
break;
case 'blacklist':
blacklist(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;
}
}
}
module.exports = function(opts)
{
return new ConsoleModules(opts);
};
module.exports.moduleId = '__console__';
const kill = function(app, agent, msg, cb)
{
let serverIds = msg.ids;
const servers = agent.idMap;
if (!serverIds.length)
{
serverIds = Object.keys(servers);
}
const count = serverIds.length;
const latch = countDownLatch.createCountDownLatch(count, {timeout: Constants.TIME.TIME_WAIT_MASTER_KILL}, function(isTimeout)
{
if (!isTimeout)
{
utils.invokeCallback(cb, null, {code: 'ok'});
return;
}
else
{
utils.invokeCallback(cb, null, {
code : 'remained',
serverIds : serverIds});
}
setTimeout(function()
{
process.exit(-1);
}, Constants.TIME.TIME_WAIT_MONITOR_KILL);
});
const agentRequestCallback = function(requestMsg)
{
const index = serverIds.indexOf(requestMsg);
if (index > -1)
{
serverIds.splice(index, 1);
latch.done();
}
};
for (let i = 0; i < count; i++) {
const serverId = serverIds[i];
agent.request(serverId, module.exports.moduleId, {signal: msg.signal}, agentRequestCallback);
}
};
const stop = function(app, agent, msg, cb)
{
const serverIds = msg.ids;
if (serverIds.length)
{
const servers = app.getServers();
app.set(Constants.RESERVED.STOP_SERVERS, serverIds);
for (let i = 0; i < serverIds.length; i++)
{
const serverId = serverIds[i];
if (!servers[serverId])
{
utils.invokeCallback(cb, new Error('Cannot find the server to stop.'), null);
}
else
{
agent.notifyById(serverId, module.exports.moduleId, {signal: msg.signal});
}
}
utils.invokeCallback(cb, null, {status: 'part'});
}
else
{
const serverIds = Object.keys(app.getServers());
app.set(Constants.RESERVED.STOP_SERVERS, serverIds);
agent.notifyAll(module.exports.moduleId, {signal: msg.signal});
setTimeout(function()
{
app.stop(true);
utils.invokeCallback(cb, null, {status: 'all'});
}, Constants.TIME.TIME_WAIT_STOP);
}
};
const restart = function(app, agent, msg, cb)
{
let successFlag;
const successIds = [];
const serverIds = msg.ids;
const type = msg.type;
let servers;
if (!serverIds.length && Boolean(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 (const key in servers)
{
serverIds.push(key);
}
}
const count = serverIds.length;
const 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));
});
const request = function(id)
{
return (function()
{
agent.request(id, module.exports.moduleId, {signal: msg.signal}, function(msg)
{
if (!utils.size(msg))
{
latch.done();
return;
}
setTimeout(function()
{
runServer(app, msg, function(err, status)
{
if (err)
{
logger.error(`restart ${id} failed.`);
}
else
{
successIds.push(id);
successFlag = true;
}
latch.done();
});
}, Constants.TIME.TIME_WAIT_RESTART);
});
})();
};
for (let j = 0; j < serverIds.length; j++)
{
request(serverIds[j]);
}
};
const list = function(agent, msg, cb)
{
let sid, record;
const serverInfo = {};
const count = utils.size(agent.idMap);
const latch = countDownLatch.createCountDownLatch(count, {timeout: Constants.TIME.TIME_WAIT_COUNTDOWN}, function()
{
utils.invokeCallback(cb, null, {msg: serverInfo});
});
const callback = function(msg)
{
serverInfo[msg.serverId] = msg.body;
latch.done();
};
for (sid in agent.idMap)
{
record = agent.idMap[sid];
agent.request(record.id, module.exports.moduleId, {signal: msg.signal}, callback);
}
};
const add = function(app, msg, cb)
{
if (checkCluster(msg))
{
startCluster(app, msg, cb);
}
else
{
startServer(app, msg, cb);
}
reset(ServerInfo);
};
const addCron = function(app, agent, msg, cb)
{
const cron = parseArgs(msg, CronInfo, cb);
if (cron)
sendCronInfo(cron, agent, msg, CronInfo, cb);
};
const removeCron = function(app, agent, msg, cb)
{
const cron = parseArgs(msg, RemoveCron, cb);
if (cron) sendCronInfo(cron, agent, msg, RemoveCron, cb);
};
const updateCron = function(app, agent, msg, cb)
{
const cron = parseArgs(msg, CronInfo, cb);
if (cron) sendCronInfo(cron, agent, msg, CronInfo, cb);
};
const blacklist = function(agent, msg, cb)
{
const 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(module.exports.moduleId, {signal : msg.signal,
blacklist : msg.args});
process.nextTick(function()
{
cb(null, {status: 'ok'});
});
};
const checkPort = function(server, cb)
{
if (!server.port && !server.clientPort)
{
utils.invokeCallback(cb, 'leisure');
return;
}
let p = server.port || server.clientPort;
const host = server.host;
let cmd = 'netstat -tln | grep ';
if (!utils.isLocal(host))
{
cmd = `ssh ${host} ${cmd}`;
}
exec(cmd + p, function(err, stdout, stderr)
{
if (stdout || stderr)
{
utils.invokeCallback(cb, 'busy');
}
else
{
p = server.clientPort;
exec(cmd + p, function(err, stdout, stderr)
{
if (stdout || stderr)
{
utils.invokeCallback(cb, 'busy');
}
else
{
utils.invokeCallback(cb, 'leisure');
}
});
}
});
};
const parseArgs = function(msg, info, cb)
{
const args = msg.args || [];
const length = args.length;
if (length <= 0) return;
const rs = {};
for (let i = 0; i < length; i++)
{
if (args[i].indexOf('=') < 0)
{
cb(new Error('Error server parameters format.'), null);
return;
}
const pairs = args[i].split('=');
const key = pairs[0];
if (info[key])
{
info[key] = 1;
}
rs[pairs[0]] = pairs[1];
}
return rs;
};
const sendCronInfo = function(cron, agent, msg, info, cb)
{
if (isReady(info) && (cron.serverId || cron.serverType))
{
if (cron.serverId)
{
agent.notifyById(cron.serverId, module.exports.moduleId, {signal : msg.signal,
cron : cron});
}
else
{
agent.notifyByType(cron.serverType, module.exports.moduleId, {signal : msg.signal,
cron : cron});
}
process.nextTick(function()
{
cb(null, {status: 'ok'});
});
}
else
{
cb(new Error('Miss necessary server parameters.'), null);
}
reset(info);
};
const startServer = function(app, msg, cb)
{
const server = parseArgs(msg, ServerInfo, cb);
if (server && isReady(ServerInfo))
{
runServer(app, server, cb);
}
else
{
cb(new Error('Miss necessary server parameters.'), null);
}
};
const runServer = function(app, server, cb)
{
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)
{
utils.invokeCallback(cb, new Error(err), null);
return;
}
});
process.nextTick(function()
{
utils.invokeCallback(cb, null, {status: 'ok'});
});
}
});
};
const startCluster = function(app, msg, cb)
{
const serverMap = {};
const fails = [];
let successFlag;
const serverInfo = parseArgs(msg, ClusterInfo, cb);
if (!serverInfo) utils.invokeCallback(cb, new Error('all servers start failed.'));
utils.loadCluster(app, serverInfo, serverMap);
const count = utils.size(serverMap);
const latch = countDownLatch.createCountDownLatch(count, function()
{
if (!successFlag)
{
utils.invokeCallback(cb, new Error('all servers start failed.'));
return;
}
utils.invokeCallback(cb, null, fails);
});
const start = function(server)
{
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);
latch.done();
}
});
process.nextTick(function()
{
successFlag = true;
latch.done();
});
}
});
})();
};
for (const key in serverMap)
{
const server = serverMap[key];
start(server);
}
};
const checkCluster = function(msg)
{
let flag = false;
const args = msg.args;
for (let i = 0; i < args.length; i++)
{
if (utils.startsWith(args[i], Constants.RESERVED.CLUSTER_COUNT))
{
flag = true;
break;
}
}
return flag;
};
const isReady = function(info)
{
for (const key in info)
{
if (info[key])
{
return false;
}
}
return true;
};
const reset = function(info)
{
for (const key in info)
{
info[key] = 0;
}
};
const ServerInfo = {
host : 0,
port : 0,
id : 0,
serverType : 0
};
const CronInfo = {
id : 0,
action : 0,
time : 0
};
const RemoveCron = {
id : 0
};
const ClusterInfo = {
host : 0,
port : 0,
clusterCount : 0
};