Skip to content

Remember seed uri and DNS-lookup during rediscovery #220

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 57 additions & 21 deletions src/v1/internal/connection-providers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,20 @@ import Session from '../session';
import RoundRobinArray from './round-robin-array';
import RoutingTable from './routing-table';
import Rediscovery from './rediscovery';
import hasFeature from './features';
import {DnsHostNameResolver, DummyHostNameResolver} from './host-name-resolvers';

class ConnectionProvider {

acquireConnection(mode) {
throw new Error('Abstract method');
throw new Error('Abstract function');
}

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

constructor(address, connectionPool, driverOnErrorCallback) {
super();
this._routingTable = new RoutingTable(new RoundRobinArray([address]));
this._seedRouter = address;
this._routingTable = new RoutingTable(new RoundRobinArray([this._seedRouter]));
this._rediscovery = new Rediscovery();
this._connectionPool = connectionPool;
this._driverOnErrorCallback = driverOnErrorCallback;
this._hostNameResolver = LoadBalancer._createHostNameResolver();
}

acquireConnection(mode) {
Expand Down Expand Up @@ -109,7 +113,42 @@ export class LoadBalancer extends ConnectionProvider {
_refreshRoutingTable(currentRoutingTable) {
const knownRouters = currentRoutingTable.routers.toArray();

const refreshedTablePromise = knownRouters.reduce((refreshedTablePromise, currentRouter, currentIndex) => {
return this._fetchNewRoutingTable(knownRouters, currentRoutingTable).then(newRoutingTable => {
if (LoadBalancer._isValidRoutingTable(newRoutingTable)) {
// one of the known routers returned a valid routing table - use it
return newRoutingTable;
}

if (!newRoutingTable) {
// returned routing table was undefined, this means a connection error happened and the last known
// router did not return a valid routing table, so we need to forget it
const lastRouterIndex = knownRouters.length - 1;
LoadBalancer._forgetRouter(currentRoutingTable, knownRouters, lastRouterIndex);
}

// none of the known routers returned a valid routing table - try to use seed router address for rediscovery
return this._fetchNewRoutingTableUsingSeedRouterAddress(knownRouters, this._seedRouter);
}).then(newRoutingTable => {
if (LoadBalancer._isValidRoutingTable(newRoutingTable)) {
this._updateRoutingTable(newRoutingTable);
return newRoutingTable;
}

// none of the existing routers returned valid routing table, throw exception
throw newError('Could not perform discovery. No routing servers available.', SERVICE_UNAVAILABLE);
});
}

_fetchNewRoutingTableUsingSeedRouterAddress(knownRouters, seedRouter) {
return this._hostNameResolver.resolve(seedRouter).then(resolvedRouterAddresses => {
// filter out all addresses that we've already tried
const newAddresses = resolvedRouterAddresses.filter(address => knownRouters.indexOf(address) < 0);
return this._fetchNewRoutingTable(newAddresses, null);
});
}

_fetchNewRoutingTable(routerAddresses, routingTable) {
return routerAddresses.reduce((refreshedTablePromise, currentRouter, currentIndex) => {
return refreshedTablePromise.then(newRoutingTable => {
if (newRoutingTable) {
if (!newRoutingTable.writers.isEmpty()) {
Expand All @@ -120,28 +159,14 @@ export class LoadBalancer extends ConnectionProvider {
// returned routing table was undefined, this means a connection error happened and we need to forget the
// previous router and try the next one
const previousRouterIndex = currentIndex - 1;
this._forgetRouter(currentRoutingTable, knownRouters, previousRouterIndex);
LoadBalancer._forgetRouter(routingTable, routerAddresses, previousRouterIndex);
}

// try next router
const session = this._createSessionForRediscovery(currentRouter);
return this._rediscovery.lookupRoutingTableOnRouter(session, currentRouter);
});
}, Promise.resolve(null));

return refreshedTablePromise.then(newRoutingTable => {
if (newRoutingTable && !newRoutingTable.writers.isEmpty()) {
this._updateRoutingTable(newRoutingTable);
return newRoutingTable;
}

// forget the last known router because it did not return a valid routing table
const lastRouterIndex = knownRouters.length - 1;
this._forgetRouter(currentRoutingTable, knownRouters, lastRouterIndex);

// none of the existing routers returned valid routing table, throw exception
throw newError('Could not perform discovery. No routing servers available.', SERVICE_UNAVAILABLE);
});
}

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

_forgetRouter(routingTable, routersArray, routerIndex) {
static _isValidRoutingTable(routingTable) {
return routingTable && !routingTable.writers.isEmpty();
}

static _forgetRouter(routingTable, routersArray, routerIndex) {
const address = routersArray[routerIndex];
if (address) {
if (routingTable && address) {
routingTable.forgetRouter(address);
}
}

static _createHostNameResolver() {
if (hasFeature('dns_lookup')) {
return new DnsHostNameResolver();
}
return new DummyHostNameResolver();
}
}

export class SingleConnectionProvider extends ConnectionProvider {
Expand Down
2 changes: 2 additions & 0 deletions src/v1/internal/connector.js
Original file line number Diff line number Diff line change
Expand Up @@ -500,5 +500,7 @@ export {
connect,
parseScheme,
parseUrl,
parseHost,
parsePort,
Connection
}
11 changes: 11 additions & 0 deletions src/v1/internal/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ const FEATURES = {
} catch (e) {
return false;
}
},
dns_lookup: () => {
try {
const lookupFunction = require('dns').lookup;
if (lookupFunction && typeof lookupFunction === 'function') {
return true;
}
return false;
} catch (e) {
return false;
}
}
};

Expand Down
70 changes: 70 additions & 0 deletions src/v1/internal/host-name-resolvers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright (c) 2002-2017 "Neo Technology,","
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {parseHost, parsePort} from './connector';

class HostNameResolver {

resolve() {
throw new Error('Abstract function');
}
}

export class DummyHostNameResolver extends HostNameResolver {

resolve(seedRouter) {
return resolveToItself(seedRouter);
}
}

export class DnsHostNameResolver extends HostNameResolver {

constructor() {
super();
this._dns = require('dns');
}

resolve(seedRouter) {
const seedRouterHost = parseHost(seedRouter);
const seedRouterPort = parsePort(seedRouter);

return new Promise((resolve) => {
this._dns.lookup(seedRouterHost, {all: true}, (error, addresses) => {
if (error) {
resolve(resolveToItself(seedRouter));
} else {
const addressesWithPorts = addresses.map(address => addressWithPort(address, seedRouterPort));
resolve(addressesWithPorts);
}
});
});
}
}

function resolveToItself(address) {
return Promise.resolve([address]);
}

function addressWithPort(addressObject, port) {
const address = addressObject.address;
if (port) {
return address + ':' + port;
}
return address;
}
Loading