From be132887f8062e22ba0b90e619e38999f459e815 Mon Sep 17 00:00:00 2001 From: Alex Wolden Date: Sun, 1 May 2016 19:54:23 -0700 Subject: [PATCH] added event support --- CHANGES.md | 1 + README.md | 12 ++++++++++++ src/EurekaClient.js | 9 ++++++++- test/EurekaClient.test.js | 41 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fb53c2f..cff3209 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,4 @@ ## 3.0.0 + - Added support for the following events: `started`, `registered`, `deregistered`, `heartbeat`, and `registryUpdated`. - Improved the stability of the client when it encounters downstream DNS errors, as a side-effect the callback for `fetchRegistries()` now returns errors when they are encountered. - Populate registry cache with instances that have a status of `UP`, `filterUpInstances` can be set to `false` to disable. diff --git a/README.md b/README.md index 0b8dcaa..77b36cc 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,18 @@ option | default value | description `eureka.fetchMetadata` | `true` | fetch AWS metadata when in AWS environment, see [Configuring for AWS environments](#configuring-for-aws-environments) `eureka.useLocalMetadata` | `false` | use local IP and local hostname from metadata when in an AWS environment. +## Events + +Eureka client is an instance of `EventEmitter` and provides the following events for consumption: + +event | data provided | description +---- | --- | --- +`started` | N/A | Fired when eureka client is fully registered and all registries have been updated. +`registered` | N/A | Fired when the eureka client is registered with eureka. +`deregistered` | N/A | Fired when the eureka client is deregistered with eureka. +`heartbeat` | N/A | Fired when the eureka client has successfully renewed it's lease with eureka. +`registryUpdated` | N/A | Fired when the eureka client has successfully update it's registries. + ## Debugging The library uses [request](https://github.com/request/request) for all service calls, and debugging can be turned on by passing `NODE_DEBUG=request` when you start node. This allows you you double-check the URL being called as well as other request properties. diff --git a/src/EurekaClient.js b/src/EurekaClient.js index af2c415..25f3daf 100644 --- a/src/EurekaClient.js +++ b/src/EurekaClient.js @@ -5,6 +5,7 @@ import merge from 'deepmerge'; import path from 'path'; import dns from 'dns'; import { series } from 'async'; +import { EventEmitter } from 'events'; import AwsMetadata from './AwsMetadata'; import Logger from './Logger'; @@ -28,9 +29,10 @@ function getYaml(file) { return yml; } -export default class Eureka { +export default class Eureka extends EventEmitter { constructor(config = {}) { + super(); // Allow passing in a custom logger: this.logger = config.logger || new Logger(); @@ -134,6 +136,7 @@ export default class Eureka { }, ], (err, ...rest) => { if (err) this.logger.warn('Error starting the Eureka Client', err); + this.emit('started'); callback(err, ...rest); }); } @@ -189,6 +192,7 @@ export default class Eureka { 'registered with eureka: ', `${this.config.instance.app}/${this.instanceId}` ); + this.emit('registered'); return callback(null); } else if (error) { this.logger.warn('Error registering with eureka client.', error); @@ -216,6 +220,7 @@ export default class Eureka { 'de-registered with eureka: ', `${this.config.instance.app}/${this.instanceId}` ); + this.emit('deregistered'); return callback(null); } else if (error) { this.logger.warn('Error deregistering with eureka', error); @@ -250,6 +255,7 @@ export default class Eureka { }, (error, response, body) => { if (!error && response.statusCode === 200) { this.logger.debug('eureka heartbeat success'); + this.emit('heartbeat'); } else if (!error && response.statusCode === 404) { this.logger.warn('eureka heartbeat FAILED, Re-registering app'); this.register(); @@ -322,6 +328,7 @@ export default class Eureka { if (!error && response.statusCode === 200) { this.logger.debug('retrieved registry successfully'); this.transformRegistry(JSON.parse(body)); + this.emit('registryUpdated'); return callback(null); } else if (error) { this.logger.warn('Error fetching registry', error); diff --git a/test/EurekaClient.test.js b/test/EurekaClient.test.js index dce1e32..435bde4 100644 --- a/test/EurekaClient.test.js +++ b/test/EurekaClient.test.js @@ -3,6 +3,7 @@ import sinon from 'sinon'; import chai, { expect } from 'chai'; import sinonChai from 'sinon-chai'; import request from 'request'; +import { EventEmitter } from 'events'; import dns from 'dns'; import { join } from 'path'; import merge from 'deepmerge'; @@ -29,6 +30,10 @@ function makeConfig(overrides = {}) { describe('Eureka client', () => { describe('Eureka()', () => { + it('should extend EventEmitter', () => { + expect(new Eureka(makeConfig())).to.be.instanceof(EventEmitter); + }); + it('should throw an error if no config is found', () => { function fn() { return new Eureka(); @@ -131,12 +136,16 @@ describe('Eureka client', () => { fetchRegistrySpy = sinon.stub(client, 'fetchRegistry').callsArg(0); heartbeatsSpy = sinon.stub(client, 'startHeartbeats'); registryFetchSpy = sinon.stub(client, 'startRegistryFetches'); + const eventSpy = sinon.spy(); + client.on('started', eventSpy); client.start(() => { expect(registerSpy).to.have.been.calledOnce; expect(fetchRegistrySpy).to.have.been.calledOnce; expect(heartbeatsSpy).to.have.been.calledOnce; expect(registryFetchSpy).to.have.been.calledOnce; + expect(registryFetchSpy).to.have.been.calledOnce; + expect(eventSpy).to.have.been.calledOnce; done(); }); }); @@ -228,6 +237,13 @@ describe('Eureka client', () => { afterEach(() => { request.post.restore(); }); + it('should trigger register event', () => { + sinon.stub(request, 'post').yields(null, { statusCode: 204 }, null); + const eventSpy = sinon.spy(); + client.on('registered', eventSpy); + client.register(); + expect(eventSpy).to.have.been.calledOnce; + }); it('should call register URI', () => { sinon.stub(request, 'post').yields(null, { statusCode: 204 }, null); @@ -283,6 +299,14 @@ describe('Eureka client', () => { request.del.restore(); }); + it('should should trigger deregister event', () => { + sinon.stub(request, 'del').yields(null, { statusCode: 200 }, null); + const eventSpy = sinon.spy(); + client.on('deregistered', eventSpy); + client.register(); + client.deregister(); + }); + it('should call deregister URI', () => { sinon.stub(request, 'del').yields(null, { statusCode: 200 }, null); const deregisterCb = sinon.spy(); @@ -335,6 +359,15 @@ describe('Eureka client', () => { }); }); + it('should trigger a heartbeat event', () => { + sinon.stub(request, 'put').yields(null, { statusCode: 200 }, null); + const eventSpy = sinon.spy(); + client.on('heartbeat', eventSpy); + client.renew(); + + expect(eventSpy).to.have.been.calledOnce; + }); + it('should re-register on 404', () => { sinon.stub(request, 'put').yields(null, { statusCode: 404 }, null); sinon.stub(request, 'post').yields(null, { statusCode: 204 }, null); @@ -518,6 +551,14 @@ describe('Eureka client', () => { client.transformRegistry.restore(); }); + it('should should trigger registryUpdated event', () => { + sinon.stub(request, 'get').yields(null, { statusCode: 200 }, null); + const eventSpy = sinon.spy(); + client.on('registryUpdated', eventSpy); + client.fetchRegistry(); + expect(eventSpy).to.have.been.calledOnce; + }); + it('should call registry URI', () => { sinon.stub(request, 'get').yields(null, { statusCode: 200 }, null); const registryCb = sinon.spy();