UNPKG

@rocketsoftware/eureka-js-client

Version:

A JavaScript implementation the Netflix OSS service registry, Eureka.

324 lines (289 loc) 12.3 kB
/* eslint-disable no-unused-expressions */ import sinon from 'sinon'; import chai, { expect } from 'chai'; import sinonChai from 'sinon-chai'; import dns from 'dns'; import merge from 'lodash/merge'; import DnsClusterResolver from '../src/DnsClusterResolver'; chai.use(sinonChai); function makeConfig(overrides = {}) { const config = { instance: { dataCenterInfo: { metadata: { 'availability-zone': '1b' } }, }, eureka: { host: 'eureka.mydomain.com', servicePath: '/eureka/v2/apps/', port: 9999, maxRetries: 0, ec2Region: 'my-region', }, }; return merge({}, config, overrides); } describe('DNS Cluster Resolver', () => { describe('DnsClusterResolver', () => { it('should throw error when ec2Region is undefined', () => { const config = makeConfig(); config.eureka.ec2Region = undefined; function fn() { return new DnsClusterResolver(config); } expect(fn).to.throw(); }); }); describe('startClusterRefresh()', () => { let dnsResolver; let refreshStub; let clock; beforeEach(() => { clock = sinon.useFakeTimers(); }); afterEach(() => { dnsResolver.refreshCurrentCluster.restore(); clock.restore(); }); it('should start cluster refreshes on interval', () => { dnsResolver = new DnsClusterResolver(makeConfig({ eureka: { clusterRefreshInterval: 300000 }, })); refreshStub = sinon.stub(dnsResolver, 'refreshCurrentCluster'); clock.tick(300000); expect(refreshStub).to.have.been.calledOnce; clock.tick(300000); expect(refreshStub).to.have.been.calledTwice; clock.restore(); }); it('should log warning on refresh failure', () => { dnsResolver = new DnsClusterResolver(makeConfig({ eureka: { clusterRefreshInterval: 300000 }, })); refreshStub = sinon.stub(dnsResolver, 'refreshCurrentCluster'); refreshStub.yields(new Error('fail')); clock.tick(300000); expect(refreshStub).to.have.been.calledOnce; clock.tick(300000); expect(refreshStub).to.have.been.calledTwice; clock.restore(); }); }); describe('resolveEurekaUrl()', () => { let dnsResolver; beforeEach(() => { dnsResolver = new DnsClusterResolver(makeConfig()); }); afterEach(() => { dnsResolver.resolveClusterHosts.restore(); }); it('should return base Eureka URL using current cluster host', () => { const resolveHostsStub = sinon.stub(dnsResolver, 'resolveClusterHosts'); resolveHostsStub.yields(null, ['a.mydomain.com', 'b.mydomain.com', 'c.mydomain.com']); dnsResolver.resolveEurekaUrl((err, eurekaUrl) => { expect(eurekaUrl).to.equal('http://a.mydomain.com:9999/eureka/v2/apps/'); }); }); it('should return base Eureka URL using next cluster host on retry', () => { const resolveHostsStub = sinon.stub(dnsResolver, 'resolveClusterHosts'); resolveHostsStub.yields(null, ['a.mydomain.com', 'b.mydomain.com', 'c.mydomain.com']); dnsResolver.resolveEurekaUrl((err, eurekaUrl) => { expect(eurekaUrl).to.equal('http://b.mydomain.com:9999/eureka/v2/apps/'); expect(dnsResolver.serverList).to.eql(['b.mydomain.com', 'c.mydomain.com', 'a.mydomain.com']); }, 1); }); it('should return error when resolve fails', () => { const resolveHostsStub = sinon.stub(dnsResolver, 'resolveClusterHosts'); resolveHostsStub.yields(new Error('fail')); dnsResolver.resolveEurekaUrl((err) => { expect(err).to.not.equal(undefined); expect(err.message).to.equal('fail'); }); }); }); describe('getCurrentCluster()', () => { let dnsResolver; beforeEach(() => { dnsResolver = new DnsClusterResolver(makeConfig()); }); afterEach(() => { dnsResolver.resolveClusterHosts.restore(); }); it('should call cluster refresh if server list is undefined', () => { const resolveHostsStub = sinon.stub(dnsResolver, 'resolveClusterHosts'); resolveHostsStub.onCall(0).yields(null, ['a', 'b', 'c']); resolveHostsStub.onCall(1).yields(null, ['f', 'a', 'c']); dnsResolver.getCurrentCluster((err, serverList) => { expect(serverList).to.include.members(['a', 'b', 'c']); dnsResolver.getCurrentCluster((errTwo, serverListTwo) => { expect(serverListTwo).to.include.members(['a', 'b', 'c']); }); }); }); it('should return error when refresh fails', () => { const resolveHostsStub = sinon.stub(dnsResolver, 'resolveClusterHosts'); resolveHostsStub.yields(new Error('fail')); dnsResolver.getCurrentCluster((err) => { expect(err).to.not.equal(undefined); expect(err.message).to.equal('fail'); }); }); }); describe('refreshCurrentCluster', () => { let dnsResolver; beforeEach(() => { dnsResolver = new DnsClusterResolver(makeConfig()); }); afterEach(() => { dnsResolver.resolveClusterHosts.restore(); }); it('should refresh server list', () => { const resolveHostsStub = sinon.stub(dnsResolver, 'resolveClusterHosts'); resolveHostsStub.onCall(0).yields(null, ['a', 'b', 'c']); resolveHostsStub.onCall(1).yields(null, ['a', 'b', 'c', 'd']); dnsResolver.refreshCurrentCluster((err) => { expect(err).to.equal(undefined); expect(dnsResolver.serverList).to.eql(['a', 'b', 'c']); dnsResolver.refreshCurrentCluster((errTwo) => { expect(errTwo).to.equal(undefined); expect(dnsResolver.serverList).to.eql(['a', 'b', 'c', 'd']); }); }); }); it('should maintain server list when cluster remains unchanged', () => { const resolveHostsStub = sinon.stub(dnsResolver, 'resolveClusterHosts'); resolveHostsStub.onCall(0).yields(null, ['a', 'b', 'c']); resolveHostsStub.onCall(1).yields(null, ['c', 'a', 'b']); dnsResolver.refreshCurrentCluster((err) => { expect(err).to.equal(undefined); expect(dnsResolver.serverList).to.eql(['a', 'b', 'c']); dnsResolver.refreshCurrentCluster((errTwo) => { expect(errTwo).to.equal(undefined); expect(dnsResolver.serverList).to.eql(['a', 'b', 'c']); }); }); }); it('should return error when resolve fails', () => { const resolveHostsStub = sinon.stub(dnsResolver, 'resolveClusterHosts'); resolveHostsStub.yields(new Error('fail')); dnsResolver.refreshCurrentCluster((err) => { expect(err).to.not.equal(undefined); expect(err.message).to.equal('fail'); }); }); }); describe('resolveClusterHosts()', () => { const eurekaHosts = [ '1a.eureka.mydomain.com', '1b.eureka.mydomain.com', '1c.eureka.mydomain.com', ]; afterEach(() => { dns.resolveTxt.restore(); }); it('should resolve hosts using DNS', (done) => { const dnsResolver = new DnsClusterResolver(makeConfig()); const resolveStub = sinon.stub(dns, 'resolveTxt'); resolveStub.withArgs('txt.my-region.eureka.mydomain.com').yields(null, [eurekaHosts]); resolveStub.withArgs('txt.1a.eureka.mydomain.com').yields(null, [['1.2.3.4']]); resolveStub.withArgs('txt.1b.eureka.mydomain.com').yields(null, [['2.2.3.4']]); resolveStub.withArgs('txt.1c.eureka.mydomain.com').yields(null, [['3.2.3.4']]); dnsResolver.resolveClusterHosts((err, hosts) => { expect(hosts).to.include.members(['1.2.3.4', '2.2.3.4', '3.2.3.4']); done(); }); }); it('should resolve hosts using DNS and zone affinity', (done) => { const dnsResolver = new DnsClusterResolver(makeConfig({ eureka: { preferSameZone: true }, })); const resolveStub = sinon.stub(dns, 'resolveTxt'); resolveStub.withArgs('txt.my-region.eureka.mydomain.com').yields(null, [eurekaHosts]); resolveStub.withArgs('txt.1a.eureka.mydomain.com').yields(null, [['1.2.3.4']]); resolveStub.withArgs('txt.1b.eureka.mydomain.com').yields(null, [['2.2.3.4']]); resolveStub.withArgs('txt.1c.eureka.mydomain.com').yields(null, [['3.2.3.4']]); dnsResolver.resolveClusterHosts((err, hosts) => { expect(hosts[0]).to.equal('2.2.3.4'); expect(hosts).to.include.members(['1.2.3.4', '2.2.3.4', '3.2.3.4']); dnsResolver.resolveClusterHosts((error, hostsTwo) => { expect(hostsTwo[0]).to.equal('2.2.3.4'); expect(hostsTwo).to.include.members(['1.2.3.4', '2.2.3.4', '3.2.3.4']); done(); }); }); }); it('should resolve hosts when dataCenterInfo is undefined', (done) => { const config = { instance: {}, eureka: { preferSameZone: true, host: 'eureka.mydomain.com', servicePath: '/eureka/v2/apps/', port: 9999, maxRetries: 0, ec2Region: 'my-region', }, }; const dnsResolver = new DnsClusterResolver(config); const resolveStub = sinon.stub(dns, 'resolveTxt'); resolveStub.withArgs('txt.my-region.eureka.mydomain.com').yields(null, [eurekaHosts]); resolveStub.withArgs('txt.1a.eureka.mydomain.com').yields(null, [['1.2.3.4']]); resolveStub.withArgs('txt.1b.eureka.mydomain.com').yields(null, [['2.2.3.4']]); resolveStub.withArgs('txt.1c.eureka.mydomain.com').yields(null, [['3.2.3.4']]); dnsResolver.resolveClusterHosts((err, hosts) => { expect(hosts).to.include.members(['1.2.3.4', '2.2.3.4', '3.2.3.4']); dnsResolver.resolveClusterHosts((error, hostsTwo) => { expect(hostsTwo).to.include.members(['1.2.3.4', '2.2.3.4', '3.2.3.4']); done(); }); }); }); it('should return error when initial DNS lookup fails', () => { const resolveCb = sinon.spy(); const dnsResolver = new DnsClusterResolver(makeConfig()); const resolveStub = sinon.stub(dns, 'resolveTxt'); resolveStub.withArgs('txt.my-region.eureka.mydomain.com') .yields(new Error('dns error'), null); function shouldNotThrow() { dnsResolver.resolveClusterHosts(resolveCb); } expect(shouldNotThrow).to.not.throw(); expect(dns.resolveTxt).to.have.been.calledWithMatch('txt.my-region.eureka.mydomain.com'); expect(resolveCb).to.have.been.calledWithMatch({ message: 'Error resolving eureka cluster ' + 'for region [my-region] using DNS: [Error: dns error]', }); }); it('should return error when DNS lookup fails for an individual zone', () => { const resolveCb = sinon.spy(); const dnsResolver = new DnsClusterResolver(makeConfig({ eureka: { host: 'eureka.mydomain.com', port: 9999, ec2Region: 'my-region' }, })); const resolveStub = sinon.stub(dns, 'resolveTxt'); resolveStub.withArgs('txt.my-region.eureka.mydomain.com').yields(null, [eurekaHosts]); resolveStub.withArgs('txt.1a.eureka.mydomain.com').yields(null, [['1.2.3.4']]); resolveStub.withArgs('txt.1b.eureka.mydomain.com').yields(new Error('dns error'), null); resolveStub.withArgs('txt.1c.eureka.mydomain.com').yields(null, [['3.2.3.4']]); function shouldNotThrow() { dnsResolver.resolveClusterHosts(resolveCb); } expect(shouldNotThrow).to.not.throw(); expect(resolveCb).to.have.been.calledWithMatch({ message: 'Error resolving cluster zone txt.1b.eureka.mydomain.com: [Error: dns error]', }); }); it('should return error when no hosts were found', (done) => { const dnsResolver = new DnsClusterResolver(makeConfig()); const resolveStub = sinon.stub(dns, 'resolveTxt'); resolveStub.withArgs('txt.my-region.eureka.mydomain.com').yields(null, [eurekaHosts]); resolveStub.withArgs('txt.1a.eureka.mydomain.com').yields(null, []); resolveStub.withArgs('txt.1b.eureka.mydomain.com').yields(null, []); resolveStub.withArgs('txt.1c.eureka.mydomain.com').yields(null, []); dnsResolver.resolveClusterHosts((err) => { expect(err).to.not.equal(undefined); expect(err.message).to.equal('Unable to locate any Eureka hosts in any ' + 'zone via DNS @ txt.my-region.eureka.mydomain.com'); done(); }); }); }); });