diff --git a/.gitignore b/.gitignore index c870b67e29..dc29cac0c8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ enterprise/server-enterprise/neo4j-home integrationtests/data dependency-reduced-pom.xml venv +testkit-backend/bin/ +testkit/CAs/ 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 e851ba20eb..eacd6b2a39 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java +++ b/driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java @@ -18,19 +18,13 @@ */ package org.neo4j.driver.internal; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.net.URI; -import java.net.UnknownHostException; -import java.util.List; import java.util.Objects; import java.util.stream.Stream; import org.neo4j.driver.net.ServerAddress; import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toList; /** * Holds a host and port pair that denotes a Bolt server address. @@ -40,12 +34,11 @@ public class BoltServerAddress implements ServerAddress public static final int DEFAULT_PORT = 7687; public static final BoltServerAddress LOCAL_DEFAULT = new BoltServerAddress( "localhost", DEFAULT_PORT ); - private final String host; // This could either be the same as originalHost or it is an IP address resolved from the original host. - private final int port; + protected final String host; // Host or IP address. + private final String connectionHost; // Either is equal to the host or is explicitly provided on creation and is expected to be a resolved IP address. + protected final int port; private final String stringValue; - private InetAddress resolved; - public BoltServerAddress( String address ) { this( uriFrom( address ) ); @@ -58,15 +51,17 @@ public BoltServerAddress( URI uri ) public BoltServerAddress( String host, int port ) { - this( host, null, port ); + this( host, host, port ); } - private BoltServerAddress( String host, InetAddress resolved, int port ) + public BoltServerAddress( String host, String connectionHost, int port ) { this.host = requireNonNull( host, "host" ); - this.resolved = resolved; + this.connectionHost = requireNonNull( connectionHost, "connectionHost" ); this.port = requireValidPort( port ); - this.stringValue = resolved != null ? String.format( "%s(%s):%d", host, resolved.getHostAddress(), port ) : String.format( "%s:%d", host, port ); + this.stringValue = host.equals( connectionHost ) + ? String.format( "%s:%d", host, port ) + : String.format( "%s(%s):%d", host, connectionHost, port ); } public static BoltServerAddress from( ServerAddress address ) @@ -87,14 +82,14 @@ public boolean equals( Object o ) { return false; } - BoltServerAddress that = (BoltServerAddress) o; - return port == that.port && host.equals( that.host ); + BoltServerAddress address = (BoltServerAddress) o; + return port == address.port && host.equals( address.host ) && connectionHost.equals( address.connectionHost ); } @Override public int hashCode() { - return Objects.hash( host, port ); + return Objects.hash( host, connectionHost, port ); } @Override @@ -103,44 +98,6 @@ public String toString() return stringValue; } - /** - * Create a {@link SocketAddress} from this bolt address. This method always attempts to resolve the hostname into - * an {@link InetAddress}. - * - * @return new socket address. - * @see InetSocketAddress - */ - public SocketAddress toSocketAddress() - { - return resolved == null ? new InetSocketAddress( host, port ) : new InetSocketAddress( resolved, port ); - } - - /** - * Resolve the host name down to an IP address - * - * @return a new address instance - * @throws UnknownHostException if no IP address for the host could be found - * @see InetAddress#getByName(String) - */ - public BoltServerAddress resolve() throws UnknownHostException - { - return new BoltServerAddress( host, InetAddress.getByName( host ), port ); - } - - /** - * Resolve the host name down to all IP addresses that can be resolved to - * - * @return an array of new address instances that holds resolved addresses - * @throws UnknownHostException if no IP address for the host could be found - * @see InetAddress#getAllByName(String) - */ - public List resolveAll() throws UnknownHostException - { - return Stream.of( InetAddress.getAllByName( host ) ) - .map( address -> new BoltServerAddress( host, address, port ) ) - .collect( toList() ); - } - @Override public String host() { @@ -153,9 +110,21 @@ public int port() return port; } - public boolean isResolved() + public String connectionHost() + { + return connectionHost; + } + + /** + * Create a stream of unicast addresses. + *

+ * While this implementation just returns a stream of itself, the subclasses may provide multiple addresses. + * + * @return stream of unicast addresses. + */ + public Stream unicastStream() { - return resolved != null; + return Stream.of( this ); } private static String hostFrom( URI uri ) diff --git a/driver/src/main/java/org/neo4j/driver/internal/DefaultDomainNameResolver.java b/driver/src/main/java/org/neo4j/driver/internal/DefaultDomainNameResolver.java new file mode 100644 index 0000000000..fc94c3d51d --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/DefaultDomainNameResolver.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) "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.internal; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class DefaultDomainNameResolver implements DomainNameResolver +{ + private static final DefaultDomainNameResolver INSTANCE = new DefaultDomainNameResolver(); + + public static DefaultDomainNameResolver getInstance() + { + return INSTANCE; + } + + private DefaultDomainNameResolver() + { + } + + @Override + public InetAddress[] resolve( String name ) throws UnknownHostException + { + return InetAddress.getAllByName( name ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/DomainNameResolver.java b/driver/src/main/java/org/neo4j/driver/internal/DomainNameResolver.java new file mode 100644 index 0000000000..94d6f613b2 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/DomainNameResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) "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.internal; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * A resolver function used by the driver to resolve domain names. + */ +@FunctionalInterface +public interface DomainNameResolver +{ + /** + * Resolve the given domain name to a set of addresses. + * + * @param name the name to resolve. + * @return the resolved addresses. + * @throws UnknownHostException must be thrown if the given name can not be resolved to at least one address. + */ + InetAddress[] resolve( String name ) throws UnknownHostException; +} 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 df6f919b52..0d2bde136f 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java +++ b/driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java @@ -127,7 +127,7 @@ protected static MetricsProvider createDriverMetrics( Config config, Clock clock protected ChannelConnector createConnector( ConnectionSettings settings, SecurityPlan securityPlan, Config config, Clock clock, RoutingContext routingContext ) { - return new ChannelConnectorImpl( settings, securityPlan, config.logging(), clock, routingContext ); + return new ChannelConnectorImpl( settings, securityPlan, config.logging(), clock, routingContext, getDomainNameResolver() ); } private InternalDriver createDriver( URI uri, SecurityPlan securityPlan, BoltServerAddress address, ConnectionPool connectionPool, @@ -210,7 +210,7 @@ protected LoadBalancer createLoadBalancer( BoltServerAddress address, Connection LoadBalancingStrategy loadBalancingStrategy = new LeastConnectedLoadBalancingStrategy( connectionPool, config.logging() ); ServerAddressResolver resolver = createResolver( config ); return new LoadBalancer( address, routingSettings, connectionPool, eventExecutorGroup, createClock(), - config.logging(), loadBalancingStrategy, resolver ); + config.logging(), loadBalancingStrategy, resolver, getDomainNameResolver() ); } private static ServerAddressResolver createResolver( Config config ) @@ -271,6 +271,17 @@ protected Bootstrap createBootstrap( EventLoopGroup eventLoopGroup ) return BootstrapFactory.newBootstrap( eventLoopGroup ); } + /** + * Provides an instance of {@link DomainNameResolver} that is used for domain name resolution. + *

+ * This method is protected only for testing + * + * @return the instance of {@link DomainNameResolver}. + */ + protected DomainNameResolver getDomainNameResolver() + { + return DefaultDomainNameResolver.getInstance(); + } private static void assertNoRoutingContext( URI uri, RoutingSettings routingSettings ) { diff --git a/driver/src/main/java/org/neo4j/driver/internal/ResolvedBoltServerAddress.java b/driver/src/main/java/org/neo4j/driver/internal/ResolvedBoltServerAddress.java new file mode 100644 index 0000000000..6fa357f987 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/ResolvedBoltServerAddress.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) "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.internal; + +import java.net.InetAddress; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.joining; + +/** + * An explicitly resolved version of {@link BoltServerAddress} that always contains one or more resolved IP addresses. + */ +public class ResolvedBoltServerAddress extends BoltServerAddress +{ + private static final String HOST_ADDRESSES_FORMAT = "%s%s:%d"; + private static final int MAX_HOST_ADDRESSES_IN_STRING_VALUE = 5; + private static final String HOST_ADDRESS_DELIMITER = ","; + private static final String HOST_ADDRESSES_PREFIX = "("; + private static final String HOST_ADDRESSES_SUFFIX = ")"; + private static final String TRIMMED_HOST_ADDRESSES_SUFFIX = ",..." + HOST_ADDRESSES_SUFFIX; + + private final Set resolvedAddresses; + private final String stringValue; + + public ResolvedBoltServerAddress( String host, int port, InetAddress[] resolvedAddressesArr ) + { + super( host, port ); + requireNonNull( resolvedAddressesArr, "resolvedAddressesArr" ); + if ( resolvedAddressesArr.length == 0 ) + { + throw new IllegalArgumentException( + "The resolvedAddressesArr must not be empty, check your DomainNameResolver is compliant with the interface contract" ); + } + resolvedAddresses = Collections.unmodifiableSet( new LinkedHashSet<>( Arrays.asList( resolvedAddressesArr ) ) ); + stringValue = createStringRepresentation(); + } + + /** + * Create a stream of unicast addresses. + *

+ * The stream is created from the list of resolved IP addresses. Each unicast address is given a unique IP address as the connectionHost value. + * + * @return stream of unicast addresses. + */ + @Override + public Stream unicastStream() + { + return resolvedAddresses.stream().map( address -> new BoltServerAddress( host, address.getHostAddress(), port ) ); + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + if ( !super.equals( o ) ) + { + return false; + } + ResolvedBoltServerAddress that = (ResolvedBoltServerAddress) o; + return resolvedAddresses.equals( that.resolvedAddresses ); + } + + @Override + public int hashCode() + { + return Objects.hash( super.hashCode(), resolvedAddresses ); + } + + @Override + public String toString() + { + return stringValue; + } + + private String createStringRepresentation() + { + String hostAddresses = resolvedAddresses.stream() + .limit( MAX_HOST_ADDRESSES_IN_STRING_VALUE ) + .map( InetAddress::getHostAddress ) + .collect( joining( HOST_ADDRESS_DELIMITER, HOST_ADDRESSES_PREFIX, + resolvedAddresses.size() > MAX_HOST_ADDRESSES_IN_STRING_VALUE + ? TRIMMED_HOST_ADDRESSES_SUFFIX + : HOST_ADDRESSES_SUFFIX ) ); + return String.format( HOST_ADDRESSES_FORMAT, host, hostAddresses, port ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java index d927b8c9ec..0bfe976f1a 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java @@ -24,21 +24,23 @@ import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; +import io.netty.resolver.AddressResolverGroup; -import java.util.Map; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import org.neo4j.driver.AuthToken; +import org.neo4j.driver.AuthTokens; +import org.neo4j.driver.Logging; +import org.neo4j.driver.exceptions.ClientException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; +import org.neo4j.driver.internal.DomainNameResolver; import org.neo4j.driver.internal.async.inbound.ConnectTimeoutHandler; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.security.InternalAuthToken; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.util.Clock; -import org.neo4j.driver.AuthToken; -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Logging; -import org.neo4j.driver.Value; -import org.neo4j.driver.exceptions.ClientException; import static java.util.Objects.requireNonNull; @@ -52,15 +54,18 @@ public class ChannelConnectorImpl implements ChannelConnector private final int connectTimeoutMillis; private final Logging logging; private final Clock clock; + private final DomainNameResolver domainNameResolver; + private final AddressResolverGroup addressResolverGroup; public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan securityPlan, Logging logging, - Clock clock, RoutingContext routingContext ) + Clock clock, RoutingContext routingContext, DomainNameResolver domainNameResolver ) { - this( connectionSettings, securityPlan, new ChannelPipelineBuilderImpl(), logging, clock, routingContext ); + this( connectionSettings, securityPlan, new ChannelPipelineBuilderImpl(), logging, clock, routingContext, domainNameResolver ); } public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan securityPlan, - ChannelPipelineBuilder pipelineBuilder, Logging logging, Clock clock, RoutingContext routingContext ) + ChannelPipelineBuilder pipelineBuilder, Logging logging, Clock clock, RoutingContext routingContext, + DomainNameResolver domainNameResolver ) { this.userAgent = connectionSettings.userAgent(); this.authToken = requireValidAuthToken( connectionSettings.authToken() ); @@ -70,6 +75,8 @@ public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan this.pipelineBuilder = pipelineBuilder; this.logging = requireNonNull( logging ); this.clock = requireNonNull( clock ); + this.domainNameResolver = requireNonNull( domainNameResolver ); + this.addressResolverGroup = new NettyDomainNameResolverGroup( this.domainNameResolver ); } @Override @@ -77,8 +84,19 @@ public ChannelFuture connect( BoltServerAddress address, Bootstrap bootstrap ) { bootstrap.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeoutMillis ); bootstrap.handler( new NettyChannelInitializer( address, securityPlan, connectTimeoutMillis, clock, logging ) ); + bootstrap.resolver( addressResolverGroup ); + + SocketAddress socketAddress; + try + { + socketAddress = new InetSocketAddress( domainNameResolver.resolve( address.connectionHost() )[0], address.port() ); + } + catch ( Throwable t ) + { + socketAddress = InetSocketAddress.createUnresolved( address.connectionHost(), address.port() ); + } - ChannelFuture channelConnected = bootstrap.connect( address.toSocketAddress() ); + ChannelFuture channelConnected = bootstrap.connect( socketAddress ); Channel channel = channelConnected.channel(); ChannelPromise handshakeCompleted = channel.newPromise(); diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyDomainNameResolver.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyDomainNameResolver.java new file mode 100644 index 0000000000..87351e252b --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyDomainNameResolver.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) "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.internal.async.connection; + +import io.netty.resolver.InetNameResolver; +import io.netty.util.concurrent.EventExecutor; +import io.netty.util.concurrent.Promise; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.List; + +import org.neo4j.driver.internal.DomainNameResolver; + +public class NettyDomainNameResolver extends InetNameResolver +{ + private final DomainNameResolver domainNameResolver; + + public NettyDomainNameResolver( EventExecutor executor, DomainNameResolver domainNameResolver ) + { + super( executor ); + this.domainNameResolver = domainNameResolver; + } + + @Override + protected void doResolve( String inetHost, Promise promise ) + { + try + { + promise.setSuccess( domainNameResolver.resolve( inetHost )[0] ); + } + catch ( UnknownHostException e ) + { + promise.setFailure( e ); + } + } + + @Override + protected void doResolveAll( String inetHost, Promise> promise ) + { + try + { + promise.setSuccess( Arrays.asList( domainNameResolver.resolve( inetHost ) ) ); + } + catch ( UnknownHostException e ) + { + promise.setFailure( e ); + } + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyDomainNameResolverGroup.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyDomainNameResolverGroup.java new file mode 100644 index 0000000000..720e213be6 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyDomainNameResolverGroup.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) "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.internal.async.connection; + +import io.netty.resolver.AddressResolver; +import io.netty.resolver.AddressResolverGroup; +import io.netty.util.concurrent.EventExecutor; + +import java.net.InetSocketAddress; + +import org.neo4j.driver.internal.DomainNameResolver; + +public class NettyDomainNameResolverGroup extends AddressResolverGroup +{ + private final DomainNameResolver domainNameResolver; + + public NettyDomainNameResolverGroup( DomainNameResolver domainNameResolver ) + { + this.domainNameResolver = domainNameResolver; + } + + @Override + protected AddressResolver newResolver( EventExecutor executor ) throws Exception + { + return new NettyDomainNameResolver( executor, domainNameResolver ).asAddressResolver(); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/AddressSet.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/AddressSet.java index b4ab6c731d..c4cc3f2b20 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/AddressSet.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/AddressSet.java @@ -19,6 +19,7 @@ package org.neo4j.driver.internal.cluster; import java.util.Arrays; +import java.util.Iterator; import java.util.Set; import org.neo4j.driver.internal.BoltServerAddress; @@ -39,9 +40,35 @@ public int size() return addresses.length; } - public synchronized void update( Set addresses ) + public synchronized void retainAllAndAdd( Set newAddresses ) { - this.addresses = addresses.toArray( NONE ); + BoltServerAddress[] addressesArr = new BoltServerAddress[newAddresses.size()]; + int insertionIdx = 0; + for ( BoltServerAddress address : addresses ) + { + if ( newAddresses.remove( address ) ) + { + addressesArr[insertionIdx] = address; + insertionIdx++; + } + } + Iterator addressIterator = newAddresses.iterator(); + for ( ; insertionIdx < addressesArr.length && addressIterator.hasNext(); insertionIdx++ ) + { + addressesArr[insertionIdx] = addressIterator.next(); + } + addresses = addressesArr; + } + + public synchronized void replaceIfPresent( BoltServerAddress oldAddress, BoltServerAddress newAddress ) + { + for ( int i = 0; i < addresses.length; i++ ) + { + if ( addresses[i].equals( oldAddress ) ) + { + addresses[i] = newAddress; + } + } } public synchronized void remove( BoltServerAddress address ) diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterComposition.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterComposition.java index 7d9f378499..7bddb70c21 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterComposition.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterComposition.java @@ -45,7 +45,9 @@ private ClusterComposition( long expirationTimestamp ) this.expirationTimestamp = expirationTimestamp; } - /** For testing */ + /** + * For testing + */ public ClusterComposition( long expirationTimestamp, Set readers, @@ -83,7 +85,8 @@ public Set routers() return new LinkedHashSet<>( routers ); } - public long expirationTimestamp() { + public long expirationTimestamp() + { return this.expirationTimestamp; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterCompositionLookupResult.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterCompositionLookupResult.java new file mode 100644 index 0000000000..a374918089 --- /dev/null +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterCompositionLookupResult.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) "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.internal.cluster; + +import java.util.Optional; +import java.util.Set; + +import org.neo4j.driver.internal.BoltServerAddress; + +public class ClusterCompositionLookupResult +{ + private final ClusterComposition composition; + + private final Set resolvedInitialRouters; + + public ClusterCompositionLookupResult( ClusterComposition composition ) + { + this( composition, null ); + } + + public ClusterCompositionLookupResult( ClusterComposition composition, Set resolvedInitialRouters ) + { + this.composition = composition; + this.resolvedInitialRouters = resolvedInitialRouters; + } + + public ClusterComposition getClusterComposition() + { + return composition; + } + + public Optional> getResolvedInitialRouters() + { + return Optional.ofNullable( resolvedInitialRouters ); + } +} diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterRoutingTable.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterRoutingTable.java index 6cb925a3ff..3604a5ffc9 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterRoutingTable.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterRoutingTable.java @@ -47,7 +47,7 @@ public class ClusterRoutingTable implements RoutingTable public ClusterRoutingTable( DatabaseName ofDatabase, Clock clock, BoltServerAddress... routingAddresses ) { this( ofDatabase, clock ); - routers.update( new LinkedHashSet<>( asList( routingAddresses ) ) ); + routers.retainAllAndAdd( new LinkedHashSet<>( asList( routingAddresses ) ) ); } private ClusterRoutingTable( DatabaseName ofDatabase, Clock clock ) @@ -86,9 +86,9 @@ public boolean hasBeenStaleFor( long extraTime ) public synchronized void update( ClusterComposition cluster ) { expirationTimestamp = cluster.expirationTimestamp(); - readers.update( cluster.readers() ); - writers.update( cluster.writers() ); - routers.update( cluster.routers() ); + readers.retainAllAndAdd( cluster.readers() ); + writers.retainAllAndAdd( cluster.writers() ); + routers.retainAllAndAdd( cluster.routers() ); preferInitialRouter = !cluster.hasWriters(); } @@ -140,6 +140,12 @@ public void forgetWriter( BoltServerAddress toRemove ) writers.remove( toRemove ); } + @Override + public void replaceRouterIfPresent( BoltServerAddress oldRouter, BoltServerAddress newRouter ) + { + routers.replaceIfPresent( oldRouter, newRouter ); + } + @Override public boolean preferInitialRouter() { 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 c9288c54cd..5faea2186b 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 @@ -18,6 +18,7 @@ */ package org.neo4j.driver.internal.cluster; +import java.net.UnknownHostException; import java.util.List; import java.util.concurrent.CompletionStage; @@ -27,7 +28,7 @@ public interface Rediscovery { - CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, Bookmark bookmark ); + CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, Bookmark bookmark ); - List resolve(); + List resolve() throws UnknownHostException; } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java index 777d97c213..52c76d784c 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RediscoveryImpl.java @@ -21,14 +21,15 @@ import io.netty.util.concurrent.EventExecutorGroup; import java.net.UnknownHostException; +import java.util.Collection; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logger; @@ -37,15 +38,17 @@ import org.neo4j.driver.exceptions.SecurityException; import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.spi.Connection; +import org.neo4j.driver.internal.DomainNameResolver; +import org.neo4j.driver.internal.ResolvedBoltServerAddress; import org.neo4j.driver.internal.spi.ConnectionPool; import org.neo4j.driver.internal.util.Futures; +import org.neo4j.driver.net.ServerAddress; import org.neo4j.driver.net.ServerAddressResolver; import static java.lang.String.format; import static java.util.Collections.emptySet; +import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; -import static java.util.stream.Collectors.toList; import static org.neo4j.driver.internal.util.Futures.completedWithNull; import static org.neo4j.driver.internal.util.Futures.failedFuture; @@ -64,9 +67,10 @@ public class RediscoveryImpl implements Rediscovery private final ClusterCompositionProvider provider; private final ServerAddressResolver resolver; private final EventExecutorGroup eventExecutorGroup; + private final DomainNameResolver domainNameResolver; public RediscoveryImpl( BoltServerAddress initialRouter, RoutingSettings settings, ClusterCompositionProvider provider, - EventExecutorGroup eventExecutorGroup, ServerAddressResolver resolver, Logger logger ) + EventExecutorGroup eventExecutorGroup, ServerAddressResolver resolver, Logger logger, DomainNameResolver domainNameResolver ) { this.initialRouter = initialRouter; this.settings = settings; @@ -74,20 +78,22 @@ public RediscoveryImpl( BoltServerAddress initialRouter, RoutingSettings setting this.provider = provider; this.resolver = resolver; this.eventExecutorGroup = eventExecutorGroup; + this.domainNameResolver = requireNonNull( domainNameResolver ); } /** - * Given a database and its current routing table, and the global connection pool, use the global cluster composition provider to fetch a new - * cluster composition, which would be used to update the routing table of the given database and global connection pool. + * Given a database and its current routing table, and the global connection pool, use the global cluster composition provider to fetch a new cluster + * composition, which would be used to update the routing table of the given database and global connection pool. * - * @param routingTable current routing table of the given database. + * @param routingTable current routing table of the given database. * @param connectionPool connection pool. - * @return new cluster composition. + * @return new cluster composition and an optional set of resolved initial router addresses. */ @Override - public CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, Bookmark bookmark ) + public CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, + Bookmark bookmark ) { - CompletableFuture result = new CompletableFuture<>(); + CompletableFuture result = new CompletableFuture<>(); // if we failed discovery, we will chain all errors into this one. ServiceUnavailableException baseError = new ServiceUnavailableException( String.format( NO_ROUTERS_AVAILABLE, routingTable.database().description() ) ); lookupClusterComposition( routingTable, connectionPool, 0, 0, result, bookmark, baseError ); @@ -95,43 +101,47 @@ public CompletionStage lookupClusterComposition( RoutingTabl } private void lookupClusterComposition( RoutingTable routingTable, ConnectionPool pool, - int failures, long previousDelay, CompletableFuture result, Bookmark bookmark, Throwable baseError ) + int failures, long previousDelay, CompletableFuture result, Bookmark bookmark, + Throwable baseError ) { - lookup( routingTable, pool, bookmark, baseError ).whenComplete( ( composition, completionError ) -> - { - Throwable error = Futures.completionExceptionCause( completionError ); - if ( error != null ) - { - result.completeExceptionally( error ); - } - else if ( composition != null ) - { - result.complete( composition ); - } - else - { - int newFailures = failures + 1; - if ( newFailures >= settings.maxRoutingFailures() ) - { - // now we throw our saved error out - result.completeExceptionally( baseError ); - } - else - { - long nextDelay = Math.max( settings.retryTimeoutDelay(), previousDelay * 2 ); - logger.info( "Unable to fetch new routing table, will try again in " + nextDelay + "ms" ); - eventExecutorGroup.next().schedule( - () -> lookupClusterComposition( routingTable, pool, newFailures, nextDelay, result, bookmark, baseError ), - nextDelay, TimeUnit.MILLISECONDS - ); - } - } - } ); + lookup( routingTable, pool, bookmark, baseError ) + .whenComplete( + ( compositionLookupResult, completionError ) -> + { + Throwable error = Futures.completionExceptionCause( completionError ); + if ( error != null ) + { + result.completeExceptionally( error ); + } + else if ( compositionLookupResult != null ) + { + result.complete( compositionLookupResult ); + } + else + { + int newFailures = failures + 1; + if ( newFailures >= settings.maxRoutingFailures() ) + { + // now we throw our saved error out + result.completeExceptionally( baseError ); + } + else + { + long nextDelay = Math.max( settings.retryTimeoutDelay(), previousDelay * 2 ); + logger.info( "Unable to fetch new routing table, will try again in " + nextDelay + "ms" ); + eventExecutorGroup.next().schedule( + () -> lookupClusterComposition( routingTable, pool, newFailures, nextDelay, result, bookmark, baseError ), + nextDelay, TimeUnit.MILLISECONDS + ); + } + } + } ); } - private CompletionStage lookup( RoutingTable routingTable, ConnectionPool connectionPool, Bookmark bookmark, Throwable baseError ) + private CompletionStage lookup( RoutingTable routingTable, ConnectionPool connectionPool, Bookmark bookmark, + Throwable baseError ) { - CompletionStage compositionStage; + CompletionStage compositionStage; if ( routingTable.preferInitialRouter() ) { @@ -145,109 +155,132 @@ private CompletionStage lookup( RoutingTable routingTable, C return compositionStage; } - private CompletionStage lookupOnKnownRoutersThenOnInitialRouter( RoutingTable routingTable, ConnectionPool connectionPool, - Bookmark bookmark, Throwable baseError ) + private CompletionStage lookupOnKnownRoutersThenOnInitialRouter( RoutingTable routingTable, ConnectionPool connectionPool, + Bookmark bookmark, Throwable baseError ) { Set seenServers = new HashSet<>(); - return lookupOnKnownRouters( routingTable, connectionPool, seenServers, bookmark, baseError ).thenCompose( composition -> - { - if ( composition != null ) - { - return completedFuture( composition ); - } - return lookupOnInitialRouter( routingTable, connectionPool, seenServers, bookmark, baseError ); - } ); + return lookupOnKnownRouters( routingTable, connectionPool, seenServers, bookmark, baseError ) + .thenCompose( + compositionLookupResult -> + { + if ( compositionLookupResult != null ) + { + return completedFuture( + compositionLookupResult ); + } + return lookupOnInitialRouter( + routingTable, connectionPool, + seenServers, bookmark, + baseError ); + } ); } - private CompletionStage lookupOnInitialRouterThenOnKnownRouters( RoutingTable routingTable, - ConnectionPool connectionPool, Bookmark bookmark, Throwable baseError ) + private CompletionStage lookupOnInitialRouterThenOnKnownRouters( RoutingTable routingTable, + ConnectionPool connectionPool, Bookmark bookmark, + Throwable baseError ) { Set seenServers = emptySet(); - return lookupOnInitialRouter( routingTable, connectionPool, seenServers, bookmark, baseError ).thenCompose( composition -> - { - if ( composition != null ) - { - return completedFuture( composition ); - } - return lookupOnKnownRouters( routingTable, connectionPool, new HashSet<>(), bookmark, baseError ); - } ); + return lookupOnInitialRouter( routingTable, connectionPool, seenServers, bookmark, baseError ) + .thenCompose( + compositionLookupResult -> + { + if ( compositionLookupResult != null ) + { + return completedFuture( + compositionLookupResult ); + } + return lookupOnKnownRouters( + routingTable, connectionPool, + new HashSet<>(), bookmark, + baseError ); + } ); } - private CompletionStage lookupOnKnownRouters( RoutingTable routingTable, ConnectionPool connectionPool, Set seenServers, Bookmark bookmark, - Throwable baseError ) + private CompletionStage lookupOnKnownRouters( RoutingTable routingTable, ConnectionPool connectionPool, + Set seenServers, Bookmark bookmark, + Throwable baseError ) { BoltServerAddress[] addresses = routingTable.routers().toArray(); CompletableFuture result = completedWithNull(); for ( BoltServerAddress address : addresses ) { - result = result.thenCompose( composition -> - { - if ( composition != null ) - { - return completedFuture( composition ); - } - else - { - return lookupOnRouter( address, routingTable, connectionPool, bookmark, baseError ) - .whenComplete( ( ignore, error ) -> seenServers.add( address ) ); - } - } ); + result = result + .thenCompose( + composition -> + { + if ( composition != null ) + { + return completedFuture( composition ); + } + else + { + return lookupOnRouter( address, true, routingTable, connectionPool, seenServers, bookmark, baseError ); + } + } ); } - return result; + return result.thenApply( composition -> composition != null ? new ClusterCompositionLookupResult( composition ) : null ); } - private CompletionStage lookupOnInitialRouter( RoutingTable routingTable, ConnectionPool connectionPool, Set seenServers, Bookmark bookmark, - Throwable baseError ) + private CompletionStage lookupOnInitialRouter( RoutingTable routingTable, ConnectionPool connectionPool, + Set seenServers, Bookmark bookmark, + Throwable baseError ) { - List addresses; + List resolvedRouters; try { - addresses = resolve(); + resolvedRouters = resolve(); } catch ( Throwable error ) { return failedFuture( error ); } - addresses.removeAll( seenServers ); + Set resolvedRouterSet = new HashSet<>( resolvedRouters ); + resolvedRouters.removeAll( seenServers ); CompletableFuture result = completedWithNull(); - for ( BoltServerAddress address : addresses ) + for ( BoltServerAddress address : resolvedRouters ) { - result = result.thenCompose( composition -> - { - if ( composition != null ) - { - return completedFuture( composition ); - } - return lookupOnRouter( address, routingTable, connectionPool, bookmark, baseError ); - } ); + result = result.thenCompose( + composition -> + { + if ( composition != null ) + { + return completedFuture( composition ); + } + return lookupOnRouter( address, false, routingTable, connectionPool, null, bookmark, baseError ); + } ); } - return result; + return result.thenApply( composition -> composition != null ? new ClusterCompositionLookupResult( composition, resolvedRouterSet ) : null ); } - private CompletionStage lookupOnRouter( BoltServerAddress routerAddress, - RoutingTable routingTable, ConnectionPool connectionPool, Bookmark bookmark, Throwable baseError ) + private CompletionStage lookupOnRouter( BoltServerAddress routerAddress, boolean resolveAddress, + RoutingTable routingTable, ConnectionPool connectionPool, + Set seenServers, Bookmark bookmark, Throwable baseError ) { - CompletionStage connectionStage = connectionPool.acquire( routerAddress ); + CompletableFuture addressFuture = CompletableFuture.completedFuture( routerAddress ); - return connectionStage + return addressFuture + .thenApply( address -> resolveAddress ? resolveByDomainNameOrThrowCompletionException( address, routingTable ) : address ) + .thenApply( address -> addAndReturn( seenServers, address ) ) + .thenCompose( connectionPool::acquire ) .thenCompose( connection -> provider.getClusterComposition( connection, routingTable.database(), bookmark ) ) - .handle( ( response, error ) -> { - Throwable cause = Futures.completionExceptionCause( error ); - if ( cause != null ) - { - return handleRoutingProcedureError( cause, routingTable, routerAddress, baseError ); - } - else - { - return response; - } - } ); + .handle( ( response, error ) -> + { + Throwable cause = Futures.completionExceptionCause( error ); + if ( cause != null ) + { + return handleRoutingProcedureError( cause, routingTable, routerAddress, baseError ); + } + else + { + return response; + } + } ); } private ClusterComposition handleRoutingProcedureError( Throwable error, RoutingTable routingTable, - BoltServerAddress routerAddress, Throwable baseError ) + BoltServerAddress routerAddress, Throwable baseError ) { if ( error instanceof SecurityException || error instanceof FatalDiscoveryException ) { @@ -265,24 +298,67 @@ private ClusterComposition handleRoutingProcedureError( Throwable error, Routing } @Override - public List resolve() + public List resolve() throws UnknownHostException + { + List resolvedAddresses = new LinkedList<>(); + UnknownHostException exception = null; + for ( ServerAddress serverAddress : resolver.resolve( initialRouter ) ) + { + try + { + resolveAllByDomainName( serverAddress ).unicastStream().forEach( resolvedAddresses::add ); + } + catch ( UnknownHostException e ) + { + if ( exception == null ) + { + exception = e; + } + else + { + exception.addSuppressed( e ); + } + } + } + + // give up only if there are no addresses to work with at all + if ( resolvedAddresses.isEmpty() && exception != null ) + { + throw exception; + } + + return resolvedAddresses; + } + + private T addAndReturn( Collection collection, T element ) { - return resolver.resolve( initialRouter ) - .stream() - .map( BoltServerAddress::from ) - .collect( toList() ); // collect to list to preserve the order + if ( collection != null ) + { + collection.add( element ); + } + return element; } - private Stream resolveAll( BoltServerAddress address ) + private BoltServerAddress resolveByDomainNameOrThrowCompletionException( BoltServerAddress address, RoutingTable routingTable ) { try { - return address.resolveAll().stream(); + ResolvedBoltServerAddress resolvedAddress = resolveAllByDomainName( address ); + routingTable.replaceRouterIfPresent( address, resolvedAddress ); + return resolvedAddress.unicastStream() + .findFirst() + .orElseThrow( + () -> new IllegalStateException( + "Unexpected condition, the ResolvedBoltServerAddress must always have at least one unicast address" ) ); } - catch ( UnknownHostException e ) + catch ( Throwable e ) { - logger.error( "Failed to resolve address `" + address + "` to IPs due to error: " + e.getMessage(), e ); - return Stream.of( address ); + throw new CompletionException( e ); } } + + private ResolvedBoltServerAddress resolveAllByDomainName( ServerAddress address ) throws UnknownHostException + { + return new ResolvedBoltServerAddress( address.host(), address.port(), domainNameResolver.resolve( address.host() ) ); + } } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTable.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTable.java index 77e4f8ea49..7fa7000bda 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTable.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTable.java @@ -20,8 +20,8 @@ import java.util.Set; -import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.AccessMode; +import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.DatabaseName; public interface RoutingTable @@ -46,5 +46,7 @@ public interface RoutingTable void forgetWriter( BoltServerAddress toRemove ); + void replaceRouterIfPresent( BoltServerAddress oldRouter, BoltServerAddress newRouter ); + boolean preferInitialRouter(); } diff --git a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerImpl.java b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerImpl.java index 27cd03202c..7605b48ee8 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerImpl.java +++ b/driver/src/main/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerImpl.java @@ -18,6 +18,8 @@ */ package org.neo4j.driver.internal.cluster; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -41,6 +43,7 @@ public class RoutingTableHandlerImpl implements RoutingTableHandler private final Rediscovery rediscovery; private final Logger log; private final long routingTablePurgeDelayMs; + private final Set resolvedInitialRouters = new HashSet<>(); public RoutingTableHandlerImpl( RoutingTable routingTable, Rediscovery rediscovery, ConnectionPool connectionPool, RoutingTableRegistry routingTableRegistry, Logger log, long routingTablePurgeDelayMs ) @@ -105,13 +108,25 @@ else if ( routingTable.isStaleFor( context.mode() ) ) } } - private synchronized void freshClusterCompositionFetched( ClusterComposition composition ) + private synchronized void freshClusterCompositionFetched( ClusterCompositionLookupResult compositionLookupResult ) { try { - routingTable.update( composition ); + routingTable.update( compositionLookupResult.getClusterComposition() ); routingTableRegistry.removeAged(); - connectionPool.retainAll( routingTableRegistry.allServers() ); + + Set addressesToRetain = new LinkedHashSet<>(); + routingTableRegistry.allServers().stream() + .flatMap( BoltServerAddress::unicastStream ) + .forEach( addressesToRetain::add ); + compositionLookupResult.getResolvedInitialRouters().ifPresent( + addresses -> + { + resolvedInitialRouters.clear(); + resolvedInitialRouters.addAll( addresses ); + } ); + addressesToRetain.addAll( resolvedInitialRouters ); + connectionPool.retainAll( addressesToRetain ); log.debug( "Updated routing table for database '%s'. %s", databaseName.description(), routingTable ); 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 34b4dce032..3a6bd6683f 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 @@ -31,6 +31,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.DomainNameResolver; import org.neo4j.driver.internal.async.ConnectionContext; import org.neo4j.driver.internal.async.connection.RoutingConnection; import org.neo4j.driver.internal.cluster.AddressSet; @@ -50,6 +51,7 @@ import org.neo4j.driver.net.ServerAddressResolver; import static java.lang.String.format; +import static java.util.Objects.requireNonNull; import static org.neo4j.driver.internal.async.ImmutableConnectionContext.simple; import static org.neo4j.driver.internal.messaging.request.MultiDatabaseUtil.supportsMultiDatabase; import static org.neo4j.driver.internal.util.Futures.completedWithNull; @@ -68,22 +70,24 @@ public class LoadBalancer implements ConnectionProvider private final Rediscovery rediscovery; public LoadBalancer( BoltServerAddress initialRouter, RoutingSettings settings, ConnectionPool connectionPool, - EventExecutorGroup eventExecutorGroup, Clock clock, Logging logging, - LoadBalancingStrategy loadBalancingStrategy, ServerAddressResolver resolver ) + EventExecutorGroup eventExecutorGroup, Clock clock, Logging logging, + LoadBalancingStrategy loadBalancingStrategy, ServerAddressResolver resolver, DomainNameResolver domainNameResolver ) { - this( connectionPool, createRediscovery( eventExecutorGroup, initialRouter, resolver, settings, clock, logging ), settings, loadBalancingStrategy, - eventExecutorGroup, clock, loadBalancerLogger( logging ) ); + this( connectionPool, createRediscovery( eventExecutorGroup, initialRouter, resolver, settings, clock, logging, requireNonNull( domainNameResolver ) ), + settings, + loadBalancingStrategy, + eventExecutorGroup, clock, loadBalancerLogger( logging ) ); } private LoadBalancer( ConnectionPool connectionPool, Rediscovery rediscovery, RoutingSettings settings, LoadBalancingStrategy loadBalancingStrategy, - EventExecutorGroup eventExecutorGroup, Clock clock, Logger log ) + EventExecutorGroup eventExecutorGroup, Clock clock, Logger log ) { this( connectionPool, createRoutingTables( connectionPool, rediscovery, settings, clock, log ), rediscovery, loadBalancingStrategy, eventExecutorGroup, - log ); + log ); } LoadBalancer( ConnectionPool connectionPool, RoutingTableRegistry routingTables, Rediscovery rediscovery, LoadBalancingStrategy loadBalancingStrategy, - EventExecutorGroup eventExecutorGroup, Logger log ) + EventExecutorGroup eventExecutorGroup, Logger log ) { this.connectionPool = connectionPool; this.routingTables = routingTables; @@ -252,11 +256,11 @@ private static RoutingTableRegistry createRoutingTables( ConnectionPool connecti } private static Rediscovery createRediscovery( EventExecutorGroup eventExecutorGroup, BoltServerAddress initialRouter, ServerAddressResolver resolver, - RoutingSettings settings, Clock clock, Logging logging ) + RoutingSettings settings, Clock clock, Logging logging, DomainNameResolver domainNameResolver ) { Logger log = loadBalancerLogger( logging ); ClusterCompositionProvider clusterCompositionProvider = new RoutingProcedureClusterCompositionProvider( clock, settings.routingContext() ); - return new RediscoveryImpl( initialRouter, settings, clusterCompositionProvider, eventExecutorGroup, resolver, log ); + return new RediscoveryImpl( initialRouter, settings, clusterCompositionProvider, eventExecutorGroup, resolver, log, domainNameResolver ); } private static Logger loadBalancerLogger( Logging logging ) diff --git a/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java b/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java index a8b6c8051b..a939ebebde 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/ChannelConnectorImplIT.java @@ -42,14 +42,15 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; +import org.neo4j.driver.internal.DefaultDomainNameResolver; import org.neo4j.driver.internal.RevocationStrategy; import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.async.inbound.ConnectTimeoutHandler; import org.neo4j.driver.internal.cluster.RoutingContext; -import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.security.SecurityPlan; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.FakeClock; import org.neo4j.driver.util.DatabaseExtension; import org.neo4j.driver.util.ParallelizableIT; @@ -233,7 +234,8 @@ private ChannelConnectorImpl newConnector( AuthToken authToken, SecurityPlan sec int connectTimeoutMillis ) { ConnectionSettings settings = new ConnectionSettings( authToken, "test", connectTimeoutMillis ); - return new ChannelConnectorImpl( settings, securityPlan, DEV_NULL_LOGGING, new FakeClock(), RoutingContext.EMPTY ); + return new ChannelConnectorImpl( settings, securityPlan, DEV_NULL_LOGGING, new FakeClock(), RoutingContext.EMPTY, + DefaultDomainNameResolver.getInstance() ); } private static SecurityPlan trustAllCertificates() throws GeneralSecurityException diff --git a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java index e7ae1beff4..055056c1cc 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java @@ -27,14 +27,9 @@ import java.io.IOException; import java.net.URI; -import java.util.ArrayList; import java.util.Comparator; -import java.util.HashSet; import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.neo4j.driver.AccessMode; @@ -43,25 +38,17 @@ import org.neo4j.driver.Config; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Logger; import org.neo4j.driver.Record; -import org.neo4j.driver.Result; import org.neo4j.driver.Session; -import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionWork; import org.neo4j.driver.async.AsyncSession; -import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.exceptions.SessionExpiredException; -import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.DriverFactory; -import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; import org.neo4j.driver.internal.security.SecurityPlanImpl; -import org.neo4j.driver.internal.util.DriverFactoryWithClock; import org.neo4j.driver.internal.util.DriverFactoryWithFixedRetryLogic; import org.neo4j.driver.internal.util.Futures; -import org.neo4j.driver.internal.util.SleeplessClock; import org.neo4j.driver.net.ServerAddress; import org.neo4j.driver.net.ServerAddressResolver; import org.neo4j.driver.reactive.RxResult; @@ -70,1138 +57,156 @@ import org.neo4j.driver.util.StubServerController; import static java.util.Arrays.asList; -import static java.util.Collections.singleton; -import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.IsEqual.equalTo; import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.startsWith; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.neo4j.driver.SessionConfig.builder; -import static org.neo4j.driver.internal.InternalBookmark.parse; import static org.neo4j.driver.util.StubServer.INSECURE_CONFIG; import static org.neo4j.driver.util.StubServer.insecureBuilder; -import static org.neo4j.driver.util.TestUtil.asOrderedSet; -class RoutingDriverBoltKitIT -{ - private static StubServerController stubController; - - @BeforeAll - public static void setup() - { - stubController = new StubServerController(); - } - - @AfterEach - public void killServers() - { - stubController.reset(); - } - - @Test - void shouldHandleAcquireReadSession() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a read server - StubServer readServer = stubController.startStub( "read_server_v3_read.script", 9005 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - List result = session.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( "n.name" ).asString() ); - - assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) ); - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( readServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldHandleAcquireReadTransaction() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a read server - StubServer readServer = stubController.startStub( "read_server_v3_read_tx.script", 9005 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - - { - List result = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( "n.name" ).asString() ) ); - - assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) ); - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( readServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldHandleAcquireReadSessionAndTransaction() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a read server - StubServer readServer = stubController.startStub( "read_server_v3_read_tx.script", 9005 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ); Transaction tx = session.beginTransaction() ) - { - List result = tx.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( "n.name" ).asString() ); - - assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) ); - tx.commit(); - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( readServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldRoundRobinReadServers() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START two read servers - StubServer readServer1 = stubController.startStub( "read_server_v3_read.script", 9005 ); - StubServer readServer2 = stubController.startStub( "read_server_v3_read.script", 9006 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) - { - // Run twice, one on each read server - for ( int i = 0; i < 2; i++ ) - { - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - assertThat( session.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( "n.name" ).asString() ), - equalTo( asList( "Bob", "Alice", "Tina" ) ) ); - } - } - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( readServer1.exitStatus(), equalTo( 0 ) ); - assertThat( readServer2.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldRoundRobinReadServersWhenUsingTransaction() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START two read servers - StubServer readServer1 = stubController.startStub( "read_server_v3_read_tx.script", 9005 ); - StubServer readServer2 = stubController.startStub( "read_server_v3_read_tx.script", 9006 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) - { - // Run twice, one on each read server - for ( int i = 0; i < 2; i++ ) - { - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ); - Transaction tx = session.beginTransaction() ) - { - assertThat( tx.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( "n.name" ).asString() ), - equalTo( asList( "Bob", "Alice", "Tina" ) ) ); - tx.commit(); - } - } - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( readServer1.exitStatus(), equalTo( 0 ) ); - assertThat( readServer2.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldThrowSessionExpiredIfReadServerDisappears() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a read server - final StubServer readServer = stubController.startStub( "dead_read_server.script", 9005 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - - //Expect - assertThrows( SessionExpiredException.class, () -> - { - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - session.run( "MATCH (n) RETURN n.name" ); - } - } ); - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( readServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldThrowSessionExpiredIfReadServerDisappearsWhenUsingTransaction() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a read server - final StubServer readServer = stubController.startStub( "dead_read_server_tx.script", 9005 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - - //Expect - SessionExpiredException e = assertThrows( SessionExpiredException.class, () -> - { - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ); - Transaction tx = session.beginTransaction() ) - { - tx.run( "MATCH (n) RETURN n.name" ); - tx.commit(); - } - } ); - assertEquals( "Server at 127.0.0.1:9005 is no longer available", e.getMessage() ); - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( readServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldThrowSessionExpiredIfWriteServerDisappears() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a dead write server - final StubServer writeServer = stubController.startStub( "dead_write_server.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - - //Expect - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) - { - assertThrows( SessionExpiredException.class, () -> session.run( "CREATE (n {name:'Bob'})" ).consume() ); - } - finally - { - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer.exitStatus(), equalTo( 0 ) ); - } - } - - @Test - void shouldThrowSessionExpiredIfWriteServerDisappearsWhenUsingTransaction() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a dead write servers - final StubServer writeServer = stubController.startStub( "dead_read_server_tx.script", 9007 ); - - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - //Expect - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ); Transaction tx = session.beginTransaction() ) - { - assertThrows( SessionExpiredException.class, () -> tx.run( "MATCH (n) RETURN n.name" ).consume() ); - } - finally - { - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer.exitStatus(), equalTo( 0 ) ); - } - } - - @Test - void shouldHandleAcquireWriteSession() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a write server - StubServer writeServer = stubController.startStub( "write_server_v3_write.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) - { - session.run( "CREATE (n {name:'Bob'})" ); - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldHandleAcquireWriteTransaction() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a write server - StubServer writeServer = stubController.startStub( "write_server_v3_write_tx.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); Session session = driver.session() ) - { - session.writeTransaction( t -> t.run( "CREATE (n {name:'Bob'})" ) ); - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldHandleAcquireWriteSessionAndTransaction() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a write server - StubServer writeServer = stubController.startStub( "write_server_v3_write_tx.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ); Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE (n {name:'Bob'})" ); - tx.commit(); - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldRoundRobinWriteSessions() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a write server - StubServer writeServer1 = stubController.startStub( "write_server_v3_write.script", 9007 ); - StubServer writeServer2 = stubController.startStub( "write_server_v3_write.script", 9008 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) - { - for ( int i = 0; i < 2; i++ ) - { - try ( Session session = driver.session() ) - { - session.run( "CREATE (n {name:'Bob'})" ); - } - } - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer1.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer2.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldRoundRobinWriteSessionsInTransaction() throws Exception - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a write server - StubServer writeServer1 = stubController.startStub( "write_server_v3_write_tx.script", 9007 ); - StubServer writeServer2 = stubController.startStub( "write_server_v3_write_tx.script", 9008 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) - { - for ( int i = 0; i < 2; i++ ) - { - try ( Session session = driver.session(); Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE (n {name:'Bob'})" ); - tx.commit(); - } - } - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer1.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer2.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldFailOnNonDiscoverableServer() throws IOException, InterruptedException - { - // Given - stubController.startStub( "discover_not_supported_9001.script", 9001 ); - - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - final Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - - //Expect - assertThrows( ServiceUnavailableException.class, driver::verifyConnectivity ); - } - - @Test - void shouldFailRandomFailureInGetServers() throws IOException, InterruptedException - { - // Given - stubController.startStub( "discover_failed.script", 9001 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - final Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - - //Expect - assertThrows( ServiceUnavailableException.class, driver::verifyConnectivity ); - } - - @Test - void shouldHandleLeaderSwitchWhenWriting() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a write server that doesn't accept writes - stubController.startStub( "not_able_to_write_server.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - boolean failed = false; - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) - { - session.run( "CREATE ()" ).consume(); - } - catch ( SessionExpiredException e ) - { - failed = true; - assertThat( e.getMessage(), equalTo( "Server at 127.0.0.1:9007 no longer accepts writes" ) ); - } - assertTrue( failed ); - - driver.close(); - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldHandleLeaderSwitchWhenWritingWithoutConsuming() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a write server that doesn't accept writes - stubController.startStub( "not_able_to_write_server.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - boolean failed = false; - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) - { - session.run( "CREATE ()" ); - } - catch ( SessionExpiredException e ) - { - failed = true; - assertThat( e.getMessage(), equalTo( "Server at 127.0.0.1:9007 no longer accepts writes" ) ); - } - assertTrue( failed ); - - driver.close(); - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldHandleLeaderSwitchWhenWritingInTransaction() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - //START a write server that doesn't accept writes - stubController.startStub( "not_able_to_write_server.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - boolean failed = false; - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ); Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE ()" ).consume(); - } - catch ( SessionExpiredException e ) - { - failed = true; - assertThat( e.getMessage(), equalTo( "Server at 127.0.0.1:9007 no longer accepts writes" ) ); - } - assertTrue( failed ); - - driver.close(); - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunction() throws IOException, InterruptedException - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_twice_v4.script", 9001 ); - - // START a write server that fails on the first write attempt but then succeeds on the second - StubServer writeServer = stubController.startStub( "not_able_to_write_server_tx_func_retries.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - - Driver driver = GraphDatabase.driver( uri, Config.builder().withMaxTransactionRetryTime( 1, TimeUnit.MILLISECONDS ).build() ); - List names; - - try ( Session session = driver.session( builder().withDatabase( "mydatabase" ).build() ) ) - { - names = session.writeTransaction( tx -> - { - tx.run( "RETURN 1" ); - try - { - Thread.sleep( 100 ); - } - catch ( InterruptedException ex ) - { - } - return tx.run( "MATCH (n) RETURN n.name" ).list( RoutingDriverBoltKitIT::extractNameField ); - } ); - } - - assertEquals( asList( "Foo", "Bar" ), names ); - - // Finally - driver.close(); - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionAsync() throws IOException, InterruptedException - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_twice_v4.script", 9001 ); - - // START a write server that fails on the first write attempt but then succeeds on the second - StubServer writeServer = stubController.startStub( "not_able_to_write_server_tx_func_retries.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - - Driver driver = GraphDatabase.driver( uri, Config.builder().withMaxTransactionRetryTime( 1, TimeUnit.MILLISECONDS ).build() ); - AsyncSession session = driver.asyncSession( builder().withDatabase( "mydatabase" ).build() ); - List names = Futures.blockingGet( session.writeTransactionAsync( - tx -> tx.runAsync( "RETURN 1" ) - .thenComposeAsync( ignored -> { - try - { - Thread.sleep( 100 ); - } - catch ( InterruptedException ex ) - { - } - return tx.runAsync( "MATCH (n) RETURN n.name" ); - } ) - .thenComposeAsync( cursor -> cursor.listAsync( RoutingDriverBoltKitIT::extractNameField ) ) ) ); - - assertEquals( asList( "Foo", "Bar" ), names ); - - // Finally - driver.close(); - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer.exitStatus(), equalTo( 0 ) ); - } - - private static String extractNameField(Record record) - { - return record.get( 0 ).asString(); - } - - // This does not exactly reproduce the async and blocking versions above, as we don't have any means of ignoring - // the flux of the RETURN 1 query (not pulling the result) like we do in above, so this is "just" a test for - // a leader going away during the execution of a flux. - @Test - void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionRX() throws IOException, InterruptedException - { - // Given - StubServer server = stubController.startStub( "acquire_endpoints_twice_v4.script", 9001 ); - - // START a write server that fails on the first write attempt but then succeeds on the second - StubServer writeServer = stubController.startStub( "not_able_to_write_server_tx_func_retries_rx.script", 9007 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - - Driver driver = GraphDatabase.driver( uri, Config.builder().withMaxTransactionRetryTime( 1, TimeUnit.MILLISECONDS ).build() ); - - Flux fluxOfNames = Flux.usingWhen( Mono.fromSupplier( () -> driver.rxSession( builder().withDatabase( "mydatabase" ).build() ) ), - session -> session.writeTransaction( tx -> - { - RxResult result = tx.run( "RETURN 1" ); - return Flux.from( result.records() ).limitRate( 100 ).thenMany( tx.run( "MATCH (n) RETURN n.name" ).records() ).limitRate( 100 ).map( - RoutingDriverBoltKitIT::extractNameField ); - } ), RxSession::close ); - - StepVerifier.create( fluxOfNames ).expectNext( "Foo", "Bar" ).verifyComplete(); - - // Finally - driver.close(); - assertThat( server.exitStatus(), equalTo( 0 ) ); - assertThat( writeServer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldSendInitialBookmark() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer writer = stubController.startStub( "write_tx_with_bookmarks.script", 9007 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withBookmarks( parse( "OldBookmark" ) ).build() ) ) - { - try ( Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE (n {name:'Bob'})" ); - tx.commit(); - } - - assertEquals( parse( "NewBookmark" ), session.lastBookmark() ); - } - - assertThat( router.exitStatus(), equalTo( 0 ) ); - assertThat( writer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldUseWriteSessionModeAndInitialBookmark() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer writer = stubController.startStub( "write_tx_with_bookmarks.script", 9008 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).withBookmarks( parse( "OldBookmark" ) ).build() ) ) - { - try ( Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE (n {name:'Bob'})" ); - tx.commit(); - } - - assertEquals( parse( "NewBookmark" ), session.lastBookmark() ); - } - - assertThat( router.exitStatus(), equalTo( 0 ) ); - assertThat( writer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldUseReadSessionModeAndInitialBookmark() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer writer = stubController.startStub( "read_tx_with_bookmarks.script", 9005 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withBookmarks( parse( "OldBookmark" ) ).build() ) ) - { - try ( Transaction tx = session.beginTransaction() ) - { - List records = tx.run( "MATCH (n) RETURN n.name AS name" ).list(); - assertEquals( 2, records.size() ); - assertEquals( "Bob", records.get( 0 ).get( "name" ).asString() ); - assertEquals( "Alice", records.get( 1 ).get( "name" ).asString() ); - tx.commit(); - } - - assertEquals( parse( "NewBookmark" ), session.lastBookmark() ); - } - - assertThat( router.exitStatus(), equalTo( 0 ) ); - assertThat( writer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldPassBookmarkFromTransactionToTransaction() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer writer = stubController.startStub( "write_read_tx_with_bookmarks.script", 9007 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); - Session session = driver.session( builder().withBookmarks( parse( "BookmarkA" ) ).build() ) ) - { - try ( Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE (n {name:'Bob'})" ); - tx.commit(); - } - - assertEquals( parse( "BookmarkB" ), session.lastBookmark() ); - - try ( Transaction tx = session.beginTransaction() ) - { - List records = tx.run( "MATCH (n) RETURN n.name AS name" ).list(); - assertEquals( 1, records.size() ); - assertEquals( "Bob", records.get( 0 ).get( "name" ).asString() ); - tx.commit(); - } - - assertEquals( parse( "BookmarkC" ), session.lastBookmark() ); - } - - assertThat( router.exitStatus(), equalTo( 0 ) ); - assertThat( writer.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldRetryReadTransactionUntilSuccess() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer brokenReader = stubController.startStub( "dead_read_server_tx.script", 9005 ); - StubServer reader = stubController.startStub( "read_server_v3_read_tx.script", 9006 ); - - try ( Driver driver = newDriverWithSleeplessClock( "neo4j://127.0.0.1:9001" ); Session session = driver.session() ) - { - AtomicInteger invocations = new AtomicInteger(); - List records = session.readTransaction( queryWork( "MATCH (n) RETURN n.name", invocations ) ); - - assertEquals( 3, records.size() ); - assertEquals( 2, invocations.get() ); - } - finally - { - assertEquals( 0, router.exitStatus() ); - assertEquals( 0, brokenReader.exitStatus() ); - assertEquals( 0, reader.exitStatus() ); - } - } - - @Test - void shouldRetryWriteTransactionUntilSuccess() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer brokenWriter = stubController.startStub( "dead_write_server.script", 9007 ); - StubServer writer = stubController.startStub( "write_server_v3_write_tx.script", 9008 ); - - try ( Driver driver = newDriverWithSleeplessClock( "neo4j://127.0.0.1:9001" ); Session session = driver.session() ) - { - AtomicInteger invocations = new AtomicInteger(); - List records = session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) ); - - assertEquals( 0, records.size() ); - assertEquals( 2, invocations.get() ); - } - finally - { - assertEquals( 0, router.exitStatus() ); - assertEquals( 0, brokenWriter.exitStatus() ); - assertEquals( 0, writer.exitStatus() ); - } - } - - @Test - void shouldRetryWriteTransactionUntilSuccessWithWhenLeaderIsRemoved() throws Exception - { - // This test simulates a router in a cluster when a leader is removed. - // The router first returns a RT with a writer inside. - // However this writer is killed while the driver is running a tx with it. - // Then at the second time the router returns the same RT with the killed writer inside. - // At the third round, the router removes the the writer server from RT reply. - // Finally, the router returns a RT with a reachable writer. - StubServer router = stubController.startStub( "acquire_endpoints_v3_leader_killed.script", 9001 ); - StubServer brokenWriter = stubController.startStub( "dead_write_server.script", 9004 ); - StubServer writer = stubController.startStub( "write_server_v3_write_tx.script", 9008 ); - - Logger logger = mock( Logger.class ); - Config config = insecureBuilder().withLogging( ignored -> logger ).build(); - try ( Driver driver = newDriverWithSleeplessClock( "neo4j://127.0.0.1:9001", config ); Session session = driver.session() ) - { - AtomicInteger invocations = new AtomicInteger(); - List records = session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) ); - - assertEquals( 0, records.size() ); - assertEquals( 2, invocations.get() ); - } - finally - { - assertEquals( 0, router.exitStatus() ); - assertEquals( 0, brokenWriter.exitStatus() ); - assertEquals( 0, writer.exitStatus() ); - } - verify( logger, times( 3 ) ).warn( startsWith( "Transaction failed and will be retried in" ), any( SessionExpiredException.class ) ); - verify( logger ).warn( startsWith( "Failed to obtain a connection towards address 127.0.0.1:9004" ), any( SessionExpiredException.class ) ); - } - - @Test - void shouldRetryWriteTransactionUntilSuccessWithWhenLeaderIsRemovedV3() throws Exception - { - // This test simulates a router in a cluster when a leader is removed. - // The router first returns a RT with a writer inside. - // However this writer is killed while the driver is running a tx with it. - // Then at the second time the router returns the same RT with the killed writer inside. - // At the third round, the router removes the the writer server from RT reply. - // Finally, the router returns a RT with a reachable writer. - StubServer router = stubController.startStub( "acquire_endpoints_v3_leader_killed.script", 9001 ); - StubServer brokenWriter = stubController.startStub( "database_shutdown_at_commit.script", 9004 ); - StubServer writer = stubController.startStub( "write_server_v3_write_tx.script", 9008 ); - - Logger logger = mock( Logger.class ); - Config config = insecureBuilder().withLogging( ignored -> logger ).build(); - try ( Driver driver = newDriverWithSleeplessClock( "neo4j://127.0.0.1:9001", config ); Session session = driver.session() ) - { - AtomicInteger invocations = new AtomicInteger(); - List records = session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) ); - - assertEquals( 0, records.size() ); - assertEquals( 2, invocations.get() ); - } - finally - { - assertEquals( 0, router.exitStatus() ); - assertEquals( 0, brokenWriter.exitStatus() ); - assertEquals( 0, writer.exitStatus() ); - } - verify( logger, times( 1 ) ).warn( startsWith( "Transaction failed and will be retried in" ), any( TransientException.class ) ); - verify( logger, times( 2 ) ).warn( startsWith( "Transaction failed and will be retried in" ), any( SessionExpiredException.class ) ); - verify( logger ).warn( startsWith( "Failed to obtain a connection towards address 127.0.0.1:9004" ), any( SessionExpiredException.class ) ); - } - - @Test - void shouldRetryReadTransactionUntilFailure() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer brokenReader1 = stubController.startStub( "dead_read_server_tx.script", 9005 ); - StubServer brokenReader2 = stubController.startStub( "dead_read_server_tx.script", 9006 ); - - try ( Driver driver = newDriverWithFixedRetries( "neo4j://127.0.0.1:9001", 1 ); Session session = driver.session() ) - { - AtomicInteger invocations = new AtomicInteger(); - assertThrows( SessionExpiredException.class, () -> session.readTransaction( queryWork( "MATCH (n) RETURN n.name", invocations ) ) ); - assertEquals( 2, invocations.get() ); - } - finally - { - assertEquals( 0, router.exitStatus() ); - assertEquals( 0, brokenReader1.exitStatus() ); - assertEquals( 0, brokenReader2.exitStatus() ); - } - } - - @Test - void shouldRetryWriteTransactionUntilFailure() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer brokenWriter1 = stubController.startStub( "dead_write_server.script", 9007 ); - StubServer brokenWriter2 = stubController.startStub( "dead_write_server.script", 9008 ); - - try ( Driver driver = newDriverWithFixedRetries( "neo4j://127.0.0.1:9001", 1 ); Session session = driver.session() ) - { - AtomicInteger invocations = new AtomicInteger(); - assertThrows( SessionExpiredException.class, () -> session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) ) ); - assertEquals( 2, invocations.get() ); - } - finally - { - assertEquals( 0, router.exitStatus() ); - assertEquals( 0, brokenWriter1.exitStatus() ); - assertEquals( 0, brokenWriter2.exitStatus() ); - } - } - - @Test - void shouldRetryReadTransactionAndPerformRediscoveryUntilSuccess() throws Exception - { - StubServer router1 = stubController.startStub( "acquire_endpoints_v3_9010.script", 9010 ); - StubServer brokenReader1 = stubController.startStub( "dead_read_server_tx.script", 9005 ); - StubServer brokenReader2 = stubController.startStub( "dead_read_server_tx.script", 9006 ); - StubServer router2 = stubController.startStub( "discover_servers_9010.script", 9003 ); - StubServer reader = stubController.startStub( "read_server_v3_read_tx.script", 9004 ); - - try ( Driver driver = newDriverWithSleeplessClock( "neo4j://127.0.0.1:9010" ); Session session = driver.session() ) - { - AtomicInteger invocations = new AtomicInteger(); - List records = session.readTransaction( queryWork( "MATCH (n) RETURN n.name", invocations ) ); - - assertEquals( 3, records.size() ); - assertEquals( 3, invocations.get() ); - } - finally - { - assertEquals( 0, router1.exitStatus() ); - assertEquals( 0, brokenReader1.exitStatus() ); - assertEquals( 0, brokenReader2.exitStatus() ); - assertEquals( 0, router2.exitStatus() ); - assertEquals( 0, reader.exitStatus() ); - } - } +/** + * New tests should be added to testkit (https://github.com/neo4j-drivers/testkit). + * + * This class exists only for the following: + * - to keep the remaining tests that are due to be migrated + * - to keep the tests that are currently not portable + */ +@Deprecated +class RoutingDriverBoltKitIT +{ + private static StubServerController stubController; - @Test - void shouldRetryWriteTransactionAndPerformRediscoveryUntilSuccess() throws Exception + @BeforeAll + public static void setup() { - StubServer router1 = stubController.startStub( "discover_servers_9010.script", 9010 ); - StubServer brokenWriter1 = stubController.startStub( "dead_write_server.script", 9001 ); - StubServer router2 = stubController.startStub( "acquire_endpoints_v3_9010.script", 9002 ); - StubServer brokenWriter2 = stubController.startStub( "dead_write_server.script", 9008 ); - StubServer writer = stubController.startStub( "write_server_v3_write_tx.script", 9007 ); - - try ( Driver driver = newDriverWithSleeplessClock( "neo4j://127.0.0.1:9010" ); Session session = driver.session() ) - { - AtomicInteger invocations = new AtomicInteger(); - List records = session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) ); - - assertEquals( 0, records.size() ); - assertEquals( 3, invocations.get() ); - } - finally - { - assertEquals( 0, router1.exitStatus() ); - assertEquals( 0, brokenWriter1.exitStatus() ); - assertEquals( 0, router2.exitStatus() ); - assertEquals( 0, writer.exitStatus() ); - assertEquals( 0, brokenWriter2.exitStatus() ); - } + stubController = new StubServerController(); } - @Test - void shouldUseInitialRouterForRediscoveryWhenAllOtherRoutersAreDead() throws Exception + @AfterEach + public void killServers() { - // initial router does not have itself in the returned set of routers - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ) ) - { - driver.verifyConnectivity(); - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - // restart router on the same port with different script that contains itself as reader - assertEquals( 0, router.exitStatus() ); - - router = stubController.startStub( "rediscover_using_initial_router.script", 9001 ); - - List names = readStrings( "MATCH (n) RETURN n.name AS name", session ); - assertEquals( asList( "Bob", "Alice" ), names ); - } - } - - assertEquals( 0, router.exitStatus() ); + stubController.reset(); } + // Async is not currently supported in testkit. @Test - void shouldInvokeProcedureGetRoutingTableWhenServerVersionPermits() throws Exception + void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionAsync() throws IOException, InterruptedException { - // stub server is both a router and reader - StubServer server = stubController.startStub( "get_routing_table.script", 9001 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); Session session = driver.session() ) - { - List records = session.run( "MATCH (n) RETURN n.name AS name" ).list(); - assertEquals( 3, records.size() ); - assertEquals( "Alice", records.get( 0 ).get( "name" ).asString() ); - assertEquals( "Bob", records.get( 1 ).get( "name" ).asString() ); - assertEquals( "Eve", records.get( 2 ).get( "name" ).asString() ); - } - finally - { - assertEquals( 0, server.exitStatus() ); - } - } + // Given + StubServer server = stubController.startStub( "acquire_endpoints_twice_v4.script", 9001 ); - @Test - void shouldSendRoutingContextToServer() throws Exception - { - // stub server is both a router and reader - StubServer server = stubController.startStub( "get_routing_table_with_context.script", 9001 ); + // START a write server that fails on the first write attempt but then succeeds on the second + StubServer writeServer = stubController.startStub( "not_able_to_write_server_tx_func_retries.script", 9007 ); + URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - URI uri = URI.create( "neo4j://127.0.0.1:9001/?policy=my_policy®ion=china" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); Session session = driver.session() ) - { - List records = session.run( "MATCH (n) RETURN n.name AS name" ).list(); - assertEquals( 2, records.size() ); - assertEquals( "Alice", records.get( 0 ).get( "name" ).asString() ); - assertEquals( "Bob", records.get( 1 ).get( "name" ).asString() ); - } - finally - { - assertEquals( 0, server.exitStatus() ); - } - } + Driver driver = GraphDatabase.driver( uri, Config.builder().withMaxTransactionRetryTime( 1, TimeUnit.MILLISECONDS ).build() ); + AsyncSession session = driver.asyncSession( builder().withDatabase( "mydatabase" ).build() ); + List names = Futures.blockingGet( session.writeTransactionAsync( + tx -> tx.runAsync( "RETURN 1" ) + .thenComposeAsync( ignored -> { + try + { + Thread.sleep( 100 ); + } + catch ( InterruptedException ex ) + { + } + return tx.runAsync( "MATCH (n) RETURN n.name" ); + } ) + .thenComposeAsync( cursor -> cursor.listAsync( RoutingDriverBoltKitIT::extractNameField ) ) ) ); - @Test - void shouldSendRoutingContextInHelloMessage() throws Exception - { - // stub server is both a router and reader - StubServer server = StubServer.start( "routing_context_in_hello_neo4j.script", 9001 ); + assertEquals( asList( "Foo", "Bar" ), names ); - URI uri = URI.create( "neo4j://127.0.0.1:9001/?policy=my_policy®ion=china" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); Session session = driver.session() ) - { - List records = session.run( "MATCH (n) RETURN n.name AS name" ).list(); - assertEquals( 2, records.size() ); - assertEquals( "Alice", records.get( 0 ).get( "name" ).asString() ); - assertEquals( "Bob", records.get( 1 ).get( "name" ).asString() ); - } - finally - { - assertEquals( 0, server.exitStatus() ); - } + // Finally + driver.close(); + assertThat( server.exitStatus(), equalTo( 0 ) ); + assertThat( writeServer.exitStatus(), equalTo( 0 ) ); } - @Test - void shouldSendEmptyRoutingContextInHelloMessage() throws Exception + private static String extractNameField( Record record ) { - // stub server is both a router and reader - StubServer server = StubServer.start( "empty_routing_context_in_hello_neo4j.script", 9001 ); - - URI uri = URI.create( "neo4j://127.0.0.1:9001/" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); Session session = driver.session() ) - { - List records = session.run( "MATCH (n) RETURN n.name AS name" ).list(); - assertEquals( 2, records.size() ); - assertEquals( "Alice", records.get( 0 ).get( "name" ).asString() ); - assertEquals( "Bob", records.get( 1 ).get( "name" ).asString() ); - } - finally - { - assertEquals( 0, server.exitStatus() ); - } + return record.get( 0 ).asString(); } - @Test - void shouldServeReadsButFailWritesWhenNoWritersAvailable() throws Exception - { - StubServer router1 = stubController.startStub( "discover_no_writers_9010.script", 9010 ); - StubServer router2 = stubController.startStub( "discover_no_writers_9010.script", 9004 ); - StubServer reader = stubController.startStub( "read_server_v3_read_tx.script", 9003 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9010", INSECURE_CONFIG ); Session session = driver.session() ) - { - assertEquals( asList( "Bob", "Alice", "Tina" ), readStrings( "MATCH (n) RETURN n.name", session ) ); - - assertThrows( SessionExpiredException.class, () -> session.run( "CREATE (n {name:'Bob'})" ).consume() ); - } - finally - { - assertEquals( 0, router1.exitStatus() ); - assertEquals( 0, router2.exitStatus() ); - assertEquals( 0, reader.exitStatus() ); - } - } + // RX is not currently supported in testkit. + // This does not exactly reproduce the async and blocking versions above, as we don't have any means of ignoring + // the flux of the RETURN 1 query (not pulling the result) like we do in above, so this is "just" a test for + // a leader going away during the execution of a flux. @Test - void shouldAcceptRoutingTableWithoutWritersAndThenRediscover() throws Exception + void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionRX() throws IOException, InterruptedException { - // first router does not have itself in the resulting routing table so connection - // towards it will be closed after rediscovery - StubServer router1 = stubController.startStub( "discover_no_writers_9010.script", 9010 ); - StubServer router2 = null; - StubServer reader = stubController.startStub( "read_server_v3_read_tx.script", 9003 ); - StubServer writer = stubController.startStub( "write_with_bookmarks.script", 9007 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9010", INSECURE_CONFIG ) ) - { - driver.verifyConnectivity(); - try ( Session session = driver.session() ) - { - // start another router which knows about writes, use same address as the initial router - router2 = stubController.startStub( "acquire_endpoints_v3_9010.script", 9010 ); - - assertEquals( asList( "Bob", "Alice", "Tina" ), readStrings( "MATCH (n) RETURN n.name", session ) ); + // Given + StubServer server = stubController.startStub( "acquire_endpoints_twice_v4.script", 9001 ); - Result createResult = session.run( "CREATE (n {name:'Bob'})" ); - assertFalse( createResult.hasNext() ); - } - } - finally - { - assertEquals( 0, router1.exitStatus() ); - assertNotNull( router2 ); - assertEquals( 0, router2.exitStatus() ); - assertEquals( 0, reader.exitStatus() ); - assertEquals( 0, writer.exitStatus() ); - } - } + // START a write server that fails on the first write attempt but then succeeds on the second + StubServer writeServer = stubController.startStub( "not_able_to_write_server_tx_func_retries_rx.script", 9007 ); + URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - @Test - void shouldTreatRoutingTableWithSingleRouterAsValid() throws Exception - { - StubServer router = stubController.startStub( "discover_one_router.script", 9010 ); - StubServer reader1 = stubController.startStub( "read_server_v3_read.script", 9003 ); - StubServer reader2 = stubController.startStub( "read_server_v3_read.script", 9004 ); + Driver driver = GraphDatabase.driver( uri, Config.builder().withMaxTransactionRetryTime( 1, TimeUnit.MILLISECONDS ).build() ); - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9010", INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - // returned routing table contains only one router, this should be fine and we should be able to - // read multiple times without additional rediscovery + Flux fluxOfNames = Flux.usingWhen( Mono.fromSupplier( () -> driver.rxSession( builder().withDatabase( "mydatabase" ).build() ) ), + session -> session.writeTransaction( tx -> + { + RxResult result = tx.run( "RETURN 1" ); + return Flux.from( result.records() ).limitRate( 100 ).thenMany( tx.run( "MATCH (n) RETURN n.name" ).records() ).limitRate( 100 ).map( + RoutingDriverBoltKitIT::extractNameField ); + } ), RxSession::close ); - Result readResult1 = session.run( "MATCH (n) RETURN n.name" ); - assertEquals( 3, readResult1.list().size() ); - assertEquals( "127.0.0.1:9003", readResult1.consume().server().address() ); + StepVerifier.create( fluxOfNames ).expectNext( "Foo", "Bar" ).verifyComplete(); - Result readResult2 = session.run( "MATCH (n) RETURN n.name" ); - assertEquals( 3, readResult2.list().size() ); - assertEquals( "127.0.0.1:9004", readResult2.consume().server().address() ); - } - finally - { - assertEquals( 0, router.exitStatus() ); - assertEquals( 0, reader1.exitStatus() ); - assertEquals( 0, reader2.exitStatus() ); - } + // Finally + driver.close(); + assertThat( server.exitStatus(), equalTo( 0 ) ); + assertThat( writeServer.exitStatus(), equalTo( 0 ) ); } + // fixed retries are not currently supported in testkit @Test - void shouldSendMultipleBookmarks() throws Exception + void shouldRetryReadTransactionUntilFailure() throws Exception { StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer writer = stubController.startStub( "multiple_bookmarks.script", 9007 ); + StubServer brokenReader1 = stubController.startStub( "dead_read_server_tx.script", 9005 ); + StubServer brokenReader2 = stubController.startStub( "dead_read_server_tx.script", 9006 ); - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", INSECURE_CONFIG ); Session session = driver.session( builder().withBookmarks( - InternalBookmark.parse( asOrderedSet( "neo4j:bookmark:v1:tx5", "neo4j:bookmark:v1:tx29", "neo4j:bookmark:v1:tx94", "neo4j:bookmark:v1:tx56", - "neo4j:bookmark:v1:tx16", "neo4j:bookmark:v1:tx68" ) ) ).build() ) ) + try ( Driver driver = newDriverWithFixedRetries( "neo4j://127.0.0.1:9001", 1 ); Session session = driver.session() ) { - try ( Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE (n {name:'Bob'})" ); - tx.commit(); - } - - assertEquals( parse( "neo4j:bookmark:v1:tx95" ), session.lastBookmark() ); + AtomicInteger invocations = new AtomicInteger(); + assertThrows( SessionExpiredException.class, () -> session.readTransaction( queryWork( "MATCH (n) RETURN n.name", invocations ) ) ); + assertEquals( 2, invocations.get() ); } finally { assertEquals( 0, router.exitStatus() ); - assertEquals( 0, writer.exitStatus() ); + assertEquals( 0, brokenReader1.exitStatus() ); + assertEquals( 0, brokenReader2.exitStatus() ); } } + // fixed retries are not currently supported in testkit @Test - void shouldForgetAddressOnDatabaseUnavailableError() throws Exception + void shouldRetryWriteTransactionUntilFailure() throws Exception { - // perform initial discovery using router1 - StubServer router1 = stubController.startStub( "discover_servers_9010.script", 9010 ); - - // attempt to write using writer1 which fails with 'Neo.TransientError.General.DatabaseUnavailable' - // it should then be forgotten and trigger new rediscovery - StubServer writer1 = stubController.startStub( "writer_unavailable.script", 9001 ); - - // perform rediscovery using router2, it should return a valid writer2 - StubServer router2 = stubController.startStub( "acquire_endpoints_v3_9010.script", 9002 ); - - // write on writer2 should be successful - StubServer writer2 = stubController.startStub( "write_server_v3_write_tx.script", 9007 ); + StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); + StubServer brokenWriter1 = stubController.startStub( "dead_write_server.script", 9007 ); + StubServer brokenWriter2 = stubController.startStub( "dead_write_server.script", 9008 ); - try ( Driver driver = newDriverWithSleeplessClock( "neo4j://127.0.0.1:9010" ); Session session = driver.session() ) + try ( Driver driver = newDriverWithFixedRetries( "neo4j://127.0.0.1:9001", 1 ); Session session = driver.session() ) { AtomicInteger invocations = new AtomicInteger(); - List records = session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) ); - - assertThat( records, hasSize( 0 ) ); + assertThrows( SessionExpiredException.class, () -> session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) ) ); assertEquals( 2, invocations.get() ); } finally { - assertEquals( 0, router1.exitStatus() ); - assertEquals( 0, writer1.exitStatus() ); - assertEquals( 0, router2.exitStatus() ); - assertEquals( 0, writer2.exitStatus() ); + assertEquals( 0, router.exitStatus() ); + assertEquals( 0, brokenWriter1.exitStatus() ); + assertEquals( 0, brokenWriter2.exitStatus() ); } } @@ -1219,53 +224,8 @@ void shouldFailInitialDiscoveryWhenConfiguredResolverThrows() verify( resolver ).resolve( ServerAddress.of( "my.server.com", 9001 ) ); } - @Test - void shouldUseResolverDuringRediscoveryWhenExistingRoutersFail() throws Exception - { - StubServer router1 = stubController.startStub( "get_routing_table.script", 9001 ); - StubServer router2 = stubController.startStub( "acquire_endpoints_v3.script", 9042 ); - StubServer reader = stubController.startStub( "read_server_v3_read_tx.script", 9005 ); - - AtomicBoolean resolverInvoked = new AtomicBoolean(); - ServerAddressResolver resolver = address -> - { - if ( resolverInvoked.compareAndSet( false, true ) ) - { - // return the address first time - return singleton( address ); - } - if ( "127.0.0.1".equals( address.host() ) && address.port() == 9001 ) - { - // return list of addresses where onl 9042 is functional - return new HashSet<>( - asList( ServerAddress.of( "127.0.0.1", 9010 ), ServerAddress.of( "127.0.0.1", 9011 ), ServerAddress.of( "127.0.0.1", 9042 ) ) ); - } - throw new AssertionError(); - }; - - Config config = insecureBuilder().withResolver( resolver ).build(); - - try ( Driver driver = GraphDatabase.driver( "neo4j://127.0.0.1:9001", config ) ) - { - try ( Session session = driver.session() ) - { - // run first query against 9001, which should return result and exit - List names1 = session.run( "MATCH (n) RETURN n.name AS name" ).list( record -> record.get( "name" ).asString() ); - assertEquals( asList( "Alice", "Bob", "Eve" ), names1 ); - - // run second query with retries, it should rediscover using 9042 returned by the resolver and read from 9005 - List names2 = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ).list( RoutingDriverBoltKitIT::extractNameField ) ); - assertEquals( asList( "Bob", "Alice", "Tina" ), names2 ); - } - } - finally - { - assertEquals( 0, router1.exitStatus() ); - assertEquals( 0, router2.exitStatus() ); - assertEquals( 0, reader.exitStatus() ); - } - } - + // general error reporting and handling should be improved before this can be moved to testkit + // also, backend closes socket on general errors and it negatively impacts testkit's teardown process @Test void useSessionAfterDriverIsClosed() throws Exception { @@ -1293,80 +253,6 @@ void useSessionAfterDriverIsClosed() throws Exception } } - @Test - void shouldRevertToInitialRouterIfKnownRouterThrowsProtocolErrors() throws Exception - { - ServerAddressResolver resolver = a -> - { - SortedSet addresses = new TreeSet<>( new PortBasedServerAddressComparator() ); - addresses.add( ServerAddress.of( "127.0.0.1", 9001 ) ); - addresses.add( ServerAddress.of( "127.0.0.1", 9003 ) ); - return addresses; - }; - - Config config = insecureBuilder().withResolver( resolver ).build(); - - StubServer router1 = stubController.startStub( "acquire_endpoints_v3_point_to_empty_router_and_exit.script", 9001 ); - StubServer router2 = stubController.startStub( "acquire_endpoints_v3_empty.script", 9004 ); - StubServer router3 = stubController.startStub( "acquire_endpoints_v3_three_servers_and_exit.script", 9003 ); - StubServer reader = stubController.startStub( "read_server_v3_read_tx.script", 9002 ); - - try ( Driver driver = GraphDatabase.driver( "neo4j://my.virtual.host:8080", config ) ) - { - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - List records = session.readTransaction( tx -> tx.run( "MATCH (n) RETURN n.name" ).list() ); - assertEquals( 3, records.size() ); - } - } - finally - { - assertEquals( 0, router1.exitStatus() ); - assertEquals( 0, router2.exitStatus() ); - assertEquals( 0, router3.exitStatus() ); - assertEquals( 0, reader.exitStatus() ); - } - } - - @Test - void shouldServerWithBoltV4SupportMultiDb() throws Throwable - { - StubServer server = stubController.startStub( "support_multidb_v4.script", 9001 ); - try ( Driver driver = GraphDatabase.driver( "neo4j://localhost:9001", INSECURE_CONFIG ) ) - { - assertTrue( driver.supportsMultiDb() ); - } - finally - { - assertEquals( 0, server.exitStatus() ); - } - } - - @Test - void shouldServerWithBoltV3NotSupportMultiDb() throws Throwable - { - StubServer server = stubController.startStub( "support_multidb_v3.script", 9001 ); - try ( Driver driver = GraphDatabase.driver( "neo4j://localhost:9001", INSECURE_CONFIG ) ) - { - assertFalse( driver.supportsMultiDb() ); - } - finally - { - assertEquals( 0, server.exitStatus() ); - } - } - - private static Driver newDriverWithSleeplessClock( String uriString, Config config ) - { - DriverFactory driverFactory = new DriverFactoryWithClock( new SleeplessClock() ); - return newDriver( uriString, driverFactory, config ); - } - - private static Driver newDriverWithSleeplessClock( String uriString ) - { - return newDriverWithSleeplessClock( uriString, INSECURE_CONFIG ); - } - private static Driver newDriverWithFixedRetries( String uriString, int retries ) { DriverFactory driverFactory = new DriverFactoryWithFixedRetryLogic( retries ); @@ -1390,20 +276,6 @@ private static TransactionWork> queryWork( final String query, fina }; } - private static List readStrings( final String query, Session session ) - { - return session.readTransaction( tx -> - { - List records = tx.run( query ).list(); - List names = new ArrayList<>( records.size() ); - for ( Record record : records ) - { - names.add( record.get( 0 ).asString() ); - } - return names; - } ); - } - static class PortBasedServerAddressComparator implements Comparator { @Override diff --git a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitIT.java b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitIT.java deleted file mode 100644 index be71b344eb..0000000000 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitIT.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (c) "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.integration; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.net.URI; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; - -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Config; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Record; -import org.neo4j.driver.Session; -import org.neo4j.driver.exceptions.FatalDiscoveryException; -import org.neo4j.driver.exceptions.ServiceUnavailableException; -import org.neo4j.driver.integration.RoutingDriverBoltKitIT.PortBasedServerAddressComparator; -import org.neo4j.driver.Bookmark; -import org.neo4j.driver.net.ServerAddress; -import org.neo4j.driver.net.ServerAddressResolver; -import org.neo4j.driver.util.StubServer; - -import static java.util.Arrays.asList; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.core.IsEqual.equalTo; -import static org.hamcrest.junit.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.neo4j.driver.SessionConfig.builder; -import static org.neo4j.driver.internal.InternalBookmark.parse; -import static org.neo4j.driver.util.StubServer.INSECURE_CONFIG; -import static org.neo4j.driver.util.StubServer.insecureBuilder; - -class RoutingDriverMultidatabaseBoltKitIT -{ - @Test - void shouldDiscoverForDatabase() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer router = StubServer.start( "acquire_endpoints_v4.script", 9001 ); - //START a read server - StubServer reader = StubServer.start( "read_server_v4_read.script", 9005 ); - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withDatabase( "mydatabase" ).build() ) ) - { - List result = session.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( "n.name" ).asString() ); - - assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) ); - } - // Finally - assertThat( router.exitStatus(), equalTo( 0 ) ); - assertThat( reader.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldRetryOnEmptyDiscoveryResult() throws IOException, InterruptedException, StubServer.ForceKilled - { - ServerAddressResolver resolver = a -> { - SortedSet addresses = new TreeSet<>( new PortBasedServerAddressComparator() ); - addresses.add( ServerAddress.of( "127.0.0.1", 9001 ) ); - addresses.add( ServerAddress.of( "127.0.0.1", 9002 ) ); - return addresses; - }; - - StubServer emptyRouter = StubServer.start( "acquire_endpoints_v4_empty.script", 9001 ); - StubServer realRouter = StubServer.start( "acquire_endpoints_v4_virtual_host.script", 9002 ); - StubServer reader = StubServer.start( "read_server_v4_read.script", 9005 ); - - Config config = insecureBuilder().withResolver( resolver ).build(); - try ( Driver driver = GraphDatabase.driver( "neo4j://my.virtual.host:8080", config ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withDatabase( "mydatabase" ).build() ) ) - { - List result = session.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( "n.name" ).asString() ); - - assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) ); - } - // Finally - assertThat( emptyRouter.exitStatus(), equalTo( 0 ) ); - assertThat( realRouter.exitStatus(), equalTo( 0 ) ); - assertThat( reader.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldThrowRoutingErrorIfDatabaseNotFound() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer server = StubServer.start( "acquire_endpoints_v4_database_not_found.script", 9001 ); - - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ); - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withDatabase( "mydatabase" ).build() ) ) - { - final FatalDiscoveryException error = assertThrows( FatalDiscoveryException.class, () -> { - session.run( "MATCH (n) RETURN n.name" ); - } ); - - assertThat( error.code(), equalTo( "Neo.ClientError.Database.DatabaseNotFound" ) ); - } - // Finally - assertThat( server.exitStatus(), equalTo( 0 ) ); - } - - @Test - void shouldBeAbleToServeReachableDatabase() throws IOException, InterruptedException, StubServer.ForceKilled - { - // Given - StubServer router = StubServer.start( "acquire_endpoints_v4_multi_db.script", 9001 ); - StubServer readServer = StubServer.start( "read_server_v4_read.script", 9005 ); - - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) - { - try( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withDatabase( "unreachable" ).build() ) ) - { - final ServiceUnavailableException error = assertThrows( ServiceUnavailableException.class, () -> { - session.run( "MATCH (n) RETURN n.name" ); - } ); - - assertThat( error.getMessage(), containsString( "Could not perform discovery for database 'unreachable'" ) ); - } - - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).withDatabase( "mydatabase" ).build() ) ) - { - List result = session.run( "MATCH (n) RETURN n.name" ).list( record -> record.get( "n.name" ).asString() ); - - assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) ); - } - } - // Finally - assertThat( router.exitStatus(), equalTo( 0 ) ); - assertThat( readServer.exitStatus(), equalTo( 0 ) ); - } - - - @Test - void shouldDriverVerifyConnectivity() throws Throwable - { - StubServer router = StubServer.start( "acquire_endpoints_v4_verify_connectivity.script", 9001 ); - StubServer readServer = StubServer.start( "read_server_v4_read.script", 9005 ); - - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) - { - driver.verifyConnectivity(); - try ( Session session = driver.session( builder().withDatabase( "mydatabase" ).withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - List records = session.run( "MATCH (n) RETURN n.name" ).list(); - assertEquals( 3, records.size() ); - } - - Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ); - - driver.close(); - - assertThrows( IllegalStateException.class, () -> session.run( "MATCH (n) RETURN n.name" ) ); - } - finally - { - assertEquals( 0, readServer.exitStatus() ); - assertEquals( 0, router.exitStatus() ); - } - } - - @Test - void shouldPassSystemBookmarkWhenGettingRoutingTableForMultiDB() throws Throwable - { - Bookmark sysBookmark = parse( "sys:1234" ); - Bookmark fooBookmark = parse( "foo:5678" ); - StubServer router = StubServer.start( "acquire_endpoints_v4_with_bookmark.script", 9001 ); - StubServer readServer = StubServer.start( "read_server_v4_read_with_bookmark.script", 9005 ); - - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) - { - try ( Session session = driver.session( builder() - .withDatabase( "foo" ) - .withDefaultAccessMode( AccessMode.READ ) - .withBookmarks( sysBookmark, fooBookmark ) - .build() ) ) - { - List records = session.run( "MATCH (n) RETURN n.name" ).list(); - assertEquals( 3, records.size() ); - assertThat( session.lastBookmark(), equalTo( parse( "foo:6678" ) ) ); - } - } - finally - { - assertEquals( 0, readServer.exitStatus() ); - assertEquals( 0, router.exitStatus() ); - } - } - - @Test - void shouldIgnoreSystemBookmarkWhenGettingRoutingTable() throws Throwable - { - Bookmark sysBookmark = parse( "sys:1234" ); - Bookmark fooBookmark = parse( "foo:5678" ); - StubServer router = StubServer.start( "acquire_endpoints_v3.script", 9001 ); - StubServer readServer = StubServer.start( "read_server_v3_read_with_bookmark.script", 9005 ); - - URI uri = URI.create( "neo4j://127.0.0.1:9001" ); - try ( Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG ) ) - { - try ( Session session = driver.session( builder() - .withDefaultAccessMode( AccessMode.READ ) - .withBookmarks( sysBookmark, fooBookmark ) // you can still send, the real server will reject in session run of course. - .build() ) ) - { - List records = session.run( "MATCH (n) RETURN n.name" ).list(); - assertEquals( 3, records.size() ); - assertThat( session.lastBookmark(), equalTo( parse( "foo:6678" ) ) ); - } - } - finally - { - assertEquals( 0, readServer.exitStatus() ); - assertEquals( 0, router.exitStatus() ); - } - } -} diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java index 12b1e493f8..5c219e33fd 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/ConnectionPoolImplIT.java @@ -30,6 +30,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.ConnectionSettings; +import org.neo4j.driver.internal.DefaultDomainNameResolver; import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; @@ -147,7 +148,7 @@ private ConnectionPoolImpl newPool() throws Exception FakeClock clock = new FakeClock(); ConnectionSettings connectionSettings = new ConnectionSettings( neo4j.authToken(), "test", 5000 ); ChannelConnector connector = new ChannelConnectorImpl( connectionSettings, SecurityPlanImpl.insecure(), - DEV_NULL_LOGGING, clock, RoutingContext.EMPTY ); + DEV_NULL_LOGGING, clock, RoutingContext.EMPTY, DefaultDomainNameResolver.getInstance() ); PoolSettings poolSettings = newSettings(); Bootstrap bootstrap = BootstrapFactory.newBootstrap( 1 ); return new ConnectionPoolImpl( connector, bootstrap, poolSettings, DEV_NULL_METRICS, DEV_NULL_LOGGING, clock, true ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java index 693738af70..52d8d935d6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java +++ b/driver/src/test/java/org/neo4j/driver/internal/async/pool/NettyChannelPoolIT.java @@ -35,11 +35,12 @@ import org.neo4j.driver.Value; import org.neo4j.driver.exceptions.AuthenticationException; import org.neo4j.driver.internal.ConnectionSettings; +import org.neo4j.driver.internal.DefaultDomainNameResolver; import org.neo4j.driver.internal.async.connection.BootstrapFactory; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.cluster.RoutingContext; -import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.security.InternalAuthToken; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.internal.util.FakeClock; import org.neo4j.driver.internal.util.ImmediateSchedulingEventExecutor; import org.neo4j.driver.util.DatabaseExtension; @@ -184,7 +185,7 @@ private NettyChannelPool newPool( AuthToken authToken, int maxConnections ) { ConnectionSettings settings = new ConnectionSettings( authToken, "test", 5_000 ); ChannelConnectorImpl connector = new ChannelConnectorImpl( settings, SecurityPlanImpl.insecure(), DEV_NULL_LOGGING, - new FakeClock(), RoutingContext.EMPTY ); + new FakeClock(), RoutingContext.EMPTY, DefaultDomainNameResolver.getInstance() ); return new NettyChannelPool( neo4j.address(), connector, bootstrap, poolHandler, ChannelHealthChecker.ACTIVE, 1_000, maxConnections ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/AddressSetTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/AddressSetTest.java index e078b0b8f5..57b80fac8a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/AddressSetTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/AddressSetTest.java @@ -37,7 +37,7 @@ void shouldPreserveOrderWhenAdding() throws Exception Set servers = addresses( "one", "two", "tre" ); AddressSet set = new AddressSet(); - set.update( servers ); + set.retainAllAndAdd( servers ); assertArrayEquals( new BoltServerAddress[]{ new BoltServerAddress( "one" ), @@ -46,7 +46,7 @@ void shouldPreserveOrderWhenAdding() throws Exception // when servers.add( new BoltServerAddress( "fyr" ) ); - set.update( servers ); + set.retainAllAndAdd( servers ); // then assertArrayEquals( new BoltServerAddress[]{ @@ -62,7 +62,7 @@ void shouldPreserveOrderWhenRemoving() throws Exception // given Set servers = addresses( "one", "two", "tre" ); AddressSet set = new AddressSet(); - set.update( servers ); + set.retainAllAndAdd( servers ); assertArrayEquals( new BoltServerAddress[]{ new BoltServerAddress( "one" ), @@ -84,7 +84,7 @@ void shouldPreserveOrderWhenRemovingThroughUpdate() throws Exception // given Set servers = addresses( "one", "two", "tre" ); AddressSet set = new AddressSet(); - set.update( servers ); + set.retainAllAndAdd( servers ); assertArrayEquals( new BoltServerAddress[]{ new BoltServerAddress( "one" ), @@ -93,7 +93,7 @@ void shouldPreserveOrderWhenRemovingThroughUpdate() throws Exception // when servers.remove( new BoltServerAddress( "one" ) ); - set.update( servers ); + set.retainAllAndAdd( servers ); // then assertArrayEquals( new BoltServerAddress[]{ @@ -115,7 +115,7 @@ void shouldExposeEmptyArrayWhenEmpty() void shouldExposeCorrectArray() { AddressSet addressSet = new AddressSet(); - addressSet.update( addresses( "one", "two", "tre" ) ); + addressSet.retainAllAndAdd( addresses( "one", "two", "tre" ) ); BoltServerAddress[] addresses = addressSet.toArray(); @@ -137,7 +137,7 @@ void shouldHaveSizeZeroWhenEmpty() void shouldHaveCorrectSize() { AddressSet addressSet = new AddressSet(); - addressSet.update( addresses( "one", "two" ) ); + addressSet.retainAllAndAdd( addresses( "one", "two" ) ); assertEquals( 2, addressSet.size() ); } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/ClusterCompositionTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/ClusterCompositionTest.java index 99deaae06c..a89d86a763 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/ClusterCompositionTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/ClusterCompositionTest.java @@ -27,10 +27,10 @@ import java.util.Map; import java.util.Set; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.internal.InternalRecord; import org.neo4j.driver.Record; import org.neo4j.driver.Value; +import org.neo4j.driver.internal.BoltServerAddress; +import org.neo4j.driver.internal.InternalRecord; import static java.util.Arrays.asList; import static org.hamcrest.Matchers.contains; @@ -38,13 +38,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.neo4j.driver.Values.value; import static org.neo4j.driver.internal.util.ClusterCompositionUtil.A; import static org.neo4j.driver.internal.util.ClusterCompositionUtil.B; import static org.neo4j.driver.internal.util.ClusterCompositionUtil.C; import static org.neo4j.driver.internal.util.ClusterCompositionUtil.D; import static org.neo4j.driver.internal.util.ClusterCompositionUtil.E; import static org.neo4j.driver.internal.util.ClusterCompositionUtil.F; -import static org.neo4j.driver.Values.value; class ClusterCompositionTest { @@ -150,8 +150,8 @@ void parseCorrectRecord() Value[] values = { value( 42L ), value( asList( serversEntry( "READ", A, B ), - serversEntry( "WRITE", C, D ), - serversEntry( "ROUTE", E, F ) ) ) + serversEntry( "WRITE", C, D ), + serversEntry( "ROUTE", E, F ) ) ) }; Record record = new InternalRecord( asList( "ttl", "servers" ), values ); @@ -171,8 +171,8 @@ void parsePreservesOrderOfReaders() Value[] values = { value( 42L ), value( asList( serversEntry( "READ", A, C, E, B, F, D ), - serversEntry( "WRITE" ), - serversEntry( "ROUTE" ) ) ) + serversEntry( "WRITE" ), + serversEntry( "ROUTE" ) ) ) }; Record record = new InternalRecord( asList( "ttl", "servers" ), values ); @@ -189,8 +189,8 @@ void parsePreservesOrderOfWriters() Value[] values = { value( 42L ), value( asList( serversEntry( "READ" ), - serversEntry( "WRITE", C, F, D, A, B, E ), - serversEntry( "ROUTE" ) ) ) + serversEntry( "WRITE", C, F, D, A, B, E ), + serversEntry( "ROUTE" ) ) ) }; Record record = new InternalRecord( asList( "ttl", "servers" ), values ); @@ -207,8 +207,8 @@ void parsePreservesOrderOfRouters() Value[] values = { value( 42L ), value( asList( serversEntry( "READ" ), - serversEntry( "WRITE" ), - serversEntry( "ROUTE", F, D, A, B, C, E ) ) ) + serversEntry( "WRITE" ), + serversEntry( "ROUTE", F, D, A, B, C, E ) ) ) }; Record record = new InternalRecord( asList( "ttl", "servers" ), values ); @@ -220,7 +220,7 @@ void parsePreservesOrderOfRouters() } private static ClusterComposition newComposition( long expirationTimestamp, Set readers, - Set writers, Set routers ) + Set writers, Set routers ) { return new ClusterComposition( expirationTimestamp, readers, writers, routers ); } 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 19b1174909..536684547d 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 @@ -23,6 +23,8 @@ import org.mockito.ArgumentCaptor; import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,6 +37,8 @@ import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.DatabaseName; +import org.neo4j.driver.internal.DefaultDomainNameResolver; +import org.neo4j.driver.internal.DomainNameResolver; import org.neo4j.driver.internal.InternalBookmark; import org.neo4j.driver.internal.spi.Connection; import org.neo4j.driver.internal.spi.ConnectionPool; @@ -50,7 +54,6 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; @@ -90,7 +93,7 @@ void shouldUseFirstRouterInTable() Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) ); RoutingTable table = routingTableMock( B ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( expectedComposition, actualComposition ); verify( table, never() ).forget( B ); @@ -111,7 +114,7 @@ void shouldSkipFailingRouters() Rediscovery rediscovery = newRediscovery( A, compositionProvider, mock( ServerAddressResolver.class ) ); RoutingTable table = routingTableMock( A, B, C ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( expectedComposition, actualComposition ); verify( table ).forget( A ); @@ -156,7 +159,7 @@ void shouldFallbackToInitialRouterWhenKnownRoutersFail() Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver ); RoutingTable table = routingTableMock( B, C ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( expectedComposition, actualComposition ); verify( table ).forget( B ); @@ -181,7 +184,7 @@ void shouldFailImmediatelyWhenClusterCompositionProviderReturnsFailure() RoutingTable table = routingTableMock( B, C ); // When - ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( validComposition, composition ); ArgumentCaptor argument = ArgumentCaptor.forClass( DiscoveryException.class ); @@ -208,7 +211,7 @@ void shouldResolveInitialRouterAddress() Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver ); RoutingTable table = routingTableMock( B, C ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( expectedComposition, actualComposition ); verify( table ).forget( B ); @@ -237,7 +240,7 @@ void shouldResolveInitialRouterAddressUsingCustomResolver() Rediscovery rediscovery = newRediscovery( A, compositionProvider, resolver ); RoutingTable table = routingTableMock( B, C ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( expectedComposition, actualComposition ); verify( table ).forget( B ); @@ -308,7 +311,7 @@ void shouldUseInitialRouterAfterDiscoveryReturnsNoWriters() RoutingTable table = new ClusterRoutingTable( defaultDatabase(), new FakeClock() ); table.update( noWritersComposition ); - ClusterComposition composition2 = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition composition2 = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( validComposition, composition2 ); } @@ -327,7 +330,7 @@ void shouldUseInitialRouterToStartWith() Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver ); RoutingTable table = routingTableMock( true, B, C, D ); - ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( validComposition, composition ); } @@ -348,7 +351,7 @@ void shouldUseKnownRoutersWhenInitialRouterFails() Rediscovery rediscovery = newRediscovery( initialRouter, compositionProvider, resolver ); RoutingTable table = routingTableMock( true, D, E ); - ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition composition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( validComposition, composition ); verify( table ).forget( initialRouter ); verify( table ).forget( D ); @@ -375,10 +378,11 @@ void shouldRetryConfiguredNumberOfTimesWithDelay() ImmediateSchedulingEventExecutor eventExecutor = new ImmediateSchedulingEventExecutor(); RoutingSettings settings = new RoutingSettings( maxRoutingFailures, retryTimeoutDelay, 0 ); - Rediscovery rediscovery = new RediscoveryImpl( A, settings, compositionProvider, eventExecutor, resolver, DEV_NULL_LOGGER ); - RoutingTable table = routingTableMock(A, B ); + Rediscovery rediscovery = + new RediscoveryImpl( A, settings, compositionProvider, eventExecutor, resolver, DEV_NULL_LOGGER, DefaultDomainNameResolver.getInstance() ); + RoutingTable table = routingTableMock( A, B ); - ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ); + ClusterComposition actualComposition = await( rediscovery.lookupClusterComposition( table, pool, empty() ) ).getClusterComposition(); assertEquals( expectedComposition, actualComposition ); verify( table, times( maxRoutingFailures ) ).forget( A ); @@ -399,7 +403,8 @@ void shouldNotLogWhenSingleRetryAttemptFails() ImmediateSchedulingEventExecutor eventExecutor = new ImmediateSchedulingEventExecutor(); RoutingSettings settings = new RoutingSettings( maxRoutingFailures, retryTimeoutDelay, 0 ); Logger logger = mock( Logger.class ); - Rediscovery rediscovery = new RediscoveryImpl( A, settings, compositionProvider, eventExecutor, resolver, logger ); + Rediscovery rediscovery = + new RediscoveryImpl( A, settings, compositionProvider, eventExecutor, resolver, logger, DefaultDomainNameResolver.getInstance() ); RoutingTable table = routingTableMock( A ); ServiceUnavailableException e = @@ -412,16 +417,20 @@ void shouldNotLogWhenSingleRetryAttemptFails() } @Test - void shouldNotResolveToIPs() + void shouldResolveToIP() throws UnknownHostException { ServerAddressResolver resolver = resolverMock( A, A ); - Rediscovery rediscovery = new RediscoveryImpl( A, null, null, null, resolver, null ); + DomainNameResolver domainNameResolver = mock( DomainNameResolver.class ); + InetAddress localhost = InetAddress.getLocalHost(); + when( domainNameResolver.resolve( A.host() ) ).thenReturn( new InetAddress[]{localhost} ); + Rediscovery rediscovery = new RediscoveryImpl( A, null, null, null, resolver, null, domainNameResolver ); List addresses = rediscovery.resolve(); verify( resolver, times( 1 ) ).resolve( A ); + verify( domainNameResolver, times( 1 ) ).resolve( A.host() ); assertEquals( 1, addresses.size() ); - assertFalse( addresses.get( 0 ).isResolved() ); + assertEquals( new BoltServerAddress( A.host(), localhost.getHostAddress(), A.port() ), addresses.get( 0 ) ); } private Rediscovery newRediscovery( BoltServerAddress initialRouter, ClusterCompositionProvider compositionProvider, @@ -434,7 +443,8 @@ private Rediscovery newRediscovery( BoltServerAddress initialRouter, ClusterComp ServerAddressResolver resolver, Logger logger ) { RoutingSettings settings = new RoutingSettings( 1, 0, 0 ); - return new RediscoveryImpl( initialRouter, settings, compositionProvider, GlobalEventExecutor.INSTANCE, resolver, logger ); + return new RediscoveryImpl( initialRouter, settings, compositionProvider, GlobalEventExecutor.INSTANCE, resolver, logger, + DefaultDomainNameResolver.getInstance() ); } @SuppressWarnings( "unchecked" ) @@ -494,7 +504,7 @@ private static RoutingTable routingTableMock( boolean preferInitialRouter, BoltS { RoutingTable routingTable = mock( RoutingTable.class ); AddressSet addressSet = new AddressSet(); - addressSet.update( asOrderedSet( routers ) ); + addressSet.retainAllAndAdd( asOrderedSet( routers ) ); when( routingTable.routers() ).thenReturn( addressSet ); when( routingTable.database() ).thenReturn( defaultDatabase() ); when( routingTable.preferInitialRouter() ).thenReturn( preferInitialRouter ); diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerTest.java index e7763d74e2..85cd88be53 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/RoutingTableHandlerTest.java @@ -111,7 +111,7 @@ void acquireShouldUpdateRoutingTableWhenKnownRoutingTableIsStale() ClusterComposition clusterComposition = new ClusterComposition( 42, readers, writers, routers ); Rediscovery rediscovery = mock( RediscoveryImpl.class ); when( rediscovery.lookupClusterComposition( eq( routingTable ), eq( connectionPool ), any() ) ) - .thenReturn( completedFuture( clusterComposition ) ); + .thenReturn( completedFuture( new ClusterCompositionLookupResult( clusterComposition ) ) ); RoutingTableHandler handler = newRoutingTableHandler( routingTable, rediscovery, connectionPool ); @@ -158,7 +158,7 @@ void shouldRetainAllFetchedAddressesInConnectionPoolAfterFetchingOfRoutingTable( Rediscovery rediscovery = newRediscoveryMock(); when( rediscovery.lookupClusterComposition( any(), any(), any() ) ).thenReturn( completedFuture( - new ClusterComposition( 42, asOrderedSet( A, B ), asOrderedSet( B, C ), asOrderedSet( A, C ) ) ) ); + new ClusterCompositionLookupResult( new ClusterComposition( 42, asOrderedSet( A, B ), asOrderedSet( B, C ), asOrderedSet( A, C ) ) ) ) ); RoutingTableRegistry registry = new RoutingTableRegistry() { @@ -253,7 +253,7 @@ private static RoutingTable newStaleRoutingTableMock( AccessMode mode ) when( routingTable.isStaleFor( mode ) ).thenReturn( true ); AddressSet addresses = new AddressSet(); - addresses.update( new HashSet<>( singletonList( LOCAL_DEFAULT ) ) ); + addresses.retainAllAndAdd( new HashSet<>( singletonList( LOCAL_DEFAULT ) ) ); when( routingTable.readers() ).thenReturn( addresses ); when( routingTable.writers() ).thenReturn( addresses ); when( routingTable.database() ).thenReturn( defaultDatabase() ); @@ -272,7 +272,7 @@ private static Rediscovery newRediscoveryMock() Set noServers = Collections.emptySet(); ClusterComposition clusterComposition = new ClusterComposition( 1, noServers, noServers, noServers ); when( rediscovery.lookupClusterComposition( any( RoutingTable.class ), any( ConnectionPool.class ), any( InternalBookmark.class ) ) ) - .thenReturn( completedFuture( clusterComposition ) ); + .thenReturn( completedFuture( new ClusterCompositionLookupResult( clusterComposition ) ) ); return rediscovery; } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java index be9e1449e5..520c81946c 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/LoadBalancerTest.java @@ -414,7 +414,7 @@ private static LoadBalancer newLoadBalancer( ConnectionPool connectionPool, Rout when( routingTables.ensureRoutingTable( any( ConnectionContext.class ) ) ).thenReturn( CompletableFuture.completedFuture( handler ) ); Rediscovery rediscovery = mock( Rediscovery.class ); return new LoadBalancer( connectionPool, routingTables, rediscovery, new LeastConnectedLoadBalancingStrategy( connectionPool, DEV_NULL_LOGGING ), - GlobalEventExecutor.INSTANCE, DEV_NULL_LOGGER ); + GlobalEventExecutor.INSTANCE, DEV_NULL_LOGGER ); } private static LoadBalancer newLoadBalancer( ConnectionPool connectionPool, Rediscovery rediscovery ) @@ -428,6 +428,6 @@ private static LoadBalancer newLoadBalancer( ConnectionPool connectionPool, Rout { // Used only in testing return new LoadBalancer( connectionPool, routingTables, rediscovery, new LeastConnectedLoadBalancingStrategy( connectionPool, DEV_NULL_LOGGING ), - GlobalEventExecutor.INSTANCE, DEV_NULL_LOGGER ); + GlobalEventExecutor.INSTANCE, DEV_NULL_LOGGER ); } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java index 61172275c0..4f235b5535 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java +++ b/driver/src/test/java/org/neo4j/driver/internal/cluster/loadbalancing/RoutingTableAndConnectionPoolTest.java @@ -48,6 +48,7 @@ import org.neo4j.driver.internal.async.pool.PoolSettings; import org.neo4j.driver.internal.async.pool.TestConnectionPool; import org.neo4j.driver.internal.cluster.ClusterComposition; +import org.neo4j.driver.internal.cluster.ClusterCompositionLookupResult; import org.neo4j.driver.internal.cluster.Rediscovery; import org.neo4j.driver.internal.cluster.RoutingTable; import org.neo4j.driver.internal.cluster.RoutingTableRegistry; @@ -326,30 +327,31 @@ private LoadBalancer newLoadBalancer( ConnectionPool connectionPool, RoutingTabl { Rediscovery rediscovery = mock( Rediscovery.class ); return new LoadBalancer( connectionPool, routingTables, rediscovery, new LeastConnectedLoadBalancingStrategy( connectionPool, logging ), - GlobalEventExecutor.INSTANCE, logging.getLog( "LB" ) ); + GlobalEventExecutor.INSTANCE, logging.getLog( "LB" ) ); } - private CompletableFuture clusterComposition( BoltServerAddress... addresses ) + private CompletableFuture clusterComposition( BoltServerAddress... addresses ) { return clusterComposition( Duration.ofSeconds( 30 ).toMillis(), addresses ); } - private CompletableFuture expiredClusterComposition( BoltServerAddress... addresses ) + private CompletableFuture expiredClusterComposition( BoltServerAddress... addresses ) { return clusterComposition( -STALE_ROUTING_TABLE_PURGE_DELAY_MS - 1, addresses ); } - private CompletableFuture clusterComposition( long expireAfterMs, BoltServerAddress... addresses ) + private CompletableFuture clusterComposition( long expireAfterMs, BoltServerAddress... addresses ) { HashSet servers = new HashSet<>( Arrays.asList( addresses ) ); ClusterComposition composition = new ClusterComposition( clock.millis() + expireAfterMs, servers, servers, servers ); - return CompletableFuture.completedFuture( composition ); + return CompletableFuture.completedFuture( new ClusterCompositionLookupResult( composition ) ); } private class RandomizedRediscovery implements Rediscovery { @Override - public CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, Bookmark bookmark ) + public CompletionStage lookupClusterComposition( RoutingTable routingTable, ConnectionPool connectionPool, + Bookmark bookmark ) { // when looking up a new routing table, we return a valid random routing table back Set servers = new HashSet<>(); @@ -367,7 +369,7 @@ public CompletionStage lookupClusterComposition( RoutingTabl servers.add( A ); } ClusterComposition composition = new ClusterComposition( clock.millis() + 1, servers, servers, servers ); - return CompletableFuture.completedFuture( composition ); + return CompletableFuture.completedFuture( new ClusterCompositionLookupResult( composition ) ); } @Override 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 8b92004c29..aa7341582c 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 @@ -20,25 +20,14 @@ import org.junit.jupiter.api.Test; -import java.net.SocketAddress; import java.net.URI; -import java.util.List; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.net.ServerAddress; import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.everyItem; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; 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; @@ -59,17 +48,6 @@ void portShouldUseDefaultIfNotSupplied() assertThat( new BoltServerAddress( "localhost" ).port(), equalTo( BoltServerAddress.DEFAULT_PORT ) ); } - @Test - void shouldAlwaysResolveAddress() - { - BoltServerAddress boltAddress = new BoltServerAddress( "localhost" ); - - SocketAddress socketAddress1 = boltAddress.toSocketAddress(); - SocketAddress socketAddress2 = boltAddress.toSocketAddress(); - - assertNotSame( socketAddress1, socketAddress2 ); - } - @Test void shouldHaveCorrectToString() { @@ -146,58 +124,10 @@ void shouldUseUriWithHostAndPort() assertEquals( 12345, address.port() ); } - @Test - void shouldResolveDNSToIPs() throws Exception - { - BoltServerAddress address = new BoltServerAddress( "google.com", 80 ); - List resolved = address.resolveAll(); - assertThat( resolved, hasSize( greaterThanOrEqualTo( 1 ) ) ); - assertThat( resolved, everyItem( equalTo( address ) ) ); - } - - @Test - void shouldResolveLocalhostIPDNSToIPs() throws Exception - { - BoltServerAddress address = new BoltServerAddress( "127.0.0.1", 80 ); - List resolved = address.resolveAll(); - assertThat( resolved, hasSize( 1 ) ); - assertThat( resolved, everyItem( equalTo( address ) ) ); - } - - @Test - void shouldResolveLocalhostDNSToIPs() throws Exception - { - BoltServerAddress address = new BoltServerAddress( "localhost", 80 ); - List resolved = address.resolveAll(); - assertThat( resolved, hasSize( greaterThanOrEqualTo( 1 ) ) ); - assertThat( resolved, everyItem( equalTo( address ) ) ); - } - - @Test - void shouldResolveIPv6LocalhostDNSToIPs() throws Exception - { - BoltServerAddress address = new BoltServerAddress( "[::1]", 80 ); - List resolved = address.resolveAll(); - assertThat( resolved, hasSize( greaterThanOrEqualTo( 1 ) ) ); - assertThat( resolved, everyItem( equalTo( address ) ) ); - } - @Test void shouldIncludeHostAndPortInToString() { BoltServerAddress address = new BoltServerAddress( "localhost", 8081 ); assertThat( address.toString(), equalTo( "localhost:8081" ) ); } - - @Test - void shouldIncludeHostResolvedIPAndPortInToStringWhenResolved() throws Exception - { - BoltServerAddress address = new BoltServerAddress( "localhost", 8081 ); - BoltServerAddress resolved = address.resolve(); - - assertThat( resolved.toString(), not( equalTo( "localhost:8081" ) ) ); - assertThat( resolved.toString(), anyOf( containsString( "(127.0.0.1)" ), containsString( "(::1)" ) ) ); - assertThat( resolved.toString(), startsWith( "localhost" ) ); - assertThat( resolved.toString(), endsWith( "8081" ) ); - } } diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/ClusterCompositionUtil.java b/driver/src/test/java/org/neo4j/driver/internal/util/ClusterCompositionUtil.java index 53804f82b1..86698f0bc6 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/ClusterCompositionUtil.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/ClusterCompositionUtil.java @@ -29,16 +29,18 @@ public final class ClusterCompositionUtil { - private ClusterCompositionUtil() {} + private ClusterCompositionUtil() + { + } public static final long NEVER_EXPIRE = System.currentTimeMillis() + TimeUnit.HOURS.toMillis( 1 ); - public static final BoltServerAddress A = new BoltServerAddress( "1111:11" ); - public static final BoltServerAddress B = new BoltServerAddress( "2222:22" ); - public static final BoltServerAddress C = new BoltServerAddress( "3333:33" ); - public static final BoltServerAddress D = new BoltServerAddress( "4444:44" ); - public static final BoltServerAddress E = new BoltServerAddress( "5555:55" ); - public static final BoltServerAddress F = new BoltServerAddress( "6666:66" ); + public static final BoltServerAddress A = new BoltServerAddress( "192.168.100.100:11" ); + public static final BoltServerAddress B = new BoltServerAddress( "192.168.100.101:22" ); + public static final BoltServerAddress C = new BoltServerAddress( "192.168.100.102:33" ); + public static final BoltServerAddress D = new BoltServerAddress( "192.168.100.103:44" ); + public static final BoltServerAddress E = new BoltServerAddress( "192.168.100.104:55" ); + public static final BoltServerAddress F = new BoltServerAddress( "192.168.100.105:66" ); public static final List EMPTY = new ArrayList<>(); diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java b/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java index fc650673ca..9be58dea1b 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/MessageRecordingDriverFactory.java @@ -28,7 +28,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import org.neo4j.driver.Config; +import org.neo4j.driver.Logging; import org.neo4j.driver.internal.ConnectionSettings; +import org.neo4j.driver.internal.DefaultDomainNameResolver; import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; @@ -39,8 +42,6 @@ import org.neo4j.driver.internal.messaging.Message; import org.neo4j.driver.internal.messaging.MessageFormat; import org.neo4j.driver.internal.security.SecurityPlan; -import org.neo4j.driver.Config; -import org.neo4j.driver.Logging; public class MessageRecordingDriverFactory extends DriverFactory { @@ -56,7 +57,8 @@ protected ChannelConnector createConnector( ConnectionSettings settings, Securit RoutingContext routingContext ) { ChannelPipelineBuilder pipelineBuilder = new MessageRecordingChannelPipelineBuilder(); - return new ChannelConnectorImpl( settings, securityPlan, pipelineBuilder, config.logging(), clock, routingContext ); + return new ChannelConnectorImpl( settings, securityPlan, pipelineBuilder, config.logging(), clock, routingContext, + DefaultDomainNameResolver.getInstance() ); } private class MessageRecordingChannelPipelineBuilder extends ChannelPipelineBuilderImpl diff --git a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java index e36720ffbd..7bc9faa59a 100644 --- a/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java +++ b/driver/src/test/java/org/neo4j/driver/internal/util/io/ChannelTrackingDriverFactoryWithFailingMessageFormat.java @@ -18,14 +18,15 @@ */ package org.neo4j.driver.internal.util.io; +import org.neo4j.driver.Config; import org.neo4j.driver.internal.ConnectionSettings; +import org.neo4j.driver.internal.DefaultDomainNameResolver; import org.neo4j.driver.internal.async.connection.ChannelConnector; import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl; import org.neo4j.driver.internal.cluster.RoutingContext; import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.internal.util.Clock; import org.neo4j.driver.internal.util.FailingMessageFormat; -import org.neo4j.driver.Config; public class ChannelTrackingDriverFactoryWithFailingMessageFormat extends ChannelTrackingDriverFactory { @@ -40,7 +41,8 @@ public ChannelTrackingDriverFactoryWithFailingMessageFormat( Clock clock ) protected ChannelConnector createRealConnector( ConnectionSettings settings, SecurityPlan securityPlan, Config config, Clock clock, RoutingContext routingContext ) { - return new ChannelConnectorImpl( settings, securityPlan, pipelineBuilder, config.logging(), clock, routingContext ); + return new ChannelConnectorImpl( settings, securityPlan, pipelineBuilder, config.logging(), clock, routingContext, + DefaultDomainNameResolver.getInstance() ); } public FailingMessageFormat getFailingMessageFormat() diff --git a/driver/src/test/java/org/neo4j/driver/util/Neo4jRunner.java b/driver/src/test/java/org/neo4j/driver/util/Neo4jRunner.java index 1b0769e7ab..4db2773451 100644 --- a/driver/src/test/java/org/neo4j/driver/util/Neo4jRunner.java +++ b/driver/src/test/java/org/neo4j/driver/util/Neo4jRunner.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.InetSocketAddress; import java.net.StandardSocketOptions; import java.net.URI; import java.nio.channels.SocketChannel; @@ -286,7 +287,8 @@ private ServerStatus serverStatus() { SocketChannel soChannel = SocketChannel.open(); soChannel.setOption( StandardSocketOptions.SO_REUSEADDR, true ); - soChannel.connect( boltAddress().toSocketAddress() ); + BoltServerAddress address = boltAddress(); + soChannel.connect( new InetSocketAddress( address.connectionHost(), address.port() ) ); soChannel.close(); return ServerStatus.ONLINE; } diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java b/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java index 8f02f3207d..9fdd6cdbb6 100644 --- a/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java +++ b/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java @@ -19,6 +19,7 @@ package org.neo4j.driver.util.cc; import java.io.FileNotFoundException; +import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.nio.file.Path; @@ -29,10 +30,10 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import org.neo4j.driver.Bookmark; import org.neo4j.driver.Driver; import org.neo4j.driver.Record; import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.Bookmark; import org.neo4j.driver.util.TestUtil; import org.neo4j.driver.util.cc.ClusterMemberRoleDiscoveryFactory.ClusterMemberRoleDiscovery; @@ -400,7 +401,7 @@ private static BoltServerAddress newBoltServerAddress( URI uri ) { try { - return new BoltServerAddress( uri ).resolve(); + return new BoltServerAddress( InetAddress.getByName( uri.getHost() ).getHostAddress(), uri.getPort() ); } catch ( UnknownHostException e ) { diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java index e64b2d551f..68ec80aa1f 100644 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java +++ b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileNotFoundException; +import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.nio.file.Path; @@ -111,7 +112,7 @@ private static BoltServerAddress newBoltServerAddress( URI uri ) { try { - return new BoltServerAddress( uri ).resolve(); + return new BoltServerAddress( InetAddress.getByName( uri.getHost() ).getHostAddress(), uri.getPort() ); } catch ( UnknownHostException e ) { diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java index 6803df576a..0b48f2a299 100644 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java +++ b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java @@ -18,6 +18,7 @@ */ package org.neo4j.driver.util.cc; +import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.HashMap; @@ -27,14 +28,14 @@ import org.neo4j.driver.AccessMode; import org.neo4j.driver.Driver; import org.neo4j.driver.Record; -import org.neo4j.driver.Session; import org.neo4j.driver.Result; +import org.neo4j.driver.Session; import org.neo4j.driver.Values; import org.neo4j.driver.internal.BoltServerAddress; import org.neo4j.driver.internal.util.ServerVersion; -import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.SessionConfig.builder; +import static org.neo4j.driver.Values.parameters; import static org.neo4j.driver.internal.util.Iterables.single; public class ClusterMemberRoleDiscoveryFactory @@ -145,7 +146,7 @@ private static BoltServerAddress newBoltServerAddress( URI uri ) { try { - return new BoltServerAddress( uri ).resolve(); + return new BoltServerAddress( InetAddress.getByName( uri.getHost() ).getHostAddress(), uri.getPort() ); } catch ( UnknownHostException e ) { diff --git a/driver/src/test/resources/acquire_endpoints_v3_9010.script b/driver/src/test/resources/acquire_endpoints_v3_9010.script deleted file mode 100644 index 44a7fc6269..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v3_9010.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9010"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v3_empty.script b/driver/src/test/resources/acquire_endpoints_v3_empty.script deleted file mode 100644 index 9f63f22d6c..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v3_empty.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "my.virtual.host:8080"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, []] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v3_leader_killed.script b/driver/src/test/resources/acquire_endpoints_v3_leader_killed.script deleted file mode 100644 index 1319603044..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v3_leader_killed.script +++ /dev/null @@ -1,25 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9001"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9004"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9001"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9004"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9001"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": [],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9001"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v3_point_to_empty_router_and_exit.script b/driver/src/test/resources/acquire_endpoints_v3_point_to_empty_router_and_exit.script deleted file mode 100644 index f6598a6d52..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v3_point_to_empty_router_and_exit.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "my.virtual.host:8080"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9010"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9011"], "role": "READ"},{"addresses": ["127.0.0.1:9004"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v3_three_servers_and_exit.script b/driver/src/test/resources/acquire_endpoints_v3_three_servers_and_exit.script deleted file mode 100644 index c6ad010fd4..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v3_three_servers_and_exit.script +++ /dev/null @@ -1,12 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE -!: AUTO BEGIN - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "my.virtual.host:8080"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9001"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9002","127.0.0.1:9003"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {} - diff --git a/driver/src/test/resources/acquire_endpoints_v4.script b/driver/src/test/resources/acquire_endpoints_v4.script deleted file mode 100644 index bff325a385..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v4.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 4 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": { "address": "127.0.0.1:9001"}, "database": "mydatabase"} {"mode": "r", "db": "system"} - PULL {"n": -1} -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v4_database_not_found.script b/driver/src/test/resources/acquire_endpoints_v4_database_not_found.script deleted file mode 100644 index 2067c0be07..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v4_database_not_found.script +++ /dev/null @@ -1,9 +0,0 @@ -!: BOLT 4 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {"address": "127.0.0.1:9001" }, "database": "mydatabase"} {"mode": "r", "db": "system"} - PULL {"n": -1} -S: FAILURE {"code": "Neo.ClientError.Database.DatabaseNotFound", "message": "wut!"} - IGNORED diff --git a/driver/src/test/resources/acquire_endpoints_v4_empty.script b/driver/src/test/resources/acquire_endpoints_v4_empty.script deleted file mode 100644 index 8a7d6e3b70..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v4_empty.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 4 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": { "address": "my.virtual.host:8080" }, "database": "mydatabase"} {"mode": "r", "db": "system"} - PULL {"n": -1} -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, []] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v4_multi_db.script b/driver/src/test/resources/acquire_endpoints_v4_multi_db.script deleted file mode 100644 index 8b1f4ce589..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v4_multi_db.script +++ /dev/null @@ -1,15 +0,0 @@ -!: BOLT 4 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": { "address": "127.0.0.1:9001" }, "database": "unreachable"} {"mode": "r", "db": "system"} - PULL {"n": -1} -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, []] - SUCCESS {} -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": { "address": "127.0.0.1:9001" }, "database": "mydatabase"} {"mode": "r", "db": "system"} - PULL {"n": -1} -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v4_verify_connectivity.script b/driver/src/test/resources/acquire_endpoints_v4_verify_connectivity.script deleted file mode 100644 index 83ccc11f00..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v4_verify_connectivity.script +++ /dev/null @@ -1,15 +0,0 @@ -!: BOLT 4 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {"address": "127.0.0.1:9001"}, "database": "system"} {"mode": "r", "db": "system"} - PULL {"n": -1} -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {"address": "127.0.0.1:9001"}, "database": "mydatabase"} {"mode": "r", "db": "system"} - PULL {"n": -1} -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v4_virtual_host.script b/driver/src/test/resources/acquire_endpoints_v4_virtual_host.script deleted file mode 100644 index 8d7da13a5d..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v4_virtual_host.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 4 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": { "address": "my.virtual.host:8080" }, "database": "mydatabase"} {"mode": "r", "db": "system"} - PULL {"n": -1} -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/acquire_endpoints_v4_with_bookmark.script b/driver/src/test/resources/acquire_endpoints_v4_with_bookmark.script deleted file mode 100644 index 004908f278..0000000000 --- a/driver/src/test/resources/acquire_endpoints_v4_with_bookmark.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 4 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.routing.getRoutingTable($context, $database)" {"context": {"address": "127.0.0.1:9001"}, "database": "foo"} {"mode": "r", "db": "system", "bookmarks": ["sys:1234", "foo:5678"]} - PULL {"n": -1} -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9007","127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9005","127.0.0.1:9006"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {"bookmark": "sys:2234"} diff --git a/driver/src/test/resources/dead_read_server.script b/driver/src/test/resources/dead_read_server.script deleted file mode 100644 index 186d3d81fb..0000000000 --- a/driver/src/test/resources/dead_read_server.script +++ /dev/null @@ -1,8 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "MATCH (n) RETURN n.name" {} {"mode": "r"} -C: PULL_ALL -S: diff --git a/driver/src/test/resources/discover_failed.script b/driver/src/test/resources/discover_failed.script deleted file mode 100644 index dc4d7dc34a..0000000000 --- a/driver/src/test/resources/discover_failed.script +++ /dev/null @@ -1,12 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {}} {} - PULL_ALL -S: FAILURE {"code": "Neo.ClientError.General.Unknown", "message": "wut!"} -S: IGNORED -C: RESET -S: SUCCESS {} -S: diff --git a/driver/src/test/resources/discover_no_writers.script b/driver/src/test/resources/discover_no_writers.script deleted file mode 100644 index 915e8fbada..0000000000 --- a/driver/src/test/resources/discover_no_writers.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9001"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": [],"role": "WRITE"}, {"addresses": ["127.0.0.1:9002","127.0.0.1:9003"], "role": "READ"},{"addresses": ["127.0.0.1:9004","127.0.0.1:9005"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/discover_no_writers_9010.script b/driver/src/test/resources/discover_no_writers_9010.script deleted file mode 100644 index 860c371bf9..0000000000 --- a/driver/src/test/resources/discover_no_writers_9010.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9010"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": [],"role": "WRITE"}, {"addresses": ["127.0.0.1:9002","127.0.0.1:9003"], "role": "READ"},{"addresses": ["127.0.0.1:9004","127.0.0.1:9005"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/discover_one_router.script b/driver/src/test/resources/discover_one_router.script deleted file mode 100644 index 74d8afc424..0000000000 --- a/driver/src/test/resources/discover_one_router.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9010"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9001","127.0.0.1:9002"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9003","127.0.0.1:9004"], "role": "READ"},{"addresses": ["127.0.0.1:9005"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/discover_servers_9010.script b/driver/src/test/resources/discover_servers_9010.script deleted file mode 100644 index fe2b0b6df8..0000000000 --- a/driver/src/test/resources/discover_servers_9010.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9010"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9001"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9002","127.0.0.1:9003","127.0.0.1:9004"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9002","127.0.0.1:9003"], "role": "ROUTE"}]] - SUCCESS {} diff --git a/driver/src/test/resources/empty_routing_context_in_hello_neo4j.script b/driver/src/test/resources/empty_routing_context_in_hello_neo4j.script deleted file mode 100644 index f23e4a20b6..0000000000 --- a/driver/src/test/resources/empty_routing_context_in_hello_neo4j.script +++ /dev/null @@ -1,17 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO GOODBYE - -C: HELLO {"scheme": "none", "user_agent": "neo4j-java/dev", "routing": { "address": "127.0.0.1:9001"} } -S: SUCCESS {"server": "Neo4j/3.5.0", "connection_id": "bolt-123456789"} -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": { "address": "127.0.0.1:9001"} } {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"], "role": "READ"},{"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - RECORD ["Alice"] - RECORD ["Bob"] - SUCCESS {} diff --git a/driver/src/test/resources/get_routing_table.script b/driver/src/test/resources/get_routing_table.script deleted file mode 100644 index 8e8207a294..0000000000 --- a/driver/src/test/resources/get_routing_table.script +++ /dev/null @@ -1,18 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9001"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"], "role": "READ"},{"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - RECORD ["Alice"] - RECORD ["Bob"] - RECORD ["Eve"] - SUCCESS {} -S: diff --git a/driver/src/test/resources/get_routing_table_with_context.script b/driver/src/test/resources/get_routing_table_with_context.script deleted file mode 100644 index 803ce97f8a..0000000000 --- a/driver/src/test/resources/get_routing_table_with_context.script +++ /dev/null @@ -1,16 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"policy": "my_policy", "region": "china", "address": "127.0.0.1:9001"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"], "role": "READ"},{"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - RECORD ["Alice"] - RECORD ["Bob"] - SUCCESS {} diff --git a/driver/src/test/resources/not_able_to_write_server.script b/driver/src/test/resources/not_able_to_write_server.script deleted file mode 100644 index 6cef3e3361..0000000000 --- a/driver/src/test/resources/not_able_to_write_server.script +++ /dev/null @@ -1,13 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE -!: AUTO BEGIN -!: AUTO ROLLBACK - -C: RUN "CREATE ()" {} {} -C: PULL_ALL -S: FAILURE {"code": "Neo.ClientError.Cluster.NotALeader", "message": "blabla"} -S: IGNORED -C: RESET -S: SUCCESS {} diff --git a/driver/src/test/resources/read_server_v3_read_tx.script b/driver/src/test/resources/read_server_v3_read_tx.script deleted file mode 100644 index eb231ceef1..0000000000 --- a/driver/src/test/resources/read_server_v3_read_tx.script +++ /dev/null @@ -1,16 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: BEGIN { "mode": "r" } -S: SUCCESS {} -C: RUN "MATCH (n) RETURN n.name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["n.name"]} - RECORD ["Bob"] - RECORD ["Alice"] - RECORD ["Tina"] - SUCCESS {} -C: COMMIT -S: SUCCESS { "bookmark": "ABookmark" } diff --git a/driver/src/test/resources/read_server_v3_read_with_bookmark.script b/driver/src/test/resources/read_server_v3_read_with_bookmark.script deleted file mode 100644 index c44e456e02..0000000000 --- a/driver/src/test/resources/read_server_v3_read_with_bookmark.script +++ /dev/null @@ -1,12 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "MATCH (n) RETURN n.name" {} { "mode": "r", "bookmarks": ["sys:1234", "foo:5678"] } - PULL_ALL -S: SUCCESS {"fields": ["n.name"]} - RECORD ["Bob"] - RECORD ["Alice"] - RECORD ["Tina"] - SUCCESS { "bookmark": "foo:6678" } diff --git a/driver/src/test/resources/read_server_v4_read_with_bookmark.script b/driver/src/test/resources/read_server_v4_read_with_bookmark.script deleted file mode 100644 index 837b12b59e..0000000000 --- a/driver/src/test/resources/read_server_v4_read_with_bookmark.script +++ /dev/null @@ -1,12 +0,0 @@ -!: BOLT 4 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "MATCH (n) RETURN n.name" {} { "mode": "r", "db": "foo", "bookmarks": ["sys:1234", "foo:5678"] } - PULL { "n": 1000 } -S: SUCCESS {"fields": ["n.name"]} - RECORD ["Bob"] - RECORD ["Alice"] - RECORD ["Tina"] - SUCCESS { "bookmark": "foo:6678" } diff --git a/driver/src/test/resources/read_tx_with_bookmarks.script b/driver/src/test/resources/read_tx_with_bookmarks.script deleted file mode 100644 index b2ff13ad4f..0000000000 --- a/driver/src/test/resources/read_tx_with_bookmarks.script +++ /dev/null @@ -1,15 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: BEGIN {"bookmarks": ["OldBookmark"], "mode": "r"} -S: SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - RECORD ["Bob"] - RECORD ["Alice"] - SUCCESS {} -C: COMMIT -S: SUCCESS {"bookmark": "NewBookmark"} diff --git a/driver/src/test/resources/rediscover_using_initial_router.script b/driver/src/test/resources/rediscover_using_initial_router.script deleted file mode 100644 index 0424345453..0000000000 --- a/driver/src/test/resources/rediscover_using_initial_router.script +++ /dev/null @@ -1,18 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE -!: AUTO BEGIN -!: AUTO COMMIT - -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": {"address": "127.0.0.1:9001"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9008"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9001","127.0.0.1:9009","127.0.0.1:9010"], "role": "READ"},{"addresses": ["127.0.0.1:9001","127.0.0.1:9011"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - RECORD ["Bob"] - RECORD ["Alice"] - SUCCESS {} diff --git a/driver/src/test/resources/routing_context_in_hello_neo4j.script b/driver/src/test/resources/routing_context_in_hello_neo4j.script deleted file mode 100644 index 57db70c3b7..0000000000 --- a/driver/src/test/resources/routing_context_in_hello_neo4j.script +++ /dev/null @@ -1,17 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO GOODBYE - -C: HELLO {"scheme": "none", "user_agent": "neo4j-java/dev", "routing": { "address": "127.0.0.1:9001", "region": "china", "policy": "my_policy"}} -S: SUCCESS {"server": "Neo4j/3.5.0", "connection_id": "bolt-123456789"} -C: RUN "CALL dbms.cluster.routing.getRoutingTable($context)" {"context": { "address": "127.0.0.1:9001", "policy": "my_policy", "region": "china"}} {} - PULL_ALL -S: SUCCESS {"fields": ["ttl", "servers"]} - RECORD [9223372036854775807, [{"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"],"role": "WRITE"}, {"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"], "role": "READ"},{"addresses": ["127.0.0.1:9001", "127.0.0.1:9002"], "role": "ROUTE"}]] - SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - RECORD ["Alice"] - RECORD ["Bob"] - SUCCESS {} diff --git a/driver/src/test/resources/write_read_tx_with_bookmarks.script b/driver/src/test/resources/write_read_tx_with_bookmarks.script deleted file mode 100644 index 73d106108d..0000000000 --- a/driver/src/test/resources/write_read_tx_with_bookmarks.script +++ /dev/null @@ -1,22 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO GOODBYE -!: AUTO HELLO - -C: BEGIN {"bookmarks": ["BookmarkA"]} -S: SUCCESS {} -C: RUN "CREATE (n {name:'Bob'})" {} {} - PULL_ALL -S: SUCCESS {} - SUCCESS {} -C: COMMIT -S: SUCCESS {"bookmark": "BookmarkB"} -C: BEGIN {"bookmarks": ["BookmarkB"]} -S: SUCCESS {} -C: RUN "MATCH (n) RETURN n.name AS name" {} {} - PULL_ALL -S: SUCCESS {"fields": ["name"]} - RECORD ["Bob"] - SUCCESS {} -C: COMMIT -S: SUCCESS {"bookmark": "BookmarkC"} diff --git a/driver/src/test/resources/write_server_v3_write.script b/driver/src/test/resources/write_server_v3_write.script deleted file mode 100644 index 4527fb8302..0000000000 --- a/driver/src/test/resources/write_server_v3_write.script +++ /dev/null @@ -1,9 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CREATE (n {name:'Bob'})" {} {} - PULL_ALL -S: SUCCESS {} - SUCCESS {} diff --git a/driver/src/test/resources/write_server_v3_write_tx.script b/driver/src/test/resources/write_server_v3_write_tx.script deleted file mode 100644 index a99222c641..0000000000 --- a/driver/src/test/resources/write_server_v3_write_tx.script +++ /dev/null @@ -1,13 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: BEGIN {} -S: SUCCESS {} -C: RUN "CREATE (n {name:'Bob'})" {} {} - PULL_ALL -S: SUCCESS {} - SUCCESS {} -C: COMMIT -S: SUCCESS { "bookmark": "ABookmark" } diff --git a/driver/src/test/resources/write_tx_with_bookmarks.script b/driver/src/test/resources/write_tx_with_bookmarks.script deleted file mode 100644 index b5c178310a..0000000000 --- a/driver/src/test/resources/write_tx_with_bookmarks.script +++ /dev/null @@ -1,13 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: BEGIN {"bookmarks": ["OldBookmark"]} -S: SUCCESS {} -C: RUN "CREATE (n {name:'Bob'})" {} {} - PULL_ALL -S: SUCCESS {} - SUCCESS {} -C: COMMIT -S: SUCCESS {"bookmark": "NewBookmark"} diff --git a/driver/src/test/resources/write_with_bookmarks.script b/driver/src/test/resources/write_with_bookmarks.script deleted file mode 100644 index 79821f9fdb..0000000000 --- a/driver/src/test/resources/write_with_bookmarks.script +++ /dev/null @@ -1,9 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE - -C: RUN "CREATE (n {name:'Bob'})" {} {"bookmarks": ["ABookmark"]} - PULL_ALL -S: SUCCESS {} - SUCCESS {} diff --git a/driver/src/test/resources/writer_unavailable.script b/driver/src/test/resources/writer_unavailable.script deleted file mode 100644 index b041ff3b8e..0000000000 --- a/driver/src/test/resources/writer_unavailable.script +++ /dev/null @@ -1,10 +0,0 @@ -!: BOLT 3 -!: AUTO RESET -!: AUTO HELLO -!: AUTO GOODBYE -!: AUTO BEGIN - -C: RUN "CREATE (n {name:'Bob'})" {} {} -C: PULL_ALL -S: FAILURE {"code": "Neo.TransientError.General.DatabaseUnavailable", "message": "Database is busy doing store copy"} -S: IGNORED diff --git a/testkit-backend/pom.xml b/testkit-backend/pom.xml index df538d1e08..e70a5ddc9e 100644 --- a/testkit-backend/pom.xml +++ b/testkit-backend/pom.xml @@ -35,7 +35,7 @@ org.projectlombok lombok - 1.18.12 + 1.18.16 provided diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java index 3bb94ccb28..5581006247 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import neo4j.org.testkit.backend.messages.TestkitModule; import neo4j.org.testkit.backend.messages.requests.TestkitRequest; @@ -147,7 +146,7 @@ else if ( currentLine.equals( "#request end" ) ) // Error to track String id = testkitState.newId(); testkitState.getErrors().put( id, (Neo4jException) e ); - writeResponse( driverError( id ) ); + writeResponse( driverError( id, (Neo4jException) e ) ); System.out.println( "Neo4jException: " + e ); } else @@ -174,9 +173,15 @@ else if ( currentLine.equals( "#request end" ) ) } } - private DriverError driverError( String id ) + private DriverError driverError( String id, Neo4jException e ) { - return DriverError.builder().data( DriverError.DriverErrorBody.builder().id( id ).build() ).build(); + return DriverError.builder().data( + DriverError.DriverErrorBody.builder() + .id( id ) + .errorType( e.getClass().getName() ) + .code( e.code() ) + .build() ) + .build(); } public void processRequest( String request ) diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java index dc413a7ec6..a13bbb7460 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java @@ -21,8 +21,10 @@ import lombok.Getter; import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import java.net.InetAddress; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; @@ -30,6 +32,7 @@ import org.neo4j.driver.Result; import org.neo4j.driver.Transaction; import org.neo4j.driver.exceptions.Neo4jException; +import org.neo4j.driver.net.ServerAddress; @Getter public class TestkitState @@ -42,6 +45,8 @@ public class TestkitState private int idGenerator = 0; private final Consumer responseWriter; private final Supplier processor; + private final Map> idToServerAddresses = new HashMap<>(); + private final Map idToResolvedAddresses = new HashMap<>(); public TestkitState( Consumer responseWriter, Supplier processor ) { diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CheckMultiDBSupport.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CheckMultiDBSupport.java new file mode 100644 index 0000000000..e16baa9523 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CheckMultiDBSupport.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.requests; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.MultiDBSupport; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; + +@Setter +@Getter +@NoArgsConstructor +public class CheckMultiDBSupport implements TestkitRequest +{ + private CheckMultiDBSupportBody data; + + @Override + public TestkitResponse process( TestkitState testkitState ) + { + String driverId = data.getDriverId(); + boolean available = testkitState.getDrivers().get( driverId ).supportsMultiDb(); + return MultiDBSupport.builder().data( MultiDBSupport.MultiDBSupportBody.builder().available( available ).build() ).build(); + } + + @Setter + @Getter + @NoArgsConstructor + public static class CheckMultiDBSupportBody + { + private String driverId; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/DomainNameResolutionCompleted.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/DomainNameResolutionCompleted.java new file mode 100644 index 0000000000..b5605a64d4 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/DomainNameResolutionCompleted.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.requests; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; + +@Setter +@Getter +@NoArgsConstructor +public class DomainNameResolutionCompleted implements TestkitRequest +{ + private DomainNameResolutionCompletedBody data; + + @Override + public TestkitResponse process( TestkitState testkitState ) + { + testkitState.getIdToResolvedAddresses().put( + data.getRequestId(), + data.getAddresses() + .stream() + .map( + addr -> + { + try + { + return InetAddress.getByName( addr ); + } + catch ( UnknownHostException e ) + { + throw new RuntimeException( e ); + } + } ) + .toArray( InetAddress[]::new ) ); + return null; + } + + @Setter + @Getter + @NoArgsConstructor + private static class DomainNameResolutionCompletedBody + { + private String requestId; + private List addresses; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java index 2937c09363..f1d24aa79f 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/NewDriver.java @@ -20,18 +20,30 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.Setter; import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.DomainNameResolutionRequired; import neo4j.org.testkit.backend.messages.responses.Driver; +import neo4j.org.testkit.backend.messages.responses.ResolverResolutionRequired; import neo4j.org.testkit.backend.messages.responses.TestkitErrorResponse; import neo4j.org.testkit.backend.messages.responses.TestkitResponse; +import java.net.URI; import java.util.Optional; +import java.util.concurrent.TimeUnit; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; -import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.internal.DefaultDomainNameResolver; +import org.neo4j.driver.internal.DomainNameResolver; +import org.neo4j.driver.internal.DriverFactory; +import org.neo4j.driver.internal.SecuritySettings; +import org.neo4j.driver.internal.cluster.RoutingSettings; +import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlan; +import org.neo4j.driver.net.ServerAddressResolver; @Setter @Getter @@ -59,11 +71,72 @@ public TestkitResponse process( TestkitState testkitState ) } Config.ConfigBuilder configBuilder = Config.builder(); + if ( data.isResolverRegistered() ) + { + configBuilder.withResolver( callbackResolver( testkitState ) ); + } + DomainNameResolver domainNameResolver = DefaultDomainNameResolver.getInstance(); + if ( data.isDomainNameResolverRegistered() ) + { + domainNameResolver = callbackDomainNameResolver( testkitState ); + } Optional.ofNullable( data.userAgent ).ifPresent( configBuilder::withUserAgent ); - testkitState.getDrivers().putIfAbsent( id, GraphDatabase.driver( data.uri, authToken, configBuilder.build() ) ); + Optional.ofNullable( data.connectionTimeoutMs ).ifPresent( timeout -> configBuilder.withConnectionTimeout( timeout, TimeUnit.MILLISECONDS ) ); + testkitState.getDrivers().putIfAbsent( id, driver( URI.create( data.uri ), authToken, configBuilder.build(), domainNameResolver ) ); return Driver.builder().data( Driver.DriverBody.builder().id( id ).build() ).build(); } + private ServerAddressResolver callbackResolver( TestkitState testkitState ) + { + return address -> + { + String callbackId = testkitState.newId(); + ResolverResolutionRequired.ResolverResolutionRequiredBody body = + ResolverResolutionRequired.ResolverResolutionRequiredBody.builder() + .id( callbackId ) + .address( String.format( "%s:%d", address.host(), address.port() ) ) + .build(); + ResolverResolutionRequired response = + ResolverResolutionRequired.builder() + .data( body ) + .build(); + testkitState.getResponseWriter().accept( response ); + testkitState.getProcessor().get(); + return testkitState.getIdToServerAddresses().remove( callbackId ); + }; + } + + private DomainNameResolver callbackDomainNameResolver( TestkitState testkitState ) + { + return address -> + { + String callbackId = testkitState.newId(); + DomainNameResolutionRequired.DomainNameResolutionRequiredBody body = + DomainNameResolutionRequired.DomainNameResolutionRequiredBody.builder() + .id( callbackId ) + .name( address ) + .build(); + DomainNameResolutionRequired response = + DomainNameResolutionRequired.builder() + .data( body ) + .build(); + testkitState.getResponseWriter().accept( response ); + testkitState.getProcessor().get(); + return testkitState.getIdToResolvedAddresses().remove( callbackId ); + }; + } + + private org.neo4j.driver.Driver driver( URI uri, AuthToken authToken, Config config, DomainNameResolver domainNameResolver ) + { + RoutingSettings routingSettings = RoutingSettings.DEFAULT; + RetrySettings retrySettings = RetrySettings.DEFAULT; + SecuritySettings.SecuritySettingsBuilder securitySettingsBuilder = new SecuritySettings.SecuritySettingsBuilder(); + SecuritySettings securitySettings = securitySettingsBuilder.build(); + SecurityPlan securityPlan = securitySettings.createSecurityPlan( uri.getScheme() ); + return new DriverFactoryWithDomainNameResolver( domainNameResolver ) + .newInstance( uri, authToken, routingSettings, retrySettings, config, securityPlan ); + } + @Setter @Getter @NoArgsConstructor @@ -72,5 +145,20 @@ public static class NewDriverBody private String uri; private AuthorizationToken authorizationToken; private String userAgent; + private boolean resolverRegistered; + private boolean domainNameResolverRegistered; + private Long connectionTimeoutMs; + } + + @RequiredArgsConstructor + private static class DriverFactoryWithDomainNameResolver extends DriverFactory + { + private final DomainNameResolver domainNameResolver; + + @Override + protected DomainNameResolver getDomainNameResolver() + { + return domainNameResolver; + } } } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResolverResolutionCompleted.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResolverResolutionCompleted.java new file mode 100644 index 0000000000..9408cea5f4 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResolverResolutionCompleted.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.requests; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.stream.Collectors; + +import org.neo4j.driver.internal.BoltServerAddress; + +@Setter +@Getter +@NoArgsConstructor +public class ResolverResolutionCompleted implements TestkitRequest +{ + private ResolverResolutionCompletedBody data; + + @Override + public TestkitResponse process( TestkitState testkitState ) + { + testkitState.getIdToServerAddresses().put( data.getRequestId(), data.getAddresses().stream().map( BoltServerAddress::new ) + .collect( Collectors.toCollection( LinkedHashSet::new ) ) ); + return null; + } + + @Setter + @Getter + @NoArgsConstructor + public static class ResolverResolutionCompletedBody + { + private String requestId; + private List addresses; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResultConsume.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResultConsume.java new file mode 100644 index 0000000000..3a4df65624 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResultConsume.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.requests; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.NullRecord; +import neo4j.org.testkit.backend.messages.responses.ResultSummary; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; + +import org.neo4j.driver.Result; +import org.neo4j.driver.exceptions.NoSuchRecordException; + +@Setter +@Getter +@NoArgsConstructor +public class ResultConsume implements TestkitRequest +{ + private ResultConsumeBody data; + + @Override + public TestkitResponse process( TestkitState testkitState ) + { + try + { + Result result = testkitState.getResults().get( data.getResultId() ); + result.consume(); + return ResultSummary.builder().build(); + } + catch ( NoSuchRecordException ignored ) + { + return NullRecord.builder().build(); + } + } + + @Setter + @Getter + @NoArgsConstructor + public static class ResultConsumeBody + { + private String resultId; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java index b1a9e0ceb2..4f7d053948 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java @@ -27,11 +27,14 @@ @JsonSubTypes( { @JsonSubTypes.Type( NewDriver.class ), @JsonSubTypes.Type( NewSession.class ), @JsonSubTypes.Type( SessionRun.class ), @JsonSubTypes.Type( ResultNext.class ), + @JsonSubTypes.Type( ResultConsume.class ), @JsonSubTypes.Type( VerifyConnectivity.class ), @JsonSubTypes.Type( SessionClose.class ), @JsonSubTypes.Type( DriverClose.class ), @JsonSubTypes.Type( RetryableNegative.class ), @JsonSubTypes.Type( SessionReadTransaction.class ), @JsonSubTypes.Type( TransactionRun.class ), @JsonSubTypes.Type( RetryablePositive.class ), @JsonSubTypes.Type( SessionBeginTransaction.class ), @JsonSubTypes.Type( TransactionCommit.class ), - @JsonSubTypes.Type( SessionLastBookmarks.class ), @JsonSubTypes.Type( SessionWriteTransaction.class ) + @JsonSubTypes.Type( SessionLastBookmarks.class ), @JsonSubTypes.Type( SessionWriteTransaction.class ), + @JsonSubTypes.Type( ResolverResolutionCompleted.class ), @JsonSubTypes.Type( CheckMultiDBSupport.class ), + @JsonSubTypes.Type( DomainNameResolutionCompleted.class ) } ) public interface TestkitRequest { diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/VerifyConnectivity.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/VerifyConnectivity.java new file mode 100644 index 0000000000..e985452acc --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/VerifyConnectivity.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.requests; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import neo4j.org.testkit.backend.TestkitState; +import neo4j.org.testkit.backend.messages.responses.Driver; +import neo4j.org.testkit.backend.messages.responses.TestkitResponse; + +@Setter +@Getter +@NoArgsConstructor +public class VerifyConnectivity implements TestkitRequest +{ + private VerifyConnectivityBody data; + + @Override + public TestkitResponse process( TestkitState testkitState ) + { + String id = data.getDriverId(); + testkitState.getDrivers().get( id ).verifyConnectivity(); + return Driver.builder().data( Driver.DriverBody.builder().id( id ).build() ).build(); + } + + @Setter + @Getter + @NoArgsConstructor + public static class VerifyConnectivityBody + { + private String driverId; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DomainNameResolutionRequired.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DomainNameResolutionRequired.java new file mode 100644 index 0000000000..3f803cc021 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DomainNameResolutionRequired.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.responses; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Builder +public class DomainNameResolutionRequired implements TestkitResponse +{ + private DomainNameResolutionRequiredBody data; + + @Override + public String testkitName() + { + return "DomainNameResolutionRequired"; + } + + @Setter + @Getter + @Builder + public static class DomainNameResolutionRequiredBody + { + private String id; + + private String name; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java index bd19554770..d7379a7484 100644 --- a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java @@ -41,5 +41,9 @@ public String testkitName() public static class DriverErrorBody { private String id; + + private String errorType; + + private String code; } } diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/MultiDBSupport.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/MultiDBSupport.java new file mode 100644 index 0000000000..67b9cc7b83 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/MultiDBSupport.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.responses; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Builder +public class MultiDBSupport implements TestkitResponse +{ + private final MultiDBSupportBody data; + + @Override + public String testkitName() + { + return "MultiDBSupport"; + } + + @Setter + @Getter + @Builder + public static class MultiDBSupportBody + { + private final String id; + + private final boolean available; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ResolverResolutionRequired.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ResolverResolutionRequired.java new file mode 100644 index 0000000000..685e3a3742 --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ResolverResolutionRequired.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.responses; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Builder +public class ResolverResolutionRequired implements TestkitResponse +{ + private ResolverResolutionRequiredBody data; + + @Override + public String testkitName() + { + return "ResolverResolutionRequired"; + } + + @Setter + @Getter + @Builder + public static class ResolverResolutionRequiredBody + { + private String id; + + private String address; + } +} diff --git a/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ResultSummary.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ResultSummary.java new file mode 100644 index 0000000000..0df7a42e1a --- /dev/null +++ b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ResultSummary.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) "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 neo4j.org.testkit.backend.messages.responses; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Builder +public class ResultSummary implements TestkitResponse +{ + @Override + public String testkitName() + { + return "ResultSummary"; + } +} diff --git a/testkit/.dockerignore b/testkit/.dockerignore new file mode 100644 index 0000000000..fe7d40a54e --- /dev/null +++ b/testkit/.dockerignore @@ -0,0 +1,2 @@ +*.py + diff --git a/testkit/Dockerfile b/testkit/Dockerfile new file mode 100644 index 0000000000..8d2de55dc8 --- /dev/null +++ b/testkit/Dockerfile @@ -0,0 +1,16 @@ +# Install Maven 3.6, Java 11, Java 8 and Python3 +FROM maven:3.6.3-openjdk-8 + +RUN apt-get --quiet --quiet update \ + && apt-get --quiet --quiet install -y bash python3 \ + && rm -rf /var/lib/apt/lists/* + +ENV PYTHON=python3 +ENV JAVA_HOME=/usr/local/openjdk-8 +ENV PATH=$JAVA_HOME/bin:$PATH + +# Install our own CAs on the image. +# Assumes Linux Debian based image. +# JAVA_HOME needed by update-ca-certificates hook to update Java with changed system CAs. +COPY CAs/* /usr/local/share/ca-certificates/ +RUN update-ca-certificates diff --git a/testkit/backend.py b/testkit/backend.py new file mode 100644 index 0000000000..beeb957468 --- /dev/null +++ b/testkit/backend.py @@ -0,0 +1,14 @@ +""" +Executed in Java driver container. +Assumes driver and backend has been built. +Responsible for starting the test backend. +""" +import os, subprocess + + +if __name__ == "__main__": + err = open("/artifacts/backenderr.log", "w") + out = open("/artifacts/backendout.log", "w") + subprocess.check_call( + ["java", "-jar", "testkit-backend/target/testkit-backend.jar"], stdout=out, stderr=err) + diff --git a/testkit/build.py b/testkit/build.py new file mode 100644 index 0000000000..a7c3f83042 --- /dev/null +++ b/testkit/build.py @@ -0,0 +1,14 @@ +""" +Executed in java driver container. +Responsible for building driver and test backend. +""" +import subprocess + + +def run(args): + subprocess.run( + args, universal_newlines=True, stderr=subprocess.STDOUT, check=True) + + +if __name__ == "__main__": + run(["mvn", "clean", "install", "-P", "!determine-revision", "-DskipTests"]) diff --git a/testkit/integration.py b/testkit/integration.py new file mode 100644 index 0000000000..922f896494 --- /dev/null +++ b/testkit/integration.py @@ -0,0 +1,4 @@ + +if __name__ == "__main__": + print("Integration tests not ported to testkit") + diff --git a/testkit/stress.py b/testkit/stress.py new file mode 100644 index 0000000000..e0578e36c9 --- /dev/null +++ b/testkit/stress.py @@ -0,0 +1,34 @@ +""" +Executed in java driver container. +Responsible for invoking Java stress test suite. +The stress test might be invoked multiple times against different versions +of Neo4j. +Assumes driver has been built before. +""" +import subprocess +import os + +if __name__ == "__main__": + uri = "%s://%s:%s" % ( + os.environ["TEST_NEO4J_SCHEME"], + os.environ["TEST_NEO4J_HOST"], + os.environ["TEST_NEO4J_PORT"]) + password = os.environ["TEST_NEO4J_PASS"] + is_cluster = os.environ.get("TEST_NEO4J_IS_CLUSTER", False) + if is_cluster: + suite = "CausalClusteringStressIT" + else: + suite = "SingleInstanceStressIT" + + cmd = [ + "mvn", "surefire:test", + "--file", "./driver/pom.xml", + "-Dtest=%s,AbstractStressTestBase" % suite, + "-DexternalClusterUri=%s" % uri, + "-Dneo4jUserPassword=%s" % password, + "-DthreadCount=10", + "-DexecutionTimeSeconds=10", + "-Dmaven.gitcommitid.skip=true", + ] + subprocess.run(cmd, universal_newlines=True, + stderr=subprocess.STDOUT, check=True) diff --git a/testkit/unittests.py b/testkit/unittests.py new file mode 100644 index 0000000000..cf24eb9f28 --- /dev/null +++ b/testkit/unittests.py @@ -0,0 +1,15 @@ +""" +Executed in Java driver container. +Responsible for running unit tests. +Assumes driver has been setup by build script prior to this. +""" +import subprocess + + +def run(args): + subprocess.run( + args, universal_newlines=True, stderr=subprocess.STDOUT, check=True) + + +if __name__ == "__main__": + run(["mvn", "test", "-Dmaven.gitcommitid.skip"])