queryda
Version:
watch/report/alert tool for elasticsearch
242 lines (205 loc) • 7.19 kB
JavaScript
(function() {
var Worker, events, http, log,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
log = require("loglevel");
http = require("http");
url = require("url");
events = require("events");
var cassandra = require('cassandra-driver');
/**
* The Worker does most of the magic. It connects to cassandra, queries
* data, analyzes the result, compares it to the expectation and raises an alarm
* when appropriate.
*
* @class Worker
* @extends events.EventEmitter
*/
module.exports = Worker = (function(superClass) {
extend(Worker, superClass);
Worker.ResultCodes = {
Success: {
code: 0,
label: "SUCCESS"
},
ValidationFailed: {
code: 1,
label: "ALARM_VALIDATION_FAILED"
},
NoResults: {
code: 2,
label: "ALARM_NO_RESULTS_RECEIVED"
},
NotFound: {
code: 4,
label: "ALARM_NOT_FOUND_404"
},
InvalidResponse: {
code: 5,
label: "ALARM_INVALID_RESPONSE"
},
ConnectionRefused: {
code: 6,
label: "ALARM_CONNECTION_REFUSED"
},
UnhandledError: {
code: 99,
label: "ALARM_UNHANDLED_ERROR"
}
};
/**
* Create a new Worker, prepare data, setup request options.
*
* @constructor
* @param id {String} identifies this individual Worker instance
* @param host {String} cassandra hostname to connect to
* @param cqlquery {Object} valid cassandra query
* @param params {Object} valid variables for query
* @param validator {ResultValidator} a validator object that takes the response and compares it against a given expectation
*/
function Worker(id, host, cqlquery, params, validator) {
this.id = id;
this.host = host;
this.cqlquery = cqlquery;
this.params = params;
this.validator = validator;
this.onError = bind(this.onError, this);
this.onResponse = bind(this.onResponse, this);
this.raiseAlarm = bind(this.raiseAlarm, this);
this.start = bind(this.start, this);
if (!this.id || !this.host || !this.cqlquery || !this.validator) {
console.log(id,host,cqlquery,params );
throw new Error("Worker.constructor: invalid number of required options received: " + (JSON.stringify(arguments)));
}
}
/**
* Execute request and hand over control to onResponse callback.
*
* @method start
*/
Worker.prototype.start = function() {
var data, e, error1;
this.options = {
contactPoints: [ this.host||"127.0.0.1" ],
};
log.debug("Worker(" + this.id + ").sendCQLRequest: connecting to cassandra at: " + this.host);
try {
var client = new cassandra.Client(this.options);
client.connect()
.then(function () {
log.debug("Worker(" + this.id + ").sendCQLRequest: cqlquery is: ", this.cqlquery, this.params);
return client.execute(this.cqlquery);
}.bind(this))
.then(function(data){
this.onResponse(data);
return client.shutdown();
}.bind(this))
.catch(function(err){
return log.error("Worker(" + this.id + ").start: unhandled error: " + err);
return client.shutdown();
}.bind(this));
return true;
} catch(err){
return log.error("Worker(" + this.id + ").start: unhandled error: " + err);
}
};
/**
* Gets passed CQL response data (as object) and pre-validates the contents.
* If data is invalid or result is empty an error is raised. Valid results
* are handed over to the ResultValidator for further analysis. If any alarm
* condition is met, raiseAlarm is called with the appropriate alarm.
*
* @method handleResponseData
* @param data {Object} result set as returned by ES
*/
Worker.prototype.handleResponseData = function(data) {
var numHits, rc, result;
result = null;
var alarms = [];
rc = Worker.ResultCodes;
if (!data || typeof data === "undefined") {
result = rc.InvalidResponse;
} else {
log.debug("Worker(" + this.id + ").onResponse: cql query returned " + JSON.stringify(data) );
if (data === '') {
result = rc.NoResults;
return false;
} else {
// Iterate Validators
this.validator.forEach(function(validator, i){
if (!validator.validate(data)) {
result = rc.ValidationFailed;
alarms.push(i);
// return false;
} else {
result = rc.Success;
}
});
// if (result === rc.Success) {
if (alarms.length === 0) {
return true;
} else {
// Iterate Alarms
for (idx = 0; idx < alarms.length; ++idx) {
this.raiseAlarm(result.label + ": " + (this.validator[idx].getMessage()));
}
process.exitCode = result.code;
return false;
}
}
}
};
/**
* Raise alarm - emits "alarm" event that can be handled by interested
* listeners.
*
* @method raiseAlarm
* @emits alarm
* @param message {String} error message
* @param data {object} any additional data
*/
Worker.prototype.raiseAlarm = function(message) {
log.debug("Worker(" + this.id + ").raiseAlarm: raising alarm: " + message);
return this.emit("alarm", message, {
name: this.id
});
};
/**
* http.request: success callback
*
* @method onResponse
*/
Worker.prototype.onResponse = function(body) {
// log.debug("Worker(" + this.id + ").onResponse: response was: ", body);
try {
return this.handleResponseData(body.rows);
return;
} catch (error1) {
e = error1;
log.error("Worker(" + this.id + ").onResponse: failed to parse response data",e);
this.raiseAlarm("" + Worker.ResultCodes.NotFound.label);
process.exitCode = Worker.ResultCodes.NotFound.code;
return process.exit();
}
};
/**
* http.request: error callback
*
* @method onError
*/
Worker.prototype.onError = function(error) {
if (error.code === "ECONNREFUSED") {
log.error("ERROR: connection refused, please make sure Cassandra is running and accessible under " + this.options.host);
this.raiseAlarm("" + Worker.ResultCodes.ConnectionRefused.label);
process.exitCode = Worker.ResultCodes.ConnectionRefused.code;
} else {
log.debug("Worker(" + this.id + ").onError: unhandled error: ", error);
this.raiseAlarm(Worker.ResultCodes.UnhandledError.label + ": " + error);
process.exitCode = Worker.ResultCodes.UnhandledError.code;
}
return;
};
return Worker;
})(events.EventEmitter);
}).call(this);