node-moog
Version:
Publish events to the MOOG AIOps REST LAM
366 lines (337 loc) • 13.2 kB
JavaScript
/**
* 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);