diff --git a/lib/API.js b/lib/API.js index 0296c2d31..c1a23f369 100644 --- a/lib/API.js +++ b/lib/API.js @@ -17,7 +17,7 @@ const fclone = require('fclone'); var conf = require('../constants.js'); var Client = require('./Client'); var Common = require('./Common'); -var KMDaemon = require('./Interactor/InteractorDaemonizer'); +var KMDaemon = require('keymetrics-agent/src/InteractorClient'); var Config = require('./tools/Config'); var Modularizer = require('./API/Modules/Modularizer.js'); var path_structure = require('../paths.js'); diff --git a/lib/API/Configuration.js b/lib/API/Configuration.js index 01b4439a7..cebc8529e 100644 --- a/lib/API/Configuration.js +++ b/lib/API/Configuration.js @@ -1,5 +1,4 @@ -var Password = require('../Interactor/Password.js'); var Common = require('../Common.js'); var cst = require('../../constants.js'); var UX = require('./CliUx'); @@ -7,7 +6,7 @@ var chalk = require('chalk'); var async = require('async'); var Configuration = require('../Configuration.js'); //@todo double check that imported methods works -var InteractorDaemonizer = require('../Interactor/InteractorDaemonizer'); +var InteractorDaemonizer = require('keymetrics-agent/src/InteractorClient'); module.exports = function(CLI) { @@ -63,36 +62,6 @@ module.exports = function(CLI) { return false; } - /** - * Specific when setting pm2 password - * Used for restricted remote actions - * Also alert Interactor that password has been set - */ - if (key.indexOf('pm2:passwd') > -1) { - value = Password.generate(value); - Configuration.set(key, value, function(err) { - if (err) - return cb ? cb(Common.retErr(err)) : that.exitCli(cst.ERROR_EXIT); - InteractorDaemonizer.launchRPC(that._conf, function(err) { - if (err) { - displayConf('pm2', function() { - return cb ? cb(null, {success:true}) : that.exitCli(cst.SUCCESS_EXIT) - }); - return false; - } - InteractorDaemonizer.rpc.passwordSet(function() { - InteractorDaemonizer.disconnectRPC(function() { - displayConf('pm2', function() { - return cb ? cb(null, {success:true}) : that.exitCli(cst.SUCCESS_EXIT); - }); - }); - }); - return false; - }); - }); - return false; - } - /** * Set value */ diff --git a/lib/API/Interaction.js b/lib/API/Interaction.js index 1bde2cd71..8d9557d4f 100644 --- a/lib/API/Interaction.js +++ b/lib/API/Interaction.js @@ -6,7 +6,8 @@ var chalk = require('chalk'); var async = require('async'); var path = require('path'); var fs = require('fs'); -var KMDaemon = require('../Interactor/InteractorDaemonizer'); +var KMDaemon = require('keymetrics-agent/src/InteractorClient'); +var pkg = require('../../package.json') module.exports = function(CLI) { @@ -45,7 +46,8 @@ module.exports = function(CLI) { KMDaemon.launchAndInteract(that._conf, { secret_key : secret_key || null, public_key : public_key || null, - machine_name : machine_name || null + machine_name : machine_name || null, + pm2_version: pkg.version }, function(err, dt) { if (err) { return cb ? cb(err) : that.exitCli(cst.ERROR_EXIT); @@ -118,7 +120,8 @@ module.exports = function(CLI) { public_key : null, secret_key : null, machine_name : null, - info_node : null + info_node : null, + pm2_version: pkg.version }, function(err, dt) { if (err) { Common.printError(err); @@ -143,7 +146,8 @@ module.exports = function(CLI) { public_key : public_key, secret_key : cmd, machine_name : machine, - info_node : info_node.infoNode || null + info_node : info_node.infoNode || null, + pm2_version: pkg.version } KMDaemon.launchAndInteract(that._conf, infos, function(err, dt) { diff --git a/lib/API/Keymetrics/cli-api.js b/lib/API/Keymetrics/cli-api.js index 3ad3ea03f..18107f85b 100644 --- a/lib/API/Keymetrics/cli-api.js +++ b/lib/API/Keymetrics/cli-api.js @@ -5,11 +5,12 @@ var chalk = require('chalk'); var async = require('async'); var path = require('path'); var fs = require('fs'); -var KMDaemon = require('../../Interactor/InteractorDaemonizer'); +var KMDaemon = require('keymetrics-agent/src/InteractorClient'); var KM = require('./kmapi.js'); var Table = require('cli-table-redemption'); var open = require('../../tools/open.js'); var promptly = require('promptly'); +var pkg = require('../../../package.json') module.exports = function(CLI) { @@ -127,7 +128,8 @@ module.exports = function(CLI) { function linkOpenExit(target_bucket) { KMDaemon.launchAndInteract(cst, { public_key : target_bucket.public_id, - secret_key : target_bucket.secret_id + secret_key : target_bucket.secret_id, + pm2_version: pkg.version }, function(err, dt) { open('https://app.keymetrics.io/#/r/' + target_bucket.public_id); setTimeout(function() { @@ -194,7 +196,8 @@ module.exports = function(CLI) { KMDaemon.launchAndInteract(cst, { public_key : target_bucket.public_id, - secret_key : target_bucket.secret_id + secret_key : target_bucket.secret_id, + pm2_version: pkg.version }, function(err, dt) { linkOpenExit(target_bucket); }); diff --git a/lib/Client.js b/lib/Client.js index 0b7855157..1ddc0d224 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -6,7 +6,7 @@ var debug = require('debug')('pm2:client'); var Common = require('./Common.js'); -var KMDaemon = require('./Interactor/InteractorDaemonizer.js'); +var KMDaemon = require('keymetrics-agent/src/InteractorClient'); var rpc = require('pm2-axon-rpc'); var async = require('async'); var axon = require('pm2-axon'); @@ -15,6 +15,7 @@ var fs = require('fs'); var path = require('path'); var mkdirp = require('mkdirp'); var shelljs = require('shelljs'); +var pkg = require('../package.json') function noop() {} @@ -79,7 +80,8 @@ Client.prototype.start = function(cb) { KMDaemon.launchAndInteract(that.conf, { machine_name : that.machine_name, public_key : that.public_key, - secret_key : that.secret_key + secret_key : that.secret_key, + pm2_version : pkg.version }, function(err, data, interactor_proc) { that.interactor_process = interactor_proc; }); @@ -278,7 +280,8 @@ Client.prototype.launchDaemon = function(opts, cb) { KMDaemon.launchAndInteract(that.conf, { machine_name : that.machine_name, public_key : that.public_key, - secret_key : that.secret_key + secret_key : that.secret_key, + pm2_version : pkg.version }, function(err, data, interactor_proc) { that.interactor_process = interactor_proc; return cb(null, child); diff --git a/lib/Common.js b/lib/Common.js index a8eeec1ff..41e4bb0f2 100644 --- a/lib/Common.js +++ b/lib/Common.js @@ -23,7 +23,7 @@ var isBinary = require('./tools/isbinaryfile.js'); var cst = require('../constants.js'); var extItps = require('./API/interpreter.json'); var Config = require('./tools/Config'); -var KMDaemon = require('./Interactor/InteractorDaemonizer.js'); +var KMDaemon = require('keymetrics-agent/src/InteractorClient'); var Common = module.exports; diff --git a/lib/Interactor/Cipher.js b/lib/Interactor/Cipher.js deleted file mode 100644 index 1204a3eb8..000000000 --- a/lib/Interactor/Cipher.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -var crypto = require('crypto'); - -const CIPHER_ALGORITHM = 'aes256'; - -var Cipher = module.exports = {}; - -/** - * Description - * @method decipherMessage - * @param {} msg - * @return ret - */ -Cipher.decipherMessage = function(msg, key) { - var ret = {}; - - try { - var decipher = crypto.createDecipher(CIPHER_ALGORITHM, key); - var decipheredMessage = decipher.update(msg, 'hex', 'utf8'); - decipheredMessage += decipher.final('utf8'); - ret = JSON.parse(decipheredMessage); - } catch(e) { - return null; - } - - return ret; -} - -/** - * Description - * @method cipherMessage - * @param {} data - * @param {} key - * @return - */ -Cipher.cipherMessage = function(data, key) { - try { - var cipher = crypto.createCipher(CIPHER_ALGORITHM, key); - var cipheredData = cipher.update(data, 'utf8', 'hex'); - cipheredData += cipher.final('hex'); - return cipheredData; - } catch(e) { - return null; - } -} diff --git a/lib/Interactor/Daemon.js b/lib/Interactor/Daemon.js deleted file mode 100644 index 4bcf9c782..000000000 --- a/lib/Interactor/Daemon.js +++ /dev/null @@ -1,443 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -var fs = require('fs'); -var ipm2 = require('./pm2-interface.js'); -var rpc = require('pm2-axon-rpc'); -var axon = require('pm2-axon'); -var debug = require('debug')('interface:driver'); // Interface -var chalk = require('chalk'); -var Url = require('url'); -var os = require('os'); -var domain = require('domain'); -var fmt = require('../tools/fmt.js'); -var pkg = require('../../package.json'); -var PM2 = require('../..'); - -var cst = require('../../constants.js'); -var Cipher = require('./Cipher.js'); -var ReverseInteractor = require('./ReverseInteractor.js'); -var PushInteractor = require('./PushInteractor.js'); -var Utility = require('../Utility.js'); -var WatchDog = require('./WatchDog.js'); -var Conf = require('../Configuration.js'); -var HttpRequest = require('./HttpRequest.js'); -var InternalIP = require('./internal-ip.js'); - -global._pm2_password_protected = false; - -// Flag for log streaming status -global._logs = false; - -var Daemon = module.exports = { - connectToPM2 : function() { - return ipm2(); - }, - exit : function() { - var self = this; - - this.opts.pm2_instance.disconnect(function() { - console.log('Connection to PM2 via CLI closed'); - }); - - process.nextTick(function() { - try { - fs.unlinkSync(cst.INTERACTOR_RPC_PORT); - fs.unlinkSync(cst.INTERACTOR_PID_PATH); - } catch(e) {} - - if (self.opts.ipm2) - self.opts.ipm2.disconnect(); - - console.log('Exiting Interactor'); - - if (!this._rpc || !this._rpc.sock) - return process.exit(cst.ERROR_EXIT); - - this._rpc.sock.close(function() { - console.log('RPC closed - Interactor killed'); - process.exit(cst.SUCCESS_EXIT); - }); - }); - }, - activateRPC : function() { - console.log('Launching Interactor exposure'); - - var self = this; - var rep = axon.socket('rep'); - var daemon_server = new rpc.Server(rep); - var sock = rep.bind(cst.INTERACTOR_RPC_PORT); - - daemon_server.expose({ - kill : function(cb) { - console.log('Killing interactor'); - cb(null); - return Daemon.exit(); - }, - passwordSet : function(cb) { - global._pm2_password_protected = true; - return cb(null); - }, - getInfos : function(cb) { - if (self.opts && - self.opts.DAEMON_ACTIVE == true) - return cb(null, { - machine_name : self.opts.MACHINE_NAME, - public_key : self.opts.PUBLIC_KEY, - secret_key : self.opts.SECRET_KEY, - remote_host : cst.REMOTE_HOST, - remote_port : cst.REMOTE_PORT, - reverse_interaction : self.opts.REVERSE_INTERACT, - socket_path : cst.INTERACTOR_RPC_PORT, - pm2_home_monitored : cst.PM2_HOME - }); - else { - return cb(null); - } - } - }); - return daemon_server; - }, - formatMetada : function() { - var cpu, memory; - - var self = this; - - try { - cpu = os.cpus(); - memory = Math.floor(os.totalmem() / 1024 / 1024); - } catch(e) { - cpu = 0; - memory = 0; - }; - - var ciphered_data = Cipher.cipherMessage(JSON.stringify({ - MACHINE_NAME : this.opts.MACHINE_NAME, - PUBLIC_KEY : this.opts.PUBLIC_KEY, - PM2_VERSION : this.opts.PM2_VERSION, - RECYCLE : this.opts.RECYCLE || false, - MEMORY : memory, - HOSTNAME : os.hostname(), - CPUS : cpu.length - }), this.opts.SECRET_KEY); - - return ciphered_data; - }, - pingKeepAlive : function() { - var self = this; - - (function checkInternet() { - require('dns').lookup('google.com',function(err) { - if (err && (err.code == 'ENOTFOUND' || err.code == 'EAI_AGAIN')) { - if (self.opts._connection_is_up == true) - console.error('[CRITICAL] Internet is unreachable (via DNS lookup strategy)'); - self.opts._connection_is_up = false; - } else { - if (self.opts._connection_is_up == false) { - console.log('[TENTATIVE] Reactivating connection'); - PushInteractor.connectRemote(); - ReverseInteractor.reconnect(); - } - self.opts._connection_is_up = true; - } - setTimeout(checkInternet, 15000); - }); - })(); - }, - changeUrls : function(push_url, reverse) { - if (push_url) - PushInteractor.connectRemote(push_url); - if (reverse) - ReverseInteractor.changeUrl(reverse); - }, - refreshWorker : function() { - var self = this; - - function refreshMetadata() { - var ciphered_data = Daemon.formatMetada(); - - HttpRequest.post({ - url : self.opts.ROOT_URL, - port : self.opts.ROOT_PORT, - data : { - public_id : self.opts.PUBLIC_KEY, - data : ciphered_data - } - }, function(err, km_data) { - if (err) return console.error(err); - - /** protect against malformated data **/ - if (!km_data || - !km_data.endpoints || - !km_data.endpoints.push || - !km_data.endpoints.reverse) { - console.error('[CRITICAL] Malformated data received, skipping...'); - return false; - } - - /************************************** - * Urls has changed = update workers * - **************************************/ - - if ((Daemon.current_km_data.endpoints.push != km_data.endpoints.push) || - (Daemon.current_km_data.endpoints.reverse != km_data.endpoints.reverse)) { - self.changeUrls(km_data.endpoints.push, km_data.endpoints.reverse); - Daemon.current_km_data = km_data; - } - else { - debug('[REFRESH META] No need to update URL (same)', km_data); - } - return false; - }); - - }; - - // Refresh metadata every minutes - setInterval(function() { - refreshMetadata(); - }, 60000); - }, - validateData : function() { - var opts = {}; - - opts.MACHINE_NAME = process.env.PM2_MACHINE_NAME; - opts.PUBLIC_KEY = process.env.PM2_PUBLIC_KEY; - opts.SECRET_KEY = process.env.PM2_SECRET_KEY; - opts.RECYCLE = process.env.KM_RECYCLE ? JSON.parse(process.env.KM_RECYCLE) : false; - opts.REVERSE_INTERACT = JSON.parse(process.env.PM2_REVERSE_INTERACT); - opts.PM2_VERSION = pkg.version; - - if (!opts.MACHINE_NAME) { - console.error('You must provide a PM2_MACHINE_NAME environment variable'); - process.exit(cst.ERROR_EXIT); - } - else if (!opts.PUBLIC_KEY) { - console.error('You must provide a PM2_PUBLIC_KEY environment variable'); - process.exit(cst.ERROR_EXIT); - } - else if (!opts.SECRET_KEY) { - console.error('You must provide a PM2_SECRET_KEY environment variable'); - process.exit(cst.ERROR_EXIT); - } - return opts; - }, - welcome : function(cb) { - var self = this; - var ciphered_data = Daemon.formatMetada(); - - if (!ciphered_data) { - process.send({ - msg : 'Error while ciphering data', - error : true - }); - return process.exit(1); - } - - var retries = 0; - - function doWelcomeQuery(cb) { - HttpRequest.post({ - url : self.opts.ROOT_URL, - data : { - public_id : self.opts.PUBLIC_KEY, - data : ciphered_data - } - }, function(err, km_data) { - self.current_km_data = km_data; - if (err) { - console.error('Got error while connecting: %s', err.message || err); - - if (retries < 30) { - retries++; - - setTimeout(function() { - doWelcomeQuery(cb); - }, 200 * retries); - return false; - } - return cb(err); - } - - if (self.opts.RECYCLE) { - if (!km_data.name) { - console.error('Error no previous machine name for recycle option returned!'); - } - self.opts.MACHINE_NAME = km_data.name; - }; - - // For Human feedback - if (process.send) { - try { - process.send({ - error : false, - km_data : km_data, - online : true, - pid : process.pid, - machine_name : self.opts.MACHINE_NAME, - public_key : self.opts.PUBLIC_KEY, - secret_key : self.opts.SECRET_KEY, - reverse_interaction : self.opts.REVERSE_INTERACT - }); - } catch(e) { - // Just in case the CLI has been disconected - } - } - // Return get data - return cb(null, km_data); - }) - } - - doWelcomeQuery(function(err, meta) { - return cb(err, meta); - }); - }, - protectedStart : function() { - var self = this; - var d = domain.create(); - - d.once('error', function(err) { - fmt.sep(); - fmt.title('Agent global error caught'); - fmt.field('Time', new Date()); - console.error(err.message); - console.error(err.stack); - fmt.sep(); - - console.error('[Agent] Resurrecting'); - - var KMDaemon = require('../Interactor/InteractorDaemonizer'); - - KMDaemon.rescueStart(cst, function(err, dt) { - if (err) { - console.error('[Agent] Failed to rescue agent, error:'); - console.error(err.message || err); - process.exit(1); - } - console.log('[Agent] Rescued.'); - process.exit(0); - }); - }); - - d.run(function() { - self.start(); - }); - }, - start : function() { - var self = this; - - self.opts = self.validateData(); - self.opts.ipm2 = null; - self.opts.internal_ip = InternalIP(); - self.opts.pm2_instance = PM2; - self.opts._connection_is_up = true; - self.current_km_data = null; - - self.opts.pm2_instance.connect(function() { - console.log('Connected to PM2'); - }); - - self._rpc = self.activateRPC(); - - // Test mode #1 - if (cst.DEBUG) { - self.opts.ROOT_URL = '127.0.0.1'; - if (process.env.NODE_ENV == 'test') - self.opts.ROOT_PORT = 3400; - else - self.opts.ROOT_PORT = 3000; - } - else { - self.opts.ROOT_URL = cst.KEYMETRICS_ROOT_URL; - } - - if (Conf.getSync('pm2:passwd')) - global._pm2_password_protected = true; - - // Test mode #2 - if (process.env.NODE_ENV == 'local_test') { - self.opts.DAEMON_ACTIVE = true; - - self.opts.ipm2 = self.connectToPM2(); - - PushInteractor.start({ - url : 'http://127.0.0.1:4321', - conf : self.opts - }); - - ReverseInteractor.start({ - url : 'http://127.0.0.1:4322', - conf : self.opts - }); - if (process.send) - process.send({ - success : true, - debug : true - }); - return false; - } - - Daemon.welcome(function(err, km_data) { - if (err) { - if (process.send) - process.send({ - error : true, - msg : err.stack || err - }); - console.log(err.stack || err); - return Daemon.exit(); - } - - if (km_data.disabled == true) { - console.error('Interactor disabled'); - return Daemon.exit(); - } - if (km_data.pending == true) { - console.error('Interactor pending'); - return Daemon.exit(); - } - - if (km_data.active == true) { - self.opts.DAEMON_ACTIVE = true; - - self.opts.ipm2 = self.connectToPM2(); - - WatchDog.start({ - conf : self.opts - }); - - PushInteractor.start({ - url : km_data.endpoints.push, - conf : self.opts - }); - - if (self.opts.REVERSE_INTERACT == true) { - ReverseInteractor.start({ - url : km_data.endpoints.reverse, - conf : self.opts - }); - } - Daemon.refreshWorker(); - Daemon.pingKeepAlive(); - } - else { - console.log('Nothing to do, exiting'); - Daemon.exit(); - } - return false; - }); - } -}; - -/** - * MAIN - */ -if (require.main === module) { - console.log(chalk.cyan.bold('[Keymetrics.io]') + ' Launching agent'); - process.title = 'PM2: KM Agent (' + process.env.PM2_HOME + ')'; - - Utility.overrideConsole(); - Daemon.protectedStart(); -} diff --git a/lib/Interactor/Filter.js b/lib/Interactor/Filter.js deleted file mode 100644 index 52f44e6ce..000000000 --- a/lib/Interactor/Filter.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -/** - * @file Filter process and system data to be sent to server - * @author Alexandre Strzelewicz - * @project Interface - */ - -var os = require('os'); - -var cpu_info = { - number : 0, - info : 'no-data' -}; - -try { - cpu_info = { - number : os.cpus().length, - info : os.cpus()[0].model - }; -} catch(e) { -} - -var SERVER_META = { - totalMem : os.totalmem(), - hostname : os.hostname(), - type : os.type(), - platform : os.platform(), - arch : os.arch() -}; - -var Filter = {}; - -Filter.getProcessID = function(machine_name, name, id) { - return machine_name + ':' + name + ':' + id; -}; - -Filter.machineSnapshot = function(processes, conf) { - if (!processes) return null; - - var filter_procs = []; - - processes.forEach(function(proc) { - if (proc.pm2_env.pm_id.toString().indexOf('_old_') == -1) - filter_procs.push({ - pid : proc.pid, - name : proc.pm2_env.name, - interpreter : proc.pm2_env.exec_interpreter, - restart_time : proc.pm2_env.restart_time, - created_at : proc.pm2_env.created_at, - exec_mode : proc.pm2_env.exec_mode, - watching : proc.pm2_env.watch, - pm_uptime : proc.pm2_env.pm_uptime, - status : proc.pm2_env.status, - pm_id : proc.pm2_env.pm_id, - - cpu : Math.floor(proc.monit.cpu) || 0, - memory : Math.floor(proc.monit.memory) || 0, - - versioning : proc.pm2_env.versioning || null, - - node_env : proc.pm2_env.NODE_ENV || null, - - axm_actions : proc.pm2_env.axm_actions || [], - axm_monitor : proc.pm2_env.axm_monitor || {}, - axm_options : proc.pm2_env.axm_options || {}, - axm_dynamic : proc.pm2_env.dynamic || {} - }); - }); - - var node_version = process.version || ''; - - if (node_version != '') { - if (node_version.indexOf('v1.') === 0 || node_version.indexOf('v2.') === 0 || node_version.indexOf('v3.') === 0) - node_version = 'iojs ' + node_version; - } - var username = process.env.SUDO_USER || process.env.C9_USER || process.env.LOGNAME || - process.env.USER || process.env.LNAME || process.env.USERNAME; - - return { - process : filter_procs, - server : { - loadavg : os.loadavg(), - total_mem : SERVER_META.totalMem, - free_mem : os.freemem(), - cpu : cpu_info, - hostname : SERVER_META.hostname, - uptime : os.uptime(), - type : SERVER_META.type, - platform : SERVER_META.platform, - arch : SERVER_META.arch, - user : username, - interaction : conf.REVERSE_INTERACT, - pm2_version : conf.PM2_VERSION, - node_version : node_version - } - }; -}; - -Filter.monitoring = function(processes, conf) { - if (!processes) return null; - - var filter_procs = {}; - - processes.forEach(function(proc) { - filter_procs[Filter.getProcessID(conf.MACHINE_NAME, proc.pm2_env.name,proc.pm2_env.pm_id)] = [ - Math.floor(proc.monit.cpu), - Math.floor(proc.monit.memory) - ]; - }); - - return { - loadavg : os.loadavg(), - total_mem : SERVER_META.totalMem, - free_mem : os.freemem(), - processes : filter_procs - }; -}; - -module.exports = Filter; diff --git a/lib/Interactor/HttpRequest.js b/lib/Interactor/HttpRequest.js deleted file mode 100644 index 1ade6cc95..000000000 --- a/lib/Interactor/HttpRequest.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -var http = require('http'); -var https = require('https'); -var url = require('url') -var debug = require('debug')('interface:http'); - -var HttpRequest = module.exports = {}; - -HttpRequest.post = function(opts, cb) { - if (!(opts.data && opts.url)) { - return cb({ - msg: 'missing parameters', - port: opts.port, - data: opts.data, - url: opts.url - }) - } - - if (!opts.port) { - var parsed = url.parse(opts.url) - if (parsed.hostname && parsed.port) { - opts.port = parseInt(parsed.port) - opts.url = parsed.hostname - } else { - opts.port = 443 - } - } - - var options = { - hostname: opts.url, - path: '/api/node/verifyPM2', - method: 'POST', - port: opts.port, - rejectUnauthorized: false, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(JSON.stringify(opts.data)) - } - } - - var client = (opts.port === 443) ? https : http; - - var req = client.request(options, function(res){ - var dt = ''; - - res.on('data', function (chunk) { - dt += chunk; - }); - - res.on('end',function(){ - try { - cb(null, JSON.parse(dt)); - } catch(e) { - cb(e); - } - }); - - res.on('error', function(e){ - cb(e); - }); - }); - - req.on('socket', function (socket) { - /** - * Configure request timeout - */ - socket.setTimeout(7000); - socket.on('timeout', function() { - debug('Connection timeout when retrieveing PM2 metadata', options); - req.abort(); - }); - }); - - req.on('error', function(e) { - cb(e); - }); - - req.write(JSON.stringify(opts.data)); - - req.end(); -}; diff --git a/lib/Interactor/InteractorDaemonizer.js b/lib/Interactor/InteractorDaemonizer.js deleted file mode 100644 index 2d87715ff..000000000 --- a/lib/Interactor/InteractorDaemonizer.js +++ /dev/null @@ -1,535 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -'use strict'; - -var debug = require('debug')('pm2:interface:daemon'); -var fs = require('fs'); -var path = require('path'); -var util = require('util'); -var rpc = require('pm2-axon-rpc'); -var axon = require('pm2-axon'); -var chalk = require('chalk'); -var os = require('os'); -var cst = require('../../constants.js'); -var Common = require('../Common'); -var json5 = require('../tools/json5.js'); -var UX = require('../API/CliUx.js'); - -var InteractorDaemonizer = module.exports = {}; - -InteractorDaemonizer.rpc = {}; - -/** - * Description - * @method ping - * @param {} cb - * @return - */ -InteractorDaemonizer.ping = function(conf, cb) { - var req = axon.socket('req'); - var client = new rpc.Client(req); - - debug('[PING INTERACTOR] Trying to connect to Interactor daemon'); - - client.sock.once('reconnect attempt', function() { - client.sock.close(); - debug('Interactor Daemon not launched'); - return cb(false); - }); - - client.sock.once('connect', function() { - client.sock.once('close', function() { - return cb(true); - }); - client.sock.close(); - debug('Interactor Daemon alive'); - }); - - client.sock.once('error', function(e) { - if (e.code == 'EACCES') { - fs.stat(conf.INTERACTOR_RPC_PORT, function(e, stats) { - if (stats.uid === 0) { - console.error(conf.PREFIX_MSG_ERR + 'Permission denied, activate current user:'); - console.log(chalk.bold('$sudo chown ' + process.env.USER + ':' + process.env.USER + ' ' + conf.INTERACTOR_RPC_PORT)); - return process.exit(1); - } - }); - } - }); - - req.connect(conf.INTERACTOR_RPC_PORT); -}; - -InteractorDaemonizer.killInteractorDaemon = function(conf, cb) { - process.env.PM2_INTERACTOR_PROCESSING = true; - - debug('Killing interactor #1 ping'); - InteractorDaemonizer.ping(conf, function(online) { - debug('Interactor online', online); - - if (!online) { - if (!cb) Common.printError('Interactor not launched'); - - return cb(new Error('Interactor not launched')); - } - - InteractorDaemonizer.launchRPC(conf, function(err, data) { - if (err) { - setTimeout(function() { - InteractorDaemonizer.disconnectRPC(cb); - }, 100); - return false; - } - InteractorDaemonizer.rpc.kill(function(err) { - if (err) Common.printError(err); - setTimeout(function() { - InteractorDaemonizer.disconnectRPC(cb); - }, 100); - }); - return false; - }); - return false; - }); -}; - -/** - * Description - * @method launchRPC - * @param {} cb - * @return - */ -InteractorDaemonizer.launchRPC = function(conf, cb) { - var self = this; - var req = axon.socket('req'); - this.client = new rpc.Client(req); - - debug('Generating methods'); - - /** - * Description - * @method generateMethods - * @param {} cb - * @return - */ - var generateMethods = function(cb) { - self.client.methods(function(err, methods) { - Object.keys(methods).forEach(function(key) { - var method_signature = methods[key]; - debug('+-- Creating %s method', method_signature.name); - - (function(name) { - /** - * Description - * @method name - * @return - */ - self.rpc[name] = function() { - var args = Array.prototype.slice.call(arguments); - args.unshift(name); - self.client.call.apply(self.client, args); - }; - })(method_signature.name); - - }); - return cb(); - }); - }; - - this.client.sock.once('reconnect attempt', function(e) { - self.client.sock.removeAllListeners(); - return cb({success:false, msg:'reconnect attempt'}); - }); - - this.client.sock.once('error', function(e) { - console.error('Error in error catch all on Interactor'); - console.error(e.stack || e); - }); - - this.client.sock.once('connect', function() { - self.client.sock.removeAllListeners(); - generateMethods(function() { - debug('Methods generated'); - cb(null, {success:true}); - }); - }); - - this.client_sock = req.connect(conf.INTERACTOR_RPC_PORT); -}; - -/** - * Description - * @method launchOrAttach - * @param {} secret_key - * @param {} public_key - * @param {} machine_name - * @param {} cb - * @return - */ -function launchOrAttach(conf, infos, cb) { - InteractorDaemonizer.ping(conf, function(online) { - if (online) { - debug('Interactor online, restarting it...'); - InteractorDaemonizer.launchRPC(conf, function() { - InteractorDaemonizer.rpc.kill(function(err) { - daemonize(conf, infos, function(err, msg, proc) { - return cb(err, msg, proc); - }); - }); - }); - } - else { - debug('Interactor offline, launching it...'); - daemonize(conf, infos, function(err, msg, proc) { - return cb(err, msg, proc); - }); - } - return false; - }); -}; - -/** - * Description - * @method daemonize - * @param {} secret_key - * @param {} public_key - * @param {} machine_name - * @param {} cb - * @return - */ -var daemonize = function(conf, infos, cb) { - var InteractorJS = path.resolve(path.dirname(module.filename), 'Daemon.js'); - - var out = null; - var err = null; - - if (process.env.TRAVIS || process.env.NODE_ENV == 'local_test') { - // Redirect PM2 internal err and out - // to STDERR STDOUT when running with Travis - out = 1; - err = 2; - } - else { - out = fs.openSync(conf.INTERACTOR_LOG_FILE_PATH, 'a'); - err = fs.openSync(conf.INTERACTOR_LOG_FILE_PATH, 'a'); - } - - var child = require('child_process').spawn('node', [InteractorJS], { - silent : false, - detached : true, - cwd : process.cwd(), - env : util._extend({ - PM2_HOME : conf.PM2_HOME, - PM2_MACHINE_NAME : infos.machine_name, - PM2_SECRET_KEY : infos.secret_key, - PM2_PUBLIC_KEY : infos.public_key, - PM2_REVERSE_INTERACT : infos.reverse_interact, - KEYMETRICS_NODE : infos.info_node - }, process.env), - stdio : ['ipc', out, err] - }); - - console.log('[KM] Connecting'); - - fs.writeFileSync(conf.INTERACTOR_PID_PATH, child.pid); - - function onError(msg) { - debug('Error when launching Interactor, please check the agent logs'); - return cb(msg); - } - - child.once('error', onError); - - child.unref(); - - var t = setTimeout(function() { - Common.printOut(cst.PREFIX_MSG_WARNING + ' Not managed to connect to Keymetrics, retrying in background. (check %s)', cst.INTERACTOR_LOG_FILE_PATH); - child.removeAllListeners('message'); - child.removeAllListeners('error'); - child.disconnect(); - return cb(null, {}, child); - }, 7000); - - child.once('message', function(msg) { - clearTimeout(t); - debug('Interactor daemon launched', msg); - - if (msg.debug) { - return cb(null, msg, child); - } - - child.removeAllListeners('error'); - child.disconnect(); - - /***************** - * Error messages - */ - if (msg.error == true) { - console.log(chalk.red('[Keymetrics.io][ERROR]'), msg.msg); - console.log(chalk.cyan('[Keymetrics.io]') + ' Contact support contact@keymetrics.io and send us the error message'); - return cb(msg); - } - - if (msg.km_data.disabled == true) { - console.log(chalk.cyan('[Keymetrics.io]') + ' Server DISABLED BY ADMINISTRATION contact support contact@keymetrics.io with reference to your public and secret keys)'); - return cb(msg); - } - - if (msg.km_data.error == true) { - console.log(chalk.red('[Keymetrics.io][ERROR]') + ' ' + msg.km_data.msg + ' (Public: %s) (Secret: %s) (Machine name: %s)', msg.public_key, msg.secret_key, msg.machine_name); - return cb(msg); - } - - else if (msg.km_data.active == false && msg.km_data.pending == true) { - console.log(chalk.red('[Keymetrics.io]') + ' ' + chalk.bold.red('Agent PENDING') + ' - Web Access: https://app.keymetrics.io/'); - console.log(chalk.red('[Keymetrics.io]') + ' You must upgrade your bucket in order to monitor more servers.'); - - return cb(msg); - } - - if (msg.km_data.active == true) { - console.log(chalk.green.bold('[Monitoring Enabled]') + ' Dashboard access: https://app.keymetrics.io/#/r/%s', msg.public_key); - return cb(null, msg, child); - } - - return cb(null, msg, child); - }); - -}; - -InteractorDaemonizer.update = function(conf, cb) { - InteractorDaemonizer.ping(conf, function(online) { - if (!online) { - Common.printError('Interactor not launched'); - return cb(new Error('Interactor not launched')); - } - InteractorDaemonizer.launchRPC(conf, function() { - InteractorDaemonizer.rpc.kill(function(err) { - if (err) { - Common.printError(err); - return cb(new Error(err)); - } - Common.printOut('Interactor successfully killed'); - setTimeout(function() { - InteractorDaemonizer.launchAndInteract(conf, {}, function() { - return cb(null, {msg : 'Daemon launched'}); - }); - }, 500); - }); - }); - return false; - }); -}; - -/** - * Get/Update/Merge agent configuration - * @param {object} _infos - */ -InteractorDaemonizer.getOrSetConf = function(conf, infos, cb) { - var reverse_interact = true; - var version_management_active = true; - var version_management_password = null; - var secret_key; - var public_key; - var machine_name; - var info_node; - var new_connection = false; - - // 1# Load configuration file - try { - var interaction_conf = json5.parse(fs.readFileSync(conf.INTERACTION_CONF)); - - public_key = interaction_conf.public_key; - machine_name = interaction_conf.machine_name; - secret_key = interaction_conf.secret_key; - info_node = interaction_conf.info_node; - - reverse_interact = interaction_conf.reverse_interact || true; - - if (interaction_conf.version_management) { - version_management_password = interaction_conf.version_management.password || version_management_password; - version_management_active = interaction_conf.version_management.active || version_management_active; - } - } catch (e) { - debug('Interaction file does not exists'); - } - - // 2# Override with passed informations - if (infos) { - if (infos.secret_key) - secret_key = infos.secret_key; - - if (infos.public_key) - public_key = infos.public_key; - - if (infos.machine_name) - machine_name = infos.machine_name; - - if (infos.info_node) - info_node = infos.info_node; - - new_connection = true; - } - - // 3# Override with environment variables (highest-priority conf) - if (process.env.PM2_SECRET_KEY || process.env.KEYMETRICS_SECRET) - secret_key = process.env.PM2_SECRET_KEY || process.env.KEYMETRICS_SECRET; - - if (process.env.PM2_PUBLIC_KEY || process.env.KEYMETRICS_PUBLIC) - public_key = process.env.PM2_PUBLIC_KEY || process.env.KEYMETRICS_PUBLIC; - - if (new_connection && info_node == null) - info_node = process.env.KEYMETRICS_NODE || cst.KEYMETRICS_ROOT_URL; - - if (!info_node) - info_node = cst.KEYMETRICS_ROOT_URL; - - if (!secret_key) - return cb(new Error('secret key is not defined')); - - if (!public_key) - return cb(new Error('public key is not defined')); - - if (!machine_name) - machine_name = os.hostname() + '-' + require('crypto').randomBytes(4).toString('hex'); - - /** - * Write new data to configuration file - */ - try { - var new_interaction_conf = { - secret_key : secret_key, - public_key : public_key, - machine_name : machine_name, - reverse_interact : reverse_interact, - info_node : info_node, - version_management : { - active : version_management_active, - password : version_management_password - } - }; - fs.writeFileSync(conf.INTERACTION_CONF, json5.stringify(new_interaction_conf, null, 4)); - } catch(e) { - console.error('Error when writting configuration file %s', conf.INTERACTION_CONF); - return cb(e); - } - - // Don't block the event loop - process.nextTick(function() { - cb(null, new_interaction_conf); - }); -}; - -InteractorDaemonizer.disconnectRPC = function(cb) { - if (!InteractorDaemonizer.client_sock || - !InteractorDaemonizer.client_sock.close) - return cb(null, { - success : false, - msg : 'RPC connection to Interactor Daemon is not launched' - }); - - if (InteractorDaemonizer.client_sock.connected === false || - InteractorDaemonizer.client_sock.closing === true) { - return cb(null, { - success : false, - msg : 'RPC closed' - }); - } - - try { - var timer; - - debug('Closing RPC INTERACTOR'); - - InteractorDaemonizer.client_sock.once('close', function() { - debug('RPC INTERACTOR cleanly closed'); - clearTimeout(timer); - return cb ? cb(null, {success:true}) : false; - }); - - timer = setTimeout(function() { - if (InteractorDaemonizer.client_sock.destroy) - InteractorDaemonizer.client_sock.destroy(); - return cb ? cb(null, {success:true}) : false; - }, 200); - - InteractorDaemonizer.client_sock.close(); - } catch(e) { - debug('Error while closing RPC INTERACTOR', e.stack || e); - return cb ? cb(e.stack || e) : false; - } - return false; -}; - -InteractorDaemonizer.rescueStart = function(conf, cb) { - InteractorDaemonizer.getOrSetConf(conf, null, function(err, infos) { - if (err || !infos) { - return cb(err); - } - - console.log(chalk.cyan('[Keymetrics.io]') + ' Using (Public key: %s) (Private key: %s)', infos.public_key, infos.secret_key); - - daemonize(conf, infos, function(err, msg, proc) { - return cb(err, msg, proc); - }); - }); -}; - -InteractorDaemonizer.launchAndInteract = function(conf, opts, cb) { - // For Watchdog - if (process.env.PM2_AGENT_ONLINE) { - return process.nextTick(cb); - } - - process.env.PM2_INTERACTOR_PROCESSING = true; - - this.getOrSetConf(conf, opts, function(err, data) { - if (err || !data) { - return cb(err); - } - - //console.log(chalk.cyan('[Keymetrics.io]') + ' Using (Public key: %s) (Private key: %s)', data.public_key, data.secret_key); - - launchOrAttach(conf, data, function(err, msg, proc) { - if (err) - return cb(err); - return cb(null, msg, proc); - }); - return false; - }); -}; - -/** - * Description - * @method getInteractInfo - * @param {} cb - * @return - */ -InteractorDaemonizer.getInteractInfo = function(conf, cb) { - debug('Getting interaction info'); - if (process.env.PM2_NO_INTERACTION) return; - InteractorDaemonizer.ping(conf, function(online) { - if (!online) { - return cb(new Error('Interactor is offline')); - } - InteractorDaemonizer.launchRPC(conf, function() { - InteractorDaemonizer.rpc.getInfos(function(err, infos) { - if (err) - return cb(err); - - // Avoid general CLI to interfere with Keymetrics CLI commands - if (process.env.PM2_INTERACTOR_PROCESSING) - return cb(null, infos); - - InteractorDaemonizer.disconnectRPC(function() { - return cb(null, infos); - }); - return false; - }); - }); - return false; - }); -}; diff --git a/lib/Interactor/Password.js b/lib/Interactor/Password.js deleted file mode 100644 index 86b1fdd78..000000000 --- a/lib/Interactor/Password.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ -var crypto = require('crypto'); - -var saltChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; -var saltCharsCount = saltChars.length; - -function generateSalt(len) { - if (typeof len != 'number' || len <= 0 || len !== parseInt(len, 10)) throw new Error('Invalid salt length'); - if (crypto.randomBytes) { - return crypto.randomBytes(Math.ceil(len / 2)).toString('hex').substring(0, len); - } else { - for (var i = 0, salt = ''; i < len; i++) { - salt += saltChars.charAt(Math.floor(Math.random() * saltCharsCount)); - } - return salt; - } -} - -function generateHash(algorithm, salt, password, iterations) { - iterations = iterations || 1; - try { - var hash = password; - for(var i=0; i -1)) - return false; - - if (Object.keys(self.monitored_processes).length > 0 && - !self.monitored_processes[packet.process.pm_id]) - return false; - - // keep log in a buffer - if (event.match(/^log:/)) { - if (!LOGS_BUFFER[packet.process.name]) { - LOGS_BUFFER[packet.process.name] = []; - } - // push the log data - LOGS_BUFFER[packet.process.name].push(packet.data); - // delete the last one if too long - if (LOGS_BUFFER[packet.process.name].length >= cst.LOGS_BUFFER_SIZE) { - LOGS_BUFFER[packet.process.name].pop(); - } - - // don't send if not asked - if (!global._logs) return false; - } - - // attach additional info on exception - if (event === 'process:exception') { - packet.data.last_logs = LOGS_BUFFER[packet.process.name]; - packet.data = self.stackParser.attachContext(packet.data); - } - - /** - * This is a heapdump action - */ - if (event == 'axm:reply' && packet.data && packet.data.return && (packet.data.return.heapdump || packet.data.return.cpuprofile)) { - PushInteractor.sendFile(packet); - return false; - } - - if (event == 'human:event') { - packet.name = packet.data.__name + ''; - delete packet.data.__name; - } - - if (!packet.process) - return console.error('No process field [%s]', event); - - /** - * Process specific messages - * -- Reformat raw output of pm2-interface - */ - packet.process = { - pm_id : packet.process.pm_id, - name : packet.process.name, - rev : packet.process.rev || ((packet.process.versioning && packet.process.versioning.revision) ? packet.process.versioning.revision : null), - server: PushInteractor.conf.MACHINE_NAME - }; - - // agregate transaction data before sending them - if (event.indexOf('axm:trace') > -1) - return self.aggregator.aggregate(packet); - - if (event.match(/^log:/)) { - packet.log_type = event.split(':')[1]; - event = 'logs'; - } - return PushInteractor.bufferData(event, packet); - }); - }, - resetPacket : function() { - var self = this; - - this._packet = { - 'server_name' : self.conf.MACHINE_NAME, - 'status' : {}, - 'monitoring' : {} - }; - }, - bufferData : function(event, packet) { - var self = this; - var logs_limit_size = 1024 * 50; - - // if (Object.keys(self._packet).indexOf(event) == -1) { - // return console.error('SKIP unknown field name [%s]', event); - // } - debug('Buffering one more event %s', event); - - if (!(event in self._packet)) - self._packet[event] = []; - - if (packet.process && !packet.server) { - if (event === 'logs' - && (JSON.stringify(self._packet[event]).length > logs_limit_size - || self._packet[event].length > 100)) - return console.error('Logs packet larger than 50KB limit'); - - self._packet[event].push(packet); - } - else { - console.error('Got packet without any process'); - } - return false; - }, - preparePacket : function(cb) { - var self = this; - - this.ipm2.rpc.getMonitorData({}, function(err, processes) { - if (!processes) - return console.error('Cant access to getMonitorData RPC PM2 method'); - - processes = processes.filter(function (proc) { - return proc.pm2_env._km_monitored !== false; - }); - - var ret = null; - - if ((ret = Filter.monitoring(processes, PushInteractor.conf))) { - self._packet['monitoring'] = ret; - } - - if ((ret = Filter.machineSnapshot(processes, PushInteractor.conf))) { - self._packet['status'] = { - data : ret, - server_name : self.conf.MACHINE_NAME, - internal_ip : self.conf.internal_ip, - protected : global._pm2_password_protected, - rev_con : self.conf.rev_con - }; - } - - return cb ? cb(null, ret) : false; - }); - }, - /** - * Description - * @method send_data - * @return - */ - sendData : function() { - var self = this; - - if (self.socket.client && - self.socket.client.socks[0] && - self.socket.client.socks[0].bufferSize > 290000) { - self.resetPacket(); - self._reconnect_counter++; - console.log('Buffer size too high (%d), stopping buffering and sending', self.socket.client.socks[0].bufferSize); - - if (self._reconnect_counter > 20) { - console.log('[PUSH] Forcing reconnection'); - self._reconnect_counter = 0; - self.socket.reconnect(); - } - return false; - } - - this.preparePacket(function() { - var data = {}; - - if (process.env.NODE_ENV && - (process.env.NODE_ENV == 'test' || process.env.NODE_ENV == 'local_test')) { - data = { - public_key : PushInteractor.conf.PUBLIC_KEY, - sent_at : Utility.getDate(), - data : self._packet - }; - } - else { - var cipheredData = Cipher.cipherMessage(JSON.stringify(self._packet), - PushInteractor.conf.SECRET_KEY); - data = { - public_key : self.conf.PUBLIC_KEY, - sent_at : Utility.getDate(), - data : cipheredData - }; - } - - var str = JSON.stringify(data); - var t1 = new Date(); - - self.resetPacket(); - - if (!self.socket) return false; - - self.socket.client.sendv2(str, function() { - var duration_sec = (new Date() - t1) / 1000; - debugInfo('Time to flush data %ds (buffer size %d)', duration_sec); - - if (duration_sec > 1) - console.info('[WARN] Time to send data over TCP took %dseconds!', duration_sec); - - data = null; - str = null; - }); - }); - } -}; diff --git a/lib/Interactor/RemoteActions/CustomActions.js b/lib/Interactor/RemoteActions/CustomActions.js deleted file mode 100644 index 697493971..000000000 --- a/lib/Interactor/RemoteActions/CustomActions.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -var debug = require('debug')('interface:driver'); -var Cipher = require('../Cipher.js'); - -var CustomActions = module.exports = { - /** - * Method to trigger custom actions (axm actions) - */ - axmCustomActions : function() { - var self = this; - - this.socket.data('trigger:action', function(raw_msg) { - var msg = {}; - - if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' || - process.env.NODE_ENV == 'local_test')) - msg = raw_msg; - else - msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY); - - if (!msg) return console.error('Error while receiving message! #axmCustomActions'); - - console.log('New remote action %s triggered for process %s', msg.action_name, msg.process_id); - self.pm2_instance.msgProcess({ - id : msg.process_id, - msg : msg.action_name, - opts: msg.opts || null - }, function(err, data) { - if (err) { - return self.socket.send('trigger:action:failure', { - success : false, - err : err.message, - id : msg.process_id, - action_name : msg.action_name - }); - } - console.log('[REVERSE INTERACTOR] Message received from AXM for proc_id : %s and action name %s', - msg.process_id, msg.action_name); - - return self.socket.send('trigger:action:success', { - success : true, - id : msg.process_id, - action_name : msg.action_name - }); - }); - }); - - this.socket.data('trigger:scoped_action', function(raw_msg) { - var msg = {}; - - if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' || - process.env.NODE_ENV == 'local_test')) - msg = raw_msg; - else - msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY); - - if (!msg) return console.error('Error while receiving message! #axmCustomActions'); - - console.log('New SCOPED action %s triggered for process %s', msg.action_name, msg.process.pm_id); - - self.pm2_instance.msgProcess({ - id : msg.process.pm_id, - action_name : msg.action_name, - msg : msg.action_name, - opts : msg.options || {}, - uuid : msg.uuid - }, function(err, data) { - if (err) { - return self.socket.send('trigger:action:failure', { - success : false, - err : err.message, - id : msg.process.pm_id, - action_name : msg.action_name - }); - } - console.log('[REVERSE INTERACTOR] Message received from AXM for proc_id : %s and action name %s', - msg.process_id, msg.action_name); - - return self.socket.send('trigger:action:success', { - success : true, - id : msg.process.pm_id, - action_name : msg.action_name - }); - }); - }); - } -}; diff --git a/lib/Interactor/RemoteActions/Pm2Actions.js b/lib/Interactor/RemoteActions/Pm2Actions.js deleted file mode 100644 index b5edd93bb..000000000 --- a/lib/Interactor/RemoteActions/Pm2Actions.js +++ /dev/null @@ -1,344 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -var debug = require('debug')('interface:driver'); -var Url = require('url'); -var Cipher = require('../Cipher.js'); -var PushInteractor = require('../PushInteractor'); -var Conf = require('../../Configuration.js'); -var Password = require('../Password.js'); - -/** - * Allowed remote PM2 methods - * with options - * - password_required : force to pass a password in parameter - * - password_optional : if a password is set, force it - * - lock : enable the locking system (block parallel commands) - */ -var PM2_REMOTE_METHOD_ALLOWED = { - 'restart' : {}, - 'reload' : {}, - 'reset' : {}, - 'scale' : {}, - - 'install' : { password_required : true }, - 'uninstall' : { password_required : true }, - 'stop' : { password_required : true }, - 'delete' : { password_required : true }, - 'set' : {}, - 'multiset' : {}, - 'deepUpdate' : { password_required : true }, - - 'pullAndRestart' : { password_optional : true }, - 'forward' : { password_optional : true }, - 'backward' : { password_optional : true }, - - 'startLogging' : {}, - 'stopLogging' : {}, - - 'resetTransactionCache': {}, - 'resetFileCache': {}, - - // This is just for testing purproses - 'ping' : { password_required : true } -}; - -var Pm2Actions = module.exports = { - /** - * Methods to trigger PM2 actions from remote - */ - pm2Actions : function() { - var self = this; - - function executionBox(msg, cb) { - /** - * Exemple - * msg = { - * method_name : 'restart', - * parameters : {} - * } - */ - console.log('PM2 action from remote triggered "pm2 %s %j"', - msg.method_name, - msg.parameters); - - var method_name = JSON.parse(JSON.stringify(msg.method_name)); - - var parameters = ''; - - try { - parameters = JSON.parse(JSON.stringify(msg.parameters)); - } - catch(e) { - console.error(e.stack || e); - parameters = msg.parameters; - } - - if (!method_name) { - console.error('no method name'); - return cb(new Error('no method name defined')); - } - - if (!PM2_REMOTE_METHOD_ALLOWED[method_name]) { - console.error('method %s not allowed', method_name); - return cb(new Error('method ' + method_name + ' not allowed')); - } - - if (method_name === 'startLogging') { - global._logs = true; - // Stop streaming logs automatically after timeout - setTimeout(function() { - global._logs = false; - }, 120000); - return cb(null, 'Log streaming enabled'); - } else if (method_name === 'stopLogging') { - global._logs = false; - return cb(null, 'Log streaming disabled'); - } else if (method_name === 'resetTransactionCache') { - PushInteractor.aggregator.clearData(); - return cb(null, 'Transaction cache has beem reset'); - } else if (method_name === 'resetFileCache') { - PushInteractor.cache.reset(); - return cb(null, 'File cache has beem reset'); - } - - self.pm2_instance.remote(method_name, parameters, cb); - return false; - } - - function sendBackResult(data) { - self.socket.send('trigger:pm2:result', data); - }; - - this.socket.data('trigger:pm2:action', function(raw_msg) { - var d = require('domain').create(); - - var msg = {}; - - /** - * Uncipher Data - */ - if (process.env.NODE_ENV && - (process.env.NODE_ENV == 'test' || - process.env.NODE_ENV == 'local_test')) - msg = raw_msg; - else - msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY); - - d.on('error', function(e) { - console.error('Error caught in domain'); - console.error(e.stack || e); - - /** - * Send error back to - */ - sendBackResult({ - ret : { - err : e, - data : null - }, - meta : { - method_name : msg.method_name, - app_name : msg.parameters.name, - machine_name : self.conf.MACHINE_NAME, - public_key : self.conf.PUBLIC_KEY - } - }); - }); - - d.run(function() { - if (!msg) - throw new Error('Wrong SECRET KEY to uncipher package'); - - /** - * Execute command - */ - executionBox(msg, function(err, data) { - if (err) console.error(err.stack || JSON.stringify(err)); - - /** - * Send back the result - */ - sendBackResult({ - ret : { - err : err, - data : data || null - }, - meta : { - method_name : msg.method_name, - app_name : msg.parameters.name, - machine_name : self.conf.MACHINE_NAME, - public_key : self.conf.PUBLIC_KEY - } - }); - }); - }); - - }); - }, - - /**************************************************** - * - * - * Scoped PM2 Actions with streaming and multi args - * - * - ****************************************************/ - pm2ScopedActions : function() { - var self = this; - - this.socket.data('trigger:pm2:scoped:action', function(raw_msg) { - var msg = {}; - - if (process.env.NODE_ENV && (process.env.NODE_ENV == 'test' || - process.env.NODE_ENV == 'local_test')) - msg = raw_msg; - else { - /** - * Uncipher Data - */ - msg = Cipher.decipherMessage(raw_msg, self.conf.SECRET_KEY); - } - - if (!msg.uuid || - !msg.action_name) { - console.error('PM2 Scoped: Parameter missing!'); - return sendEvent('pm2:scoped:error', { - at : Date.now(), - out : 'Parameter missing', - msg : msg.uuid || null - }); - } - - sendEvent('pm2:scoped:stream', { - at : Date.now(), - out : 'Action ' + msg.action_name + ' received', - uuid : msg.uuid - }); - - executionBox(msg, function(err, data) { - if (err) { - console.error(err.stack || err); - return sendEvent('pm2:scoped:error', { - at : Date.now(), - out : err.stack || err, - uuid : msg.uuid - }); - } - return sendEvent('pm2:scoped:end', { - at : Date.now(), - out : data, - uuid : msg.uuid - }); - }); - }); - - /** - * Compact event in Push Interactor *pipe* - */ - function sendEvent(event, data) { - var packet = { - at : Date.now(), - data : { - data : data.out, - uuid : data.uuid - } - }; - - if (!PushInteractor._packet[event]) - PushInteractor._packet[event] = []; - - PushInteractor._packet[event].push(packet); - - if (process.env.NODE_ENV == 'local_test') - process.send({event : event, data : data}); - }; - - /** - * Processing - */ - function executionBox(msg, cb) { - var action_name = msg.action_name; - var opts = msg.options; - - if (!PM2_REMOTE_METHOD_ALLOWED[action_name]) { - console.error('method %s not allowed', action_name); - return cb(new Error('method ' + action_name + ' not allowed')); - } - - var action_conf = PM2_REMOTE_METHOD_ALLOWED[action_name]; - - /** - * Password checking - */ - if (action_conf.password_required === true) { - if (!msg.password) { - console.error('Missing password in query'); - return cb('Missing password in query'); - } - - var passwd = Conf.getSync('pm2:passwd'); - - if (passwd === null) { - console.error('Password at PM2 level is missing'); - return cb('Password at PM2 level is missing please set password via pm2 set pm2:passwd '); - } - - if (Password.verify(msg.password, passwd) != true) { - console.error('Password does not match'); - return cb('Password does not match'); - } - } - - if (action_conf.lock === false) - opts.lock = false; - - /** - * Fork the remote action in another process - * so we can catch the stdout/stderr and emit it - */ - var fork = require('child_process').fork; - - process.env.fork_params = JSON.stringify({ action : action_name, opts : opts}); - - console.log('Executing: pm2 %s %s', action_name, opts.args ? opts.args.join(' ') : ''); - - var app = fork(__dirname + '/ScopedExecution.js', [], { - silent : true - }); - - app.stdout.on('data', function(dt) { - console.log(dt.toString()); - sendEvent('pm2:scoped:stream', { - at : Date.now(), - out : dt.toString(), - uuid : msg.uuid - }); - }); - - app.once('error', function(dt) { - console.error('Error got?', dt); - sendEvent('pm2:scoped:error', { - at : Date.now(), - out : 'Shit happening ' + JSON.stringify(dt), - msg : msg.uuid - }); - }); - - app.on('message', function(dt) { - var ret = JSON.parse(dt); - if (ret.isFinished != true) return false; - - console.log('Action %s finished (err= %s)', - action_name, ret.err); - return cb(ret.err, ret.dt); - }); - - return false; - } - - } -}; diff --git a/lib/Interactor/RemoteActions/ScopedExecution.js b/lib/Interactor/RemoteActions/ScopedExecution.js deleted file mode 100644 index 08c962e36..000000000 --- a/lib/Interactor/RemoteActions/ScopedExecution.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -var pm2 = require('../../..'); -var domain = require('domain'); -var Utility = require('../../Utility.js'); - -var d = domain.create(); - -d.once('error', function(err) { - process.send(JSON.stringify({err: err.stack, isFinished : true})); -}); - -d.run(function() { - var params = JSON.parse(process.env.fork_params); - - console.log('Executing: pm2 %s %s', - params.action, - params.opts.args ? params.opts.args.join(' ') : ''); - - pm2.connect(function() { - pm2.remoteV2(params.action, params.opts, function(err, dt) { - process.send(JSON.stringify(Utility.clone({ - err: err, - dt: dt, - isFinished : true - }))); - pm2.disconnect(process.exit); - }); - }); -}); diff --git a/lib/Interactor/ReverseInteractor.js b/lib/Interactor/ReverseInteractor.js deleted file mode 100644 index 67673bbae..000000000 --- a/lib/Interactor/ReverseInteractor.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -var debug = require('debug')('interface:driver'); -var nssocket = require('nssocket'); -var Url = require('url'); -var Cipher = require('./Cipher.js'); -var util = require('util'); - -var ReverseInteract = { - changeUrl : function(url) { - if (!this.connected) return; - console.log('[REV] Changing URL to %s', url); - - this.network = Url.parse(url); - this.socket.connect(parseInt(this.network.port), this.network.hostname); - this.socket.reconnect(); - }, - destroy : function() { - this.socket.destroy(); - }, - reconnect : function() { - console.log('[REV] Reconnecting to %s', this.network.hostname); - this.socket.reconnect(); - }, - start : function(opts) { - var self = this; - - if (!opts.url) - throw new Error('url not declared'); - if (!opts.conf) - throw new Error('Conf not passed to ReverseInteractor'); - - this.connected = false; - this.conf = opts.conf; - this.network = Url.parse(opts.url); - this.pm2_instance = opts.conf.pm2_instance; - - this.socket = new nssocket.NsSocket({ - type : 'tcp4', - reconnect : true, - retryInterval : 2000, - max : Infinity, - maxListeners : 50 - }); - - this.socket.on('error', function(e) { - self.connected = false; - console.error('[REV] %s', e.message || e); - }); - - this.socket.on('close', function(dt) { - self.connected = false; - }); - - this.socket.on('start', function() { - self.connected = true; - opts.conf.rev_con = true; - console.log('[REV] Connected to %s:%s', self.network.hostname, self.network.port); - }); - - console.log('[REV] Connecting to %s:%s', this.network.hostname, this.network.port); - - this.socket.connect(parseInt(this.network.port), this.network.hostname); - this.onMessage(); - }, - /** - * Listening to remote events from Keymetrics - */ - onMessage : function() { - if (!this.socket) return console.error('Reverse interaction not initialized'); - - /** - * Identify this agent to Keymetrics - * via PUBLIC/PRIVATE key exchange - */ - ReverseInteract.introduceToKeymetrics(); - - ReverseInteract.axmCustomActions(); - - /** - * From Pm2Actions.js - */ - ReverseInteract.pm2Actions(); - - ReverseInteract.pm2ScopedActions(); - - return false; - }, - /** - * First method called to identify this agent - */ - introduceToKeymetrics : function() { - var self = this; - - this.socket.data('ask', function(raw_msg) { - if (process.env.NODE_ENV && process.env.NODE_ENV == 'test') { - // Dont cipher data in test environment - self.socket.send('ask:rep', { - success : true, - machine_name : self.conf.MACHINE_NAME, - public_key : self.conf.PUBLIC_KEY - }); - } else { - var ciphered_data = Cipher.cipherMessage(JSON.stringify({ - machine_name : self.conf.MACHINE_NAME - }), self.conf.SECRET_KEY); - - if (!ciphered_data) - return console.error('Got wrong ciphering data %s %s', self.conf.MACHINE_NAME, self.conf.SECRET_KEY); - - self.socket.send('ask:rep', { - data : ciphered_data, - public_key : self.conf.PUBLIC_KEY - }); - } - return false; - }); - } -}; - -util._extend(ReverseInteract, require('./RemoteActions/Pm2Actions.js')); -util._extend(ReverseInteract, require('./RemoteActions/CustomActions.js')); - -module.exports = ReverseInteract; diff --git a/lib/Interactor/TransactionAggregator.js b/lib/Interactor/TransactionAggregator.js deleted file mode 100644 index d6a46283f..000000000 --- a/lib/Interactor/TransactionAggregator.js +++ /dev/null @@ -1,684 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -/** - * Dependencies - */ -var cst = require('../../constants.js'); -var log = require('debug')('pm2:aggregator'); -var async = require('async'); -var Utility = require('./Utility.js'); -var fclone = require('fclone'); -var fs = require('fs'); -var path = require('path'); -var Histogram = require('pmx/lib/utils/probes/Histogram.js'); - -var LABELS = { - "HTTP_RESPONSE_CODE_LABEL_KEY": 'http/status_code', - "HTTP_URL_LABEL_KEY": 'http/url', - "HTTP_METHOD_LABEL_KEY": 'http/method', - "HTTP_RESPONSE_SIZE_LABEL_KEY": 'http/response/size', - "STACK_TRACE_DETAILS_KEY": 'stacktrace', - "ERROR_DETAILS_NAME": 'error/name', - "ERROR_DETAILS_MESSAGE": 'error/message', - "HTTP_SOURCE_IP": 'http/source/ip', - "HTTP_PATH_LABEL_KEY": "http/path" -} -var SPANS_DB = ['redis', 'mysql', 'pg', 'mongo', 'outbound_http']; - -/** - * - * # Data structure sent to interactor - * - * { - * 'process_name': { - * process : {}, // PM2 process meta data - * data : { - * routes : [ // array of all routes ordered by count - * { - * path: '/', // path of the route - * meta: { - * count: 50, // count of this route - * max: 300, // max latency of this route - * min: 50, // min latency of this route - * mean: 120 // mean latency of this route - * } - * variances: [{ // array of variance order by count - * spans : [ - * ... // transactions - * ], - * count: 50, // count of this variance - * max: 300, // max latency of this variance - * min: 50, // min latency of this variance - * mean: 120 // mean latency of this variance - * }] - * } - * ], - * meta : { - * trace_count : 50, // trace number - * mean_latency : 40, // global app latency in ms - * http_meter : 30, // global app req per minutes - * db_meter : 20, // number of database transaction per min - * } - * } - * } - * } - */ - -var TransactionAggregator = module.exports = function (pushInteractor) { - if (!(this instanceof TransactionAggregator)) return new TransactionAggregator(pushInteractor); - - var self = this; - this.processes = {}; - this.stackParser = pushInteractor.stackParser; - - /** - * First method to call in real environment - * - Listen to restart event for initialization period - * - Clear aggregation on process stop - * - Launch worker to attach data to be pushed to KM - */ - this.init = function () { - // New Process Online, reset & wait a bit before processing - pushInteractor.ipm2.bus.on('process:event', function (data) { - if (data.event !== 'online' || !self.processes[data.process.name]) return false; - - var rev = (data.process.versioning && data.process.versioning.revision) - ? data.process.versioning.revision : null; - - self.resetAggregation(data.process.name, { - rev: rev, - server: pushInteractor.conf.MACHINE_NAME - }); - }); - - // Process getting offline, delete aggregation - pushInteractor.ipm2.bus.on('process:event', function (data) { - if (data.event !== 'stop' || !self.processes[data.process.name]) return false; - log('Deleting aggregation for %s', data.process.name); - delete self.processes[data.process.name]; - }); - - self.launchWorker(); - }; - - /** - * Reset aggregation for target app_name - */ - this.resetAggregation = function (app_name, meta) { - log('Reseting agg for app:%s meta:%j', app_name, meta); - - if (self.processes[app_name].initialization_timeout) { - log('Reseting initialization timeout app:%s', app_name); - clearTimeout(self.processes[app_name].initialization_timeout); - clearInterval(self.processes[app_name].notifier); - self.processes[app_name].notifier = null; - } - - self.processes[app_name] = initializeRouteMeta({ - name: app_name, - rev: meta.rev, - server: meta.server - }); - - var start = Date.now(); - self.processes[app_name].notifier = setInterval(function () { - var elapsed = Date.now() - start; - // failsafe - if (elapsed / 1000 > cst.AGGREGATION_DURATION) { - clearInterval(self.processes[app_name].notifier); - self.processes[app_name].notifier = null; - } - - var msg = { - data: { - learning_duration: cst.AGGREGATION_DURATION, - elapsed: elapsed - }, - process: self.processes[app_name].process - }; - pushInteractor && pushInteractor.bufferData('axm:transaction:learning', msg); - }, 5000); - - self.processes[app_name].initialization_timeout = setTimeout(function () { - log('Initialization timeout finished for app:%s', app_name); - clearInterval(self.processes[app_name].notifier); - self.processes[app_name].notifier = null; - self.processes[app_name].initialization_timeout = null; - }, cst.AGGREGATION_DURATION); - }; - - /** - * Clear aggregated data for all process - */ - this.clearData = function () { - var self = this; - Object.keys(this.processes).forEach(function (process) { - self.resetAggregation(process, self.processes[process].process); - }); - }; - - /** - * Generate new entry for application - * - * @param {Object} process process meta - */ - function initializeRouteMeta (process) { - if (process.pm_id) delete process.pm_id; - - return { - routes: {}, - meta: { - trace_count: 0, - http_meter: new Utility.EWMA(), - db_meter: new Utility.EWMA(), - histogram: new Histogram({ measurement: 'median' }), - db_histograms: {} - }, - process: process - }; - } - - this.getAggregation = function () { - return this.processes; - }; - - this.validateData = function (packet) { - if (!packet || !packet.data) { - log('Packet malformated', packet); - return false; - } - - if (!packet.process) { - log('Got packet without process: %j', packet); - return false; - } - - if (!packet.data.spans || !packet.data.spans[0]) { - log('Trace without spans: %s', Object.keys(packet.data)); - return false; - } - - if (!packet.data.spans[0].labels) { - log('Trace spans without labels: %s', Object.keys(packet.data.spans)); - return false; - } - - return true; - } - - /** - * Main method to aggregate and compute stats for traces - * - * @param {Object} packet - * @param {Object} packet.process process metadata - * @param {Object} packet.data trace - */ - this.aggregate = function(packet) { - if (self.validateData(packet) === false) return false; - - var new_trace = packet.data; - var app_name = packet.process.name; - - if (!self.processes[app_name]) { - self.processes[app_name] = initializeRouteMeta(packet.process); - } - - var process = self.processes[app_name]; - - // Get http path of current span - var path = new_trace.spans[0].labels[LABELS.HTTP_PATH_LABEL_KEY]; - - // Cleanup spans - self.censorSpans(new_trace.spans); - - // remove spans with startTime == endTime - new_trace.spans = new_trace.spans.filter(function (span) { - return span.endTime !== span.startTime; - }); - - // compute duration of child spans - new_trace.spans.forEach(function (span) { - span.mean = Math.round(new Date(span.endTime) - new Date(span.startTime)); - delete span.endTime; - }); - - // Update app meta (mean_latency, http_meter, db_meter, trace_count) - new_trace.spans.forEach(function (span) { - if (!span.name || !span.kind) return false; - - if (span.kind === 'RPC_SERVER') { - process.meta.histogram.update(span.mean); - return process.meta.http_meter.update(); - } - - // Override outbount http queries for processing - if (span.labels && span.labels['http/method'] && span.labels['http/status_code']) { - span.labels['service'] = span.name; - span.name = 'outbound_http'; - } - - for (var i = 0, len = SPANS_DB.length; i < len; i++) { - if (span.name.indexOf(SPANS_DB[i]) > -1) { - process.meta.db_meter.update(); - if (!process.meta.db_histograms[SPANS_DB[i]]) { - process.meta.db_histograms[SPANS_DB[i]] = new Histogram({ measurement: 'mean' }); - } - process.meta.db_histograms[SPANS_DB[i]].update(span.mean); - break; - } - } - }); - - process.meta.trace_count++; - - /** - * Handle traces aggregation - */ - if (path[0] === '/' && path !== '/') { - path = path.substr(1, path.length - 1); - } - - var matched = self.matchPath(path, process.routes); - - if (!matched) { - process.routes[path] = []; - self.mergeTrace(process.routes[path], new_trace, process); - } else { - self.mergeTrace(process.routes[matched], new_trace, process); - } - - return self.processes; - }; - - /** - * Merge new trace and compute mean, min, max, count - * - * @param {Object} aggregated previous aggregated route - * @param {Object} trace - */ - this.mergeTrace = function (aggregated, trace, process) { - var self = this; - - if (!aggregated || !trace) return; - - // if the trace doesn't any spans stop aggregation here - if (trace.spans.length === 0) return; - - // create data structure if needed - if (!aggregated.variances) aggregated.variances = []; - if (!aggregated.meta) { - aggregated.meta = { - histogram: new Histogram({ measurement: 'median' }), - meter: new Utility.EWMA() - }; - } - - aggregated.meta.histogram.update(trace.spans[0].mean); - aggregated.meta.meter.update(); - - var merge = function (variance) { - // no variance found so its a new one - if (variance == null) { - delete trace.projectId; - delete trace.traceId; - trace.histogram = new Histogram({ measurement: 'median' }); - trace.histogram.update(trace.spans[0].mean); - - trace.spans.forEach(function (span) { - span.histogram = new Histogram({ measurement: 'median' }); - span.histogram.update(span.mean); - delete span.mean; - }); - - // parse strackrace - self.parseStacktrace(trace.spans); - aggregated.variances.push(trace); - } else { - // check to see if request is anormally slow, if yes send it as inquisitor - if (trace.spans[0].mean > variance.histogram.percentiles([0.95])[0.95] && - typeof pushInteractor !== 'undefined' && !process.initialization_timeout) { - // serialize and add metadata - self.parseStacktrace(trace.spans) - var data = { - trace: fclone(trace.spans), - variance: fclone(variance.spans.map(function (span) { - return { - labels: span.labels, - kind: span.kind, - name: span.name, - startTime: span.startTime, - percentiles: { - p5: variance.histogram.percentiles([0.5])[0.5], - p95: variance.histogram.percentiles([0.95])[0.95] - } - } - })), - meta: { - value: trace.spans[0].mean, - percentiles: { - p5: variance.histogram.percentiles([0.5])[0.5], - p75: variance.histogram.percentiles([0.75])[0.75], - p95: variance.histogram.percentiles([0.95])[0.95], - p99: variance.histogram.percentiles([0.99])[0.99] - }, - min: variance.histogram.getMin(), - max: variance.histogram.getMax(), - count: variance.histogram.getCount() - }, - process: process.process - }; - pushInteractor.bufferData('axm:transaction:outlier', data); - } - - // variance found, merge spans - variance.histogram.update(trace.spans[0].mean); - - // update duration of spans to be mean - self.updateSpanDuration(variance.spans, trace.spans, variance.count); - - // delete stacktrace before merging - trace.spans.forEach(function (span) { - delete span.labels.stacktrace; - }); - } - }; - - // for every variance, check spans same variance - for (var i = 0; i < aggregated.variances.length; i++) { - if (self.compareList(aggregated.variances[i].spans, trace.spans)) { - return merge(aggregated.variances[i]); - } - } - // else its a new variance - return merge(null); - }; - - /** - * Parkour simultaneously both spans list to update value of the first one using value of the second one - * The first should be variance already aggregated for which we want to merge the second one - * The second one is a new trace, so we need to re-compute mean/min/max time for each spans - */ - this.updateSpanDuration = function (spans, newSpans) { - for (var i = 0, len = spans.length; i < len; i++) { - if (!newSpans[i]) continue; - spans[i].histogram.update(newSpans[i].mean); - } - }; - - /** - * Compare two spans list by going down on each span and comparing child and attribute - */ - this.compareList = function (one, two) { - if (one.length !== two.length) return false; - - for (var i = 0, len = one; i < len; i++) { - if (one[i].name !== two[i].name) return false; - if (one[i].kind !== two[i].kind) return false; - if (!one[i].labels && two[i].labels) return false; - if (one[i].labels && !two[i].labels) return false; - if (one[i].labels.length !== two[i].labels.length) return false; - } - return true; - }; - - /** - * Will return the route if we found an already matched route - */ - this.matchPath = function (path, routes) { - // empty route is / without the fist slash - if (!path || !routes) return false; - if (path === '/') return routes[path] ? path : null; - - // remove the last slash if exist - if (path[path.length - 1] === '/') { - path = path.substr(0, path.length - 1); - } - - // split to get array of segment - path = path.split('/'); - - // if the path has only one segment, we just need to compare the key - if (path.length === 1) return routes[path[0]] ? routes[path[0]] : null; - - // check in routes already stored for match - var keys = Object.keys(routes); - for (var i = 0, len = keys.length; i < len; i++) { - var route = keys[i]; - var segments = route.split('/'); - - if (segments.length !== path.length) continue; - - for (var j = path.length - 1; j >= 0; j--) { - // different segment, try to find if new route or not - if (path[j] !== segments[j]) { - // if the aggregator already have matched that segment with a wildcard and the next segment is the same - if (self.isIdentifier(path[j]) && segments[j] === '*' && path[j - 1] === segments[j - 1]) { - return segments.join('/'); - } // case a var in url match, so we continue because they must be other var in url - else if (path[j - 1] !== undefined && path[j - 1] === segments[j - 1] && self.isIdentifier(path[j]) && self.isIdentifier(segments[j])) { - segments[j] = '*'; - // update routes in cache - routes[segments.join('/')] = routes[route]; - delete routes[keys[i]]; - return segments.join('/'); - } else { - break; - } - } - - // if finish to iterate over segment of path, we must be on the same route - if (j === 0) return segments.join('/'); - } - } - }; - - this.launchWorker = function () { - setInterval(function () { - var normalized = self.prepareAggregationforShipping(); - Object.keys(normalized).forEach(function (key) { - pushInteractor.bufferData('axm:transaction', normalized[key]); - }); - }, cst.TRACE_FLUSH_INTERVAL); - }; - - /** - * Normalize aggregation - */ - this.prepareAggregationforShipping = function () { - var normalized = {}; - - // Iterate each applications - Object.keys(self.processes).forEach(function (app_name) { - var process = self.processes[app_name]; - var routes = process.routes; - - if (process.initialization_timeout) { - log('Waiting for app %s to be initialized', app_name); - return null; - } - - normalized[app_name] = { - data: { - routes: [], - meta: fclone({ - trace_count: process.meta.trace_count, - http_meter: Math.round(process.meta.http_meter.rate(1000) * 100) / 100, - db_meter: Math.round(process.meta.db_meter.rate(1000) * 100) / 100, - http_percentiles: { - median: process.meta.histogram.percentiles([0.5])[0.5], - p95: process.meta.histogram.percentiles([0.95])[0.95], - p99: process.meta.histogram.percentiles([0.99])[0.99] - }, - db_percentiles: {} - }) - }, - process: process.process - }; - - // compute percentiles for each db spans if they exist - SPANS_DB.forEach(function (name) { - var histogram = process.meta.db_histograms[name]; - if (!histogram) return; - normalized[app_name].data.meta.db_percentiles[name] = fclone(histogram.percentiles([0.5])[0.5]); - }); - - Object.keys(routes).forEach(function (path) { - var data = routes[path]; - - // hard check for invalid data - if (!data.variances || data.variances.length === 0) return; - - // get top 5 variances of the same route - var variances = data.variances.sort(function (a, b) { - return b.count - a.count; - }).slice(0, 5); - - // create a copy without reference to stored one - var routeCopy = { - path: path === '/' ? '/' : '/' + path, - meta: fclone({ - min: data.meta.histogram.getMin(), - max: data.meta.histogram.getMax(), - count: data.meta.histogram.getCount(), - meter: Math.round(data.meta.meter.rate(1000) * 100) / 100, - median: data.meta.histogram.percentiles([0.5])[0.5], - p95: data.meta.histogram.percentiles([0.95])[0.95] - }), - variances: [] - }; - - variances.forEach(function (variance) { - // hard check for invalid data - if (!variance.spans || variance.spans.length === 0) return; - - // deep copy of variances data - var tmp = fclone({ - spans: [], - count: variance.histogram.getCount(), - min: variance.histogram.getMin(), - max: variance.histogram.getMax(), - median: variance.histogram.percentiles([0.5])[0.5], - p95: variance.histogram.percentiles([0.95])[0.95] - }); - - // get data for each span - variance.spans.forEach(function (span) { - tmp.spans.push(fclone({ - name: span.name, - labels: span.labels, - kind: span.kind, - min: span.histogram.getMin(), - max: span.histogram.getMax(), - median: span.histogram.percentiles([0.5])[0.5] - })); - }); - // push serialized into normalized data - routeCopy.variances.push(tmp); - }); - // push the route into normalized data - normalized[app_name].data.routes.push(routeCopy); - }); - }); - - return normalized; - }; - - /** - * Check if the string can be a id of some sort - * - * @param {String} id - */ - this.isIdentifier = function (id) { - id = typeof (id) !== 'string' ? id + '' : id; - - // uuid v1/v4 with/without dash - if (id.match(/[0-9a-f]{8}-[0-9a-f]{4}-[14][0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-f]{12}[14][0-9a-f]{19}/i)) - return true; - // if number - else if (id.match(/\d+/)) - return true; - // if suit of nbr/letters - else if (id.match(/[0-9]+[a-z]+|[a-z]+[0-9]+/)) - return true; - // if match pattern with multiple char spaced by . - _ @ - else if (id.match(/((?:[0-9a-zA-Z]+[@\-_.][0-9a-zA-Z]+|[0-9a-zA-Z]+[@\-_.]|[@\-_.][0-9a-zA-Z]+)+)/)) - return true; - else - return false; - } - - var REGEX_JSON_CLEANUP = /":(?!\[|{)\\"[^"]*\\"|":(["'])(?:(?=(\\?))\2.)*?\1|":(?!\[|{)[^,}\]]*|":\[[^{]*]/g - /** - * Cleanup trace data - * - delete result(s) - * - replace labels value with a question mark - * - * @param {Object} spans list of span for a trace - */ - this.censorSpans = function(spans) { - if (!spans) - return log('spans is null'); - if (cst.DEBUG) return; - - spans.forEach(function(span) { - if (!span.labels) - return; - - delete span.labels.results; - delete span.labels.result; - delete span.spanId; - delete span.parentSpanId; - delete span.labels.values; - - Object.keys(span.labels).forEach(function(key) { - if (typeof(span.labels[key]) === 'string' && key !== 'stacktrace') - span.labels[key] = span.labels[key].replace(REGEX_JSON_CLEANUP, '\": \"?\"'); - }); - }); - } - - /** - * Parse stackrace of spans to extract and normalize data - * - * @param {Object} spans list of span for a trace - */ - this.parseStacktrace = function (spans) { - var self = this; - if (!spans) - return log('spans is null'); - - spans.forEach(function (span) { - // if empty make sure that it doesnt exist - if (!span || - !span.labels || - !span.labels.stacktrace || - typeof(span.labels.stacktrace) !== 'string') - return; - - // you never know what come through that door - try { - span.labels.stacktrace = JSON.parse(span.labels.stacktrace); - } catch (e) { - return ; - } - - if (!span.labels.stacktrace || !(span.labels.stacktrace.stack_frame instanceof Array) ) return ; - // parse the stacktrace - var result = self.stackParser.parse(span.labels.stacktrace.stack_frame); - if (result) { - span.labels['source/file'] = result.callsite || undefined; - span.labels['source/context'] = result.context || undefined; - } - }); - - spans.forEach(function (span) { - if (!span || !span.labels) - return; - delete span.labels.stacktrace; - }) - } -}; diff --git a/lib/Interactor/Utility.js b/lib/Interactor/Utility.js deleted file mode 100644 index 898e734fc..000000000 --- a/lib/Interactor/Utility.js +++ /dev/null @@ -1,235 +0,0 @@ -var path = require('path'); -var isAbsolute = require('../tools/IsAbsolute.js'); - -// EWMA = ExponentiallyWeightedMovingAverage from -// https://github.com/felixge/node-measured/blob/master/lib/util/ExponentiallyMovingWeightedAverage.js -// used to compute the nbr of time per minute that a variance is hit by a new trace -function EWMA () { - this._timePeriod = 60000 - this._tickInterval = 5000 - this._alpha = 1 - Math.exp(-this._tickInterval / this._timePeriod) - this._count = 0 - this._rate = 0 - - var self = this - this._interval = setInterval(function () { - self.tick() - }, this._tickInterval) - this._interval.unref() -} - -EWMA.prototype.update = function (n) { - this._count += n || 1 -} - -EWMA.prototype.tick = function () { - var instantRate = this._count / this._tickInterval - this._count = 0 - - this._rate += (this._alpha * (instantRate - this._rate)) -} - -EWMA.prototype.rate = function (timeUnit) { - return (this._rate || 0) * timeUnit -} - -var moment = require('moment'); - -/** - * Simple cache implementation - * - * @param {Object} opts cache options - * @param {Integer} opts.ttl time to live of all the keys - * @param {Function} opts.miss function called when a key isn't found in the cache - */ -function Cache (opts) { - this._cache = {}; - this._miss = opts.miss; - this._ttl_time = opts.ttl; - this._ttl = {}; - - if (opts.ttl) { - setInterval(this._worker.bind(this), 1000); - } -} - -/** - * Task running to check TTL and potentially remove older key - */ -Cache.prototype._worker = function () { - var keys = Object.keys(this._ttl); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var value = this._ttl[key]; - if (moment().isAfter(value)) { - delete this._cache[key]; - delete this._ttl[key]; - } - } -}; - -/** - * Empty the cache - */ -Cache.prototype.reset = function () { - this._cache = null; - this._cache = {}; - this._ttl = null; - this._ttl = {}; -}; - -/** - * Get a value from the cache - * - * @param {String} key - */ -Cache.prototype.get = function (key) { - if (!key) return null; - var value = this._cache[key]; - if (value) return value; - - value = this._miss(key); - - if (value) { - this.set(key, value); - } - return value; -}; - -/** - * Set a value in the cache - * - * @param {String} key - * @param {Mixed} value - */ -Cache.prototype.set = function (key, value) { - if (!key || !value) return false; - this._cache[key] = value; - if (this._ttl_time) { - this._ttl[key] = moment().add(this._ttl_time, 'seconds'); - } - return true; -}; - -/** - * StackTraceParser is used to parse callsite from stacktrace - * and get from FS the context of the error (if available) - * - * @param {Cache} cache cache implementation used to query file from FS and get context - */ -function StackTraceParser (opts) { - this._cache = opts.cache; - this._context_size = opts.context; -} - -StackTraceParser.prototype.attachContext = function (error) { - var self = this; - if (!error) return error; - - // if pmx attached callsites we can parse them to retrieve the context - if (typeof (error.stackframes) === 'object') { - var result = self.parse(error.stackframes); - // no need to send it since there is already the stacktrace - delete error.stackframes; - delete error.__error_callsites; - - if (result) { - error.callsite = result.callsite; - error.context = result.context; - } - } - // if the stack is here we can parse it directly from the stack string - // only if the context has been retrieved from elsewhere - if (typeof error.stack === 'string' && !error.callsite) { - var siteRegex = /(\/[^\\\n]*)/g; - var tmp; - var stack = []; - - // find matching groups - while ((tmp = siteRegex.exec(error.stack))) { - stack.push(tmp[1]); - } - - // parse each callsite to match the format used by the stackParser - stack = stack.map(function (callsite) { - // remove the trailing ) if present - if (callsite[callsite.length - 1] === ')') { - callsite = callsite.substr(0, callsite.length - 1); - } - var location = callsite.split(':'); - - return location.length < 3 ? callsite : { - file_name: location[0], - line_number: parseInt(location[1]) - }; - }); - - var finalCallsite = self.parse(stack); - if (finalCallsite) { - error.callsite = finalCallsite.callsite; - error.context = finalCallsite.context; - } - } - return error; -}; - -/** - * Parse the stacktrace and return callsite + context if available - */ -StackTraceParser.prototype.parse = function (stack) { - var self = this; - if (!stack || stack.length === 0) return false; - - for (var i = 0, len = stack.length; i < len; i++) { - var callsite = stack[i]; - - // avoid null values - if (typeof callsite !== 'object') continue; - if (!callsite.file_name || !callsite.line_number) continue; - - var type = isAbsolute(callsite.file_name) || callsite.file_name[0] === '.' ? 'user' : 'core'; - - // only use the callsite if its inside user space - if (!callsite || type === 'core' || callsite.file_name.indexOf('node_modules') > -1 || - callsite.file_name.indexOf('vxx') > -1) { - continue; - } - - // get the whole context (all lines) and cache them if necessary - var context = self._cache.get(callsite.file_name); - var source = []; - if (context && context.length > 0) { - // get line before the call - var preLine = callsite.line_number - self._context_size - 1; - var pre = context.slice(preLine > 0 ? preLine : 0, callsite.line_number - 1); - if (pre && pre.length > 0) { - pre.forEach(function (line) { - source.push(line.replace(/\t/g, ' ')); - }); - } - // get the line where the call has been made - if (context[callsite.line_number - 1]) { - source.push(context[callsite.line_number - 1].replace(/\t/g, ' ').replace(' ', '>>')); - } - // and get the line after the call - var postLine = callsite.line_number + self._context_size; - var post = context.slice(callsite.line_number, postLine); - if (post && post.length > 0) { - post.forEach(function (line) { - source.push(line.replace(/\t/g, ' ')); - }); - } - } - return { - context: source.length > 0 ? source.join('\n') : 'cannot retrieve source context', - callsite: [ callsite.file_name, callsite.line_number ].join(':') - }; - } - return false; -}; - -module.exports = { - EWMA: EWMA, - Cache: Cache, - StackTraceParser: StackTraceParser -}; diff --git a/lib/Interactor/WatchDog.js b/lib/Interactor/WatchDog.js deleted file mode 100644 index 8ade9c81b..000000000 --- a/lib/Interactor/WatchDog.js +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -var PM2 = require('../..'); -var debug = require('debug')('interface:watchdog'); -var shelljs = require('shelljs'); -var csts = require('../../constants'); -var path = require('path'); - -process.env.PM2_AGENT_ONLINE = true; - -var WatchDog = module.exports = { - start : function(p) { - var self = this; - this.ipm2 = p.conf.ipm2; - this.relaunching = false; - this.pm2_instance = p.conf.pm2_instance; - - /** - * Handle PM2 connection state changes - */ - this.ipm2.on('ready', function() { - console.log('[WATCHDOG] Connected to PM2'); - self.relaunching = false; - self.autoDump(); - }); - - console.log('[WATCHDOG] Launching'); - - this.ipm2.on('reconnecting', function() { - console.log('[WATCHDOG] PM2 is disconnected - Relaunching PM2'); - - if (self.relaunching === true) return console.log('[WATCHDOG] Already relaunching PM2'); - self.relaunching = true; - - if (self.dump_interval) - clearInterval(self.dump_interval); - - return WatchDog.resurrect(); - }); - }, - resurrect : function() { - var self = this; - - console.log('[WATCHDOG] Trying to launch PM2 #1'); - - shelljs.exec('node ' + path.resolve(__dirname, '../../bin/pm2') + ' resurrect', function() { - setTimeout(function() { - self.relaunching = false; - }, 2500); - }); - }, - autoDump : function() { - var self = this; - - this.dump_interval = setInterval(function() { - if (self.relaunching == true) return false; - - self.pm2_instance.dump(function(err) { - if (err) return console.error('[WATCHDOG] Error when dumping'); - debug('PM2 process list dumped'); - return false; - }); - }, 5 * 60 * 1000); - } -}; diff --git a/lib/Interactor/internal-ip.js b/lib/Interactor/internal-ip.js deleted file mode 100644 index b3f1b7c99..000000000 --- a/lib/Interactor/internal-ip.js +++ /dev/null @@ -1,40 +0,0 @@ -var os = require('os'); - -var type = { - v4: { - def: '127.0.0.1', - family: 'IPv4' - }, - v6: { - def: '::1', - family: 'IPv6' - } -}; - -function internalIp(version) { - var options = type[version]; - var ret = options.def; - var interfaces = os.networkInterfaces(); - - Object.keys(interfaces).forEach(function (el) { - interfaces[el].forEach(function (el2) { - if (!el2.internal && el2.family === options.family) { - ret = el2.address; - } - }); - }); - - return ret; -} - -function v4() { - return internalIp('v4'); -} - -function v6() { - return internalIp('v6'); -} - -module.exports = v4; -module.exports.v4 = v4; -module.exports.v6 = v6; diff --git a/lib/Interactor/pm2-interface.js b/lib/Interactor/pm2-interface.js deleted file mode 100644 index 17d40303a..000000000 --- a/lib/Interactor/pm2-interface.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright 2013 the PM2 project authors. All rights reserved. - * Use of this source code is governed by a license that - * can be found in the LICENSE file. - */ - -/** - * Dependencies - */ - -var axon = require('pm2-axon'); -var cst = require('../../constants.js'); -var util = require('util'); -var rpc = require('pm2-axon-rpc'); -var log = require('debug')('pm2:interface'); -var EventEmitter = require('events').EventEmitter; - -/** - * Export with conf - */ -module.exports = function(opts){ - var sub_port = opts && opts.sub_port || cst.DAEMON_PUB_PORT; - var rpc_port = opts && opts.rpc_port || cst.DAEMON_RPC_PORT; - - return new IPM2(sub_port, rpc_port); -}; - -/** - * IPM2, Pm2 Interface - */ - -var IPM2 = function(sub_port, rpc_port) { - if (!(this instanceof IPM2)) return new IPM2(sub_port, rpc_port); - var self = this; - - EventEmitter.call(this); - - this.sub_port = sub_port; - this.rpc_port = rpc_port; - - - var sub = axon.socket('sub-emitter'); - var sub_sock = this.sub_sock = sub.connect(sub_port); - this.bus = sub; - - var req = axon.socket('req'); - var rpc_sock = this.rpc_sock = req.connect(rpc_port); - this.rpc_client = new rpc.Client(req); - - this.rpc = {}; - - rpc_sock.on('connect', function() { - log('rpc_sock:ready'); - self.emit('rpc_sock:ready'); - generateMethods(function() { - self.emit('ready'); - }); - }); - - rpc_sock.on('close', function() { - log('rpc_sock:closed'); - self.emit('close'); - }); - - rpc_sock.on('reconnect attempt', function() { - log('rpc_sock:reconnecting'); - self.emit('reconnecting'); - }); - - sub_sock.on('connect', function() { - log('sub_sock ready'); - self.emit('sub_sock:ready'); - }); - - sub_sock.on('close', function() { - log('sub_sock:closed'); - self.emit('closed'); - }); - - sub_sock.on('reconnect attempt', function() { - log('sub_sock:reconnecting'); - self.emit('reconnecting'); - }); - - /** - * Disconnect socket connections. This will allow Node to exit automatically. - * Further calls to PM2 from this object will throw an error. - */ - this.disconnect = function () { - self.sub_sock.close(); - self.rpc_sock.close(); - }; - - /** - * Generate method by requesting exposed methods by PM2 - * You can now control/interact with PM2 - */ - var generateMethods = function(cb) { - log('Requesting and generating RPC methods'); - self.rpc_client.methods(function(err, methods) { - Object.keys(methods).forEach(function(key) { - var method_signature, md; - method_signature = md = methods[key]; - - log('+-- Creating %s method', md.name); - - (function(name) { - self.rpc[name] = function() { - log(name); - var args = Array.prototype.slice.call(arguments); - args.unshift(name); - self.rpc_client.call.apply(self.rpc_client, args); - }; - })(md.name); - - }); - return cb(); - }); - }; -}; - -util.inherits(IPM2, EventEmitter); diff --git a/lib/Satan.js b/lib/Satan.js index 1fc6651e7..968a4bbbe 100644 --- a/lib/Satan.js +++ b/lib/Satan.js @@ -328,7 +328,7 @@ Satan.launchDaemon = function launchDaemon(cb) { debug('Launching daemon'); var SatanJS = p.resolve(p.dirname(module.filename), 'Satan.js'); - var InteractorDaemonizer = require('./Interactor/InteractorDaemonizer.js'); + var InteractorDaemonizer = require('keymetrics-agent/src/InteractorClient'); var node_args = []; @@ -384,7 +384,7 @@ Satan.launchDaemon = function launchDaemon(cb) { debug('PM2 daemon launched with return message: ', msg); child.removeListener('error', onError); child.disconnect(); - InteractorDaemonizer.launchAndInteract({}, function(err, data) { + InteractorDaemonizer.launchAndInteract({}, {}, function(err, data) { if (data) debug('Interactor launched'); return cb ? cb(null, child) : false; diff --git a/package.json b/package.json index 3702dbaec..e182d9b3e 100644 --- a/package.json +++ b/package.json @@ -170,6 +170,7 @@ "eventemitter2": "5.0.1", "fclone": "1.0.11", "fs-extra": "^5.0.0", + "keymetrics-agent": "0.2.0", "mkdirp": "0.5.1", "moment": "^2.19", "needle": "^2.2.0", diff --git a/test/interface/aggregator.mocha.js b/test/interface/aggregator.mocha.js deleted file mode 100644 index b90d0d790..000000000 --- a/test/interface/aggregator.mocha.js +++ /dev/null @@ -1,239 +0,0 @@ - -process.env.DEBUG='pm2:aggregator'; -var should = require('should'); -var Aggregator = require('../../lib/Interactor/TransactionAggregator.js'); -var Utility = require('../../lib/Interactor/Utility.js'); -var Plan = require('../helpers/plan.js'); -var TraceFactory = require('./misc/trace_factory.js'); -var TraceMock = require('./misc/trace.json'); -var path = require('path'); -var fs = require('fs'); - -describe('Transactions Aggregator', function() { - var aggregator; - var stackParser; - - it('should instanciate context cache', function() { - var cache = new Utility.Cache({ - miss: function (key) { - try { - var content = fs.readFileSync(path.resolve(key)); - return content.toString().split(/\r?\n/); - } catch (err) { - return undefined; - } - } - }) - - stackParser = new Utility.StackTraceParser({ cache: cache, context: 2}); - }); - - it('should instanciate aggregator', function() { - aggregator = new Aggregator({ stackParser: stackParser}); - }); - - describe('.censorSpans', function() { - var trace = TraceFactory.generateTrace('/yoloswag/swag', 2); - - it('should not fail', function() { - aggregator.censorSpans(null); - }); - - it('should censor span', function() { - should.exist(trace.spans[1].labels.results); - aggregator.censorSpans(trace.spans); - should.not.exist(trace.spans[1].labels.results); - trace.spans[1].labels.cmd.should.containEql('?'); - }); - }); - - describe('.isIdentifier', function() { - it('should be an identifier (api version)', function() { - aggregator.isIdentifier('v1').should.equal(true); - }); - - it('should be an identifier (number)', function() { - aggregator.isIdentifier('123').should.equal(true); - }); - - it('should be an identifier (random str)', function() { - aggregator.isIdentifier('65f4ez656').should.equal(true); - }); - - it('should be an identifier (uuid)', function() { - aggregator.isIdentifier('123e4567-e89b-12d3-a456-426655440000').should.equal(true); - aggregator.isIdentifier('123e4567e89b12d3a456426655440000').should.equal(true); - }); - - it('should be an identifier', function() { - aggregator.isIdentifier('toto-toto-tooa').should.equal(true); - aggregator.isIdentifier('toto@toto.fr').should.equal(true); - aggregator.isIdentifier('toto@toto.fr').should.equal(true); - aggregator.isIdentifier('fontawesome-webfont.eot').should.equal(true); - aggregator.isIdentifier('life_is_just_fantasy').should.equal(true); - aggregator.isIdentifier('OR-IS_THIS-REAL_LIFE').should.equal(true); - }); - - it('should be NOT an identifier', function() { - aggregator.isIdentifier('bucket').should.equal(false); - aggregator.isIdentifier('admin').should.equal(false); - aggregator.isIdentifier('auth').should.equal(false); - aggregator.isIdentifier('users').should.equal(false); - aggregator.isIdentifier('version').should.equal(false); - }); - }); - - describe('.matchPath - aggregate', function() { - var routes = { - 'bucket/6465577': { spans: true } - }; - - it('should not fail', function() { - aggregator.matchPath(); - aggregator.matchPath('/'); - aggregator.matchPath('/', {}); - aggregator.matchPath('/', { - '/' : {} - }); - }); - - it('should match first route', function() { - var match = aggregator.matchPath('bucket/67754', routes); - should.exist(match); - match.should.be.a.String(); - match.should.equal('bucket/*'); - should.exist(routes['bucket/*']) - }); - - it('should NOT match any route', function() { - should.not.exist(aggregator.matchPath('toto/67754', routes)); - }); - - it('should match aggregated route with *', function() { - var match = aggregator.matchPath('bucket/87998', routes); - should.exist(match); - match.should.be.a.String(); - match.should.equal('bucket/*'); - should.exist(routes['bucket/*']) - }); - }); - - describe('merging trace together', function() { - var trace = TraceFactory.generateTrace('yoloswag/swag', 2); - var ROUTES = { - 'yoloswag/swag': {} - }; - - it('should not fail', function() { - aggregator.mergeTrace() - aggregator.mergeTrace(null, trace) - aggregator.mergeTrace({}, trace) - aggregator.mergeTrace({}) - }); - - it('should add a trace', function() { - aggregator.mergeTrace(ROUTES['yoloswag/swag'], trace) - ROUTES['yoloswag/swag'].meta.histogram.getCount().should.be.equal(1); - ROUTES['yoloswag/swag'].variances.length.should.be.equal(1); - ROUTES['yoloswag/swag'].variances[0].spans.length.should.be.equal(3); - }); - - it('should merge with the first variance', function() { - aggregator.mergeTrace(ROUTES['yoloswag/swag'], trace); - ROUTES['yoloswag/swag'].variances.length.should.be.equal(1); - ROUTES['yoloswag/swag'].variances[0].histogram.getCount().should.be.equal(2); - }); - - it('should merge as a new variance with the same route', function () { - var trace2 = TraceFactory.generateTrace('yoloswag/swag', 3) - trace2.spans.forEach(function (span) { - span.min = span.max = span.mean = Math.round(new Date(span.endTime) - new Date(span.startTime)); - }) - aggregator.mergeTrace(ROUTES['yoloswag/swag'], trace2); - ROUTES['yoloswag/swag'].meta.histogram.getCount().should.be.equal(3); - ROUTES['yoloswag/swag'].variances.length.should.be.equal(2); - ROUTES['yoloswag/swag'].variances[0].histogram.getCount().should.be.equal(2); - ROUTES['yoloswag/swag'].variances[1].histogram.getCount().should.be.equal(1); - ROUTES['yoloswag/swag'].variances[1].spans.length.should.be.equal(4); - }); - }); - - describe('.aggregate', function() { - it('should not fail', function() { - var dt = aggregator.aggregate(null); - should(dt).be.false(); - }); - - it('should aggregate', function() { - // Simulate some data - var packet = TraceFactory.generatePacket('yoloswag/swag', 'appname'); - aggregator.aggregate(packet); - packet = TraceFactory.generatePacket('yoloswag/swag', 'appname'); - aggregator.aggregate(packet); - packet = TraceFactory.generatePacket('yoloswag/swag', 'appname'); - aggregator.aggregate(packet); - packet = TraceFactory.generatePacket('sisi/aight', 'appname'); - aggregator.aggregate(packet); - packet = TraceFactory.generatePacket('sisi/aight', 'APP2'); - aggregator.aggregate(packet); - - var agg = aggregator.getAggregation(); - - // should get 2 apps in agg - should.exist(agg['appname']); - should.exist(agg['APP2']); - - // should contain 2 routes for appname - Object.keys(agg['appname'].routes).length.should.eql(2); - should.exist(agg['appname'].process); - agg['appname'].meta.trace_count.should.eql(4); - should.exist(agg['appname'].meta.histogram.percentiles([0.5])[0.5]); - - // should pm_id not taken into account - should.not.exist(agg['appname'].process.pm_id); - }); - }); - - describe('.normalizeAggregation', function() { - it('should get normalized aggregattion', function(done) { - var ret = aggregator.prepareAggregationforShipping(); - should.exist(ret['appname'].process.server); - should.exist(ret['APP2'].process.server); - done(); - }); - }); - - describe('.resetAggregation and .clearData', function() { - it('should get transactions', function() { - var cache = aggregator.getAggregation(); - Object.keys(cache).length.should.eql(2); - }); - - it('should .resetAggregation for "appname" app', function() { - var cache = aggregator.getAggregation(); - - cache['appname'].meta.trace_count.should.eql(4); - Object.keys(cache['appname'].routes).length.should.eql(2); - - aggregator.resetAggregation('appname', {}) - cache = aggregator.getAggregation(); - Object.keys(cache).length.should.eql(2); - - cache['appname'].meta.trace_count.should.eql(0); - Object.keys(cache['appname'].routes).length.should.eql(0); - }); - - it('should .clearData', function() { - var cache = aggregator.getAggregation(); - cache['APP2'].meta.trace_count.should.eql(1); - Object.keys(cache['APP2'].routes).length.should.eql(1); - aggregator.clearData(); - - cache = aggregator.getAggregation(); - cache['APP2'].meta.trace_count.should.eql(0); - Object.keys(cache['APP2'].routes).length.should.eql(0); - }); - - }); - -}); diff --git a/test/interface/bus.fork.spec.mocha.js b/test/interface/bus.fork.spec.mocha.js index 3ed46921f..88e52737b 100644 --- a/test/interface/bus.fork.spec.mocha.js +++ b/test/interface/bus.fork.spec.mocha.js @@ -1,6 +1,5 @@ var should = require('should'); -var Ipm2 = require('../../lib/Interactor/pm2-interface'); var PM2 = require('../..'); var Plan = require('../helpers/plan.js'); diff --git a/test/interface/bus.spec.mocha.js b/test/interface/bus.spec.mocha.js index 706d5a1eb..bbf593a9e 100644 --- a/test/interface/bus.spec.mocha.js +++ b/test/interface/bus.spec.mocha.js @@ -1,6 +1,5 @@ var should = require('should'); -var Ipm2 = require('../../lib/Interactor/pm2-interface'); var PM2 = require('../..'); var Plan = require('../helpers/plan.js'); diff --git a/test/interface/cache.mocha.js b/test/interface/cache.mocha.js deleted file mode 100644 index ecd307710..000000000 --- a/test/interface/cache.mocha.js +++ /dev/null @@ -1,86 +0,0 @@ - -var should = require('should'); -var Utility = require('../../lib/Interactor/Utility.js'); -var path = require('path'); -var fs = require('fs'); - -describe('Cache Utility', function() { - var aggregator; - var stackParser; - var cache; - - it('should instanciate context cache', function() { - cache = new Utility.Cache({ - miss: function (key) { - try { - var content = fs.readFileSync(path.resolve(key)); - return content.toString().split(/\r?\n/); - } catch (err) { - return null; - } - } - }) - }); - - it('should get null without key', function() { - should(cache.get()).be.null(); - }); - - it('should get null with unknow value', function() { - should(cache.get('toto')).be.null(); - }); - - it('should get null', function() { - should(cache.get()).be.null(); - }); - - it('should set null', function() { - should(cache.set()).be.false(); - }); - - it('should not set key without value', function() { - should(cache.set('toto')).be.false(); - }); - - it('should set value', function() { - should(cache.set('toto', 'val')).be.true(); - }); - - it('should get value', function() { - should(cache.get('toto')).eql('val'); - }); - - it('should reset', function() { - cache.reset(); - }); - - it('should get null with unknow value', function() { - should(cache.get('toto')).be.null(); - }); - - it('should instanciate context cache with ttl', function() { - cache = new Utility.Cache({ - miss: function (key) { - try { - var content = fs.readFileSync(path.resolve(key)); - return content.toString().split(/\r?\n/); - } catch (err) { - return null; - } - }, - ttl: 1 - }); - }); - - it('should add a key', function () { - should(cache.set('toto', 'yeslife')).be.true(); - }); - - it('should wait one second to see the key disapear', function (done) { - setTimeout(function () { - should(cache.get('toto')).be.null(); - done(); - }, 3000); - }); - -}); diff --git a/test/interface/custom-actions.mocha.js b/test/interface/custom-actions.mocha.js deleted file mode 100644 index 5d6117ec3..000000000 --- a/test/interface/custom-actions.mocha.js +++ /dev/null @@ -1,169 +0,0 @@ - - -var PM2 = require('../..'); -var should = require('should'); -var nssocket = require('nssocket'); -var events = require('events'); -var util = require('util'); - -var Cipher = require('../../lib/Interactor/Cipher.js'); -var cst = require('../../constants.js'); -var Plan = require('../helpers/plan.js'); -var Interactor = require('../../lib/Interactor/InteractorDaemonizer.js'); - -var server = new events.EventEmitter(); -var pm2_bus; - -process.env.NODE_ENV = 'local_test'; - -var meta_connect = { - secret_key : 'osef', - public_key : 'osef', - machine_name : 'osef' -}; - -/** - * Mock server receiving data - * @method forkInteractor - * @return CallExpression - */ -function createMockServer(cb) { - var server = nssocket.createServer(function(_socket) { - - console.log('Got new connection in Mock server'); - - server.on('cmd', function(data) { - console.log('Sending command %j', data); - _socket.send(data._type, data); - }); - - _socket.data('*', function(data) { - this.event.forEach(function(ev) { - server.emit(ev, data); - }); - }); - - }); - - server.on('error', function(e) { - throw new Error(e); - }); - - server.on('listening', function() { - cb(null, server); - }); - - server.listen(4322, '0.0.0.0'); -} - -function startSomeApps(pm2, cb) { - pm2.start({ - script : './events/custom_action.js', - name : 'custom-action' - }, cb); -} - -describe('Custom actions', function() { - var server; - var interactor; - var pm2 = new PM2.custom({ - independent : true, - cwd : __dirname + '/../fixtures', - secret_key : 'osef', - public_key : 'osef', - machine_name : 'osef', - daemon_mode: true - });; - - before(function(done) { - createMockServer(function(err, _server) { - server = _server; - pm2.connect(function(err) { - startSomeApps(pm2, function() { - pm2.launchBus(function(err, bus) { - pm2_bus = bus; - setTimeout(done, 500); - }); - }); - }); - }); - }); - - after(function(done) { - server.close(); - pm2.destroy(done); - }); - - it('should send ask, receive ask:rep and identify agent', function(done) { - server.once('ask:rep', function(pck) { - var data = Cipher.decipherMessage(pck.data, meta_connect.secret_key); - data.machine_name.should.eql(meta_connect.machine_name); - done(); - }); - - server.emit('cmd', { _type : 'ask' }); - }); - - /** - * PM2 agent is now identified - */ - - it('should trigger remote action successfully', function(done) { - var plan = new Plan(2, done); - - var success = function(pck) { - plan.ok(true); - server.removeListener('trigger:action:failure', failure); - }; - - var failure = function(pck) { - console.log(pck); - plan.ok(false); - }; - - server.once('trigger:action:success', success); - - server.once('trigger:action:failure', failure); - - pm2_bus.on('axm:reply', function(pck) { - pck.data.return.success.should.be.true; - pck.data.return.subobj.a.should.eql('b'); - plan.ok(true); - }); - - server.emit('cmd', { - _type : 'trigger:action', - process_id : 0, - action_name : 'refresh:db' - }); - }); - - it('should trigger failure action', function(done) { - var plan = new Plan(1, done); - - var success = function(pck) { - plan.ok(false); - }; - - var failure = function(pck) { - server.removeListener('trigger:action:success', success); - plan.ok(true); - }; - - server.once('trigger:action:success', success); - - server.once('trigger:action:failure', failure); - - pm2_bus.on('axm:reply', function(pck) { - plan.ok(false); - }); - - server.emit('cmd', { - _type : 'trigger:action', - process_id : 0, - action_name : 'unknown:action' - }); - }); - - -}); diff --git a/test/interface/exception.e2e.mocha.js b/test/interface/exception.e2e.mocha.js deleted file mode 100644 index 65ef7f78e..000000000 --- a/test/interface/exception.e2e.mocha.js +++ /dev/null @@ -1,88 +0,0 @@ - -process.env.NODE_ENV = 'local_test'; -process.env.KM_URL_REFRESH_RATE = 1000; - -var axon = require('pm2-axon'); -var PM2 = require('../..'); -var should = require('should'); -var sub; - -function listen(cb) { - sub = axon.socket('sub'); - sub.bind(8080, cb); -} - -function listenRev(cb) { - var listener_server = require('nssocket').createServer(function(_socket) { - }); - - listener_server.listen(4322, '0.0.0.0', function() { - console.log('Reverse interact online'); - cb(); - }); -} - -describe('Programmatically test interactor', function() { - var pm2; - - before(function(done) { - listen(function() { - listenRev(function() { - - pm2 = new PM2.custom({ - public_key : 'xxx', - secret_key : 'yyy', - cwd : __dirname + '/../fixtures/interface' - }); - - pm2.connect(function() { - pm2.kill(function() { - done(); - }); - }); - }); - }); - }); - - after(function(done) { - pm2.kill(done); - }); - - describe('application testing', function() { - it('should start test application', function(done) { - sub.once('message', function(data) { - var packet = JSON.parse(data); - packet.data['process:event'].length.should.eql(2) - done(); - }); - - pm2.start({ - script : 'process_exception_with_logs.js', - name : 'API' - }, function(err, data) { - if (err) done(err); - //console.log(arguments); - }); - }); - - it('should get transaction trace via interactor output', function(done) { - (function callAgain() { - sub.once('message', function(data) { - var packet = JSON.parse(data); - - if (packet.data['process:exception']) { - packet.data['process:exception'][0].data.callsite.should.containEql('process_exception_with_logs.js:7'); - packet.data['process:exception'][0].data.context.should.containEql('console.log'); - should.exist(packet.data['process:exception'][0].data.last_logs); - //console.log - done(); - } - else callAgain(); - }); - })() - - pm2.trigger('API', 'exception'); - }); - - }); -}); diff --git a/test/interface/filter.mocha.js b/test/interface/filter.mocha.js deleted file mode 100644 index 9e48d7cff..000000000 --- a/test/interface/filter.mocha.js +++ /dev/null @@ -1,23 +0,0 @@ - -var Filter = require('../../lib/Interactor/Filter.js'); -var should = require('should'); -var os = require('os'); - -describe('Filter Utility', function() { - it('should .machineSnapshot works as expected', function() { - var filtered = Filter.machineSnapshot([], { - REVERSE_INTERACT : true, - PM2_VERSION : '2.2.0' - }); - filtered.server.should.have.properties(['loadavg', 'total_mem', 'free_mem']); - should(filtered.server.total_mem).eql(os.totalmem()); - should(filtered.server.arch).eql(os.arch()); - }); - - it('should .monitoring works as expected', function() { - var filtered = Filter.monitoring([], {}); - filtered.should.have.properties(['loadavg', 'total_mem', 'free_mem', 'processes']); - filtered.total_mem.should.eql(os.totalmem()); - }); - -}); diff --git a/test/interface/interactor.connect.mocha.js b/test/interface/interactor.connect.mocha.js deleted file mode 100644 index f99740388..000000000 --- a/test/interface/interactor.connect.mocha.js +++ /dev/null @@ -1,227 +0,0 @@ - -process.env.NODE_ENV = 'local_test'; -process.env.TRAVIS = true; -process.env.DEBUG='interface:*'; - -var PM2 = require('../..'); -var should = require('should'); -var nssocket = require('nssocket'); -var events = require('events'); -var util = require('util'); -var axon = require('pm2-axon'); -var sock = axon.socket('sub'); - -var pub_sock = sock.bind(8080); -var Cipher = require('../../lib/Interactor/Cipher.js'); -var cst = require('../../constants.js'); -var Plan = require('../helpers/plan.js'); -var Configuration = require('../../lib/Configuration.js'); -var Helpers = require('../helpers/apps.js'); - -var server = null; -var listener_server; - -var _socket_list = []; - -var meta_connect = { - secret_key : 'osef', - public_key : 'osef', - machine_name : 'osef' -}; - -/** - * Mock server receiving data - * @method forkInteractor - * @return CallExpression - */ -function createMockServer(cb) { - - pub_sock.server.on('connection', function(socket) { - _socket_list.push(socket); - console.log('Got new connection on mock server'); - }); - - server = new events.EventEmitter(); - - listener_server = nssocket.createServer(function(_socket) { - server.on('cmd', function(data) { - _socket.send(data._type, data); - }); - - _socket.data('*', function(data) { - this.event.forEach(function(ev) { - server.emit(ev, data); - }); - }); - - }); - - listener_server.on('error', function(e) { - throw new Error(e); - }); - - listener_server.on('listening', function() { - cb(null, server); - }); - - listener_server.listen(4322, '0.0.0.0'); -} - -describe('Interactor testing', function() { - var server; - var interactor; - var pm2_bus; - - var pm2 = new PM2.custom({ - independent : true, - cwd : __dirname + '/../fixtures', - secret_key : 'osef', - public_key : 'osef', - machine_name : 'osef', - daemon_mode: true - }); - - before(function(done) { - Configuration.unset('pm2:passwd', function(err, data) { - createMockServer(function(err, _server) { - server = _server; - - pm2.connect(function(err, data) { - Helpers.startSomeApps(pm2, function(err, dt) { - done(); - }); - }); - }); - }); - }); - - after(function(done) { - listener_server.close(); - pm2.destroy(done); - }); - - describe('Interactor methods', function() { - it('should display info', function(done) { - pm2.interactInfos(function(err, meta) { - meta.should.have.properties([ - 'machine_name', - 'public_key', - 'secret_key', - 'socket_path', - 'pm2_home_monitored' - ]) - - meta.pm2_home_monitored.should.eql(pm2.pm2_home); - done(); - }); - }); - }); - - describe('Input command / Output data checks', function() { - it('should send ask, receive ask:rep and identify agent', function(done) { - server.once('ask:rep', function(pck) { - var data = Cipher.decipherMessage(pck.data, meta_connect.secret_key); - data.machine_name.should.eql(meta_connect.machine_name); - done(); - }); - - server.emit('cmd', { _type : 'ask' }); - }); - - it('should get status via PushInteractor and PM2 should be statused as not protected', function(done) { - sock.once('message', function(data) { - var dt = JSON.parse(data); - - dt.public_key.should.eql('osef'); - - var status = dt.data.status.data; - var procs = status.process; - var server = status.server; - - procs.length.should.eql(1); - - var meta = dt.data.status; - should.exists(dt.sent_at); - meta.protected.should.be.false(); - meta.rev_con.should.be.true(); - meta.server_name.should.eql('osef'); - done(); - }); - - it('should get status via PushInteractor and PM2 should be statused as not protected', function(done) { - sock.once('message', function(data) { - var dt = JSON.parse(data); - - dt.public_key.should.eql('osef'); - - var status = dt.data.status.data; - var procs = status.process; - var server = status.server; - - procs.length.should.eql(1); - - var meta = dt.data.status; - - should.exists(dt.sent_at); - meta.protected.should.be.false(); - meta.rev_con.should.be.true(); - meta.server_name.should.eql('osef'); - - done(); - }); - }); - }); - - describe('General behaviors', function() { - it('should receive event application restart', function(done) { - - sock.once('message', function(data) { - var dt = JSON.parse(data); - var monitoring = dt.data.monitoring; - var process_event = dt.data['process:event']; - - //console.log(JSON.stringify(process_event, '', 2)); - done(); - }); - - pm2.restart('all', function() {}); - }); - }); - - describe('PM2 password checking', function() { - it('should set a password', function(done) { - pm2.set('pm2:passwd', 'testpass', function(err, data) { - should.not.exists(err); - setTimeout(done, 1000); - }); - }); - - it('should interactor be notified of password set', function(done) { - sock.once('message', function(data) { - var dt = JSON.parse(data); - // Has switched to true - dt.data.status.protected.should.be.true(); - done(); - }); - }); - }); - - }); - - describe('Offline', function() { - it('should handle offline gracefully', function(done) { - _socket_list.forEach(function(socket, i) { - _socket_list[i].destroy(); - }); - - sock.closeSockets(); - - pub_sock.server.close(function() { - console.log('Server closed'); - }); - setTimeout(done, 500); - }); - }); - - -}); diff --git a/test/interface/interactor.daemonizer.mocha.js b/test/interface/interactor.daemonizer.mocha.js deleted file mode 100644 index 35dba90c8..000000000 --- a/test/interface/interactor.daemonizer.mocha.js +++ /dev/null @@ -1,164 +0,0 @@ - -var should = require('should'); -var fs = require('fs'); -var os = require('os'); -var default_conf = require('../../constants'); -var interactorDaemonizer = require('../../lib/Interactor/InteractorDaemonizer'); -var json5 = require('../../lib/tools/json5.js'); - -describe('Daemonizer interactor', function() { - before(function(done) { - delete process.env.PM2_SECRET_KEY; - delete process.env.PM2_PUBLIC_KEY; - delete process.env.KEYMETRICS_NODE; - - try { - fs.unlinkSync(default_conf.INTERACTION_CONF); - } catch(e) {} - done(); - }); - - describe('General tests', function() { - it('should try get set keys but get error because nothing exposed', function(done) { - interactorDaemonizer.getOrSetConf(default_conf, null, function(err, data) { - err.should.not.be.null(); - done(); - }); - }); - }); - - describe('Default behavior', function() { - after(function() { - fs.unlinkSync(default_conf.INTERACTION_CONF); - }); - - it('should set right node by default', function(done) { - interactorDaemonizer.getOrSetConf(default_conf, { - secret_key : 'xxx', - public_key : 'yyy', - machine_name : null, - info_node : null - }, function(err, data) { - should(err).be.null(); - data.info_node.should.eql(default_conf.KEYMETRICS_ROOT_URL); - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.info_node.should.eql(default_conf.KEYMETRICS_ROOT_URL); - - return done(); - }); - }); - - it('should retrieve data from file without env variable', function(done) { - interactorDaemonizer.getOrSetConf(default_conf, null, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('xxx'); - data.public_key.should.eql('yyy'); - data.info_node.should.eql(default_conf.KEYMETRICS_ROOT_URL); - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.info_node.should.eql(default_conf.KEYMETRICS_ROOT_URL); - - return done(); - }); - }); - - it('should set new keys and write in configuration file', function(done) { - interactorDaemonizer.getOrSetConf(default_conf, { - secret_key : 'XXXS2', - public_key : 'XXXP2', - info_node : 'test2.url' - }, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS2'); - data.public_key.should.eql('XXXP2'); - data.info_node.should.eql('test2.url'); - - var interaction_conf = json5.parse(fs.readFileSync(default_conf.INTERACTION_CONF)); - interaction_conf.secret_key.should.eql('XXXS2'); - interaction_conf.public_key.should.eql('XXXP2'); - interaction_conf.info_node.should.eql('test2.url'); - - should.exist(interaction_conf.version_management.active); - should(interaction_conf.version_management.password).be.null(); - - interaction_conf.machine_name.should.startWith(os.hostname()); - return done(); - }); - }); - - it('should retrieve data from file without env variable', function(done) { - interactorDaemonizer.getOrSetConf(default_conf, null, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS2'); - data.public_key.should.eql('XXXP2'); - data.info_node.should.eql('test2.url'); - return done(); - }); - }); - - it('should retrieve the same data with null fields', function(done) { - interactorDaemonizer.getOrSetConf(default_conf, { - secret_key : null, - public_key : null, - machine_name : null, - info_node : null - }, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS2'); - data.public_key.should.eql('XXXP2'); - data.info_node.should.eql('test2.url'); - return done(); - }); - }); - - }); - - describe('Environment variable override', function() { - before(function() { - process.env.PM2_SECRET_KEY = 'XXXS'; - process.env.PM2_PUBLIC_KEY = 'XXXP'; - process.env.KEYMETRICS_NODE = 'test.url'; - }); - - after(function() { - delete process.env.PM2_SECRET_KEY; - delete process.env.PM2_PUBLIC_KEY; - delete process.env.KEYMETRICS_NODE; - }); - - it('should work with env variables and create file', function(done) { - - interactorDaemonizer.getOrSetConf(default_conf, { - secret_key : null, - public_key : null, - machine_name : null, - info_node : null - }, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS'); - data.public_key.should.eql('XXXP'); - data.info_node.should.eql('test.url'); - - should.exist(data.version_management.active); - should(data.version_management.password).be.null(); - try { - fs.statSync(default_conf.INTERACTION_CONF); - } catch(e) { - return done(e); - } - return done(); - }); - }); - - it('should retrieve data from file without env variable', function(done) { - interactorDaemonizer.getOrSetConf(default_conf, null, function(err, data) { - should(err).be.null(); - data.secret_key.should.eql('XXXS'); - data.public_key.should.eql('XXXP'); - data.info_node.should.eql('test.url'); - return done(); - }); - }); - }); -}); diff --git a/test/interface/misc/trace.json b/test/interface/misc/trace.json deleted file mode 100644 index 962497e64..000000000 --- a/test/interface/misc/trace.json +++ /dev/null @@ -1,167 +0,0 @@ -[{ - "projectId": 0, - "traceId": "43fd648369374111b4ee56565c7cecb2", - "spans": [ - { - "name": "/api/bucket", - "parentSpanId": "0", - "spanId": 36, - "kind": "RPC_SERVER", - "labels": { - "http/method": "OPTIONS", - "http/url": "http://cl1.km.io/api/bucket", - "http/source/ip": "::ffff:127.0.0.1", - "http/status_code": "204" - }, - "startTime": "2016-11-13T16:55:51.677Z", - "endTime": "2016-11-13T16:55:51.680Z" - }, - { - "name": "redis-set", - "parentSpanId": 36, - "spanId": 37, - "kind": "RPC_CLIENT", - "labels": { - "command": "set", - "arguments": "[\"sess:rYrMvzCAwhlXrIOp8swjKxvIYh1UN5EF\",\"{\\\"cookie\\\":{\\\"originalMaxAge\\\":null,\\\"expires\\\":null,\\\"secure\\\":false,\\\"httpOnly\\\":true,\\\"domain\\\":\\\".km.io\\\",\\\"path\\\":\\\"/\\\"},\\\"passport\\\":{}}\",\"EX\",120]", - "result": "OK" - }, - "startTime": "2016-11-13T16:55:51.678Z", - "endTime": "2016-11-13T16:55:51.678Z" - } - ] -},{ - "projectId": 0, - "traceId": "43887a4ff20c44b990c8ec6540440690", - "spans": [ - { - "name": "/api/bucket", - "parentSpanId": "0", - "spanId": 38, - "kind": "RPC_SERVER", - "labels": { - "http/method": "GET", - "http/url": "http://cl1.km.io/api/bucket", - "http/source/ip": "::ffff:127.0.0.1", - "express/request.route.path": "/", - "http/status_code": "304" - }, - "startTime": "2016-11-13T16:55:51.779Z", - "endTime": "2016-11-13T16:55:51.829Z" - }, - { - "name": "redis-get", - "parentSpanId": 38, - "spanId": 39, - "kind": "RPC_CLIENT", - "labels": { - "command": "get", - "arguments": "[\"sess:As-xHUEPTRSvi9lrK8j3gWO_mxnl7Llk\"]", - "result": "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"secure\":false,\"httpOnly\":true,\"domain\":\".km.io\",\"path\":\"/\"},\"passport\":{}}" - }, - "startTime": "2016-11-13T16:55:51.781Z", - "endTime": "2016-11-13T16:55:51.782Z" - }, - { - "name": "mongo-cursor", - "parentSpanId": 38, - "spanId": 40, - "kind": "RPC_CLIENT", - "labels": { - "db": "devdb6.tokens", - "cmd": "{\"find\":\"devdb6.tokens\",\"limit\":-1,\"skip\":0,\"query\":{\"type\":\"access_token\",\"token\":\"u009vf00u9cdyf8yyzzhpkxrrhq07euhfd3p106mfgbbqq3icfd9katq7oz2oe6bwqxhuzhl9uzkquq8mgnz0k80oryr7i4ym2litx317uakb8dcm9y5irqi7l2f5e81\"},\"slaveOk\":false,\"batchSize\":1}", - "results": "{_id:{_bsontype:ObjectID,id:X(ÂÓð†\f#\u0002í\\},token:u009vf00u9cdyf8yyzzhpkxrrhq07euhfd3p106mfgbbqq3icfd9katq7oz2oe6bwqxhuz..." - }, - "startTime": "2016-11-13T16:55:51.783Z", - "endTime": "2016-11-13T16:55:51.786Z" - }, - { - "name": "mongo-cursor", - "parentSpanId": 38, - "spanId": 41, - "kind": "RPC_CLIENT", - "labels": { - "db": "devdb6.users", - "cmd": "{\"find\":\"devdb6.users\",\"limit\":0,\"skip\":0,\"query\":{\"_id\":{\"$in\":[\"57f26987af49d57472b3d104\"]}},\"slaveOk\":false}", - "results": "{_id:{_bsontype:ObjectID,id:Wòi‡¯IÕtr³Ñ\u0004},short_id:k3l0x,auth_token:iuub9912b8ruh6h7dgoi,username:alexandre,email:alex..." - }, - "startTime": "2016-11-13T16:55:51.787Z", - "endTime": "2016-11-13T16:55:51.789Z" - }, - { - "name": "mongo-cursor", - "parentSpanId": 38, - "spanId": 42, - "kind": "RPC_CLIENT", - "labels": { - "db": "devdb6.clients", - "cmd": "{\"find\":\"devdb6.clients\",\"limit\":0,\"skip\":0,\"query\":{\"_id\":{\"$in\":[\"57fe6588ffc3dd335c78b7e0\"]}},\"slaveOk\":false}", - "results": "{_id:{_bsontype:ObjectID,id:WþeÿÃÝ3\\x·à},name:Keymetrics Dashboard,clientID:5413907556,clientSecret:2393878333,autho..." - }, - "startTime": "2016-11-13T16:55:51.788Z", - "endTime": "2016-11-13T16:55:51.794Z" - }, - { - "name": "mongo-cursor", - "parentSpanId": 38, - "spanId": 43, - "kind": "RPC_CLIENT", - "labels": { - "db": "devdb6.users", - "cmd": "{\"find\":\"devdb6.users\",\"limit\":0,\"skip\":0,\"query\":{\"_id\":{\"$in\":[\"57f26987af49d57472b3d104\"]}},\"slaveOk\":false}" - }, - "startTime": "2016-11-13T16:55:51.789Z", - "endTime": "2016-11-13T16:55:51.790Z" - }, - { - "name": "mongo-cursor", - "parentSpanId": 38, - "spanId": 44, - "kind": "RPC_CLIENT", - "labels": { - "db": "devdb6.buckets", - "cmd": "{\"find\":\"devdb6.buckets\",\"limit\":0,\"skip\":0,\"query\":{\"_id\":{\"$in\":[\"57f269abaf49d57472b3d109\",\"581e81e03f91d96e46eb0f65\",\"581e831316d4875e482dd174\",\"581e836716d4875e482dd175\",\"581e83f316d4875e482dd176\",\"581e840c4a4ade7c49389b1e\",\"581e843e8c08e8b9494604a3\",\"581e84a20f148e364b5331f3\",\"581e859e0b5bce564d4b246e\",\"581e86a7eb307d47504fe25f\",\"581e86f9eb307d47504fe260\"]}},\"slaveOk\":false}", - "results": "{_id:{_bsontype:ObjectID,id:Wòi«¯IÕtr³Ñ\t},secret_id:mcp6snmnon1asmt,public_id:mxfiwlvzracelhl,node_cache:{_id:{_bsonty..." - }, - "startTime": "2016-11-13T16:55:51.792Z", - "endTime": "2016-11-13T16:55:51.794Z" - }, - { - "name": "mongo-cursor", - "parentSpanId": 38, - "spanId": 45, - "kind": "RPC_CLIENT", - "labels": { - "db": "devdb6.buckets", - "cmd": "{\"find\":\"devdb6.buckets\",\"limit\":0,\"skip\":0,\"query\":{\"_id\":{\"$in\":[\"57f269abaf49d57472b3d109\",\"581e81e03f91d96e46eb0f65\",\"581e831316d4875e482dd174\",\"581e836716d4875e482dd175\",\"581e83f316d4875e482dd176\",\"581e840c4a4ade7c49389b1e\",\"581e843e8c08e8b9494604a3\",\"581e84a20f148e364b5331f3\",\"581e859e0b5bce564d4b246e\",\"581e86a7eb307d47504fe25f\",\"581e86f9eb307d47504fe260\"]}},\"slaveOk\":false}" - }, - "startTime": "2016-11-13T16:55:51.794Z", - "endTime": "2016-11-13T16:55:51.794Z" - }, - { - "name": "mongo-cursor", - "parentSpanId": 38, - "spanId": 46, - "kind": "RPC_CLIENT", - "labels": { - "db": "devdb6.clients", - "cmd": "{\"find\":\"devdb6.clients\",\"limit\":0,\"skip\":0,\"query\":{\"_id\":{\"$in\":[\"57fe6588ffc3dd335c78b7e0\"]}},\"slaveOk\":false}" - }, - "startTime": "2016-11-13T16:55:51.794Z", - "endTime": "2016-11-13T16:55:51.795Z" - }, - { - "name": "redis-expire", - "parentSpanId": 38, - "spanId": 47, - "kind": "RPC_CLIENT", - "labels": { - "command": "expire", - "arguments": "[\"sess:As-xHUEPTRSvi9lrK8j3gWO_mxnl7Llk\",120]", - "result": "1" - }, - "startTime": "2016-11-13T16:55:51.827Z", - "endTime": "2016-11-13T16:55:51.829Z" - } - ] -}] diff --git a/test/interface/misc/trace_factory.js b/test/interface/misc/trace_factory.js deleted file mode 100644 index b2ee85b55..000000000 --- a/test/interface/misc/trace_factory.js +++ /dev/null @@ -1,146 +0,0 @@ - -var crypto = require('crypto'); -var moment = require('moment'); -var path = require('path'); -var WEBSITE_ROOT = 'http://toto.com'; -var spanId = 0; - -var random_routes = [ - '/api/bucket', - '/api/bucket/users', - '/api/bucket/chameau', - '/backo/testo' -]; - -function getRandomInt(min, max) { - min = Math.ceil(min); - return Math.floor(Math.random() * (Math.floor(max) - min)) + min; -} - -/** - * Generate Trace - * @param {String} route_path route name, default to random route name - * @param {Integer} db_query_nb number of spans, default to random number (0-10) - */ -function generateTrace(route_path, db_query_nb) { - if (!db_query_nb) - db_query_nb = getRandomInt(2, 5); - if (!route_path) - route_path = random_routes[getRandomInt(0, random_routes.length - 1)]; - var parentSpanId = ++spanId; - - var timeframe = []; - - var trace = { - projectId : 0, - traceId : crypto.randomBytes(32).toString('hex'), - spans : [{ - "name": route_path, - "parentSpanId": "0", - "spanId": parentSpanId, - "kind": "RPC_SERVER", - "labels": { - "http/method": "GET", - "http/path": route_path, - "http/url": WEBSITE_ROOT + route_path, - "http/source/ip": "::ffff:127.0.0.1", - "http/status_code": "204" - }, - "startTime": moment().subtract(db_query_nb + 1, 'seconds').toISOString(), - "endTime": moment().toISOString() - }] - }; - - for (var i = 0; i < db_query_nb; i++) { - trace.spans[i + 1] = { - "name": "mongo-cursor", - "parentSpanId": parentSpanId, - "spanId": ++spanId, - "kind": "RPC_CLIENT", - "labels": { - "db": "devdb6.tokens", - "cmd": "{\"find\":\"devdb6.tokens\",\"limit\":-1,\"skip\":0,\"query\":{\"type\":\"access_token\",\"token\":\"u00i7l2f5e81\"},\"slaveOk\":false,\"batchSize\":1}", - "results": "{_id:{_bsontype:ObjectID,id:X(Â\\},token:u009vf00..." - }, - "startTime": moment().subtract(db_query_nb - i + 1, 'seconds').toISOString(), - "endTime": moment().subtract(db_query_nb - i, 'seconds').toISOString() - }; - } - - return trace; -} - -exports.generateTrace = generateTrace; - -// Generate the same kind of data sent by pm2 -exports.generatePacket = function(route, app_name) { - return { - data : generateTrace(route), - process : { - name : app_name, - pm_id : 4, - server : 'test', - rev : 'xxx' - } - }; -}; - -exports.staticTrace = { - "spans": [ - { - "name":"/auth/signin", - "parentSpanId":"0", - "spanId":9, - "kind":"RPC_SERVER", - "labels":{ - "http/method":"POST", - "http/path":"/auth/signin", - "express/request.route.path":"/signin", - "http/status_code":"200" - }, - "startTime":"2016-11-11T14:03:18.449Z", - "endTime":"2016-11-11T14:03:18.792Z" - }, - { - "name":"mysql-query", - "parentSpanId": 9, - "spanId": 10, - "kind":"RPC_CLIENT", - "labels": { - "sql":"SELECT * FROM users WHERE mail = ?", - "values":"XXXXX", - "result":"XXXX" - }, - "startTime":"2016-11-11T14:03:18.558Z", - "endTime":"2016-11-11T14:03:18.568Z" - } - ] -}; - -exports.stacktrace = { - stack_frame: [ - { - file_name: 'events.js', - line_number: 10, - column_number: 10, - method_name: '' - }, - { - file_name: 'node_modules/express.js', - line_number: 10, - column_number: 10, - method_name: '' - }, - { - file_name: path.resolve(__dirname, 'trace_factory.js'), - line_number: 10, - column_number: 10, - method_name: '' - } - ] -} - - -if (require.main === module) { - console.log(generateTrace()); -} diff --git a/test/interface/monitor.mocha.js b/test/interface/monitor.mocha.js deleted file mode 100644 index 9baedf9c8..000000000 --- a/test/interface/monitor.mocha.js +++ /dev/null @@ -1,90 +0,0 @@ -/* eslint-env mocha */ - -process.env.NODE_ENV='test'; - -'use strict'; - -var pm2 = require('../../index.js'); -var async = require('async'); -var assert = require('assert'); -var path = require('path'); -var PushInteractor = require('../../lib/Interactor/PushInteractor.js'); - -describe('unmonitor process', function () { - before(function (done) { - pm2.connect(function (err) { - if (err) return done(err); - pm2.delete('all', function () { - return done(); - }); - }); - }); - - after(function (done) { - pm2.delete('all', function (_) { - return done(); - }); - }); - - it('should start some processes', function (done) { - async.times(3, function (n, next) { - pm2.start({ - script: path.resolve(__dirname, '../fixtures/empty.js'), - name: 'test-' + n - }, next); - }, done); - }); - - it('should have 3 processes started', function (done) { - pm2.list(function (err, processes) { - assert(err === null); - assert(processes.length === 3); - return done(err); - }); - }); - - it('should start the push interactor', function (done) { - PushInteractor.start({ - url: 'toto', - conf: { - ipm2: require('../../lib/Interactor/pm2-interface.js')() - } - }); - return setTimeout(done, 100); - }); - - it('should return three processes with interactor', function (done) { - PushInteractor.preparePacket(function (err, data) { - if (err) return done(err); - - assert(data.process.length === 3); - return done(); - }); - }); - - it('should run the unmonitor command', function (done) { - pm2.monitorState('unmonitor', '0', done); - }); - - it('should return two processes with interactor', function (done) { - PushInteractor.preparePacket(function (err, data) { - if (err) return done(err); - - assert(data.process.length === 2); - return done(); - }); - }); - - it('should run the unmonitor command', function (done) { - pm2.monitorState('monitor', '0', done); - }); - - it('should return three processes with interactor', function (done) { - PushInteractor.preparePacket(function (err, data) { - if (err) return done(err); - - assert(data.process.length === 3); - return done(); - }); - }); -}); diff --git a/test/interface/password.mocha.js b/test/interface/password.mocha.js deleted file mode 100644 index 68772fd5f..000000000 --- a/test/interface/password.mocha.js +++ /dev/null @@ -1,20 +0,0 @@ - -var Password = require('../../lib/Interactor/Password.js'); -var should = require('should'); - -describe('Password test', function() { - var crypted = ''; - - it('should crypt a password', function() { - crypted = Password.generate('testpass'); - }); - - it('should fail with wrong password', function() { - Password.verify('testpasds', crypted).should.be.false; - }); - - it('should success with right password', function() { - Password.verify('testpass', crypted).should.be.true; - }); - -}); diff --git a/test/interface/push_interactor.mocha.js b/test/interface/push_interactor.mocha.js deleted file mode 100644 index 032b3a97a..000000000 --- a/test/interface/push_interactor.mocha.js +++ /dev/null @@ -1,78 +0,0 @@ - -process.env.DEBUG='interface:push-interactor'; -process.env.NODE_ENV = 'local_test'; -process.env.PM2_PUBLIC_KEY = 'xxxx'; -process.env.PM2_SECRET_KEY = 'yyyy'; -process.env.PM2_REVERSE_INTERACT = true; -process.env.PM2_MACHINE_NAME = 'xmachine'; -process.env.KM_URL_REFRESH_RATE = 1000; - -var InterfaceD = require('../../lib/Interactor/Daemon.js'); -var Helpers = require('../helpers/apps.js'); -var axon = require('pm2-axon'); - -var pm2; - -var sock; - -function listen(cb) { - sock = axon.socket('sub'); - sock.bind(8080, cb); -} - -function listenRev(cb) { - var listener_server = require('nssocket').createServer(function(_socket) { - }); - - listener_server.listen(4322, '0.0.0.0', cb); -} - -describe('Programmatically test interactor', function() { - before(function(done) { - Helpers.forkPM2(function(err, _pm2) { - listen(function() { - listenRev(function() { - pm2 = _pm2; - done(); - }); - }); - }); - }); - - after(function(done) { - pm2.on('exit', done); - pm2.kill(); - }); - - it('should start Daemon', function(done) { - InterfaceD.start(); - setTimeout(done, 2000); - }); - - it('should receive a message', function(done) { - sock.once('message', function(data) { - data = JSON.parse(data); - done(); - }); - }); - - it('should still receive messages', function(done) { - sock.once('message', function(data) { - done(); - }); - }); - - it('should simulate server restart', function(done) { - sock.close(done); - }); - - it('should recreate connection', function(done) { - listen(done); - }); - - it('should still receive messages', function(done) { - sock.once('message', function(data) { - done(); - }); - }); -}); diff --git a/test/interface/remote.mocha.js b/test/interface/remote.mocha.js deleted file mode 100644 index 87adbcd41..000000000 --- a/test/interface/remote.mocha.js +++ /dev/null @@ -1,225 +0,0 @@ - -process.env.NODE_ENV = 'local_test'; - -var PM2 = require('../..'); -var should = require('should'); -var nssocket = require('nssocket'); -var events = require('events'); -var util = require('util'); -var Cipher = require('../../lib/Interactor/Cipher.js'); -var cst = require('../../constants.js'); - -var send_cmd = new events.EventEmitter(); -var meta_connect = { - secret_key : 'test-secret-key', - public_key : 'test-public-key', - machine_name : 'test-machine-name' -}; - -function createMockServer(cb) { - var server = nssocket.createServer(function(_socket) { - - console.log('Got new connection in Mock server'); - - send_cmd.on('cmd', function(data) { - if (process.env.DEBUG) - console.log('Sending command %j', data); - _socket.send(data._type, data); - }); - - _socket.data('*', function(data) { - this.event.forEach(function(ev) { - send_cmd.emit(ev, data); - }); - }); - - }); - - server.on('error', function(e) { - throw new Error(e); - }); - - server.on('listening', function() { - cb(null, server); - }); - - server.listen(4322, '0.0.0.0'); -} - -function startSomeApps(pm2, cb) { - pm2.start('./child.js', {instances : 4, name : 'child'}, cb); -} - -describe('REMOTE PM2 ACTIONS', function() { - var server; - var interactor; - var pm2 = new PM2.custom({ - independent : true, - cwd : __dirname + '/../fixtures', - secret_key : 'test-secret-key', - public_key : 'test-public-key', - machine_name : 'test-machine-name', - daemon_mode: true - });; - - after(function(done) { - server.close(); - pm2.destroy(done); - }); - - before(function(done) { - createMockServer(function(err, _server) { - console.log('Mock server created'); - server = _server; - pm2.connect(function(err, _pm2) { - startSomeApps(pm2, function() { - done(); - }); - }); - }); - }); - - it('should send ask, receive ask:rep and identify agent', function(done) { - send_cmd.once('ask:rep', function(pck) { - var data = Cipher.decipherMessage(pck.data, meta_connect.secret_key); - data.machine_name.should.eql(meta_connect.machine_name); - done(); - }); - - send_cmd.emit('cmd', { _type : 'ask' }); - }); - - /** - * PM2 agent is now identified - */ - it('should act on PM2', function(done) { - send_cmd.once('trigger:pm2:result', function(pck) { - if (pck.ret.data.length > 0) - done(); - else - done(new Error('wrong data rcvied')); - }); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:action', - method_name : 'restart', - parameters : {name : 'child' } - }); - }); - - it('should act on PM2 but handle failure', function(done) { - send_cmd.once('trigger:pm2:result', function(pck) { - // Error is present telling process does not exists - pck.ret.err.should.not.be.null(); - done(); - }); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:action', - method_name : 'restart', - parameters : {name : 'UNKNOWN APP' } - }); - }); - - it('should RELOAD', function(done) { - send_cmd.once('trigger:pm2:result', function(pck) { - /** - * Once remote command is finished... - */ - - should(pck.ret.err).be.null(); - - pm2.list(function(err, ret) { - ret.forEach(function(proc) { - proc.pm2_env.restart_time.should.eql(2); - }); - }); - - done(); - }); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:action', - method_name : 'reload', - parameters : {name : 'child' } - }); - }); - - it('should RESET metadata', function(done) { - send_cmd.once('trigger:pm2:result', function(pck) { - /** - * Once remote command is finished... - */ - should(pck.ret.err).be.null(); - - pm2.list(function(err, ret) { - ret.forEach(function(proc) { - proc.pm2_env.restart_time.should.eql(0); - }); - }); - - done(); - }); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:action', - method_name : 'reset', - parameters : {name : 'child' } - }); - }); - - it('should delete all processes', function(done) { - pm2.delete('all', {}, function() { - startSomeApps(pm2, function() { - pm2.list(function(err, ret) { - ret.forEach(function(proc) { - proc.pm2_env.restart_time.should.eql(0); - }); - done(); - }); - }); - }); - }); - - it('should test .remote', function(done) { - pm2.remote('restart', { - name : 'child' - }, function(err, procs) { - - pm2.list(function(err, ret) { - ret.forEach(function(proc) { - proc.pm2_env.restart_time.should.eql(1); - }); - done(); - }); - }); - }); - - it('should test .remote and handle failure', function(done) { - pm2.remote('restart', { - name : 'UNKNOWN_NAME' - }, function(err, procs) { - pm2.list(function(err, ret) { - ret.forEach(function(proc) { - proc.pm2_env.restart_time.should.eql(1); - }); - done(); - }); - }); - }); - - it('should test .remote #2', function(done) { - pm2.remote('reload', { - name : 'child' - }, function(err, procs) { - - pm2.list(function(err, ret) { - ret.forEach(function(proc) { - proc.pm2_env.restart_time.should.eql(2); - }); - done(); - }); - }); - }); - -}); diff --git a/test/interface/request.mocha.js b/test/interface/request.mocha.js deleted file mode 100644 index eaeb9d0e4..000000000 --- a/test/interface/request.mocha.js +++ /dev/null @@ -1,91 +0,0 @@ - -process.env.DEBUG="interface:*"; - -var should = require('should'); -var assert = require('assert'); -var HttpRequest = require('../../lib/Interactor/HttpRequest.js'); - -var PORT = 8080; - -function mockIrritableServer(cb) { - var http = require('http'); - var url = require('url'); - - function handleRequest(req, res) { - var uri = url.parse(req.url).pathname; - - if (uri == '/api/node/verifyPM2') { - // res.writeHead(505, {"Content-Type": "text/json"}); - // return res.end(new Buffer(50).fill('h')); - // } - // console.log(uri); - return false; - } - if (uri == '/api/misc/pm2_version') { - res.writeHead(505); - return res.end(); - } - } - - //Create a server - var server = http.createServer(handleRequest); - - //Lets start our server - server.listen(PORT, function(err){ - if (err) console.error(err); - cb(null, server); - }); -} - -describe('Http requests', function() { - var _server = null; - - before(function(done) { - mockIrritableServer(function(err, server) { - _server = server; - done(); - }); - }); - - after(function(done) { - _server.close(done); - }); - - describe('POST', function() { - it('should post to 404 URL', function(done) { - HttpRequest.post({ - port : 9999, - url : 'http://keymetrics.io/NOTHING', - data : { no : 'thing' } - }, function(err, data) { - assert(err); - assert(err.code == 'ENOTFOUND'); - assert(data == null); - done(); - }) - }); - - it('should timeout after 7secs', function(done) { - HttpRequest.post({ - port : PORT, - url : '127.0.0.1', - data : { no : 'thing' } - }, function(err, data) { - assert(err); - assert(err.code == 'ECONNRESET'); - assert(data == null); - done(); - }); - }); - - }); - - // @todo: more behavioral tests (reverse interactor failcheck) - - // @todo: do more tests when doing changeUrls - it.skip('should change urls (forcing reconnection)', function(done) { - InterfaceD.changeUrls('app.km.io', 'app.km.io:4322'); - setTimeout(done, 2000); - }); - -}); diff --git a/test/interface/scoped_pm2_actions.mocha.js b/test/interface/scoped_pm2_actions.mocha.js deleted file mode 100644 index ecf837cc0..000000000 --- a/test/interface/scoped_pm2_actions.mocha.js +++ /dev/null @@ -1,258 +0,0 @@ - -var PM2 = require('../..'); -var should = require('should'); -var nssocket = require('nssocket'); -var events = require('events'); -var util = require('util'); -var Cipher = require('../../lib/Interactor/Cipher.js'); -var cst = require('../../constants.js'); -var Plan = require('../helpers/plan.js'); -var Configuration = require('../../lib/Configuration.js'); -var Helpers = require('../helpers/apps.js'); -var Interactor = require('../../lib/Interactor/InteractorDaemonizer.js'); -var gl_interactor_process; - -var send_cmd = new events.EventEmitter(); - -process.env.NODE_ENV = 'local_test'; - -var meta_connect = { - secret_key : 'test-secret-key', - public_key : 'test-public-key', - machine_name : 'test-machine-name' -}; - -/** - * Description - * @method forkInteractor - * @return CallExpression - */ -function forkInteractor(cb) { - Interactor.launchAndInteract(meta_connect, function(err, data, interactor_process) { - gl_interactor_process = interactor_process; - cb(); - }); -} - -/** - * Mock server receiving data - * @method forkInteractor - * @return CallExpression - */ -function createMockServer(cb) { - var server = nssocket.createServer(function(_socket) { - - send_cmd.on('cmd', function(data) { - if (process.env.DEBUG) - console.log('Sending command %j', data); - _socket.send(data._type, data); - }); - - _socket.data('*', function(data) { - this.event.forEach(function(ev) { - send_cmd.emit(ev, data); - }); - }); - - }); - - server.on('error', function(e) { - throw new Error(e); - }); - - server.on('listening', function() { - cb(null, server); - }); - - server.listen(4322, '0.0.0.0'); -} - -function startSomeApps(cb) { - pm2.start('./child.js', {instances : 1, name : 'child'}, cb); -} - -var pm2 = new PM2.custom({ - independent : true, - cwd : __dirname + '/../fixtures', - secret_key : 'test-secret-key', - public_key : 'test-public-key', - machine_name : 'test-machine-name', - daemon_mode: true -}); - -describe('SCOPED PM2 ACTIONS', function() { - var server; - var interactor; - - after(function(done) { - server.close(); - pm2.destroy(done); - }); - - before(function(done) { - createMockServer(function(err, _server) { - server = _server; - pm2.connect(function() { - startSomeApps(function(err) { - gl_interactor_process = pm2.Client.interactor_process; - // @todo: would be nice to know when an app is ready - // @priotity: minor - setTimeout(done, 1500); - }); - }); - }); - }); - - it('should send ask, receive ask:rep and identify agent', function(done) { - send_cmd.once('ask:rep', function(pck) { - var data = Cipher.decipherMessage(pck.data, meta_connect.secret_key); - data.machine_name.should.eql(meta_connect.machine_name); - done(); - }); - - send_cmd.emit('cmd', { _type : 'ask' }); - }); - - /** - * PM2 agent is now identified - */ - describe('Test non auth remote commands', function() { - before(function(done) { - Configuration.unset('pm2:passwd', function(err, data) { - should.not.exists(err); - done(); - }); - }); - - it('should restart command via scoped pm2 action (no pass needed)', function(done) { - var good = false; - var plan = new Plan(2, function() { - gl_interactor_process.removeListener('message', actionCheck); - good = true; - done(); - }); - - function actionCheck(pck) { - if (good) return false; - if (pck.event == 'pm2:scoped:stream' && pck.data.out === 'Action restart received') - return plan.ok(true); - if (pck.event == 'pm2:scoped:end') - return plan.ok(true); - if (pck.event == 'pm2:scoped:error') - return plan.ok(false, pck); - return false; - } - - gl_interactor_process.on('message', actionCheck); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:scoped:action', - action_name : 'restart', - uuid : '1234', - options : { args : ['child'] } - }); - - }); - - }); - - describe('Password verification', function() { - - before(function(done) { - Configuration.unset('pm2:passwd', function(err, data) { - should.not.exists(err); - done(); - }); - }); - - it('should error when call an action that is password protected', function(done) { - function actionCheck(pck) { - if (pck.event == 'pm2:scoped:error' && pck.data.out.indexOf('Missing password') > -1) { - gl_interactor_process.removeListener('message', actionCheck); - done(); - } - }; - - gl_interactor_process.on('message', actionCheck); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:scoped:action', - action_name : 'install', - uuid : '5678', - options : { args : ['child'] } - }); - }); - - it('should fail when password passed but no pm2 password configured', function(done) { - function actionCheck(pck) { - if (pck.event == 'pm2:scoped:error' && pck.data.out.indexOf('Password at PM2') > -1) { - gl_interactor_process.removeListener('message', actionCheck); - done(); - } - }; - - gl_interactor_process.on('message', actionCheck); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:scoped:action', - action_name : 'install', - uuid : '5678', - password : 'random-pass', - options : { args : ['pm2-module'] } - }); - }); - - it('should set a password', function(done) { - pm2.set('pm2:passwd', 'testpass', function(err, data) { - should.not.exists(err); - done(); - }); - }); - - it('should fail when wrong password', function(done) { - function actionCheck(pck) { - if (pck.event == 'pm2:scoped:error' && pck.data.out.indexOf('Password does not match') > -1) { - gl_interactor_process.removeListener('message', actionCheck); - setTimeout(done, 100); - } - }; - - gl_interactor_process.on('message', actionCheck); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:scoped:action', - action_name : 'install', - uuid : '5678', - password : 'random-pass', - options : { args : ['pm2-module'] } - }); - }); - - it('should work when good password passed', function(done) { - function actionCheck(pck) { - if (pck.event === 'pm2:scoped:end') { - gl_interactor_process.removeListener('message', actionCheck); - done(); - } - if (pck.event === 'pm2:scoped:error') { - gl_interactor_process.removeListener('message', actionCheck); - done('{ERROR} Wrong password!' + JSON.stringify(pck)); - } - }; - - gl_interactor_process.on('message', actionCheck); - - send_cmd.emit('cmd', { - _type : 'trigger:pm2:scoped:action', - action_name : 'ping', - uuid : '5678', - password : 'testpass', - options : {} - }); - }); - - - }); - - -}); diff --git a/test/interface/stacktrace.mocha.js b/test/interface/stacktrace.mocha.js deleted file mode 100644 index 630f844b9..000000000 --- a/test/interface/stacktrace.mocha.js +++ /dev/null @@ -1,157 +0,0 @@ - -var should = require('should'); -var Aggregator = require('../../lib/Interactor/TransactionAggregator.js'); -var Utility = require('../../lib/Interactor/Utility.js'); -var TraceFactory = require('./misc/trace_factory.js'); -var path = require('path'); -var fs = require('fs'); -var assert = require('assert'); - -describe('StackTrace Utility', function() { - var aggregator; - var stackParser; - - it('should instanciate context cache', function() { - var cache = new Utility.Cache({ - miss: function (key) { - try { - var content = fs.readFileSync(path.resolve(key)); - return content.toString().split(/\r?\n/); - } catch (err) { - return undefined; - } - } - }) - - stackParser = new Utility.StackTraceParser({ cache: cache, context: 2}); - }); - - it('should instanciate aggregator', function() { - aggregator = new Aggregator({ stackParser: stackParser}); - }); - - describe('.parse', function() { - it('should parse stacktrace and get context', function(done) { - var obj = [{ - labels: { - stacktrace: JSON.stringify(TraceFactory.stacktrace) - } - }]; - - aggregator.parseStacktrace(obj); - obj[0].labels['source/file'].indexOf('test/interface/misc/trace_factory.js:10').should.be.above(0); - should(obj[0].labels['source/context']).eql("var random_routes = [\n '/api/bucket',\n>>'/api/bucket/users',\n '/api/bucket/chameau',\n '/backo/testo'"); - done(); - }); - - it('should handle malformated stacktraces', function() { - aggregator.parseStacktrace([{ - labels: { - stacktrace: JSON.stringify({ - stack_frame: [{ - line_number: 10, - column_number: 10, - method_name: '' - }, { - file_name: 'node_modules/express.js', - column_number: 10, - method_name: '' - }, { - file_name: path.resolve(__dirname, 'trace_factory.js'), - line_number: 10, - column_number: 10, - method_name: '' - }] - }) - } - }]); - }); - - it('should handle malformated stacktrace v1', function() { - aggregator.parseStacktrace([{ - labels: { - stacktrace: JSON.stringify({ - stack_frame: [{ - file_name: 'events.js' - },{ - file_name: 'node_modules/express.js' - },{ - file_name: path.resolve(__dirname, 'trace_factory.js') - }] - }) - } - }]); - }); - - it('should handle malformated stacktrace v2', function() { - aggregator.parseStacktrace([{ - labels: { - stacktrace: JSON.stringify({ - stack_frame: [{ - file_name: 'events.js', - column_number: 10, - method_name: '' - },{ - file_name: 'node_modules/express.js', - column_number: 10, - method_name: '' - },{ - file_name: path.resolve(__dirname, 'trace_factory.js'), - line_number: 10, - column_number: 10, - method_name: '' - }] - }) - } - }]); - }); - - it('should handle malformated stacktrace v3', function() { - aggregator.parseStacktrace([{ - labels: {} - }]); - }); - - it('should handle malformated stacktrace v4', function() { - aggregator.parseStacktrace([{ - }]); - }); - - it('should handle malformated stacktrace v5', function() { - aggregator.parseStacktrace([]); - }); - - it('should handle malformated stacktrace v5', function() { - aggregator.parseStacktrace(); - }); - - }); - - describe('.attachContext', function () { - it('should extract context from stackframes', function () { - var error = stackParser.attachContext({ - stackframes: [ - { - file_name: '/toto/tmp/lol', - line_number: 10 - } - ] - }); - assert(error !== undefined); - assert(error.stackframes === undefined); - assert(error.callsite !== undefined); - assert(error.callsite.indexOf('/toto/tmp/lol') >= 0); - }); - - it('should extract context from the stack string', function () { - var error = new Error(); - // stack is lazy so we need to load it - error.stack = error.stack; - error = stackParser.attachContext(error); - assert(error !== undefined); - assert(error.stackframes === undefined); - assert(error.callsite.indexOf(__filename) >= 0); - assert(error.context.indexOf('var error = new Error()') >= 0); - }); - }); -});