thalassa-consul
Version:
Dynamic haproxy load balancer and configuration
155 lines (129 loc) • 4.93 kB
JavaScript
var assert = require('assert')
, MuxDemux = require('mux-demux')
, EventEmitter = require('events').EventEmitter
, util = require('util')
, crdt = require('crdt')
, request = require('request')
, deepEqual = require('deep-equal')
;
var ConsulAgent = module.exports = function ConsulAgent (opts) {
if (typeof opts !== 'object') opts = {};
assert(opts.serviceReg, 'opts.serviceReg must be passed!');
assert(opts.serviceRegPort, 'opts.serviceRegPort must be passed!');
assert(opts.datacenter, 'opts.datacenter must be passed!');
assert(opts.location, 'opts.location must be passed!');
assert(opts.data, 'opts.data must be passed!');
var self = this;
this.log = (typeof opts.log === 'function') ? opts.log : function (){};
this.data = opts.data;
this.serviceReg = opts.serviceReg;
this.serviceRegPort = opts.serviceRegPort;
this.datacenter = opts.datacenter;
this.location = opts.location;
this.serviceDoc = new crdt.Doc();
this._clientMuxes = [];
this.serviceCheckUrl = '/v1/catalog/service/';
this.pollingTimeMs = 5000;
this.pollingInterval = setInterval(this.checkServiceMembers.bind(this), this.pollingTimeMs);
//don't wait for the first interval
this.checkServiceMembers();
};
util.inherits(ConsulAgent, EventEmitter);
ConsulAgent.prototype.checkServiceMembers = function () {
var self = this;
var backends = self.data.backends.toJSON();
// iterate through all of the backends
backends.forEach(function (backend) {
if (backend.type === 'dynamic') {
var url = 'http://' + self.serviceReg + ':' + self.serviceRegPort
+ '/' + self.serviceCheckUrl + backend.name + '?tag=' + self.location;
request({
uri: url,
json: true,
timeout: (self.pollingTimeMs/2)
}, function (err, resp, body) {
if (!err && resp.statusCode !== 200) {
err = new Error("Unsuccessful HTTP status code " + resp.statusCode);
}
if (err) {
self.log('error', 'ConsulAgent.checkServiceMembers failed ' + url, err);
return;
}
// filter only the members of this version
var newRegs = body.filter(function (reg) {
return (reg.ServiceTags.indexOf(backend.version) > -1);
});
// convert the format to what Thalassa expects
var newMembers = newRegs.map(function (reg) {
return {
name: reg.ServiceName,
version: backend.version,
host: reg.Address,
port: reg.ServicePort,
meta: {
tags: reg.ServiceTags,
consulNode: reg.Node
}
};
});
// update the new members
if (!deepEqual(backend.members, newMembers)) {
var oldMembers = backend.members.slice(0);
self.data.setBackendMembers(backend.key, newMembers);
self._processMemberChanges(oldMembers, newMembers);
}
});
}
});
};
ConsulAgent.prototype._processMemberChanges = function (oldMembers,newMembers) {
var self = this;
//an added member will not be in the old list,
//and a removed member will not be in the new list
for(var x=0;x<newMembers.length;x++){
var memberAdded = true;
for(var y=0;y<oldMembers.length;y++){
if(newMembers[x].host === oldMembers[y].host &&
newMembers[x].port === oldMembers[y].port){
memberAdded = false;
oldMembers.splice(y,1);
break;
}
}
if(memberAdded) self.handleServiceOnline(newMember);
}
//handle offline for removed members
oldMembers.forEach(self.handleServiceOffline.bind(self,oldMember));
};
ConsulAgent.prototype.createReadableMuxStream = function() {
var self = this;
var mx = new MuxDemux();
var id = Date.now() + String(Math.ceil(Math.random()*9999999));
self._clientMuxes[id] = mx;
mx.on('close', function () {
delete self._clientMuxes[id];
});
// services crdt
var serviceStream = self.serviceDoc.createStream({writable: false, sendClock: true});
var mxs = mx.createStream({ type: 'thalassa' });
serviceStream.pipe(mxs).pipe(serviceStream);
return mx;
};
ConsulAgent.prototype._handleServiceOnline = function (member) {
var self = this;
var id = self._generateId(member);
var activity = { type: 'activity', time: Date.now(), verb: 'online', object: id };
self.log('debug', 'ONLINE', id);
self.serviceDoc.add({ id: id, type: 'service', service: member});
self.emit('activity', activity);
};
ConsulAgent.prototype._handleServiceOffline = function (member) {
var self = this;
var id = self._generateId(member);
var activity = { type: 'activity', time: Date.now(), verb: 'offline', object: id };
self.serviceDoc.rm(id);
self.emit('activity', activity);
};
ConsulAgent.prototype._generateId = function(data){
return '/' + data.name + '/' + data.version + '/' + data.host + '/' + data.port;
};