Skip to content

Commit 7909a33

Browse files
committed
Fix for initial routers DNS resolution (neo4j#849)
* Fix for initial routers DNS resolution This update fixes the following issue: neo4j#833 The desired behaviour for getting a routing table from the initial router (either on bootstrap or when all known routers have failed) is: - resolve the domain name to all IPs - attempt getting a routing table from all of them until first one succeeds by: - getting a connection - trying to get a successful routing table response Prior to this change, the connection pools were created for host and port pairs. When domain name of the host resolves to multiple IP addresses, such pools provide connections to those IPs as a group. While this works for readers and writers, it negatively impacts the routing table fetching process as there is no guarantee which IP address the provided connection is setup for. This update delivers the following changes: - connection pools for routers are IP address based, which allows for deterministic connection retrieval - the resolved IP address set is kept up-to-date (in case known router IPs change) to make sure that the unused connection pools are flushed - the domain name resolution logic has been made configurable (it is private at the moment and is used to facilitate 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 to adopt the new changes * Updating test name
1 parent 0d9a7c8 commit 7909a33

36 files changed

+826
-314
lines changed

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

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,14 @@
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;
25+
import java.util.Collections;
26+
import java.util.LinkedHashSet;
2727
import java.util.Objects;
28-
import java.util.stream.Stream;
28+
import java.util.Set;
2929

3030
import org.neo4j.driver.net.ServerAddress;
3131

3232
import static java.util.Objects.requireNonNull;
33-
import static java.util.stream.Collectors.toList;
3433

3534
/**
3635
* Holds a host and port pair that denotes a Bolt server address.
@@ -43,8 +42,8 @@ public class BoltServerAddress implements ServerAddress
4342
private final String host; // This could either be the same as originalHost or it is an IP address resolved from the original host.
4443
private final int port;
4544
private final String stringValue;
46-
47-
private InetAddress resolved;
45+
46+
private final Set<BoltServerAddress> resolved;
4847

4948
public BoltServerAddress( String address )
5049
{
@@ -58,15 +57,15 @@ public BoltServerAddress( URI uri )
5857

5958
public BoltServerAddress( String host, int port )
6059
{
61-
this( host, null, port );
60+
this( host, port, Collections.emptySet() );
6261
}
6362

64-
private BoltServerAddress( String host, InetAddress resolved, int port )
63+
public BoltServerAddress( String host, int port, Set<BoltServerAddress> resolved )
6564
{
6665
this.host = requireNonNull( host, "host" );
67-
this.resolved = resolved;
6866
this.port = requireValidPort( port );
69-
this.stringValue = resolved != null ? String.format( "%s(%s):%d", host, resolved.getHostAddress(), port ) : String.format( "%s:%d", host, port );
67+
this.stringValue = String.format( "%s:%d", host, port );
68+
this.resolved = Collections.unmodifiableSet( new LinkedHashSet<>( resolved ) );
7069
}
7170

7271
public static BoltServerAddress from( ServerAddress address )
@@ -112,33 +111,7 @@ public String toString()
112111
*/
113112
public SocketAddress toSocketAddress()
114113
{
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() );
114+
return new InetSocketAddress( host, port );
142115
}
143116

144117
@Override
@@ -153,9 +126,9 @@ public int port()
153126
return port;
154127
}
155128

156-
public boolean isResolved()
129+
public Set<BoltServerAddress> resolved()
157130
{
158-
return resolved != null;
131+
return this.resolved;
159132
}
160133

161134
private static String hostFrom( URI uri )
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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+
public class DefaultDomainNameResolver implements DomainNameResolver
25+
{
26+
private static final DefaultDomainNameResolver INSTANCE = new DefaultDomainNameResolver();
27+
28+
public static DefaultDomainNameResolver getInstance()
29+
{
30+
return INSTANCE;
31+
}
32+
33+
private DefaultDomainNameResolver()
34+
{
35+
}
36+
37+
@Override
38+
public InetAddress[] resolve( String name ) throws UnknownHostException
39+
{
40+
return InetAddress.getAllByName( name );
41+
}
42+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
/**
25+
* A resolver function used by the driver to resolve domain names.
26+
*/
27+
@FunctionalInterface
28+
public interface DomainNameResolver
29+
{
30+
/**
31+
* Resolve the given domain name to a set of addresses.
32+
*
33+
* @param name the name to resolve.
34+
* @return the resolved addresses.
35+
* @throws UnknownHostException must be thrown if the given name can not be resolved to at least one address.
36+
*/
37+
InetAddress[] resolve( String name ) throws UnknownHostException;
38+
}

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

Lines changed: 13 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, getDomainNameResolver() );
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, getDomainNameResolver() );
214214
}
215215

216216
private static ServerAddressResolver createResolver( Config config )
@@ -271,6 +271,17 @@ protected Bootstrap createBootstrap( EventLoopGroup eventLoopGroup )
271271
return BootstrapFactory.newBootstrap( eventLoopGroup );
272272
}
273273

274+
/**
275+
* Provides an instance of {@link DomainNameResolver} that is used for domain name resolution.
276+
* <p>
277+
* <b>This method is protected only for testing</b>
278+
*
279+
* @return the instance of {@link DomainNameResolver}.
280+
*/
281+
protected DomainNameResolver getDomainNameResolver()
282+
{
283+
return DefaultDomainNameResolver.getInstance();
284+
}
274285

275286
private static void assertNoRoutingContext( URI uri, RoutingSettings routingSettings )
276287
{

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;
37+
import org.neo4j.driver.internal.DomainNameResolver;
3238
import org.neo4j.driver.internal.async.inbound.ConnectTimeoutHandler;
3339
import org.neo4j.driver.internal.cluster.RoutingContext;
3440
import org.neo4j.driver.internal.security.InternalAuthToken;
3541
import org.neo4j.driver.internal.security.SecurityPlan;
3642
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;
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.internal.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.internal.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)