diff --git a/driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java b/driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java
index 066afa2aa2..b6b85d9749 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java
@@ -24,12 +24,14 @@
import java.net.URI;
import java.net.UnknownHostException;
+import org.neo4j.driver.v1.net.ServerAddress;
+
import static java.util.Objects.requireNonNull;
/**
* Holds a host and port pair that denotes a Bolt server address.
*/
-public class BoltServerAddress
+public class BoltServerAddress implements ServerAddress
{
public static final int DEFAULT_PORT = 7687;
public static final BoltServerAddress LOCAL_DEFAULT = new BoltServerAddress( "localhost", DEFAULT_PORT );
@@ -50,11 +52,18 @@ public BoltServerAddress( URI uri )
public BoltServerAddress( String host, int port )
{
- this.host = requireNonNull( host );
- this.port = port;
+ this.host = requireNonNull( host, "host" );
+ this.port = requireValidPort( port );
this.stringValue = String.format( "%s:%d", host, port );
}
+ public static BoltServerAddress from( ServerAddress address )
+ {
+ return address instanceof BoltServerAddress
+ ? (BoltServerAddress) address
+ : new BoltServerAddress( address.host(), address.port() );
+ }
+
@Override
public boolean equals( Object o )
{
@@ -114,11 +123,13 @@ public BoltServerAddress resolve() throws UnknownHostException
}
}
+ @Override
public String host()
{
return host;
}
+ @Override
public int port()
{
return port;
@@ -194,4 +205,13 @@ private static RuntimeException invalidAddressFormat( String address )
{
return new IllegalArgumentException( "Invalid address format `" + address + "`" );
}
+
+ private static int requireValidPort( int port )
+ {
+ if ( port >= 0 && port <= 65_535 )
+ {
+ return port;
+ }
+ throw new IllegalArgumentException( "Illegal port: " + port );
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java
index 9a05a2132d..1079634017 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java
@@ -31,6 +31,7 @@
import org.neo4j.driver.internal.async.ChannelConnectorImpl;
import org.neo4j.driver.internal.async.pool.ConnectionPoolImpl;
import org.neo4j.driver.internal.async.pool.PoolSettings;
+import org.neo4j.driver.internal.cluster.DnsResolver;
import org.neo4j.driver.internal.cluster.RoutingContext;
import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.cluster.loadbalancing.LeastConnectedLoadBalancingStrategy;
@@ -58,6 +59,7 @@
import org.neo4j.driver.v1.Logging;
import org.neo4j.driver.v1.exceptions.ClientException;
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
+import org.neo4j.driver.v1.net.ServerAddressResolver;
import static java.lang.String.format;
import static org.neo4j.driver.internal.metrics.InternalAbstractMetrics.DEV_NULL_METRICS;
@@ -204,8 +206,9 @@ protected LoadBalancer createLoadBalancer( BoltServerAddress address, Connection
EventExecutorGroup eventExecutorGroup, Config config, RoutingSettings routingSettings )
{
LoadBalancingStrategy loadBalancingStrategy = createLoadBalancingStrategy( config, connectionPool );
+ ServerAddressResolver resolver = createResolver( config );
return new LoadBalancer( address, routingSettings, connectionPool, eventExecutorGroup, createClock(),
- config.logging(), loadBalancingStrategy );
+ config.logging(), loadBalancingStrategy, resolver );
}
private static LoadBalancingStrategy createLoadBalancingStrategy( Config config,
@@ -222,6 +225,12 @@ private static LoadBalancingStrategy createLoadBalancingStrategy( Config config,
}
}
+ private static ServerAddressResolver createResolver( Config config )
+ {
+ ServerAddressResolver configuredResolver = config.resolver();
+ return configuredResolver != null ? configuredResolver : new DnsResolver( config.logging() );
+ }
+
/**
* Creates new {@link Clock}.
*
diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/DnsResolver.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/DnsResolver.java
index c3638e97e9..880ad56ffd 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/cluster/DnsResolver.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/DnsResolver.java
@@ -20,42 +20,40 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.util.HashSet;
import java.util.Set;
+import java.util.stream.Stream;
import org.neo4j.driver.internal.BoltServerAddress;
import org.neo4j.driver.v1.Logger;
+import org.neo4j.driver.v1.Logging;
+import org.neo4j.driver.v1.net.ServerAddress;
+import org.neo4j.driver.v1.net.ServerAddressResolver;
-public class DnsResolver implements HostNameResolver
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toSet;
+
+public class DnsResolver implements ServerAddressResolver
{
private final Logger logger;
- public DnsResolver( Logger logger )
+ public DnsResolver( Logging logging )
{
- this.logger = logger;
+ this.logger = logging.getLog( DnsResolver.class.getSimpleName() );
}
@Override
- public Set resolve( BoltServerAddress initialRouter )
+ public Set resolve( ServerAddress initialRouter )
{
- Set addresses = new HashSet<>();
try
{
- InetAddress[] ipAddresses = InetAddress.getAllByName( initialRouter.host() );
-
- for ( InetAddress ipAddress : ipAddresses )
- {
- addresses.add( new BoltServerAddress( ipAddress.getHostAddress(), initialRouter.port() ) );
- }
-
- return addresses;
+ return Stream.of( InetAddress.getAllByName( initialRouter.host() ) )
+ .map( address -> new BoltServerAddress( address.getHostAddress(), initialRouter.port() ) )
+ .collect( toSet() );
}
catch ( UnknownHostException e )
{
- logger.error( "Failed to resolve URI `" + initialRouter + "` to IPs due to error: " + e.getMessage(), e );
-
- addresses.add( initialRouter );
- return addresses;
+ logger.error( "Failed to resolve address `" + initialRouter + "` to IPs due to error: " + e.getMessage(), e );
+ return singleton( initialRouter );
}
}
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/Rediscovery.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/Rediscovery.java
index 0e854622ca..56db7d6a88 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/cluster/Rediscovery.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/Rediscovery.java
@@ -20,8 +20,8 @@
import io.netty.util.concurrent.EventExecutorGroup;
-import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -35,9 +35,13 @@
import org.neo4j.driver.v1.Logger;
import org.neo4j.driver.v1.exceptions.SecurityException;
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
+import org.neo4j.driver.v1.net.ServerAddressResolver;
import static java.lang.String.format;
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singletonList;
import static java.util.concurrent.CompletableFuture.completedFuture;
+import static java.util.stream.Collectors.toList;
import static org.neo4j.driver.internal.util.Futures.completedWithNull;
public class Rediscovery
@@ -48,27 +52,26 @@ public class Rediscovery
private final RoutingSettings settings;
private final Logger logger;
private final ClusterCompositionProvider provider;
- private final HostNameResolver hostNameResolver;
+ private final ServerAddressResolver resolver;
private final EventExecutorGroup eventExecutorGroup;
private volatile boolean useInitialRouter;
public Rediscovery( BoltServerAddress initialRouter, RoutingSettings settings, ClusterCompositionProvider provider,
- EventExecutorGroup eventExecutorGroup, HostNameResolver hostNameResolver, Logger logger )
+ EventExecutorGroup eventExecutorGroup, ServerAddressResolver resolver, Logger logger )
{
- this( initialRouter, settings, provider, hostNameResolver, eventExecutorGroup, logger, true );
+ this( initialRouter, settings, provider, resolver, eventExecutorGroup, logger, true );
}
// Test-only constructor
- public Rediscovery( BoltServerAddress initialRouter, RoutingSettings settings, ClusterCompositionProvider provider,
- HostNameResolver hostNameResolver, EventExecutorGroup eventExecutorGroup, Logger logger,
- boolean useInitialRouter )
+ Rediscovery( BoltServerAddress initialRouter, RoutingSettings settings, ClusterCompositionProvider provider,
+ ServerAddressResolver resolver, EventExecutorGroup eventExecutorGroup, Logger logger, boolean useInitialRouter )
{
this.initialRouter = initialRouter;
this.settings = settings;
this.logger = logger;
this.provider = provider;
- this.hostNameResolver = hostNameResolver;
+ this.resolver = resolver;
this.eventExecutorGroup = eventExecutorGroup;
this.useInitialRouter = useInitialRouter;
}
@@ -163,7 +166,7 @@ private CompletionStage lookupOnKnownRoutersThenOnInitialRou
private CompletionStage lookupOnInitialRouterThenOnKnownRouters( RoutingTable routingTable,
ConnectionPool connectionPool )
{
- Set seenServers = Collections.emptySet();
+ Set seenServers = emptySet();
return lookupOnInitialRouter( routingTable, connectionPool, seenServers ).thenCompose( composition ->
{
if ( composition != null )
@@ -201,7 +204,7 @@ private CompletionStage lookupOnKnownRouters( RoutingTable r
private CompletionStage lookupOnInitialRouter( RoutingTable routingTable,
ConnectionPool connectionPool, Set seenServers )
{
- Set addresses = hostNameResolver.resolve( initialRouter );
+ List addresses = resolve( initialRouter );
addresses.removeAll( seenServers );
CompletableFuture result = completedWithNull();
@@ -255,4 +258,19 @@ private ClusterComposition handleRoutingProcedureError( Throwable error, Routing
}
}
+ private List resolve( BoltServerAddress address )
+ {
+ try
+ {
+ return resolver.resolve( address )
+ .stream()
+ .map( BoltServerAddress::from )
+ .collect( toList() ); // collect to list to preserve the order
+ }
+ catch ( Throwable error )
+ {
+ logger.error( "Resolver function failed to resolve '" + address + "'. The address will be used as is", error );
+ return singletonList( address );
+ }
+ }
}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java
index a60e3546a3..4ceb337255 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java
+++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancer.java
@@ -30,7 +30,6 @@
import org.neo4j.driver.internal.cluster.ClusterComposition;
import org.neo4j.driver.internal.cluster.ClusterCompositionProvider;
import org.neo4j.driver.internal.cluster.ClusterRoutingTable;
-import org.neo4j.driver.internal.cluster.DnsResolver;
import org.neo4j.driver.internal.cluster.Rediscovery;
import org.neo4j.driver.internal.cluster.RoutingProcedureClusterCompositionProvider;
import org.neo4j.driver.internal.cluster.RoutingSettings;
@@ -45,6 +44,7 @@
import org.neo4j.driver.v1.Logging;
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
import org.neo4j.driver.v1.exceptions.SessionExpiredException;
+import org.neo4j.driver.v1.net.ServerAddressResolver;
import static java.util.concurrent.CompletableFuture.completedFuture;
@@ -63,15 +63,15 @@ public class LoadBalancer implements ConnectionProvider, RoutingErrorHandler
public LoadBalancer( BoltServerAddress initialRouter, RoutingSettings settings, ConnectionPool connectionPool,
EventExecutorGroup eventExecutorGroup, Clock clock, Logging logging,
- LoadBalancingStrategy loadBalancingStrategy )
+ LoadBalancingStrategy loadBalancingStrategy, ServerAddressResolver resolver )
{
this( connectionPool, new ClusterRoutingTable( clock, initialRouter ),
- createRediscovery( initialRouter, settings, eventExecutorGroup, clock, logging ),
+ createRediscovery( initialRouter, settings, eventExecutorGroup, resolver, clock, logging ),
loadBalancerLogger( logging ), loadBalancingStrategy, eventExecutorGroup );
}
// Used only in testing
- public LoadBalancer( ConnectionPool connectionPool, RoutingTable routingTable, Rediscovery rediscovery,
+ LoadBalancer( ConnectionPool connectionPool, RoutingTable routingTable, Rediscovery rediscovery,
EventExecutorGroup eventExecutorGroup, Logging logging )
{
this( connectionPool, routingTable, rediscovery, loadBalancerLogger( logging ),
@@ -264,13 +264,11 @@ private BoltServerAddress selectAddress( AccessMode mode, AddressSet servers )
}
private static Rediscovery createRediscovery( BoltServerAddress initialRouter, RoutingSettings settings,
- EventExecutorGroup eventExecutorGroup, Clock clock, Logging logging )
+ EventExecutorGroup eventExecutorGroup, ServerAddressResolver resolver, Clock clock, Logging logging )
{
Logger log = loadBalancerLogger( logging );
- ClusterCompositionProvider clusterCompositionProvider =
- new RoutingProcedureClusterCompositionProvider( clock, settings );
- return new Rediscovery( initialRouter, settings, clusterCompositionProvider, eventExecutorGroup,
- new DnsResolver( log ), log );
+ ClusterCompositionProvider clusterCompositionProvider = new RoutingProcedureClusterCompositionProvider( clock, settings );
+ return new Rediscovery( initialRouter, settings, clusterCompositionProvider, eventExecutorGroup, resolver, log );
}
private static Logger loadBalancerLogger( Logging logging )
diff --git a/driver/src/main/java/org/neo4j/driver/v1/Config.java b/driver/src/main/java/org/neo4j/driver/v1/Config.java
index 3224cc4a1b..50073aef19 100644
--- a/driver/src/main/java/org/neo4j/driver/v1/Config.java
+++ b/driver/src/main/java/org/neo4j/driver/v1/Config.java
@@ -19,6 +19,8 @@
package org.neo4j.driver.v1;
import java.io.File;
+import java.net.InetAddress;
+import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@@ -28,6 +30,7 @@
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
import org.neo4j.driver.v1.exceptions.SessionExpiredException;
import org.neo4j.driver.v1.exceptions.TransientException;
+import org.neo4j.driver.v1.net.ServerAddressResolver;
import org.neo4j.driver.v1.util.Experimental;
import org.neo4j.driver.v1.util.Immutable;
import org.neo4j.driver.v1.util.Resource;
@@ -88,6 +91,7 @@ public class Config
private final RetrySettings retrySettings;
private final LoadBalancingStrategy loadBalancingStrategy;
+ private final ServerAddressResolver resolver;
private Config( ConfigBuilder builder)
{
@@ -106,6 +110,7 @@ private Config( ConfigBuilder builder)
this.connectionTimeoutMillis = builder.connectionTimeoutMillis;
this.retrySettings = builder.retrySettings;
this.loadBalancingStrategy = builder.loadBalancingStrategy;
+ this.resolver = builder.resolver;
}
/**
@@ -216,9 +221,9 @@ public TrustStrategy trustStrategy()
}
/**
- * Load balancing strategy
+ * Load balancing strategy.
*
- * @return the strategy to use
+ * @return the strategy to use.
*/
@Experimental
public LoadBalancingStrategy loadBalancingStrategy()
@@ -226,6 +231,16 @@ public LoadBalancingStrategy loadBalancingStrategy()
return loadBalancingStrategy;
}
+ /**
+ * Server address resolver.
+ *
+ * @return the resolver to use.
+ */
+ public ServerAddressResolver resolver()
+ {
+ return resolver;
+ }
+
/**
* Return a {@link ConfigBuilder} instance
* @return a {@link ConfigBuilder} instance
@@ -271,6 +286,7 @@ public static class ConfigBuilder
private long routingRetryDelayMillis = RoutingSettings.DEFAULT.retryTimeoutDelay();
private int connectionTimeoutMillis = (int) TimeUnit.SECONDS.toMillis( 5 );
private RetrySettings retrySettings = RetrySettings.DEFAULT;
+ private ServerAddressResolver resolver;
private ConfigBuilder() {}
@@ -711,6 +727,25 @@ public ConfigBuilder withMaxTransactionRetryTime( long value, TimeUnit unit )
return this;
}
+ /**
+ * Specify a custom server address resolver used by the routing driver to resolve the initial address used to create the driver.
+ * Such resolution happens:
+ *
+ * - during the very first rediscovery when driver is created
+ * - when all the known routers from the current routing table have failed and driver needs to fallback to the initial address
+ *
+ * By default driver performs a DNS lookup for the initial address using {@link InetAddress#getAllByName(String)}.
+ *
+ * @param resolver the resolver to use.
+ * @return this builder.
+ * @throws NullPointerException when the given resolver is {@code null}.
+ */
+ public ConfigBuilder withResolver( ServerAddressResolver resolver )
+ {
+ this.resolver = Objects.requireNonNull( resolver, "resolver" );
+ return this;
+ }
+
/**
* Create a config instance from this builder.
* @return a {@link Config} instance
diff --git a/driver/src/main/java/org/neo4j/driver/v1/net/ServerAddress.java b/driver/src/main/java/org/neo4j/driver/v1/net/ServerAddress.java
new file mode 100644
index 0000000000..9e638fd477
--- /dev/null
+++ b/driver/src/main/java/org/neo4j/driver/v1/net/ServerAddress.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2002-2018 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.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.
+ */
+package org.neo4j.driver.v1.net;
+
+import org.neo4j.driver.internal.BoltServerAddress;
+
+/**
+ * Represents a host and port. Host can either be an IP address or a DNS name.
+ * Both IPv4 and IPv6 hosts are supported.
+ */
+public interface ServerAddress
+{
+ /**
+ * Retrieve the host portion of this {@link ServerAddress}.
+ *
+ * @return the host, never {@code null}.
+ */
+ String host();
+
+ /**
+ * Retrieve the port portion of this {@link ServerAddress}.
+ *
+ * @return the port, always in range [0, 65535].
+ */
+ int port();
+
+ /**
+ * Create a new address with the given host and port.
+ *
+ * @param host the host portion. Should not be {@code null}.
+ * @param port the port portion. Should be in range [0, 65535].
+ * @return new server address with the specified host and port.
+ */
+ static ServerAddress of( String host, int port )
+ {
+ return new BoltServerAddress( host, port );
+ }
+}
diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/HostNameResolver.java b/driver/src/main/java/org/neo4j/driver/v1/net/ServerAddressResolver.java
similarity index 56%
rename from driver/src/main/java/org/neo4j/driver/internal/cluster/HostNameResolver.java
rename to driver/src/main/java/org/neo4j/driver/v1/net/ServerAddressResolver.java
index d5f575f071..ab5e64433f 100644
--- a/driver/src/main/java/org/neo4j/driver/internal/cluster/HostNameResolver.java
+++ b/driver/src/main/java/org/neo4j/driver/v1/net/ServerAddressResolver.java
@@ -16,13 +16,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.neo4j.driver.internal.cluster;
+package org.neo4j.driver.v1.net;
import java.util.Set;
-import org.neo4j.driver.internal.BoltServerAddress;
-
-public interface HostNameResolver
+/**
+ * A resolver function used by the routing driver to resolve the initial address used to create the driver.
+ */
+@FunctionalInterface
+public interface ServerAddressResolver
{
- Set resolve( BoltServerAddress initialRouter );
+ /**
+ * Resolve the given address to a set of other addresses.
+ * Exceptions thrown by this method will be logged and driver will continue using the original address.
+ *
+ * @param address the address to resolve.
+ * @return new set of addresses.
+ */
+ Set resolve( ServerAddress address );
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/DnsResolverTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/DnsResolverTest.java
index 224dafcac9..0633227ad4 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/cluster/DnsResolverTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/DnsResolverTest.java
@@ -25,6 +25,8 @@
import org.neo4j.driver.internal.BoltServerAddress;
import org.neo4j.driver.v1.Logger;
+import org.neo4j.driver.v1.Logging;
+import org.neo4j.driver.v1.net.ServerAddress;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.junit.MatcherAssert.assertThat;
@@ -34,33 +36,33 @@
class DnsResolverTest
{
- private DnsResolver resolver = new DnsResolver( mock( Logger.class ) );
+ private DnsResolver resolver = new DnsResolver( Logging.none() );
@Test
void shouldResolveDNSToIPs()
{
- Set resolve = resolver.resolve( new BoltServerAddress( "google.com", 80 ) );
+ Set resolve = resolver.resolve( new BoltServerAddress( "google.com", 80 ) );
assertThat( resolve.size(), greaterThanOrEqualTo( 1 ) );
}
@Test
void shouldResolveLocalhostIPDNSToIPs()
{
- Set resolve = resolver.resolve( new BoltServerAddress( "127.0.0.1", 80 ) );
+ Set resolve = resolver.resolve( new BoltServerAddress( "127.0.0.1", 80 ) );
assertThat( resolve.size(), greaterThanOrEqualTo( 1 ) );
}
@Test
void shouldResolveLocalhostDNSToIPs()
{
- Set resolve = resolver.resolve( new BoltServerAddress( "localhost", 80 ) );
+ Set resolve = resolver.resolve( new BoltServerAddress( "localhost", 80 ) );
assertThat( resolve.size(), greaterThanOrEqualTo( 1 ) );
}
@Test
void shouldResolveIPv6LocalhostDNSToIPs()
{
- Set resolve = resolver.resolve( new BoltServerAddress( "[::1]", 80 ) );
+ Set resolve = resolver.resolve( new BoltServerAddress( "[::1]", 80 ) );
assertThat( resolve.size(), greaterThanOrEqualTo( 1 ) );
}
@@ -68,8 +70,8 @@ void shouldResolveIPv6LocalhostDNSToIPs()
void shouldExceptionAndGiveDefaultValue()
{
Logger logger = mock( Logger.class );
- DnsResolver resolver = new DnsResolver( logger );
- Set resolve = resolver.resolve( new BoltServerAddress( "[/]", 80 ) );
+ DnsResolver resolver = new DnsResolver( name -> logger );
+ Set resolve = resolver.resolve( new BoltServerAddress( "[/]", 80 ) );
verify( logger ).error( any( String.class ), any( UnknownHostException.class ) );
assertThat( resolve.size(), greaterThanOrEqualTo( 1 ) );
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java
index c15625dce5..95aee9a3d4 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java
@@ -37,6 +37,7 @@
import org.neo4j.driver.v1.exceptions.ProtocolException;
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
import org.neo4j.driver.v1.exceptions.SessionExpiredException;
+import org.neo4j.driver.v1.net.ServerAddressResolver;
import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
@@ -76,7 +77,7 @@ void shouldUseFirstRouterInTable()
responsesByAddress.put( B, new Success( expectedComposition ) ); // first -> valid cluster composition
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( HostNameResolver.class ) );
+ Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) );
RoutingTable table = routingTableMock( B );
ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) );
@@ -97,7 +98,7 @@ void shouldSkipFailingRouters()
responsesByAddress.put( C, new Success( expectedComposition ) ); // third -> valid cluster composition
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( HostNameResolver.class ) );
+ Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) );
RoutingTable table = routingTableMock( A, B, C );
ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) );
@@ -119,7 +120,7 @@ void shouldFailImmediatelyOnAuthError()
responsesByAddress.put( B, authError ); // second router -> fatal auth error
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( HostNameResolver.class ) );
+ Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) );
RoutingTable table = routingTableMock( A, B, C );
AuthenticationException error = assertThrows( AuthenticationException.class, () -> await( rediscovery.lookupClusterComposition( table, pool ) ) );
@@ -140,7 +141,7 @@ void shouldFallbackToInitialRouterWhenKnownRoutersFail()
responsesByAddress.put( initialRouter, new Success( expectedComposition ) ); // initial -> valid response
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- HostNameResolver resolver = hostNameResolverMock( initialRouter, initialRouter );
+ ServerAddressResolver resolver = resolverMock( initialRouter, initialRouter );
Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver );
RoutingTable table = routingTableMock( B, C );
@@ -163,7 +164,7 @@ void shouldFailImmediatelyWhenClusterCompositionProviderReturnsFailure()
responsesByAddress.put( C, new Success( validComposition ) ); // second -> valid cluster composition
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( HostNameResolver.class ) );
+ Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) );
RoutingTable table = routingTableMock( B, C );
ProtocolException error = assertThrows( ProtocolException.class, () -> await( rediscovery.lookupClusterComposition( table, pool ) ) );
@@ -185,7 +186,7 @@ void shouldResolveInitialRouterAddress()
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
// initial router resolved to two other addresses
- HostNameResolver resolver = hostNameResolverMock( initialRouter, D, E );
+ ServerAddressResolver resolver = resolverMock( initialRouter, D, E );
Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver );
RoutingTable table = routingTableMock( B, C );
@@ -197,6 +198,57 @@ void shouldResolveInitialRouterAddress()
verify( table ).forget( D );
}
+ @Test
+ void shouldResolveInitialRouterAddressUsingCustomResolver()
+ {
+ ClusterComposition expectedComposition = new ClusterComposition( 42,
+ asOrderedSet( A, B, C ), asOrderedSet( A, B, C ), asOrderedSet( B, E ) );
+
+ ServerAddressResolver resolver = address ->
+ {
+ assertEquals( A, address );
+ return asOrderedSet( B, C, E );
+ };
+
+ Map responsesByAddress = new HashMap<>();
+ responsesByAddress.put( B, new ServiceUnavailableException( "Hi!" ) ); // first -> non-fatal failure
+ responsesByAddress.put( C, new ServiceUnavailableException( "Hi!" ) ); // second -> non-fatal failure
+ responsesByAddress.put( E, new Success( expectedComposition ) ); // resolved second -> valid response
+
+ ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
+ Rediscovery rediscovery = newRediscovery( A, compositionProvider, resolver );
+ RoutingTable table = routingTableMock( B, C );
+
+ ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) );
+
+ assertEquals( expectedComposition, actualComposition );
+ verify( table ).forget( B );
+ verify( table ).forget( C );
+ }
+
+ @Test
+ void shouldUseInitialRouterAddressAsIsWhenResolverFails()
+ {
+ ClusterComposition expectedComposition = new ClusterComposition( 42,
+ asOrderedSet( A, B ), asOrderedSet( A, B ), asOrderedSet( A, B ) );
+
+ Map responsesByAddress = singletonMap( A, new Success( expectedComposition ) );
+ ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
+
+ // failing server address resolver
+ ServerAddressResolver resolver = mock( ServerAddressResolver.class );
+ when( resolver.resolve( A ) ).thenThrow( new RuntimeException( "Resolver fails!" ) );
+
+ Rediscovery rediscovery = newRediscovery( A, compositionProvider, resolver );
+ RoutingTable table = routingTableMock();
+
+ ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool ) );
+
+ assertEquals( expectedComposition, actualComposition );
+ verify( resolver ).resolve( A );
+ verify( table, never() ).forget( any() );
+ }
+
@Test
void shouldFailWhenNoRoutersRespond()
{
@@ -206,7 +258,7 @@ void shouldFailWhenNoRoutersRespond()
responsesByAddress.put( C, new IOException( "Hi!" ) ); // third -> non-fatal failure
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( HostNameResolver.class ) );
+ Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) );
RoutingTable table = routingTableMock( A, B, C );
ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> await( rediscovery.lookupClusterComposition( table, pool ) ) );
@@ -227,7 +279,7 @@ void shouldUseInitialRouterAfterDiscoveryReturnsNoWriters()
responsesByAddress.put( initialRouter, new Success( validComposition ) ); // initial -> valid composition
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- HostNameResolver resolver = hostNameResolverMock( initialRouter, initialRouter );
+ ServerAddressResolver resolver = resolverMock( initialRouter, initialRouter );
Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver );
RoutingTable table = routingTableMock( B );
@@ -249,7 +301,7 @@ void shouldUseInitialRouterToStartWith()
responsesByAddress.put( initialRouter, new Success( validComposition ) ); // initial -> valid composition
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- HostNameResolver resolver = hostNameResolverMock( initialRouter, initialRouter );
+ ServerAddressResolver resolver = resolverMock( initialRouter, initialRouter );
Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver, true );
RoutingTable table = routingTableMock( B, C, D );
@@ -270,7 +322,7 @@ void shouldUseKnownRoutersWhenInitialRouterFails()
responsesByAddress.put( E, new Success( validComposition ) ); // second known -> valid composition
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- HostNameResolver resolver = hostNameResolverMock( initialRouter, initialRouter );
+ ServerAddressResolver resolver = resolverMock( initialRouter, initialRouter );
Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver, true );
RoutingTable table = routingTableMock( D, E );
@@ -294,7 +346,7 @@ void shouldRetryConfiguredNumberOfTimesWithDelay()
responsesByAddress.put( E, new Success( expectedComposition ) );
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- HostNameResolver resolver = mock( HostNameResolver.class );
+ ServerAddressResolver resolver = mock( ServerAddressResolver.class );
when( resolver.resolve( A ) ).thenReturn( asOrderedSet( A ) )
.thenReturn( asOrderedSet( A ) )
.thenReturn( asOrderedSet( E ) );
@@ -321,7 +373,7 @@ void shouldNotLogWhenSingleRetryAttemptFails()
Map responsesByAddress = singletonMap( A, new ServiceUnavailableException( "Hi!" ) );
ClusterCompositionProvider compositionProvider = compositionProviderMock( responsesByAddress );
- HostNameResolver resolver = hostNameResolverMock( A, A );
+ ServerAddressResolver resolver = resolverMock( A, A );
ImmediateSchedulingEventExecutor eventExecutor = new ImmediateSchedulingEventExecutor();
RoutingSettings settings = new RoutingSettings( maxRoutingFailures, retryTimeoutDelay );
@@ -339,16 +391,16 @@ void shouldNotLogWhenSingleRetryAttemptFails()
}
private Rediscovery newRediscovery( BoltServerAddress initialRouter, ClusterCompositionProvider compositionProvider,
- HostNameResolver hostNameResolver )
+ ServerAddressResolver resolver )
{
- return newRediscovery( initialRouter, compositionProvider, hostNameResolver, false );
+ return newRediscovery( initialRouter, compositionProvider, resolver, false );
}
private Rediscovery newRediscovery( BoltServerAddress initialRouter, ClusterCompositionProvider compositionProvider,
- HostNameResolver hostNameResolver, boolean useInitialRouter )
+ ServerAddressResolver resolver, boolean useInitialRouter )
{
RoutingSettings settings = new RoutingSettings( 1, 0 );
- return new Rediscovery( initialRouter, settings, compositionProvider, hostNameResolver,
+ return new Rediscovery( initialRouter, settings, compositionProvider, resolver,
GlobalEventExecutor.INSTANCE, DEV_NULL_LOGGER, useInitialRouter );
}
@@ -375,9 +427,9 @@ private static ClusterCompositionProvider compositionProviderMock(
return provider;
}
- private static HostNameResolver hostNameResolverMock( BoltServerAddress address, BoltServerAddress... resolved )
+ private static ServerAddressResolver resolverMock( BoltServerAddress address, BoltServerAddress... resolved )
{
- HostNameResolver resolver = mock( HostNameResolver.class );
+ ServerAddressResolver resolver = mock( ServerAddressResolver.class );
when( resolver.resolve( address ) ).thenReturn( asOrderedSet( resolved ) );
return resolver;
}
diff --git a/driver/src/test/java/org/neo4j/driver/internal/net/BoltServerAddressTest.java b/driver/src/test/java/org/neo4j/driver/internal/net/BoltServerAddressTest.java
index 0f5f8d4483..b3200a3b75 100644
--- a/driver/src/test/java/org/neo4j/driver/internal/net/BoltServerAddressTest.java
+++ b/driver/src/test/java/org/neo4j/driver/internal/net/BoltServerAddressTest.java
@@ -23,11 +23,16 @@
import java.net.SocketAddress;
import org.neo4j.driver.internal.BoltServerAddress;
+import org.neo4j.driver.v1.net.ServerAddress;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.junit.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.neo4j.driver.internal.BoltServerAddress.DEFAULT_PORT;
class BoltServerAddressTest
@@ -61,4 +66,53 @@ void shouldHaveCorrectToString()
assertEquals( "localhost:4242", new BoltServerAddress( "localhost", 4242 ).toString() );
assertEquals( "127.0.0.1:8888", new BoltServerAddress( "127.0.0.1", 8888 ).toString() );
}
+
+ @Test
+ void shouldVerifyHost()
+ {
+ assertThrows( NullPointerException.class, () -> new BoltServerAddress( null, 0 ) );
+ }
+
+ @Test
+ void shouldVerifyPort()
+ {
+ assertThrows( IllegalArgumentException.class, () -> new BoltServerAddress( "localhost", -1 ) );
+ assertThrows( IllegalArgumentException.class, () -> new BoltServerAddress( "localhost", -42 ) );
+ assertThrows( IllegalArgumentException.class, () -> new BoltServerAddress( "localhost", 65_536 ) );
+ assertThrows( IllegalArgumentException.class, () -> new BoltServerAddress( "localhost", 99_999 ) );
+ }
+
+ @Test
+ void shouldCreateBoltServerAddressFromServerAddress()
+ {
+ BoltServerAddress address1 = new BoltServerAddress( "my.server.com", 8899 );
+ assertSame( address1, BoltServerAddress.from( address1 ) );
+
+ BoltServerAddress address2 = new BoltServerAddress( "db.neo4j.com" );
+ assertSame( address2, BoltServerAddress.from( address2 ) );
+
+ ServerAddress address3 = mock( ServerAddress.class );
+ when( address3.host() ).thenReturn( "graph.database.com" );
+ when( address3.port() ).thenReturn( 20600 );
+ assertEquals( new BoltServerAddress( "graph.database.com", 20600 ), BoltServerAddress.from( address3 ) );
+ }
+
+ @Test
+ void shouldFailToCreateBoltServerAddressFromInvalidServerAddress()
+ {
+ ServerAddress address1 = mock( ServerAddress.class );
+ when( address1.host() ).thenReturn( null );
+ when( address1.port() ).thenReturn( 8888 );
+ assertThrows( NullPointerException.class, () -> BoltServerAddress.from( address1 ) );
+
+ ServerAddress address2 = mock( ServerAddress.class );
+ when( address2.host() ).thenReturn( "neo4j.host.com" );
+ when( address2.port() ).thenReturn( -1 );
+ assertThrows( IllegalArgumentException.class, () -> BoltServerAddress.from( address2 ) );
+
+ ServerAddress address3 = mock( ServerAddress.class );
+ when( address3.host() ).thenReturn( "my.database.org" );
+ when( address3.port() ).thenReturn( 99_000 );
+ assertThrows( IllegalArgumentException.class, () -> BoltServerAddress.from( address3 ) );
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/v1/ConfigTest.java b/driver/src/test/java/org/neo4j/driver/v1/ConfigTest.java
index 34ea86588c..2548320a32 100644
--- a/driver/src/test/java/org/neo4j/driver/v1/ConfigTest.java
+++ b/driver/src/test/java/org/neo4j/driver/v1/ConfigTest.java
@@ -23,11 +23,14 @@
import java.io.File;
import java.util.concurrent.TimeUnit;
+import org.neo4j.driver.v1.net.ServerAddressResolver;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
class ConfigTest
{
@@ -271,4 +274,19 @@ void shouldEnableAndDisableHostnameVerificationOnTrustStrategy()
assertSame( trustStrategy, trustStrategy.withoutHostnameVerification() );
assertFalse( trustStrategy.isHostnameVerificationEnabled() );
}
+
+ @Test
+ void shouldAllowToConfigureResolver()
+ {
+ ServerAddressResolver resolver = mock( ServerAddressResolver.class );
+ Config config = Config.build().withResolver( resolver ).toConfig();
+
+ assertEquals( resolver, config.resolver() );
+ }
+
+ @Test
+ void shouldNotAllowNullResolver()
+ {
+ assertThrows( NullPointerException.class, () -> Config.build().withResolver( null ) );
+ }
}
diff --git a/driver/src/test/java/org/neo4j/driver/v1/net/ServerAddressTest.java b/driver/src/test/java/org/neo4j/driver/v1/net/ServerAddressTest.java
new file mode 100644
index 0000000000..da71e2e166
--- /dev/null
+++ b/driver/src/test/java/org/neo4j/driver/v1/net/ServerAddressTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2002-2018 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.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.
+ */
+package org.neo4j.driver.v1.net;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ServerAddressTest
+{
+ @Test
+ void shouldCreateAddress()
+ {
+ ServerAddress address = ServerAddress.of( "my.database.com", 8897 );
+ assertEquals( "my.database.com", address.host() );
+ assertEquals( 8897, address.port() );
+ }
+
+ @Test
+ void shouldFailToCreateAddressWithInvalidHost()
+ {
+ assertThrows( NullPointerException.class, () -> ServerAddress.of( null, 9999 ) );
+ }
+
+ @Test
+ void shouldFailToCreateAddressWithInvalidPort()
+ {
+ assertThrows( IllegalArgumentException.class, () -> ServerAddress.of( "hello.graphs.com", -42 ) );
+ assertThrows( IllegalArgumentException.class, () -> ServerAddress.of( "hello.graphs.com", 66_000 ) );
+ }
+}