UNPKG

node-moog

Version:

Publish events to the MOOG AIOps REST LAM

366 lines (337 loc) 13.2 kB
/** * Created by Spike on 14/10/2014. * Updated by Stephen on 16/10/2014. * Updated by Stephen on 29/12/2015. * Updated by Stephen on 18/1/2017. * Updated by Stephen on 14/2/2017. * * Updated for 3.1.2 with Client SSL * Updated for 5.0.7 with basic auth [npm 1.1.0] * Update 1.2.5 add agent based proxy support */ /** * @file node-moog.js * * @fileOverview This is the module to use for constructing and sending events * to the Incident.MOOG REST Link Access Module (LAM) ready for processing and * clustering. * * @example * // require * var moog = require('node-moog'); * // returns a new event object with defaults * var moogEvent = new moog.MoogEvent(); * // return a moogREST connection * var options = {'url': 'http://testmoog:8881'}; * var moogREST = new moog.moogREST(options); * * @requires url * @requires util * @requires events * @requires http * @requires https * @requires fs * */ var urlParser = require('url'); var util = require("util"); var events = require("events"); var http = require('http'); var https = require('https'); var fs = require('fs'); /** * @description maxSockets=20 * @type {number} */ http.globalAgent.maxSockets = 20; /** * @typedef moogEvent * @description An Event structured to be read by the Incident.MOOG REST LAM * @type {object} * @property {string} signature - the id used to correlate events into a single alert. * @property {string} source - synonym for host, the main entity that is understood to be a logical component in the * system. * @property {string} source_id - an external identifier for the source entity. * @property {string} external_id - an external identifier for the specific event (event UID). * @property {string} manager - the logical name of the manager that transmits the event. * @property {string} source - the actual component or sub component that produced the event. * @property {string} class - an arbitrary classification for the event, to logically group it with other events of the * same class. * @property {string} agent_location - if the event is generated by an agent then the source entity that the agent * process executes within. * @property {string} type - an arbitrary type for the event, to logically group it with other events of the same type. * @property {number} severity - the severity from 0-5, 0-lowest 5-highest. * @property {string} description - the long form message from the event used as the human readable description. * @property {number} first_occurred - the date and time the event first occurred, in epoc seconds. * @property {string} agent_time - the date and time the agent identified the event, in epoc seconds. */ /** * Generic Debug logging function * @function * */ var debug = function () { if (!process.env.DEBUG) { return; } var stack = new Error().stack; var args = Array.prototype.slice.call(arguments); var lines = stack.split('\n'); var callee = lines[2].match(/at .* /i); util.log('[node-moog.js] ' + callee + ' -> ' + args); }; /** * Create a moogEvent to be passed to the Incident.MOOG REST LAM * * @param {moogEvent} [dEvent] - Default values to use in the constructor * @returns {Object} * @constructor */ exports.MoogEvent = function MoogEvent(dEvent) { 'use strict'; var defEvent = dEvent || {}; if (!(this instanceof MoogEvent)) { return new MoogEvent(dEvent); } this.signature = ""; this.source_id = (typeof defEvent.source_id === "undefined" || typeof defEvent.source_id !== "string") ? "NodePID-" + process.pid : defEvent.source_id; this.external_id = ""; this.manager = defEvent.manager || "node-moog"; this.source = defEvent.source || "NodePID-" + process.pid; this.class = defEvent.class || "NodePlatform-" + process.platform; this.agent_location = defEvent.agent_location || process.argv[1]; this.type = defEvent.type || "NodeRest"; this.severity = 0; this.description = ""; this.first_occurred = 0; this.agent_time = 0; this.custom_info = {}; }; /** * @typedef options * @description The connection options used to construct the http or https connection * @type {object} * @property {object} [connection] - a connection object. * @property {string} url - the to the Incident.MOOG REST endpoint including the port and protocol e.g. * 'http://moogserver:8888'. * @property {string} [auth_token='my_secret'] - the shared secret with Incident.MOOG. * @property {string} [certFile='./ssl/server.crt'] - the path to the server certificate file. * @property {string} [cafile='./ssl/client.crt'] - the path to the client certificate file. * @property {boolean} [secure=false] - set the connection certificate checking. * @property {string} [authUser] - User name for basic auth * @property {string} [authPass] - Password for basic auth * * @typedef eventHeaders * @description The default http headers. * @type {object} * @property {string} [Accept='application/json'] - must be json. * @property {string} [Content-Type='application/json'] - must be jason. * @property {string} [Connection='keep-alive'] - should be keep-alive. * @property {string} [Authorization=options.authUser + options authPass] - passed in options * * @typedef eventRequestOpts * @description The connection options (derived from the url). * @type {object} * @property {string} host - from the url. * @property {number} port=8888 - from the url or 8888. * @property {string} [method='POST'] - always POST. */ /** * Setup a connection to the Incident.MOOG REST endpoint and provide a method to send * events to that endpoint * * @param {Object} options - The Options used in setting up the connection to Incident.MOOG * @constructor */ exports.moogREST = function moogREST(options) { 'use strict'; if ((this instanceof moogREST)) { util.log("ERROR Don't use new for moogREST"); return; } var self = this; var proto = {}; if (!options.url) { util.log('ERROR No URL specified in options object'); return; } /** * * @param {string} type Either 'ca' or 'cert' * @param {string} file The filename to load * @returns {boolean} True file loaded, False file error */ self.setSSLOption = function (type, file) { if (!fs.existsSync(file)) { util.log("ERROR: https specified but can't read :" + file); return false; } try { debug('Loading ' + file); self.eventRequestOpts[type] = fs.readFileSync(file); } catch (e) { util.log("ERROR: Could not read file " + file + " : " + e); return false; } return true; }; self.options = options || {}; self.connection = options.connection; self.auth_token = options.auth_token || 'my_secret'; self.certFile = options.certFile; self.keyFile = options.keyFile; self.caFile = options.caFile; self.secure = options.rejectUnauthorized || false; self.url = urlParser.parse(options.url); debug('URL: ' + util.inspect(self.url)); self.port = options.port; self.agent = options.agent; if (options.authUser && options.authPass) { self.auth = 'Basic ' + new Buffer(options.authUser + ':' + options.authPass).toString('base64'); } self.eventHeaders = { Accept: 'application/json', 'Content-Type': 'application/json', Connection: 'keep-alive' }; if (options.authUser && options.authPass) { self.eventHeaders.Authorization = self.auth; } debug('MoogRest init ' + JSON.stringify(self)); if (!self.url.host) { util.log('WARNING: No Host defined - using localhost'); self.url.host = 'localhost'; } if (!self.url.port) { debug('INFO: No port defined in URL.'); self.url.port = self.port; } if (!self.url.protocol) { util.log('WARNING: No protocol defined - using https:'); self.url.protocol = 'https:'; } self.eventRequestOpts = { host: self.url.hostname, method: 'POST' }; if (self.agent) { self.eventRequestOpts.agent = self.agent; } if (self.url.port) { self.eventRequestOpts.port = self.url.port; } if (self.url.path) { self.eventRequestOpts.path = self.url.path; } if (self.url.protocol === 'https:') { proto = https; debug('Connect using https'); if (self.certFile) { this.setSSLOption('cert', self.certFile); } if (self.caFile) { this.setSSLOption('ca', self.caFile); } if (self.keyFile) { this.setSSLOption('key', self.keyFile); } self.eventRequestOpts.rejectUnauthorized = self.secure; // Need an agent as globalAgent will silently ignore the options //self.eventRequestOpts.agent = new https.Agent({ keepAlive: true }); } else { proto = http; debug('Connect using http'); self.eventRequestOpts.rejectUnauthorized = false; // Need an agent as globalAgent will silently ignore the options debug('eventRequestOps: ' + util.inspect(self.eventRequestOpts)); //self.eventRequestOpts.agent = new http.Agent({ keepAlive: true }); } /** * Send the event or an array of events to Incident.MOOG REST LAM * @param {Object} mEvent - A single event object of an array of event objects to send * @param callback - Callback passed by module */ self.sendEvent = function (mEvent, callback) { // Parse the data we're going to add. var myEvent = mEvent; var epochDate = Math.round(Date.now() / 1000); var num = myEvent.external_id++ || 0; var event = {}; var reqOpts = {}; var eventRequest = {}; var eventString = ''; var contentLength; // Dummy a 400 for internal validation errors // var errres = {statusCode: 400, statusMessage: 'Bad Request'}; if (myEvent instanceof Array) { event.events = myEvent; debug('Event array length ' + event.events.length); } else { if (!(mEvent instanceof self.MoogEvent)) { callback(errres, 'Non MoogEvent passed, not sent. ' + util.inspect(mEvent)); return; } myEvent.signature = mEvent.signature || mEvent.source + ":" + mEvent.class + ":" + mEvent.type; myEvent.external_id = mEvent.external_id || "REST" + num; myEvent.source = mEvent.source || "NodeRest-" + num; myEvent.severity = mEvent.severity || 2; if (mEvent.severity === 0) { myEvent.severity = 0; } myEvent.description = mEvent.description || "No Description Provided"; myEvent.first_occurred = mEvent.first_occurred || epochDate; myEvent.agent_time = mEvent.agent_time || epochDate; myEvent.manager = mEvent.manager || "node-moog"; myEvent.custom_info = mEvent.custom_info || {}; event.events = [myEvent]; } event.auth_token = self.auth_token; try { eventString = JSON.stringify(event); debug('Event to send ' + eventString); } catch (e) { callback(errres, "Error: Could not JSON.stringify the event - " + e); return; } contentLength = Buffer.byteLength(eventString, 'utf8'); self.eventHeaders['Content-Length'] = contentLength; self.eventRequestOpts.headers = self.eventHeaders; reqOpts = self.eventRequestOpts; debug('Request Options: ' + util.inspect(reqOpts)); eventRequest = proto.request(reqOpts, function (req) { var returnString = ""; var returnStatus = 0; req.on('data', function (d) { debug('sendEvent returned ' + util.inspect(returnString)); returnString += d; }); req.on('end', function () { returnStatus = req.statusCode; debug('sendEvent end ' + util.inspect(returnStatus)); if (returnString.search("Login.php") >= 0) { req.statusCode = 404; req.statusMessage = 'Not Found'; returnString = 'The page returned is the default login, please check the endpoint path.'; } callback(req, returnString); }); }); eventRequest.on('error', function (err) { debug("ERROR Can't send " + err.stack); debug("Connection: " + self.url.protocol + "//" + reqOpts.host + ":" + reqOpts.port); callback(errres, 'Connection Error. ' + self.url.protocol + "//" + reqOpts.host + ':' + reqOpts.port + util.inspect(err)); eventRequest.end(); }); debug('Now send event'); eventRequest.write(eventString); eventRequest.end(); }; return self; }; /** * Add the Event emitter to moogREST */ util.inherits(exports.moogREST, events.EventEmitter);