UNPKG

thalassa-consul

Version:
155 lines (129 loc) 4.93 kB
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; };