Skip to content

Commit 371e202

Browse files
committed
Fix for initial routers DNS resolution
This update fixes the following issue: neo4j#833 The driver will resolve all initial router domain names to IPs and will try them all until one is successful. Additional items: - a new config option to make the domain name resolution configurable, which is currently used for testing - the testkit backend has been updated to support the domain name resolution configuration (a new test has been added to testkit to cover the issue described above) - the testkit backend has been updated to support connection timeout driver configuration - several tests have been updated
1 parent 47db433 commit 371e202

29 files changed

+467
-177
lines changed

driver/src/main/java/org/neo4j/driver/Config.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
import java.util.concurrent.TimeUnit;
2525
import java.util.logging.Level;
2626

27+
import org.neo4j.driver.internal.DefaultDomainNameResolver;
2728
import org.neo4j.driver.internal.RevocationStrategy;
2829
import org.neo4j.driver.internal.SecuritySettings;
2930
import org.neo4j.driver.internal.async.pool.PoolSettings;
3031
import org.neo4j.driver.internal.cluster.RoutingSettings;
3132
import org.neo4j.driver.internal.handlers.pulln.FetchSizeUtil;
3233
import org.neo4j.driver.internal.retry.RetrySettings;
34+
import org.neo4j.driver.net.DomainNameResolver;
3335
import org.neo4j.driver.net.ServerAddressResolver;
3436
import org.neo4j.driver.util.Immutable;
3537

@@ -88,6 +90,7 @@ public class Config
8890
private final int connectionTimeoutMillis;
8991
private final RetrySettings retrySettings;
9092
private final ServerAddressResolver resolver;
93+
private final DomainNameResolver domainNameResolver;
9194

9295
private final boolean isMetricsEnabled;
9396
private final int eventLoopThreads;
@@ -112,6 +115,7 @@ private Config( ConfigBuilder builder )
112115
this.routingTablePurgeDelayMillis = builder.routingTablePurgeDelayMillis;
113116
this.retrySettings = builder.retrySettings;
114117
this.resolver = builder.resolver;
118+
this.domainNameResolver = builder.domainNameResolver;
115119
this.fetchSize = builder.fetchSize;
116120

117121
this.eventLoopThreads = builder.eventLoopThreads;
@@ -202,6 +206,16 @@ public ServerAddressResolver resolver()
202206
return resolver;
203207
}
204208

209+
/**
210+
* Domain name resolver.
211+
*
212+
* @return the resolver to use.
213+
*/
214+
public DomainNameResolver domainNameResolver()
215+
{
216+
return domainNameResolver;
217+
}
218+
205219
/**
206220
* Start building a {@link Config} object using a newly created builder.
207221
*
@@ -283,6 +297,7 @@ public static class ConfigBuilder
283297
private int connectionTimeoutMillis = (int) TimeUnit.SECONDS.toMillis( 30 );
284298
private RetrySettings retrySettings = RetrySettings.DEFAULT;
285299
private ServerAddressResolver resolver;
300+
private DomainNameResolver domainNameResolver = new DefaultDomainNameResolver();
286301
private boolean isMetricsEnabled = false;
287302
private long fetchSize = FetchSizeUtil.DEFAULT_FETCH_SIZE;
288303
private int eventLoopThreads = 0;
@@ -695,8 +710,23 @@ public ConfigBuilder withResolver( ServerAddressResolver resolver )
695710
return this;
696711
}
697712

713+
/**
714+
* Specify a custom domain name resolver used by the driver to resolve domain names.
715+
* <p>
716+
* Default implementation uses the {@link InetAddress#getAllByName(String)}.
717+
*
718+
* @param domainNameResolver the resolver to use.
719+
* @return this builder.
720+
*/
721+
public ConfigBuilder withDomainNameResolver( DomainNameResolver domainNameResolver )
722+
{
723+
this.domainNameResolver = Objects.requireNonNull( domainNameResolver, "domainNameResolver" );
724+
return this;
725+
}
726+
698727
/**
699728
* Enable driver metrics. The metrics can be obtained afterwards via {@link Driver#metrics()}.
729+
*
700730
* @return this builder.
701731
*/
702732
public ConfigBuilder withDriverMetrics()

driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,11 @@
2222
import java.net.InetSocketAddress;
2323
import java.net.SocketAddress;
2424
import java.net.URI;
25-
import java.net.UnknownHostException;
26-
import java.util.List;
2725
import java.util.Objects;
28-
import java.util.stream.Stream;
2926

3027
import org.neo4j.driver.net.ServerAddress;
3128

3229
import static java.util.Objects.requireNonNull;
33-
import static java.util.stream.Collectors.toList;
3430

3531
/**
3632
* Holds a host and port pair that denotes a Bolt server address.
@@ -44,8 +40,6 @@ public class BoltServerAddress implements ServerAddress
4440
private final int port;
4541
private final String stringValue;
4642

47-
private InetAddress resolved;
48-
4943
public BoltServerAddress( String address )
5044
{
5145
this( uriFrom( address ) );
@@ -57,16 +51,10 @@ public BoltServerAddress( URI uri )
5751
}
5852

5953
public BoltServerAddress( String host, int port )
60-
{
61-
this( host, null, port );
62-
}
63-
64-
private BoltServerAddress( String host, InetAddress resolved, int port )
6554
{
6655
this.host = requireNonNull( host, "host" );
67-
this.resolved = resolved;
6856
this.port = requireValidPort( port );
69-
this.stringValue = resolved != null ? String.format( "%s(%s):%d", host, resolved.getHostAddress(), port ) : String.format( "%s:%d", host, port );
57+
this.stringValue = String.format( "%s:%d", host, port );
7058
}
7159

7260
public static BoltServerAddress from( ServerAddress address )
@@ -112,33 +100,7 @@ public String toString()
112100
*/
113101
public SocketAddress toSocketAddress()
114102
{
115-
return resolved == null ? new InetSocketAddress( host, port ) : new InetSocketAddress( resolved, port );
116-
}
117-
118-
/**
119-
* Resolve the host name down to an IP address
120-
*
121-
* @return a new address instance
122-
* @throws UnknownHostException if no IP address for the host could be found
123-
* @see InetAddress#getByName(String)
124-
*/
125-
public BoltServerAddress resolve() throws UnknownHostException
126-
{
127-
return new BoltServerAddress( host, InetAddress.getByName( host ), port );
128-
}
129-
130-
/**
131-
* Resolve the host name down to all IP addresses that can be resolved to
132-
*
133-
* @return an array of new address instances that holds resolved addresses
134-
* @throws UnknownHostException if no IP address for the host could be found
135-
* @see InetAddress#getAllByName(String)
136-
*/
137-
public List<BoltServerAddress> resolveAll() throws UnknownHostException
138-
{
139-
return Stream.of( InetAddress.getAllByName( host ) )
140-
.map( address -> new BoltServerAddress( host, address, port ) )
141-
.collect( toList() );
103+
return new InetSocketAddress( host, port );
142104
}
143105

144106
@Override
@@ -153,11 +115,6 @@ public int port()
153115
return port;
154116
}
155117

156-
public boolean isResolved()
157-
{
158-
return resolved != null;
159-
}
160-
161118
private static String hostFrom( URI uri )
162119
{
163120
String host = uri.getHost();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.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+
package org.neo4j.driver.internal;
20+
21+
import java.net.InetAddress;
22+
import java.net.UnknownHostException;
23+
24+
import org.neo4j.driver.net.DomainNameResolver;
25+
26+
public class DefaultDomainNameResolver implements DomainNameResolver
27+
{
28+
@Override
29+
public InetAddress[] resolve( String name ) throws UnknownHostException
30+
{
31+
return InetAddress.getAllByName( name );
32+
}
33+
}

driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ protected static MetricsProvider createDriverMetrics( Config config, Clock clock
127127
protected ChannelConnector createConnector( ConnectionSettings settings, SecurityPlan securityPlan,
128128
Config config, Clock clock, RoutingContext routingContext )
129129
{
130-
return new ChannelConnectorImpl( settings, securityPlan, config.logging(), clock, routingContext );
130+
return new ChannelConnectorImpl( settings, securityPlan, config.logging(), clock, routingContext, config.domainNameResolver() );
131131
}
132132

133133
private InternalDriver createDriver( URI uri, SecurityPlan securityPlan, BoltServerAddress address, ConnectionPool connectionPool,
@@ -210,7 +210,7 @@ protected LoadBalancer createLoadBalancer( BoltServerAddress address, Connection
210210
LoadBalancingStrategy loadBalancingStrategy = new LeastConnectedLoadBalancingStrategy( connectionPool, config.logging() );
211211
ServerAddressResolver resolver = createResolver( config );
212212
return new LoadBalancer( address, routingSettings, connectionPool, eventExecutorGroup, createClock(),
213-
config.logging(), loadBalancingStrategy, resolver );
213+
config.logging(), loadBalancingStrategy, resolver, config.domainNameResolver() );
214214
}
215215

216216
private static ServerAddressResolver createResolver( Config config )

driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,22 @@
2424
import io.netty.channel.ChannelOption;
2525
import io.netty.channel.ChannelPipeline;
2626
import io.netty.channel.ChannelPromise;
27+
import io.netty.resolver.AddressResolverGroup;
2728

28-
import java.util.Map;
29+
import java.net.InetSocketAddress;
2930

31+
import org.neo4j.driver.AuthToken;
32+
import org.neo4j.driver.AuthTokens;
33+
import org.neo4j.driver.Logging;
34+
import org.neo4j.driver.exceptions.ClientException;
3035
import org.neo4j.driver.internal.BoltServerAddress;
3136
import org.neo4j.driver.internal.ConnectionSettings;
3237
import org.neo4j.driver.internal.async.inbound.ConnectTimeoutHandler;
3338
import org.neo4j.driver.internal.cluster.RoutingContext;
3439
import org.neo4j.driver.internal.security.InternalAuthToken;
3540
import org.neo4j.driver.internal.security.SecurityPlan;
3641
import org.neo4j.driver.internal.util.Clock;
37-
import org.neo4j.driver.AuthToken;
38-
import org.neo4j.driver.AuthTokens;
39-
import org.neo4j.driver.Logging;
40-
import org.neo4j.driver.Value;
41-
import org.neo4j.driver.exceptions.ClientException;
42+
import org.neo4j.driver.net.DomainNameResolver;
4243

4344
import static java.util.Objects.requireNonNull;
4445

@@ -52,15 +53,17 @@ public class ChannelConnectorImpl implements ChannelConnector
5253
private final int connectTimeoutMillis;
5354
private final Logging logging;
5455
private final Clock clock;
56+
private final AddressResolverGroup<InetSocketAddress> addressResolverGroup;
5557

5658
public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan securityPlan, Logging logging,
57-
Clock clock, RoutingContext routingContext )
59+
Clock clock, RoutingContext routingContext, DomainNameResolver domainNameResolver )
5860
{
59-
this( connectionSettings, securityPlan, new ChannelPipelineBuilderImpl(), logging, clock, routingContext );
61+
this( connectionSettings, securityPlan, new ChannelPipelineBuilderImpl(), logging, clock, routingContext, domainNameResolver );
6062
}
6163

6264
public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan securityPlan,
63-
ChannelPipelineBuilder pipelineBuilder, Logging logging, Clock clock, RoutingContext routingContext )
65+
ChannelPipelineBuilder pipelineBuilder, Logging logging, Clock clock, RoutingContext routingContext,
66+
DomainNameResolver domainNameResolver )
6467
{
6568
this.userAgent = connectionSettings.userAgent();
6669
this.authToken = requireValidAuthToken( connectionSettings.authToken() );
@@ -70,13 +73,15 @@ public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan
7073
this.pipelineBuilder = pipelineBuilder;
7174
this.logging = requireNonNull( logging );
7275
this.clock = requireNonNull( clock );
76+
this.addressResolverGroup = new NettyDomainNameResolverGroup( requireNonNull( domainNameResolver ) );
7377
}
7478

7579
@Override
7680
public ChannelFuture connect( BoltServerAddress address, Bootstrap bootstrap )
7781
{
7882
bootstrap.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis );
7983
bootstrap.handler( new NettyChannelInitializer( address, securityPlan, connectTimeoutMillis, clock, logging ) );
84+
bootstrap.resolver( addressResolverGroup );
8085

8186
ChannelFuture channelConnected = bootstrap.connect( address.toSocketAddress() );
8287

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.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+
package org.neo4j.driver.internal.async.connection;
20+
21+
import io.netty.resolver.InetNameResolver;
22+
import io.netty.util.concurrent.EventExecutor;
23+
import io.netty.util.concurrent.Promise;
24+
25+
import java.net.InetAddress;
26+
import java.net.UnknownHostException;
27+
import java.util.Arrays;
28+
import java.util.List;
29+
30+
import org.neo4j.driver.net.DomainNameResolver;
31+
32+
public class NettyDomainNameResolver extends InetNameResolver
33+
{
34+
private final DomainNameResolver domainNameResolver;
35+
36+
public NettyDomainNameResolver( EventExecutor executor, DomainNameResolver domainNameResolver )
37+
{
38+
super( executor );
39+
this.domainNameResolver = domainNameResolver;
40+
}
41+
42+
@Override
43+
protected void doResolve( String inetHost, Promise<InetAddress> promise )
44+
{
45+
try
46+
{
47+
promise.setSuccess( domainNameResolver.resolve( inetHost )[0] );
48+
}
49+
catch ( UnknownHostException e )
50+
{
51+
promise.setFailure( e );
52+
}
53+
}
54+
55+
@Override
56+
protected void doResolveAll( String inetHost, Promise<List<InetAddress>> promise )
57+
{
58+
try
59+
{
60+
promise.setSuccess( Arrays.asList( domainNameResolver.resolve( inetHost ) ) );
61+
}
62+
catch ( UnknownHostException e )
63+
{
64+
promise.setFailure( e );
65+
}
66+
}
67+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.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+
package org.neo4j.driver.internal.async.connection;
20+
21+
import io.netty.resolver.AddressResolver;
22+
import io.netty.resolver.AddressResolverGroup;
23+
import io.netty.util.concurrent.EventExecutor;
24+
25+
import java.net.InetSocketAddress;
26+
27+
import org.neo4j.driver.net.DomainNameResolver;
28+
29+
public class NettyDomainNameResolverGroup extends AddressResolverGroup<InetSocketAddress>
30+
{
31+
private final DomainNameResolver domainNameResolver;
32+
33+
public NettyDomainNameResolverGroup( DomainNameResolver domainNameResolver )
34+
{
35+
this.domainNameResolver = domainNameResolver;
36+
}
37+
38+
@Override
39+
protected AddressResolver<InetSocketAddress> newResolver( EventExecutor executor ) throws Exception
40+
{
41+
return new NettyDomainNameResolver( executor, domainNameResolver ).asAddressResolver();
42+
}
43+
}

0 commit comments

Comments
 (0)