tellki-agent
Version:
The monitoring agent for Tellki - an IT monitoring and management cloud service.
662 lines • 34.2 kB
JavaScript
(function () {
'use strict';
var path = require('path'),
fs = require('fs'),
util = require('util'),
$path = path.dirname(fs.realpathSync(__filename)),
$a = {},
$j = {},
options = {
logLevel: 2,
exitLevel: 0,
retry: 10000,
url: 'https://agent.tellki.com/agent',
writetoLog: true,
cfgPath: path.join($path, '../cfg'),
logPath: path.join($path, '../log'),
libPath: path.join($path, '../libs'),
key: null,
tags: null,
id: null
},
api = {
conf: {
read: function (file, callBack) {
var cfg = null;
fs.readFile(file, 'utf8', function (err, data) {
if (err) {
if (err.code === 'ENOENT') {
cfg = undefined;
} else {
api.log.console(err, 2);
}
}
else {
cfg = (data.length === 0) ? {} : JSON.parse(data);
}
callBack(cfg);
});
},
write: function (file, data, callBack) {
fs.writeFile(file, data, function (err) {
if (err) {
api.log.console(err, 2);
}
callBack();
});
},
unlink: function (file) {
if (fs.existsSync(file)) {
fs.unlinkSync(file);
api.log.console('Successfully deleted the configuration File. If you want restart the Agent again!', 1);
}
},
validate: function (data) {
if (data !== undefined) {
try {
if (data !== {})
return data.key !== undefined && data.uuid !== undefined;
}
catch (e) { }
}
return false;
},
file: function (p, id) {
return path.join(p, 'agent.' + ((id !== null) ? id + '.' : '') + 'cfg');
}
},
agent: {
set: function (data) {
$a = data;
},
info: function (properties, uuid, callBack) {
var createAgent = function (state, uuid) {
var os = require('os');
function getIpAddress() {
var ifaces = os.networkInterfaces();
var addr = '';
for (var dev in ifaces) {
ifaces[dev].forEach(function (details) {
if (details.family == 'IPv4' && !details.internal) {
if (addr !== '') {
addr += ',';
}
addr += details.address;
}
});
}
return addr;
};
function paths() {
return { log: properties.logFile, libs: properties.libPath, conf: properties.cfgFile };
}
function versions(v) {
process.versions.agent = v;
return process.versions;
}
return {
uuid: (uuid !== null ? uuid : (state !== null) ? state.uuid : null),
host: os.hostname(),
label: properties.id,
key: (properties.key !== null ? properties.key : (state !== null) ? state.key : null),
ip: getIpAddress(),
tz: new Date().getTimezoneOffset(),
platform: process.platform,
os: os.type(),
arch: os.arch(),
v: versions(properties.v),
tags: (properties.tags !== null ? properties.tags : (state !== null) ? state.tags : null),
op: (state !== null) ? 1 : (uuid !== null ? 1 : 0),
libs: api.agent.libs.installed(properties.libPath),
paths: paths(),
PID: process.pid,
url: properties.url,
check: (!api.obj.isnullorundefined(properties.check))
}
};
api.conf.read(properties.cfgFile, function (conf) {
if (api.conf.validate(conf)) {
api.log.console('Restoring the agent...');
callBack(JSON.stringify(createAgent(conf, uuid !== undefined ? uuid : null)));
} else {
api.log.console((uuid !== undefined ? 'Recovering' : 'Activating') + ' the new agent...');
callBack(JSON.stringify(createAgent(null, uuid !== undefined ? uuid : null)));
}
});
},
libs:
{
remove: function (installPath, callBack) {
var files = [];
if (fs.existsSync(installPath)) {
files = fs.readdirSync(installPath);
files.forEach(function (file, index) {
var curPath = path.join(installPath, file);
if (fs.lstatSync(curPath).isDirectory()) {
api.agent.libs.remove(curPath);
} else {
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(installPath);
}
if (callBack !== undefined && typeof (callBack) === 'function')
callBack();
},
install: function (installPath, setup, key, jobs, debug, callBack) {
function _install(_path, callBack) {
function runInstall() {
if (setup.install !== null) {
run.spawn(_path, {
cmd: setup.install[0],
args: setup.install.splice(1, setup.install.length - 1),
timeout: setup.timeout,
private: {},
async: false
},
debug,
function (resp, code, duration) {
callBack(code, new Date().getTime() - startAt, setup.libraryId);
});
} else {
callBack(0, new Date().getTime() - startAt, setup.libraryId);
}
}
var startAt = new Date().getTime();
if (setup.package !== undefined) {
var options = {
host: setup.package.host,
port: setup.package.port,
path: setup.package.path.replace('{key}', key),
};
var protocol = (setup.package.ssl ? require('https') : require('http')), AdmZip = require('adm-zip');
var req = protocol.get(options, function (res) {
var data = [], dataLen = 0;
res.on('data', function (chunk) {
data.push(chunk);
dataLen += chunk.length;
}).on('end', function () {
if (res.statusCode !== 200) {
api.log.console('There was a problem downloading the library, Code:' + res.statusCode, 2);
callBack(-1, new Date().getTime() - startAt, setup.libraryId);
return;
}
if (data.length === 0) {
callBack(2, new Date().getTime() - startAt, setup.libraryId);
return;
}
var buf = new Buffer(dataLen);
for (var i = 0, len = data.length, pos = 0; i < len; i++) {
data[i].copy(buf, pos);
pos += data[i].length;
}
try {
var zip = new AdmZip(buf);
zip.extractAllTo(_path, true);
AdmZip = protocol = null;
runInstall();
}
catch (e) {
api.log.console('There was a problem setting up the library:' + e, 2);
callBack(-1, new Date().getTime() - startAt, e);
return;
}
});
});
req.on('error', function (e) {
api.log.console('Error: There was a problem setting up the library:' + setup.libraryId + ':' + e.message + '@' + options.host, 2);
callBack(-1, new Date().getTime() - startAt, e.message);
return;
});
} else {
try {
fs.mkdirSync(_path);
runInstall();
}
catch (e) {
api.log.console('There was a problem setting up the library:' + setup.libraryId + ':' + e.message, 2);
callBack(-1, new Date().getTime() - startAt, e.message);
}
}
}
api.jobs.stopLib(jobs, setup.libraryId);
var _path = path.join(installPath, setup.path);
if (fs.existsSync(_path) && installPath.indexOf(_path) === -1)
api.agent.libs.remove(_path, function () {
_install(_path, callBack);
});
else
_install(_path, callBack);
},
uninstall: function (installPath, setup, key, jobs, debug, callBack) {
api.jobs.stopLib(jobs, setup.libraryId);
var _path = path.join(installPath, setup.path);
var startAt = new Date().getTime();
try {
if (setup.uninstall !== null) {
run.spawn(_path, {
cmd: setup.uninstall[0],
args: setup.uninstall.splice(1, setup.uninstall.length - 1),
timeout: setup.timeout,
private: {},
async: false
},
debug,
function (resp, code, duration) {
if (fs.existsSync(_path))
api.agent.libs.remove(_path);
callBack(code, new Date().getTime() - startAt, setup.libraryId);
});
} else {
if (fs.existsSync(_path))
api.agent.libs.remove(_path);
callBack(0, new Date().getTime() - startAt, setup.libraryId);
}
}
catch (e) {
callBack(-1, new Date().getTime() - startAt, e);
}
},
installed: function (installPath) {
var _files = [];
var files = fs.readdirSync(installPath);
for (var i in files) {
if (!files.hasOwnProperty(i)) continue;
var name = path.join(installPath, files[i]);
if (fs.statSync(name).isDirectory()) {
_files.push({ id: files[i], ts: fs.statSync(name).mtime.getTime() });
}
}
return _files;
},
upload: function (installPath, pckg, callBack) {
fs.writeFile(path.join(installPath, pckg.libraryId, pckg.fileName), pckg.fileString, pckg.fileBase, function (err) {
if (err) {
api.log.console('Error saving file "' + pckg.fileName + '"!', 1);
callBack(err.errno);
} else
callBack(0);
});
},
removeFile: function (installPath, pckg, callBack) {
fs.unlink(path.join(installPath, pckg.libraryId, pckg.fileName), function (err) {
if (err) {
api.log.console('Error removing file "' + pckg.fileName + '"!', 1);
callBack(err.errno);
} else
callBack(0);
});
}
}
},
log: {
console: function (msg, level) {
if (level === undefined)
level = 0;
if (options.logLevel > level)
return;
var _msg = level + '>' + new Date().toISOString() + ':' + msg;
if (options.writetoLog === true) {
if (options.logFile !== undefined) {
fs.appendFile(options.logFile, _msg + '\r\n', function (err) {
if (err)
process.stdout.write('2>' + err + '\n');
});
}
} else
process.stdout.write(_msg + '\n');
},
file: function (p, id) {
return path.join(p, 'agent.' + ((id !== null) ? id + '.' : '') + 'log');
}
},
jobs: {
add: function (jobs, job) {
if (jobs === null)
jobs = {};
jobs[job.conf.opId] = job;
},
remove: function (jobs, job) {
delete jobs[job.conf.opId];
},
stop: function (jobs, job) {
if (jobs !== null && jobs[job.conf.opId] !== undefined) {
jobs[job.conf.opId].pid.kill('SIGKILL');
delete jobs[job.conf.opId];
}
},
stopLib: function (jobs, libraryId) {
if (jobs !== null) {
for (var j in jobs)
if (jobs[j].conf.libraryId === libraryId)
api.jobs.stop(jobs, jobs[j]);
}
},
stopConf: function (jobs, opId) {
jobs[opId].pid.kill('SIGINT');
delete jobs[opId];
},
stopAll: function (jobs) {
for (var job in jobs) {
jobs[job].pid.kill('SIGINT');
}
jobs = {};
api.log.console('Stopped all running Jobs!');
}
},
obj: {
isnullorundefined: function (obj) {
return (obj === null || obj === undefined);
}
}
},
sockJS = require('sockjs-client'),
socket = {
retrying: null,
_sock: null,
connect: function (url, callBack) {
var sock = sockJS.create(url),
ping = null,
pong = 0;
sock.on('connection', function () {
api.log.console('Connected to Tellki\'s HUB!');
api.agent.info(options, $a.uuid, function (agent) {
sock.write(agent);
});
});
sock.on('data', function (msg) {
if (api.obj.isnullorundefined(msg)) {
return;
}
try {
var json = (typeof msg === 'object') ? msg : JSON.parse(msg);
if (json.op < 0) {
api.log.console(json.resp + ':' + json.op, 2);
sock.close();
process.exit(json.op);
}
switch (json.op) {
case 69:
pong = json.op;
break;
case 1:
api.conf.write(options.cfgFile, JSON.stringify(json.resp), function () {
api.agent.set(json.resp);
if (!api.obj.isnullorundefined(options.check)) {
api.log.console('Valid!');
sock.close();
process.exit(0);
}
api.log.console('Done! Waiting for instructions...');
ping = setInterval(function () {
if (pong === 1) {
clearInterval(ping);
api.log.console('Connection to the Tellki\'s HUB as failed...');
sock.close();
callBack();
} else {
sock.write('{"op":69,"resp":0}');
pong = 1;
}
}, 10000);
});
break;
case 3:
case 5:
api.log.console((json.op === 3 ? 'Command:' : 'Scheduler:') + msg);
var pid = run.spawn(options.libPath, json, options.logLevel === 0, function (resp, code, duration) {
sock.write(socket.response(json, resp, code, duration, $a));
if (!json.async)
api.jobs.remove($j, { conf: json });
});
api.jobs.add($j, { conf: json, pid: pid });
break;
case 100:
api.agent.libs.install(options.libPath, json, $a.key, $j, options.logLevel === 0, function (res, duration, msg) {
api.log.console('Install code:' + res + ' on libraryId:' + json.libraryId);
sock.write(socket.response(json, msg, res, duration, $a));
});
break;
case 102:
sock.write(socket.response(json, api.agent.libs.installed(options.libPath), null, null, $a));
break;
case 104:
api.agent.libs.uninstall(options.libPath, json, $a.key, $j, options.logLevel === 0, function (res, duration, msg) {
api.log.console('Uninstall code:' + res + ' on libraryId:' + json.libraryId);
sock.write(socket.response(json, msg, res, duration, $a));
});
break;
case 106:
api.agent.libs.upload(options.libPath, json, function (res) {
if (res === 0)
api.log.console('Uploaded file:' + path.join(options.libPath, json.libraryId, json.fileName) + ' on libraryId:' + json.libraryId);
sock.write(socket.response(json, null, res, 0, $a));
});
break;
case 108:
api.agent.libs.removeFile(options.libPath, json, function (res) {
if (res === 0)
api.log.console('Removed file:' + path.join(options.libPath, json.libraryId, json.fileName) + ' on libraryId:' + json.libraryId);
sock.write(socket.response(json, null, res, 0, $a));
});
break;
case 200:
(json.opId !== undefined) ? api.jobs.stopConf($j, json.opId) : api.jobs.stopAll($j);
sock.write(socket.response(json, 'STOPALL', 200, 0, $a));
api.log.console('All Jobs were stopped!');
break;
case 300:
sock.write(socket.response(json, {
Memory: util.inspect(process.memoryUsage()),
Uptime: process.uptime(),
Jobs: Object.keys($j).length,
PID: process.pid
}, 300, 0, $a));
break;
}
} catch (e) {
api.log.console('Something went wrong, unexpected error:' + e.message, 2);
}
});
sock.on('error', function (e) {
api.log.console('Lost the connection to Tellki, retry in ' + options.retry + 'ms... ' + (e !== null ? (typeof e === 'object' ? (e[0].code === undefined ? '' : e[0].code) : e) : ''), 2);
api.jobs.stopAll($j);
clearInterval(ping);
callBack();
});
sock.on('close', function () {
api.log.console('The connection was closed!', 1);
api.jobs.stopAll($j);
});
this._sock = sock;
},
start: function (url, retry) {
this.connect(url, function () {
if (socket.retrying !== null) {
return;
}
socket.retrying = setTimeout(function () {
api.log.console('Trying to reconnect to Tellki...', 1);
socket.start(url, retry);
socket.retrying = null;
}, retry);
});
},
close: function () {
this._sock.close();
},
response: function (data, resp, code, duration, agent) {
return JSON.stringify({
opId: data.opId,
op: data.op + 1,
uuid: agent.uuid,
resp: resp,
code: code !== null ? code.toString() : null,
ts: new Date().getTime(),
duration: duration
});
}
},
cp = require('child_process'),
run = {
spawn: function (libPath, cmd, debug, callBack) {
var options = {
cwd: path.resolve(api.obj.isnullorundefined(cmd.path) ? libPath : path.join(libPath, cmd.path)),
env: process.env,
start: new Date().getTime(),
duration: function () { return new Date().getTime() - options.start },
resp: '',
code: null
};
if (cmd.cmd === undefined) {
callBack('Invalid operation!', -4, options.duration());
return;
}
if (api.obj.isnullorundefined(cmd.hide))
cmd.hide = [];
var $cmd = cmd.cmd;
if (!api.obj.isnullorundefined(cmd.args)) {
for (var i = 0; i < cmd.args.length; i++)
$cmd += ' "' + (cmd.args[i] !== null ? (cmd.hide[i] !== undefined ? (cmd.args[i].length === 0 || cmd.args[i] === "\"\"" ? '' : cmd.hide[i]) : cmd.args[i]) : '') + '"';
}
if (debug) {
api.log.console('PATH:' + options.cwd);
api.log.console('CMD:' + $cmd);
}
var child = cp.spawn(cmd.cmd, cmd.args, options);
child.stdout.on('data', function (buffer) {
if (cmd.async) {
api.log.console('Sending data:' + buffer.toString('utf-8'));
callBack(buffer.toString('utf-8'), null, options.duration());
}
else
options.resp += buffer.toString('utf-8');
});
child.stderr.on('data', function (buffer) {
if (cmd.async) {
api.log.console('Sending error:' + buffer.toString('utf-8'));
callBack(buffer.toString('utf-8'), -2, options.duration());
}
else
options.resp += buffer.toString('utf-8');
});
child.on('close', function (code) {
if (options.code === null) {
options.code = (code === null ? (this.killed ? -1 : -3) : code);
api.log.console('Process exited with code:' + options.code + (cmd.libraryId !== undefined ? ' Library:' + cmd.libraryId : ''));
callBack(options.resp, options.code, options.duration());
clearTimeout(kill);
}
});
child.on('error', function (e) {
options.code = e.errno;
api.log.console('Caught exception:' + e.toString(), 2);
e.cmd = $cmd;
e.cwd = options.cwd;
callBack(JSON.stringify(e), options.code, options.duration());
clearTimeout(kill);
});
if (cmd.timeout !== null) {
var kill = setTimeout(function () {
if (child !== undefined) {
child.kill('SIGKILL');
}
}, cmd.timeout);
}
return child;
}
}
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
if (!fs.existsSync(options.cfgPath)) fs.mkdirSync(options.cfgPath);
if (!fs.existsSync(options.logPath)) fs.mkdirSync(options.logPath);
if (!fs.existsSync(options.libPath)) fs.mkdirSync(options.libPath);
options.v = fs.existsSync(path.join($path, '../package.json')) ? require(path.join($path, '../package.json')).version : '0.0.0';
var invalid = false;
if (process.argv.length > 2) {
for (var i = 2; i < process.argv.length; i++) {
switch (process.argv[i]) {
case '-check':
options.check = true;
break;
case '-id':
options.id = process.argv[++i];
break;
case '-key':
options.key = process.argv[++i];
break;
case '-url':
options.url = process.argv[++i];
break;
case '-tags':
options.tags = process.argv[++i];
break;
case '-uuid':
if (process.argv[i + 1].length === 36)
$a.uuid = process.argv[++i];
break;
case '-verbose':
options.writetoLog = false;
break;
case '-debug':
options.logLevel = 0;
break;
default:
invalid = true;
process.stdout.write('Invalid parameters: "' + process.argv[i] + '"\n');
break;
}
}
}
options.cfgFile = api.conf.file(options.cfgPath, options.id);
if (process.argv.length === 2 && !invalid) {
if (!fs.existsSync(options.cfgFile)) {
invalid = true;
process.stdout.write('Configuration file not found: "' + options.cfgFile + '". You need to reactive the agent again!\n');
}
}
if (invalid) {
process.stdout.write('Tellki agent ' + options.v + ' - Usage and parameters: tellkiagent -key {client_key}\n');
process.stdout.write('[-id {agent_name}]\n');
process.stdout.write('[-tags {tag1,tag2,tagN}]\n');
process.stdout.write('[-uuid {agent_uuid}]\n');
process.stdout.write('[-debug]\n');
process.stdout.write('[-verbose]\n');
process.stdout.write('[-url {agent_url}]\n');
process.exit(-1);
}
options.logFile = api.log.file(options.logPath, options.id);
api.log.console('PID:' + process.pid);
if (options.id !== null) api.log.console('Id:' + options.id);
api.log.console('Agent version is ' + options.v);
api.log.console('Node version is ' + process.versions.node);
api.log.console('Platform is ' + process.platform);
api.log.console('Agent path is ' + fs.realpathSync(__filename));
api.log.console('Config file ' + options.cfgFile);
api.log.console('Log file ' + options.logFile);
api.log.console('Libs path ' + options.libPath);
api.log.console('Starting the Tellki Agent... connectig to ' + options.url);
socket.start(options.url, options.retry);
setInterval(function () {
if (typeof global.gc === 'function') {
global.gc();
api.log.console('Cleared GC!');
}
api.log.console('Memory Usage:' + util.inspect(process.memoryUsage()));
api.log.console('Uptime:' + process.uptime() + 's');
api.log.console('Running Jobs:' + Object.keys($j).length);
}, 300000);
process.on('SIGINT', function () {
socket.close();
process.exit(0);
});
process.on('SIGTERM', function () {
socket.close();
process.exit(0);
});
process.on('uncaughtException', function (err) {
api.log.console('Exception:' + err.message, 2);
api.log.console('Stack:' + err.stack, 2);
api.log.console('Terminating...', 2);
process.exit(1)
})
}).call(this)