Skip to content

Make connectivity verification not part of driver creation. #609

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 4, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions driver/src/main/java/org/neo4j/driver/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ RetrySettings retrySettings()
return retrySettings;
}

/**
* @return if the metrics is enabled or not on this driver.
*/
public boolean isMetricsEnabled()
{
return isMetricsEnabled;
Expand Down
21 changes: 21 additions & 0 deletions driver/src/main/java/org/neo4j/driver/Driver.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,25 @@ public interface Driver extends AutoCloseable
*/
@Experimental
TypeSystem defaultTypeSystem();

/**
* This verifies if the driver can connect to a remote server or a cluster
* by establishing a network connection with the remote and possibly exchanging a few data before closing the connection.
*
* It throws exception if fails to connect. Use the exception to further understand the cause of the connectivity problem.
*/
void verifyConnectivity();

/**
* This verifies if the driver can connect to a remote server or cluster
* by establishing a network connection with the remote and possibly exchanging a few data before closing the connection.
*
* This operation is asynchronous and returns a {@link CompletionStage}. This stage is completed with
* {@code null} when the driver connects to the remote server or cluster successfully.
* It is completed exceptionally if the driver failed to connect the remote server or cluster.
* This exception can be used to further understand the cause of the connectivity problem.
*
* @return a {@link CompletionStage completion stage} that represents the asynchronous verification.
*/
CompletionStage<Void> verifyConnectivityAsync();
}
23 changes: 22 additions & 1 deletion driver/src/main/java/org/neo4j/driver/GraphDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,40 @@ public static Driver routingDriver( Iterable<URI> routingUris, AuthToken authTok

for ( URI uri : routingUris )
{
final Driver driver = driver( uri, authToken, config );
try
{
return driver( uri, authToken, config );
driver.verifyConnectivity();
return driver;
}
catch ( ServiceUnavailableException e )
{
log.warn( "Unable to create routing driver for URI: " + uri, e );
closeDriver( driver, uri, log );
}
catch ( Throwable e )
{
// for any other errors, we first close the driver and then rethrow the original error out.
closeDriver( driver, uri, log );
throw e;
}
}

throw new ServiceUnavailableException( "Failed to discover an available server" );
}

private static void closeDriver( Driver driver, URI uri, Logger log )
{
try
{
driver.close();
}
catch ( Throwable closeError )
{
log.warn( "Unable to close driver towards URI: " + uri, closeError );
}
}

private static void assertRoutingUris( Iterable<URI> uris )
{
for ( URI uri : uris )
Expand Down
54 changes: 9 additions & 45 deletions driver/src/main/java/org/neo4j/driver/internal/DriverFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
import java.net.URI;
import java.security.GeneralSecurityException;

import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Logger;
import org.neo4j.driver.Logging;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.internal.async.connection.BootstrapFactory;
import org.neo4j.driver.internal.async.connection.ChannelConnector;
import org.neo4j.driver.internal.async.connection.ChannelConnectorImpl;
Expand All @@ -49,19 +56,11 @@
import org.neo4j.driver.internal.spi.ConnectionProvider;
import org.neo4j.driver.internal.util.Clock;
import org.neo4j.driver.internal.util.Futures;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Config;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Logger;
import org.neo4j.driver.Logging;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.exceptions.ServiceUnavailableException;
import org.neo4j.driver.net.ServerAddressResolver;

import static java.lang.String.format;
import static org.neo4j.driver.internal.metrics.MetricsProvider.METRICS_DISABLED_PROVIDER;
import static org.neo4j.driver.internal.cluster.IdentityResolver.IDENTITY_RESOLVER;
import static org.neo4j.driver.internal.metrics.MetricsProvider.METRICS_DISABLED_PROVIDER;
import static org.neo4j.driver.internal.security.SecurityPlan.insecure;
import static org.neo4j.driver.internal.util.ErrorUtil.addSuppressed;

Expand Down Expand Up @@ -105,11 +104,7 @@ public final Driver newInstance ( URI uri, AuthToken authToken, RoutingSettings
MetricsProvider metricsProvider = createDriverMetrics( config, createClock() );
ConnectionPool connectionPool = createConnectionPool( authToken, securityPlan, bootstrap, metricsProvider, config, ownsEventLoopGroup );

InternalDriver driver = createDriver( uri, securityPlan, address, connectionPool, eventExecutorGroup, newRoutingSettings, retryLogic, metricsProvider, config );

verifyConnectivity( driver, connectionPool, config );

return driver;
return createDriver( uri, securityPlan, address, connectionPool, eventExecutorGroup, newRoutingSettings, retryLogic, metricsProvider, config );
}

protected ConnectionPool createConnectionPool( AuthToken authToken, SecurityPlan securityPlan, Bootstrap bootstrap,
Expand Down Expand Up @@ -366,30 +361,6 @@ private static void assertNoRoutingContext( URI uri, RoutingSettings routingSett
}
}

private static void verifyConnectivity( InternalDriver driver, ConnectionPool connectionPool, Config config )
{
try
{
// block to verify connectivity, close connection pool if thread gets interrupted
Futures.blockingGet( driver.verifyConnectivity(),
() -> closeConnectionPoolOnThreadInterrupt( connectionPool, config.logging() ) );
}
catch ( Throwable connectionError )
{
if ( Thread.currentThread().isInterrupted() )
{
// current thread has been interrupted while verifying connectivity
// connection pool should've been closed
throw new ServiceUnavailableException( "Unable to create driver. Thread has been interrupted.",
connectionError );
}

// we need to close the connection pool if driver creation threw exception
closeConnectionPoolAndSuppressError( connectionPool, connectionError );
throw connectionError;
}
}

private static void closeConnectionPoolAndSuppressError( ConnectionPool connectionPool, Throwable mainError )
{
try
Expand All @@ -401,11 +372,4 @@ private static void closeConnectionPoolAndSuppressError( ConnectionPool connecti
addSuppressed( mainError, closeError );
}
}

private static void closeConnectionPoolOnThreadInterrupt( ConnectionPool pool, Logging logging )
{
Logger log = logging.getLog( Driver.class.getSimpleName() );
log.warn( "Driver creation interrupted while verifying connectivity. Connection pool will be closed" );
pool.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,18 @@ public final TypeSystem defaultTypeSystem()
return InternalTypeSystem.TYPE_SYSTEM;
}

public CompletionStage<Void> verifyConnectivity()
@Override
public CompletionStage<Void> verifyConnectivityAsync()
{
return sessionFactory.verifyConnectivity();
}

@Override
public void verifyConnectivity()
{
Futures.blockingGet( verifyConnectivityAsync() );
}

/**
* Get the underlying session factory.
* <p>
Expand Down
9 changes: 6 additions & 3 deletions driver/src/test/java/org/neo4j/driver/GraphDatabaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ void boltSchemeShouldInstantiateDirectDriver() throws Exception

// When
Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG );
driver.verifyConnectivity();

// Then
assertThat( driver, is( directDriver() ) );
Expand All @@ -78,6 +79,7 @@ void boltPlusDiscoverySchemeShouldInstantiateClusterDriver() throws Exception

// When
Driver driver = GraphDatabase.driver( uri, INSECURE_CONFIG );
driver.verifyConnectivity();

// Then
assertThat( driver, is( clusterDriver() ) );
Expand Down Expand Up @@ -146,9 +148,10 @@ void shouldRespondToInterruptsWhenConnectingToUnresponsiveServer() throws Except
// setup other thread to interrupt current thread when it blocks
TestUtil.interruptWhenInWaitingState( Thread.currentThread() );

final Driver driver = GraphDatabase.driver( "bolt://localhost:" + serverSocket.getLocalPort() );
try
{
assertThrows( ServiceUnavailableException.class, () -> GraphDatabase.driver( "bolt://localhost:" + serverSocket.getLocalPort() ) );
assertThrows( ServiceUnavailableException.class, driver::verifyConnectivity );
}
finally
{
Expand Down Expand Up @@ -176,9 +179,9 @@ private static void testFailureWhenServerDoesNotRespond( boolean encrypted ) thr
{
int connectionTimeoutMillis = 1_000;
Config config = createConfig( encrypted, connectionTimeoutMillis );
final Driver driver = GraphDatabase.driver( URI.create( "bolt://localhost:" + server.getLocalPort() ), config );

ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class,
() -> GraphDatabase.driver( URI.create( "bolt://localhost:" + server.getLocalPort() ), config ) );
ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, driver::verifyConnectivity );
assertEquals( e.getMessage(), "Unable to establish connection in " + connectionTimeoutMillis + "ms" );
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ void shouldBePossibleToChangePassword() throws Exception
}

// verify old password does not work
assertThrows( AuthenticationException.class,
() -> GraphDatabase.driver( neo4j.uri(), AuthTokens.basic( "neo4j", PASSWORD ) ) );
final Driver badDriver = GraphDatabase.driver( CredentialsIT.neo4j.uri(), basic( "neo4j", PASSWORD ) );
assertThrows( AuthenticationException.class, badDriver::verifyConnectivity );

// verify new password works
try ( Driver driver = GraphDatabase.driver( neo4j.uri(), AuthTokens.basic( "neo4j", newPassword ) );
try ( Driver driver = GraphDatabase.driver( CredentialsIT.neo4j.uri(), AuthTokens.basic( "neo4j", newPassword ) );
Session session = driver.session() )
{
session.run( "RETURN 2" ).consume();
Expand Down Expand Up @@ -177,6 +177,7 @@ private void testDriverFailureOnWrongCredentials( String uri )
Config config = Config.builder().withLogging( DEV_NULL_LOGGING ).build();
AuthToken authToken = AuthTokens.basic( "neo4j", "wrongSecret" );

assertThrows( AuthenticationException.class, () -> GraphDatabase.driver( uri, authToken, config ) );
final Driver driver = GraphDatabase.driver( uri, authToken, config );
assertThrows( AuthenticationException.class, driver::verifyConnectivity );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private void testMismatchingEncryption( BoltTlsLevel tlsLevel, boolean driverEnc
Config config = newConfig( driverEncrypted );

RuntimeException e = assertThrows( RuntimeException.class,
() -> GraphDatabase.driver( neo4j.uri(), neo4j.authToken(), config ).close() );
() -> GraphDatabase.driver( neo4j.uri(), neo4j.authToken(), config ).verifyConnectivity() );

// pre 3.1 neo4j throws different exception when encryption required but not used
if ( neo4jVersion.lessThan( v3_1_0 ) && tlsLevel == BoltTlsLevel.REQUIRED )
Expand Down
52 changes: 29 additions & 23 deletions driver/src/test/java/org/neo4j/driver/integration/ErrorIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,6 @@
import java.util.function.Consumer;
import java.util.stream.Stream;

import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.messaging.response.FailureMessage;
import org.neo4j.driver.internal.retry.RetrySettings;
import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactory;
import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactoryWithFailingMessageFormat;
import org.neo4j.driver.internal.util.FailingMessageFormat;
import org.neo4j.driver.internal.util.FakeClock;
import org.neo4j.driver.AuthToken;
import org.neo4j.driver.Config;
import org.neo4j.driver.Driver;
Expand All @@ -49,6 +42,13 @@
import org.neo4j.driver.Transaction;
import org.neo4j.driver.exceptions.ClientException;
import org.neo4j.driver.exceptions.ServiceUnavailableException;
import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.messaging.response.FailureMessage;
import org.neo4j.driver.internal.retry.RetrySettings;
import org.neo4j.driver.internal.util.FailingMessageFormat;
import org.neo4j.driver.internal.util.FakeClock;
import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactory;
import org.neo4j.driver.internal.util.io.ChannelTrackingDriverFactoryWithFailingMessageFormat;
import org.neo4j.driver.util.ParallelizableIT;
import org.neo4j.driver.util.SessionExtension;

Expand Down Expand Up @@ -143,7 +143,8 @@ void shouldAllowNewTransactionAfterRecoverableError()
@Test
void shouldExplainConnectionError()
{
ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, () -> GraphDatabase.driver( "bolt://localhost:7777" ) );
final Driver driver = GraphDatabase.driver( "bolt://localhost:7777" );
ServiceUnavailableException e = assertThrows( ServiceUnavailableException.class, driver::verifyConnectivity );

assertEquals( "Unable to connect to localhost:7777, ensure the database is running " +
"and that there is a working network connection to it.", e.getMessage() );
Expand Down Expand Up @@ -179,7 +180,8 @@ void shouldGetHelpfulErrorWhenTryingToConnectToHttpPort() throws Throwable

Config config = Config.builder().withoutEncryption().build();

ClientException e = assertThrows( ClientException.class, () -> GraphDatabase.driver( "bolt://localhost:" + session.httpPort(), config ) );
final Driver driver = GraphDatabase.driver( "bolt://localhost:" + session.httpPort(), config );
ClientException e = assertThrows( ClientException.class, driver::verifyConnectivity );
assertEquals( "Server responded HTTP. Make sure you are not trying to connect to the http endpoint " +
"(HTTP defaults to port 7474 whereas BOLT defaults to port 7687)", e.getMessage() );
}
Expand Down Expand Up @@ -264,25 +266,29 @@ private Throwable testChannelErrorHandling( Consumer<FailingMessageFormat> messa
Config config = Config.builder().withLogging( DEV_NULL_LOGGING ).build();
Throwable queryError = null;

try ( Driver driver = driverFactory.newInstance( uri, authToken, routingSettings, retrySettings, config );
Session session = driver.session() )
try ( Driver driver = driverFactory.newInstance( uri, authToken, routingSettings, retrySettings, config ) )
{
messageFormatSetup.accept( driverFactory.getFailingMessageFormat() );

try
driver.verifyConnectivity();
try(Session session = driver.session() )
{
session.run( "RETURN 1" ).consume();
fail( "Exception expected" );
messageFormatSetup.accept( driverFactory.getFailingMessageFormat() );

try
{
session.run( "RETURN 1" ).consume();
fail( "Exception expected" );
}
catch ( Throwable error )
{
queryError = error;
}

assertSingleChannelIsClosed( driverFactory );
assertNewQueryCanBeExecuted( session, driverFactory );
}
catch ( Throwable error )
{
queryError = error;
}

assertSingleChannelIsClosed( driverFactory );
assertNewQueryCanBeExecuted( session, driverFactory );
}


return queryError;
}

Expand Down
Loading