Skip to content

Commit 6bb8032

Browse files
author
Zhen Li
authored
Merge pull request #220 from lutovich/1.2-seed-uri-resolution
Remember seed uri and DNS-lookup during rediscovery
2 parents 9b1d2f8 + 55527d4 commit 6bb8032

File tree

8 files changed

+842
-29
lines changed

8 files changed

+842
-29
lines changed

src/v1/internal/connection-providers.js

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,20 @@ import Session from '../session';
2323
import RoundRobinArray from './round-robin-array';
2424
import RoutingTable from './routing-table';
2525
import Rediscovery from './rediscovery';
26+
import hasFeature from './features';
27+
import {DnsHostNameResolver, DummyHostNameResolver} from './host-name-resolvers';
2628

2729
class ConnectionProvider {
2830

2931
acquireConnection(mode) {
30-
throw new Error('Abstract method');
32+
throw new Error('Abstract function');
3133
}
3234

3335
_withAdditionalOnErrorCallback(connectionPromise, driverOnErrorCallback) {
3436
// install error handler from the driver on the connection promise; this callback is installed separately
3537
// so that it does not handle errors, instead it is just an additional error reporting facility.
3638
connectionPromise.catch(error => {
37-
driverOnErrorCallback(error)
39+
driverOnErrorCallback(error);
3840
});
3941
// return the original connection promise
4042
return connectionPromise;
@@ -61,10 +63,12 @@ export class LoadBalancer extends ConnectionProvider {
6163

6264
constructor(address, connectionPool, driverOnErrorCallback) {
6365
super();
64-
this._routingTable = new RoutingTable(new RoundRobinArray([address]));
66+
this._seedRouter = address;
67+
this._routingTable = new RoutingTable(new RoundRobinArray([this._seedRouter]));
6568
this._rediscovery = new Rediscovery();
6669
this._connectionPool = connectionPool;
6770
this._driverOnErrorCallback = driverOnErrorCallback;
71+
this._hostNameResolver = LoadBalancer._createHostNameResolver();
6872
}
6973

7074
acquireConnection(mode) {
@@ -109,7 +113,42 @@ export class LoadBalancer extends ConnectionProvider {
109113
_refreshRoutingTable(currentRoutingTable) {
110114
const knownRouters = currentRoutingTable.routers.toArray();
111115

112-
const refreshedTablePromise = knownRouters.reduce((refreshedTablePromise, currentRouter, currentIndex) => {
116+
return this._fetchNewRoutingTable(knownRouters, currentRoutingTable).then(newRoutingTable => {
117+
if (LoadBalancer._isValidRoutingTable(newRoutingTable)) {
118+
// one of the known routers returned a valid routing table - use it
119+
return newRoutingTable;
120+
}
121+
122+
if (!newRoutingTable) {
123+
// returned routing table was undefined, this means a connection error happened and the last known
124+
// router did not return a valid routing table, so we need to forget it
125+
const lastRouterIndex = knownRouters.length - 1;
126+
LoadBalancer._forgetRouter(currentRoutingTable, knownRouters, lastRouterIndex);
127+
}
128+
129+
// none of the known routers returned a valid routing table - try to use seed router address for rediscovery
130+
return this._fetchNewRoutingTableUsingSeedRouterAddress(knownRouters, this._seedRouter);
131+
}).then(newRoutingTable => {
132+
if (LoadBalancer._isValidRoutingTable(newRoutingTable)) {
133+
this._updateRoutingTable(newRoutingTable);
134+
return newRoutingTable;
135+
}
136+
137+
// none of the existing routers returned valid routing table, throw exception
138+
throw newError('Could not perform discovery. No routing servers available.', SERVICE_UNAVAILABLE);
139+
});
140+
}
141+
142+
_fetchNewRoutingTableUsingSeedRouterAddress(knownRouters, seedRouter) {
143+
return this._hostNameResolver.resolve(seedRouter).then(resolvedRouterAddresses => {
144+
// filter out all addresses that we've already tried
145+
const newAddresses = resolvedRouterAddresses.filter(address => knownRouters.indexOf(address) < 0);
146+
return this._fetchNewRoutingTable(newAddresses, null);
147+
});
148+
}
149+
150+
_fetchNewRoutingTable(routerAddresses, routingTable) {
151+
return routerAddresses.reduce((refreshedTablePromise, currentRouter, currentIndex) => {
113152
return refreshedTablePromise.then(newRoutingTable => {
114153
if (newRoutingTable) {
115154
if (!newRoutingTable.writers.isEmpty()) {
@@ -120,28 +159,14 @@ export class LoadBalancer extends ConnectionProvider {
120159
// returned routing table was undefined, this means a connection error happened and we need to forget the
121160
// previous router and try the next one
122161
const previousRouterIndex = currentIndex - 1;
123-
this._forgetRouter(currentRoutingTable, knownRouters, previousRouterIndex);
162+
LoadBalancer._forgetRouter(routingTable, routerAddresses, previousRouterIndex);
124163
}
125164

126165
// try next router
127166
const session = this._createSessionForRediscovery(currentRouter);
128167
return this._rediscovery.lookupRoutingTableOnRouter(session, currentRouter);
129168
});
130169
}, Promise.resolve(null));
131-
132-
return refreshedTablePromise.then(newRoutingTable => {
133-
if (newRoutingTable && !newRoutingTable.writers.isEmpty()) {
134-
this._updateRoutingTable(newRoutingTable);
135-
return newRoutingTable;
136-
}
137-
138-
// forget the last known router because it did not return a valid routing table
139-
const lastRouterIndex = knownRouters.length - 1;
140-
this._forgetRouter(currentRoutingTable, knownRouters, lastRouterIndex);
141-
142-
// none of the existing routers returned valid routing table, throw exception
143-
throw newError('Could not perform discovery. No routing servers available.', SERVICE_UNAVAILABLE);
144-
});
145170
}
146171

147172
_createSessionForRediscovery(routerAddress) {
@@ -162,12 +187,23 @@ export class LoadBalancer extends ConnectionProvider {
162187
this._routingTable = newRoutingTable;
163188
}
164189

165-
_forgetRouter(routingTable, routersArray, routerIndex) {
190+
static _isValidRoutingTable(routingTable) {
191+
return routingTable && !routingTable.writers.isEmpty();
192+
}
193+
194+
static _forgetRouter(routingTable, routersArray, routerIndex) {
166195
const address = routersArray[routerIndex];
167-
if (address) {
196+
if (routingTable && address) {
168197
routingTable.forgetRouter(address);
169198
}
170199
}
200+
201+
static _createHostNameResolver() {
202+
if (hasFeature('dns_lookup')) {
203+
return new DnsHostNameResolver();
204+
}
205+
return new DummyHostNameResolver();
206+
}
171207
}
172208

173209
export class SingleConnectionProvider extends ConnectionProvider {

src/v1/internal/connector.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,5 +500,7 @@ export {
500500
connect,
501501
parseScheme,
502502
parseUrl,
503+
parseHost,
504+
parsePort,
503505
Connection
504506
}

src/v1/internal/features.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ const FEATURES = {
4040
} catch (e) {
4141
return false;
4242
}
43+
},
44+
dns_lookup: () => {
45+
try {
46+
const lookupFunction = require('dns').lookup;
47+
if (lookupFunction && typeof lookupFunction === 'function') {
48+
return true;
49+
}
50+
return false;
51+
} catch (e) {
52+
return false;
53+
}
4354
}
4455
};
4556

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright (c) 2002-2017 "Neo Technology,","
3+
* Network Engine for Objects in Lund AB [http://neotechnology.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import {parseHost, parsePort} from './connector';
21+
22+
class HostNameResolver {
23+
24+
resolve() {
25+
throw new Error('Abstract function');
26+
}
27+
}
28+
29+
export class DummyHostNameResolver extends HostNameResolver {
30+
31+
resolve(seedRouter) {
32+
return resolveToItself(seedRouter);
33+
}
34+
}
35+
36+
export class DnsHostNameResolver extends HostNameResolver {
37+
38+
constructor() {
39+
super();
40+
this._dns = require('dns');
41+
}
42+
43+
resolve(seedRouter) {
44+
const seedRouterHost = parseHost(seedRouter);
45+
const seedRouterPort = parsePort(seedRouter);
46+
47+
return new Promise((resolve) => {
48+
this._dns.lookup(seedRouterHost, {all: true}, (error, addresses) => {
49+
if (error) {
50+
resolve(resolveToItself(seedRouter));
51+
} else {
52+
const addressesWithPorts = addresses.map(address => addressWithPort(address, seedRouterPort));
53+
resolve(addressesWithPorts);
54+
}
55+
});
56+
});
57+
}
58+
}
59+
60+
function resolveToItself(address) {
61+
return Promise.resolve([address]);
62+
}
63+
64+
function addressWithPort(addressObject, port) {
65+
const address = addressObject.address;
66+
if (port) {
67+
return address + ':' + port;
68+
}
69+
return address;
70+
}

0 commit comments

Comments
 (0)