From 7ca9aba1d0ad0e02a165ff456ae29635e64ce8e3 Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov Date: Mon, 22 Mar 2021 13:39:35 +0000 Subject: [PATCH 1/8] Imported testkit directory --- testkit/.dockerignore | 2 ++ testkit/Dockerfile | 16 ++++++++++++++++ testkit/backend.py | 14 ++++++++++++++ testkit/build.py | 14 ++++++++++++++ testkit/integration.py | 4 ++++ testkit/stress.py | 34 ++++++++++++++++++++++++++++++++++ testkit/unittests.py | 15 +++++++++++++++ 7 files changed, 99 insertions(+) create mode 100644 testkit/.dockerignore create mode 100644 testkit/Dockerfile create mode 100644 testkit/backend.py create mode 100644 testkit/build.py create mode 100644 testkit/integration.py create mode 100644 testkit/stress.py create mode 100644 testkit/unittests.py 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"]) From 2d09a82c91ee62e969eec4f68affde6a8df66d64 Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov <11927660+injectives@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:06:29 +0000 Subject: [PATCH 2/8] Migrating tests to testkit (#832) * Migrating tests to testkit Short summary of this update: - removed migrated tests - verifyConnectivity support - resolver support - consume support Test mapping (dest: stub/routing.py): - shouldHandleAcquireReadSession -> test_should_read_successfully_from_reader_using_session_run - shouldHandleAcquireReadTransaction -> test_should_read_successfully_from_reader_using_tx_function - shouldHandleAcquireReadSessionAndTransaction -> test_should_read_successfully_from_reader_using_tx_run - shouldRoundRobinReadServers -> test_should_round_robin_readers_when_reading_using_session_run - shouldRoundRobinReadServersWhenUsingTransaction -> test_should_round_robin_readers_when_reading_using_tx_run - shouldThrowSessionExpiredIfReadServerDisappears -> test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_session_run - shouldThrowSessionExpiredIfReadServerDisappearsWhenUsingTransaction -> test_should_fail_when_reading_from_unexpectedly_interrupting_reader_using_tx_run - shouldThrowSessionExpiredIfWriteServerDisappears -> test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_session_run - shouldThrowSessionExpiredIfWriteServerDisappearsWhenUsingTransaction -> test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run - shouldHandleAcquireWriteSession -> test_should_write_successfully_on_writer_using_session_run - shouldHandleAcquireWriteTransaction -> test_should_write_successfully_on_writer_using_tx_function - shouldHandleAcquireWriteSessionAndTransaction -> test_should_write_successfully_on_writer_using_tx_run - shouldRoundRobinWriteSessions -> test_should_round_robin_writers_when_writing_using_session_run - shouldRoundRobinWriteSessionsInTransaction -> test_should_round_robin_writers_when_writing_using_tx_run - shouldFailOnNonDiscoverableServer -> test_should_fail_discovery_when_router_fails_with_procedure_not_found_code - shouldFailRandomFailureInGetServers -> test_should_fail_discovery_when_router_fails_with_unknown_code - shouldHandleLeaderSwitchWhenWriting -> test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code - shouldHandleLeaderSwitchWhenWritingWithoutConsuming -> test_should_fail_when_writing_without_explicit_consumption_on_writer_that_returns_not_a_leader_code - shouldHandleLeaderSwitchWhenWritingInTransaction -> test_should_fail_when_writing_on_writer_that_returns_not_a_leader_code_using_tx_run - shouldUseWriteSessionModeAndInitialBookmark -> test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run - shouldUseReadSessionModeAndInitialBookmark -> test_should_use_read_session_mode_and_initial_bookmark_when_reading_using_tx_run - shouldPassBookmarkFromTransactionToTransaction -> test_should_pass_bookmark_from_tx_to_tx_using_tx_run - shouldRetryReadTransactionUntilSuccess -> test_should_retry_read_tx_until_success - shouldRetryWriteTransactionUntilSuccess -> test_should_retry_write_tx_until_success - shouldRetryReadTransactionAndPerformRediscoveryUntilSuccess -> test_should_retry_read_tx_and_rediscovery_until_success - shouldRetryWriteTransactionAndPerformRediscoveryUntilSuccess -> test_should_retry_write_tx_and_rediscovery_until_success - shouldUseInitialRouterForRediscoveryWhenAllOtherRoutersAreDead -> test_should_use_initial_router_for_discovery_when_others_unavailable - shouldInvokeProcedureGetRoutingTableWhenServerVersionPermits -> test_should_successfully_read_from_readable_router_using_tx_function - shouldSendEmptyRoutingContextInHelloMessage -> test_should_send_empty_hello - shouldServeReadsButFailWritesWhenNoWritersAvailable -> test_should_serve_reads_and_fail_writes_when_no_writers_available - shouldAcceptRoutingTableWithoutWritersAndThenRediscover -> test_should_accept_routing_table_without_writers_and_then_rediscover - shouldTreatRoutingTableWithSingleRouterAsValid -> test_should_accept_routing_table_with_single_router - shouldSendMultipleBookmarks -> test_should_successfully_send_multiple_bookmarks - shouldForgetAddressOnDatabaseUnavailableError -> test_should_forget_address_on_database_unavailable_error - shouldUseResolverDuringRediscoveryWhenExistingRoutersFail -> test_should_use_resolver_during_rediscovery_when_existing_routers_fail - shouldRevertToInitialRouterIfKnownRouterThrowsProtocolErrors -> test_should_revert_to_initial_router_if_known_router_throws_protocol_errors * Removing redundant stub server scripts --- .../integration/RoutingDriverBoltKitIT.java | 941 +----------------- .../acquire_endpoints_v3_9010.script | 10 - .../acquire_endpoints_v3_empty.script | 10 - ...s_v3_point_to_empty_router_and_exit.script | 10 - ...endpoints_v3_three_servers_and_exit.script | 12 - .../test/resources/dead_read_server.script | 8 - .../src/test/resources/discover_failed.script | 12 - .../test/resources/discover_no_writers.script | 10 - .../resources/discover_no_writers_9010.script | 10 - .../test/resources/discover_one_router.script | 10 - .../resources/discover_servers_9010.script | 10 - ...mpty_routing_context_in_hello_neo4j.script | 17 - .../test/resources/get_routing_table.script | 18 - .../resources/not_able_to_write_server.script | 13 - .../resources/read_server_v3_read_tx.script | 16 - .../resources/read_tx_with_bookmarks.script | 15 - .../rediscover_using_initial_router.script | 18 - .../write_read_tx_with_bookmarks.script | 22 - .../resources/write_server_v3_write.script | 9 - .../resources/write_with_bookmarks.script | 9 - .../test/resources/writer_unavailable.script | 10 - testkit-backend/pom.xml | 2 +- .../org/testkit/backend/CommandProcessor.java | 7 +- .../org/testkit/backend/TestkitState.java | 3 + .../backend/messages/requests/NewDriver.java | 27 + .../requests/ResolverResolutionCompleted.java | 56 ++ .../messages/requests/ResultConsume.java | 61 ++ .../messages/requests/TestkitRequest.java | 4 +- .../messages/requests/VerifyConnectivity.java | 50 + .../messages/responses/DriverError.java | 2 + .../responses/ResolverResolutionRequired.java | 47 + .../messages/responses/ResultSummary.java | 35 + 32 files changed, 302 insertions(+), 1182 deletions(-) delete mode 100644 driver/src/test/resources/acquire_endpoints_v3_9010.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v3_empty.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v3_point_to_empty_router_and_exit.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v3_three_servers_and_exit.script delete mode 100644 driver/src/test/resources/dead_read_server.script delete mode 100644 driver/src/test/resources/discover_failed.script delete mode 100644 driver/src/test/resources/discover_no_writers.script delete mode 100644 driver/src/test/resources/discover_no_writers_9010.script delete mode 100644 driver/src/test/resources/discover_one_router.script delete mode 100644 driver/src/test/resources/discover_servers_9010.script delete mode 100644 driver/src/test/resources/empty_routing_context_in_hello_neo4j.script delete mode 100644 driver/src/test/resources/get_routing_table.script delete mode 100644 driver/src/test/resources/not_able_to_write_server.script delete mode 100644 driver/src/test/resources/read_server_v3_read_tx.script delete mode 100644 driver/src/test/resources/read_tx_with_bookmarks.script delete mode 100644 driver/src/test/resources/rediscover_using_initial_router.script delete mode 100644 driver/src/test/resources/write_read_tx_with_bookmarks.script delete mode 100644 driver/src/test/resources/write_server_v3_write.script delete mode 100644 driver/src/test/resources/write_with_bookmarks.script delete mode 100644 driver/src/test/resources/writer_unavailable.script create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResolverResolutionCompleted.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/ResultConsume.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/VerifyConnectivity.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ResolverResolutionRequired.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/ResultSummary.java 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..70fbd9f826 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java @@ -29,12 +29,8 @@ 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; @@ -45,16 +41,13 @@ 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; @@ -70,13 +63,10 @@ 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; @@ -89,7 +79,6 @@ 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 { @@ -107,441 +96,6 @@ 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 { @@ -635,97 +189,28 @@ void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionRX() throws IOExcept 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(); - } + { + 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 ); - assertEquals( parse( "NewBookmark" ), session.lastBookmark() ); - } + StepVerifier.create( fluxOfNames ).expectNext( "Foo", "Bar" ).verifyComplete(); - assertThat( router.exitStatus(), equalTo( 0 ) ); - assertThat( writer.exitStatus(), equalTo( 0 ) ); + // Finally + driver.close(); + assertThat( server.exitStatus(), equalTo( 0 ) ); + assertThat( writeServer.exitStatus(), equalTo( 0 ) ); } @Test - void shouldPassBookmarkFromTransactionToTransaction() throws Exception + void shouldSendInitialBookmark() throws Exception { StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer writer = stubController.startStub( "write_read_tx_with_bookmarks.script", 9007 ); + 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( "BookmarkA" ) ).build() ) ) + Session session = driver.session( builder().withBookmarks( parse( "OldBookmark" ) ).build() ) ) { try ( Transaction tx = session.beginTransaction() ) { @@ -733,69 +218,13 @@ void shouldPassBookmarkFromTransactionToTransaction() throws Exception 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() ); + assertEquals( parse( "NewBookmark" ), 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 { @@ -905,104 +334,6 @@ void shouldRetryWriteTransactionUntilFailure() throws Exception } } - @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() ); - } - } - - @Test - void shouldRetryWriteTransactionAndPerformRediscoveryUntilSuccess() throws Exception - { - 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() ); - } - } - - @Test - void shouldUseInitialRouterForRediscoveryWhenAllOtherRoutersAreDead() throws Exception - { - // 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() ); - } - - @Test - void shouldInvokeProcedureGetRoutingTableWhenServerVersionPermits() throws Exception - { - // 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() ); - } - } - @Test void shouldSendRoutingContextToServer() throws Exception { @@ -1043,168 +374,6 @@ void shouldSendRoutingContextInHelloMessage() throws Exception } } - @Test - void shouldSendEmptyRoutingContextInHelloMessage() throws Exception - { - // 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() ); - } - } - - @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() ); - } - } - - @Test - void shouldAcceptRoutingTableWithoutWritersAndThenRediscover() throws Exception - { - // 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 ) ); - - 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() ); - } - } - - @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 ); - - 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 - - Result readResult1 = session.run( "MATCH (n) RETURN n.name" ); - assertEquals( 3, readResult1.list().size() ); - assertEquals( "127.0.0.1:9003", readResult1.consume().server().address() ); - - 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() ); - } - } - - @Test - void shouldSendMultipleBookmarks() throws Exception - { - StubServer router = stubController.startStub( "acquire_endpoints_v3.script", 9001 ); - StubServer writer = stubController.startStub( "multiple_bookmarks.script", 9007 ); - - 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 ( Transaction tx = session.beginTransaction() ) - { - tx.run( "CREATE (n {name:'Bob'})" ); - tx.commit(); - } - - assertEquals( parse( "neo4j:bookmark:v1:tx95" ), session.lastBookmark() ); - } - finally - { - assertEquals( 0, router.exitStatus() ); - assertEquals( 0, writer.exitStatus() ); - } - } - - @Test - void shouldForgetAddressOnDatabaseUnavailableError() 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 ); - - 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 ) ); - - assertThat( records, hasSize( 0 ) ); - assertEquals( 2, invocations.get() ); - } - finally - { - assertEquals( 0, router1.exitStatus() ); - assertEquals( 0, writer1.exitStatus() ); - assertEquals( 0, router2.exitStatus() ); - assertEquals( 0, writer2.exitStatus() ); - } - } - @Test void shouldFailInitialDiscoveryWhenConfiguredResolverThrows() { @@ -1219,53 +388,6 @@ 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() ); - } - } - @Test void useSessionAfterDriverIsClosed() throws Exception { @@ -1293,41 +415,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 { 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_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/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/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_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/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_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..317fa8087b 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,9 @@ 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() ).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..1d966da508 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 @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; @@ -30,6 +31,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 +44,7 @@ public class TestkitState private int idGenerator = 0; private final Consumer responseWriter; private final Supplier processor; + private final Map> idToServerAddresses = new HashMap<>(); public TestkitState( Consumer responseWriter, Supplier processor ) { 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..c34bafc40d 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 @@ -23,6 +23,7 @@ import lombok.Setter; import neo4j.org.testkit.backend.TestkitState; 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; @@ -32,6 +33,7 @@ import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Config; import org.neo4j.driver.GraphDatabase; +import org.neo4j.driver.net.ServerAddressResolver; @Setter @Getter @@ -59,11 +61,35 @@ public TestkitResponse process( TestkitState testkitState ) } Config.ConfigBuilder configBuilder = Config.builder(); + if ( data.isResolverRegistered() ) + { + configBuilder.withResolver( callbackResolver( testkitState ) ); + } Optional.ofNullable( data.userAgent ).ifPresent( configBuilder::withUserAgent ); testkitState.getDrivers().putIfAbsent( id, GraphDatabase.driver( data.uri, authToken, configBuilder.build() ) ); 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( address.toString() ) + .build(); + ResolverResolutionRequired response = + ResolverResolutionRequired.builder() + .data( body ) + .build(); + testkitState.getResponseWriter().accept( response ); + testkitState.getProcessor().get(); + return testkitState.getIdToServerAddresses().remove( callbackId ); + }; + } + @Setter @Getter @NoArgsConstructor @@ -72,5 +98,6 @@ public static class NewDriverBody private String uri; private AuthorizationToken authorizationToken; private String userAgent; + private boolean resolverRegistered; } } 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..8a50c459f7 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,13 @@ @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 ) } ) 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/DriverError.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java index bd19554770..2e373e8e1d 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,7 @@ public String testkitName() public static class DriverErrorBody { private String id; + + private String errorType; } } 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"; + } +} From 9fbd10a6f7666582319e16a4fbcc95ec43c6672a Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov <11927660+injectives@users.noreply.github.com> Date: Tue, 2 Mar 2021 10:22:54 +0000 Subject: [PATCH 3/8] Migrating tests to testkit part 2 (#839) - shouldSendRoutingContextToServer -> test_should_successfully_get_routing_table_with_context - shouldSendRoutingContextInHelloMessage -> test_should_successfully_get_routing_table_with_context - shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunction -> test_should_write_successfully_on_leader_switch_using_tx_function - shouldSendInitialBookmark -> test_should_use_write_session_mode_and_initial_bookmark_when_writing_using_tx_run - shouldRetryWriteTransactionUntilSuccessWithWhenLeaderIsRemoved -> test_should_retry_write_until_success_with_leader_change_using_tx_function - shouldRetryWriteTransactionUntilSuccessWithWhenLeaderIsRemovedV3 -> test_should_retry_write_until_success_with_leader_shutdown_during_tx_using_tx_function --- .../integration/RoutingDriverBoltKitIT.java | 187 ++---------------- 1 file changed, 14 insertions(+), 173 deletions(-) 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 70fbd9f826..f3dfd859c1 100644 --- a/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java +++ b/driver/src/test/java/org/neo4j/driver/integration/RoutingDriverBoltKitIT.java @@ -39,14 +39,11 @@ 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.Session; -import org.neo4j.driver.Transaction; import org.neo4j.driver.TransactionWork; import org.neo4j.driver.async.AsyncSession; import org.neo4j.driver.exceptions.SessionExpiredException; -import org.neo4j.driver.exceptions.TransientException; import org.neo4j.driver.internal.DriverFactory; import org.neo4j.driver.internal.cluster.RoutingSettings; import org.neo4j.driver.internal.retry.RetrySettings; @@ -70,16 +67,21 @@ 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; +/** + * 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; @@ -96,43 +98,7 @@ public void killServers() stubController.reset(); } - @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 ) ); - } - + // Async is not currently supported in testkit. @Test void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionAsync() throws IOException, InterruptedException { @@ -167,11 +133,13 @@ void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionAsync() throws IOExc assertThat( writeServer.exitStatus(), equalTo( 0 ) ); } - private static String extractNameField(Record record) + private static String extractNameField( Record record ) { return record.get( 0 ).asString(); } + // 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. @@ -203,95 +171,7 @@ void shouldHandleLeaderSwitchAndRetryWhenWritingInTxFunctionRX() throws IOExcept 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 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 ) ); - } - + // fixed retries are not currently supported in testkit @Test void shouldRetryReadTransactionUntilFailure() throws Exception { @@ -313,6 +193,7 @@ void shouldRetryReadTransactionUntilFailure() throws Exception } } + // fixed retries are not currently supported in testkit @Test void shouldRetryWriteTransactionUntilFailure() throws Exception { @@ -334,46 +215,6 @@ void shouldRetryWriteTransactionUntilFailure() throws Exception } } - @Test - void shouldSendRoutingContextToServer() throws Exception - { - // stub server is both a router and reader - StubServer server = stubController.startStub( "get_routing_table_with_context.script", 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() ); - } - } - - @Test - void shouldSendRoutingContextInHelloMessage() throws Exception - { - // stub server is both a router and reader - StubServer server = StubServer.start( "routing_context_in_hello_neo4j.script", 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() ); - } - } - @Test void shouldFailInitialDiscoveryWhenConfiguredResolverThrows() { From 89932e8b0befa26ba08a97e1b4c63434b3f6f8ff Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov <11927660+injectives@users.noreply.github.com> Date: Tue, 2 Mar 2021 22:08:52 +0000 Subject: [PATCH 4/8] Migrating tests to testkit part 3 (#840) Adding support for supportsMultiDB call. And exporting the following tests to testkit: - shouldServerWithBoltV4SupportMultiDb -> test_should_successfully_check_if_support_for_multi_db_is_available - shouldServerWithBoltV3NotSupportMultiDb -> test_should_successfully_check_if_support_for_multi_db_is_available Removing redundant scripts --- .../integration/RoutingDriverBoltKitIT.java | 60 +------------------ .../acquire_endpoints_v3_leader_killed.script | 25 -------- .../get_routing_table_with_context.script | 16 ----- .../routing_context_in_hello_neo4j.script | 17 ------ .../resources/write_server_v3_write_tx.script | 13 ---- .../resources/write_tx_with_bookmarks.script | 13 ---- .../requests/CheckMultiDBSupport.java | 50 ++++++++++++++++ .../messages/requests/TestkitRequest.java | 2 +- .../messages/responses/MultiDBSupport.java | 47 +++++++++++++++ 9 files changed, 100 insertions(+), 143 deletions(-) delete mode 100644 driver/src/test/resources/acquire_endpoints_v3_leader_killed.script delete mode 100644 driver/src/test/resources/get_routing_table_with_context.script delete mode 100644 driver/src/test/resources/routing_context_in_hello_neo4j.script delete mode 100644 driver/src/test/resources/write_server_v3_write_tx.script delete mode 100644 driver/src/test/resources/write_tx_with_bookmarks.script create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/CheckMultiDBSupport.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/MultiDBSupport.java 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 f3dfd859c1..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,7 +27,6 @@ import java.io.IOException; import java.net.URI; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.concurrent.TimeUnit; @@ -48,10 +47,8 @@ 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; @@ -63,9 +60,7 @@ 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.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -229,6 +224,8 @@ void shouldFailInitialDiscoveryWhenConfiguredResolverThrows() verify( resolver ).resolve( ServerAddress.of( "my.server.com", 9001 ) ); } + // 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 { @@ -256,45 +253,6 @@ void useSessionAfterDriverIsClosed() throws Exception } } - @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 ); @@ -318,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/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/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/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_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/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/TestkitRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java index 8a50c459f7..7d74966fa5 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 @@ -33,7 +33,7 @@ @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( ResolverResolutionCompleted.class ) + @JsonSubTypes.Type( ResolverResolutionCompleted.class ), @JsonSubTypes.Type( CheckMultiDBSupport.class ) } ) public interface TestkitRequest { 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; + } +} From 0d9a7c8e900346af5a6752ddf9a6832eba385785 Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov <11927660+injectives@users.noreply.github.com> Date: Fri, 5 Mar 2021 14:40:37 +0000 Subject: [PATCH 5/8] Stub tests migration part 4 (#847) Removed RoutingDriverMultidatabaseBoltKitIT Migrated tests: - shouldDiscoverForDatabase -> test_should_read_successfully_from_reader_using_session_run (this tests seems to cover the same use-case and uses a non-default DB for 4+ versions) - shouldRetryOnEmptyDiscoveryResult -> test_should_read_successfully_on_empty_discovery_result_using_session_run - shouldThrowRoutingErrorIfDatabaseNotFound -> test_should_fail_with_routing_failure_on_db_not_found_discovery_failure - shouldBeAbleToServeReachableDatabase -> test_should_read_successfully_from_reachable_db_after_trying_unreachable_db (message check has been removed) - shouldPassSystemBookmarkWhenGettingRoutingTableForMultiDB -> test_should_pass_system_bookmark_when_getting_rt_for_multi_db (seems to be applicable to V4 only, also the stub server doesn't seem to check bookmarks) - shouldIgnoreSystemBookmarkWhenGettingRoutingTable -> test_should_ignore_system_bookmark_when_getting_rt_for_multi_db - shouldDriverVerifyConnectivity -> test_should_successfully_get_routing_table_with_context (pre-existing test that already tests the connectivity) Also removed redundant scripts and added code support to DriverError --- .../RoutingDriverMultidatabaseBoltKitIT.java | 242 ------------------ .../resources/acquire_endpoints_v4.script | 10 - ...ire_endpoints_v4_database_not_found.script | 9 - .../acquire_endpoints_v4_empty.script | 10 - .../acquire_endpoints_v4_multi_db.script | 15 -- ...re_endpoints_v4_verify_connectivity.script | 15 -- .../acquire_endpoints_v4_virtual_host.script | 10 - .../acquire_endpoints_v4_with_bookmark.script | 10 - .../read_server_v3_read_with_bookmark.script | 12 - .../read_server_v4_read_with_bookmark.script | 12 - .../org/testkit/backend/CommandProcessor.java | 8 +- .../messages/responses/DriverError.java | 2 + 12 files changed, 9 insertions(+), 346 deletions(-) delete mode 100644 driver/src/test/java/org/neo4j/driver/integration/RoutingDriverMultidatabaseBoltKitIT.java delete mode 100644 driver/src/test/resources/acquire_endpoints_v4.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v4_database_not_found.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v4_empty.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v4_multi_db.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v4_verify_connectivity.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v4_virtual_host.script delete mode 100644 driver/src/test/resources/acquire_endpoints_v4_with_bookmark.script delete mode 100644 driver/src/test/resources/read_server_v3_read_with_bookmark.script delete mode 100644 driver/src/test/resources/read_server_v4_read_with_bookmark.script 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/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/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/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/CommandProcessor.java index 317fa8087b..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 @@ -175,7 +175,13 @@ else if ( currentLine.equals( "#request end" ) ) private DriverError driverError( String id, Neo4jException e ) { - return DriverError.builder().data( DriverError.DriverErrorBody.builder().id( id ).errorType( e.getClass().getName() ).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/messages/responses/DriverError.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DriverError.java index 2e373e8e1d..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 @@ -43,5 +43,7 @@ public static class DriverErrorBody private String id; private String errorType; + + private String code; } } From 7909a33681488418b0bda01d4fdaeb112726ee45 Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov <11927660+injectives@users.noreply.github.com> Date: Wed, 17 Mar 2021 14:20:20 +0000 Subject: [PATCH 6/8] Fix for initial routers DNS resolution (#849) * Fix for initial routers DNS resolution This update fixes the following issue: https://github.com/neo4j/neo4j-java-driver/issues/833 The desired behaviour for getting a routing table from the initial router (either on bootstrap or when all known routers have failed) is: - resolve the domain name to all IPs - attempt getting a routing table from all of them until first one succeeds by: - getting a connection - trying to get a successful routing table response Prior to this change, the connection pools were created for host and port pairs. When domain name of the host resolves to multiple IP addresses, such pools provide connections to those IPs as a group. While this works for readers and writers, it negatively impacts the routing table fetching process as there is no guarantee which IP address the provided connection is setup for. This update delivers the following changes: - connection pools for routers are IP address based, which allows for deterministic connection retrieval - the resolved IP address set is kept up-to-date (in case known router IPs change) to make sure that the unused connection pools are flushed - the domain name resolution logic has been made configurable (it is private at the moment and is used to facilitate testing) - the testkit backend has been updated to support the domain name resolution configuration (a new test has been added to testkit to cover the issue described above) - the testkit backend has been updated to support connection timeout driver configuration - several tests have been updated to adopt the new changes * Updating test name --- .../driver/internal/BoltServerAddress.java | 51 +-- .../internal/DefaultDomainNameResolver.java | 42 +++ .../driver/internal/DomainNameResolver.java | 38 +++ .../neo4j/driver/internal/DriverFactory.java | 15 +- .../connection/ChannelConnectorImpl.java | 23 +- .../connection/NettyDomainNameResolver.java | 67 ++++ .../NettyDomainNameResolverGroup.java | 43 +++ .../driver/internal/cluster/AddressSet.java | 31 +- .../internal/cluster/ClusterComposition.java | 7 +- .../ClusterCompositionLookupResult.java | 52 +++ .../internal/cluster/ClusterRoutingTable.java | 14 +- .../driver/internal/cluster/Rediscovery.java | 5 +- .../internal/cluster/RediscoveryImpl.java | 315 +++++++++++------- .../driver/internal/cluster/RoutingTable.java | 4 +- .../cluster/RoutingTableHandlerImpl.java | 23 +- .../cluster/loadbalancing/LoadBalancer.java | 22 +- .../integration/ChannelConnectorImplIT.java | 6 +- .../async/pool/ConnectionPoolImplIT.java | 3 +- .../async/pool/NettyChannelPoolIT.java | 5 +- .../internal/cluster/AddressSetTest.java | 14 +- .../internal/cluster/RediscoveryTest.java | 48 +-- .../cluster/RoutingTableHandlerTest.java | 8 +- .../loadbalancing/LoadBalancerTest.java | 4 +- .../RoutingTableAndConnectionPoolTest.java | 16 +- .../internal/net/BoltServerAddressTest.java | 57 ---- .../internal/util/ClusterCompositionUtil.java | 16 +- .../util/MessageRecordingDriverFactory.java | 8 +- ...DriverFactoryWithFailingMessageFormat.java | 6 +- .../org/neo4j/driver/util/cc/Cluster.java | 5 +- .../neo4j/driver/util/cc/ClusterMember.java | 3 +- .../cc/ClusterMemberRoleDiscoveryFactory.java | 7 +- .../org/testkit/backend/TestkitState.java | 2 + .../DomainNameResolutionCompleted.java | 69 ++++ .../backend/messages/requests/NewDriver.java | 61 +++- .../messages/requests/TestkitRequest.java | 3 +- .../DomainNameResolutionRequired.java | 47 +++ 36 files changed, 826 insertions(+), 314 deletions(-) create mode 100644 driver/src/main/java/org/neo4j/driver/internal/DefaultDomainNameResolver.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/DomainNameResolver.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyDomainNameResolver.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/async/connection/NettyDomainNameResolverGroup.java create mode 100644 driver/src/main/java/org/neo4j/driver/internal/cluster/ClusterCompositionLookupResult.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/DomainNameResolutionCompleted.java create mode 100644 testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/responses/DomainNameResolutionRequired.java 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..8d8b4b23af 100644 --- a/driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java +++ b/driver/src/main/java/org/neo4j/driver/internal/BoltServerAddress.java @@ -22,15 +22,14 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; -import java.net.UnknownHostException; -import java.util.List; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Objects; -import java.util.stream.Stream; +import java.util.Set; 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. @@ -43,8 +42,8 @@ public class BoltServerAddress implements ServerAddress 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; private final String stringValue; - - private InetAddress resolved; + + private final Set resolved; public BoltServerAddress( String address ) { @@ -58,15 +57,15 @@ public BoltServerAddress( URI uri ) public BoltServerAddress( String host, int port ) { - this( host, null, port ); + this( host, port, Collections.emptySet() ); } - private BoltServerAddress( String host, InetAddress resolved, int port ) + public BoltServerAddress( String host, int port, Set resolved ) { this.host = requireNonNull( host, "host" ); - this.resolved = resolved; 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 = String.format( "%s:%d", host, port ); + this.resolved = Collections.unmodifiableSet( new LinkedHashSet<>( resolved ) ); } public static BoltServerAddress from( ServerAddress address ) @@ -112,33 +111,7 @@ public String toString() */ 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() ); + return new InetSocketAddress( host, port ); } @Override @@ -153,9 +126,9 @@ public int port() return port; } - public boolean isResolved() + public Set resolved() { - return resolved != null; + return this.resolved; } 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/async/connection/ChannelConnectorImpl.java b/driver/src/main/java/org/neo4j/driver/internal/async/connection/ChannelConnectorImpl.java index d927b8c9ec..96fd19fb33 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,22 @@ 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 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 +53,17 @@ public class ChannelConnectorImpl implements ChannelConnector private final int connectTimeoutMillis; private final Logging logging; private final Clock clock; + 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 +73,7 @@ public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan this.pipelineBuilder = pipelineBuilder; this.logging = requireNonNull( logging ); this.clock = requireNonNull( clock ); + this.addressResolverGroup = new NettyDomainNameResolverGroup( requireNonNull( domainNameResolver ) ); } @Override @@ -77,6 +81,7 @@ 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 ); ChannelFuture channelConnected = bootstrap.connect( address.toSocketAddress() ); 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..e73d9b74ad 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,18 @@ import io.netty.util.concurrent.EventExecutorGroup; import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashSet; +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 java.util.stream.Collectors; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logger; @@ -37,15 +41,16 @@ 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.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 +69,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 +80,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 +103,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 +157,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 +300,66 @@ 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 + { + resolvedAddresses.addAll( resolveAllByDomainName( BoltServerAddress.from( serverAddress ) ) ); + } + 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(); + Set resolvedAddresses = resolveAllByDomainName( address ); + routingTable.replaceRouterIfPresent( address, new BoltServerAddress( address.host(), address.port(), resolvedAddresses ) ); + return resolvedAddresses.stream().findFirst().orElseThrow( + () -> new IllegalStateException( "Domain name resolution returned empty result set and has not thrown an exception" ) ); } - 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 Set resolveAllByDomainName( BoltServerAddress address ) throws UnknownHostException + { + return Arrays.stream( domainNameResolver.resolve( address.host() ) ) + .map( inetAddress -> new BoltServerAddress( inetAddress.getHostAddress(), address.port() ) ) + .collect( Collectors.toCollection( LinkedHashSet::new ) ); + } } 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..7226ce6685 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,27 @@ else if ( routingTable.isStaleFor( context.mode() ) ) } } - private synchronized void freshClusterCompositionFetched( ClusterComposition composition ) + private synchronized void freshClusterCompositionFetched( ClusterCompositionLookupResult composition ) { try { - routingTable.update( composition ); + routingTable.update( composition.getClusterComposition() ); routingTableRegistry.removeAged(); - connectionPool.retainAll( routingTableRegistry.allServers() ); + + Set addressesToRetain = new LinkedHashSet<>(); + for ( BoltServerAddress address : routingTableRegistry.allServers() ) + { + addressesToRetain.add( address ); + addressesToRetain.addAll( address.resolved() ); + } + composition.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/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/RediscoveryTest.java b/driver/src/test/java/org/neo4j/driver/internal/cluster/RediscoveryTest.java index 19b1174909..80f20af27b 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( addresses.get( 0 ), new BoltServerAddress( localhost.getHostAddress(), A.port() ) ); } 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..5f3af8eede 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 @@ -22,20 +22,11 @@ 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; @@ -146,58 +137,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/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/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/TestkitState.java index 1d966da508..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,6 +21,7 @@ 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; @@ -45,6 +46,7 @@ public class TestkitState 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/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 c34bafc40d..258dfe2da3 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,19 +20,28 @@ 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.cluster.RoutingSettings; +import org.neo4j.driver.internal.retry.RetrySettings; +import org.neo4j.driver.internal.security.SecurityPlanImpl; import org.neo4j.driver.net.ServerAddressResolver; @Setter @@ -65,8 +74,14 @@ public TestkitResponse process( TestkitState testkitState ) { 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(); } @@ -90,6 +105,34 @@ private ServerAddressResolver callbackResolver( TestkitState testkitState ) }; } + 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; + return new DriverFactoryWithDomainNameResolver( domainNameResolver ) + .newInstance( uri, authToken, routingSettings, retrySettings, config, SecurityPlanImpl.insecure() ); + } + @Setter @Getter @NoArgsConstructor @@ -99,5 +142,19 @@ public static class NewDriverBody 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/TestkitRequest.java b/testkit-backend/src/main/java/neo4j/org/testkit/backend/messages/requests/TestkitRequest.java index 7d74966fa5..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 @@ -33,7 +33,8 @@ @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( ResolverResolutionCompleted.class ), @JsonSubTypes.Type( CheckMultiDBSupport.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/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; + } +} From ca7cd1b43bae9a364227dac5de41e95c7f80eff2 Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov <11927660+injectives@users.noreply.github.com> Date: Mon, 22 Mar 2021 10:46:10 +0000 Subject: [PATCH 7/8] Fixed SSL handling (#851) This update fixes a number of SSL-related tests in testkit and CausalClusteringIT.shouldDropBrokenOldConnections test. The connection pooling strategy has been updated to use the same connection pool when the connection host is unambiguous. Removed hardcoded domain name resolution from the BoltServerAddress and moved the logic to ChannelConnectorImpl that uses the DomainNameResolver. --- .../driver/internal/BoltServerAddress.java | 58 ++++----- .../internal/ResolvedBoltServerAddress.java | 115 ++++++++++++++++++ .../connection/ChannelConnectorImpl.java | 17 ++- .../internal/cluster/RediscoveryImpl.java | 23 ++-- .../cluster/RoutingTableHandlerImpl.java | 14 +-- .../cluster/ClusterCompositionTest.java | 24 ++-- .../internal/cluster/RediscoveryTest.java | 2 +- .../internal/net/BoltServerAddressTest.java | 13 -- .../org/neo4j/driver/util/Neo4jRunner.java | 4 +- .../backend/messages/requests/NewDriver.java | 10 +- 10 files changed, 197 insertions(+), 83 deletions(-) create mode 100644 driver/src/main/java/org/neo4j/driver/internal/ResolvedBoltServerAddress.java 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 8d8b4b23af..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,14 +18,9 @@ */ package org.neo4j.driver.internal; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.net.URI; -import java.util.Collections; -import java.util.LinkedHashSet; import java.util.Objects; -import java.util.Set; +import java.util.stream.Stream; import org.neo4j.driver.net.ServerAddress; @@ -39,11 +34,10 @@ 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 final Set resolved; public BoltServerAddress( String address ) { @@ -57,15 +51,17 @@ public BoltServerAddress( URI uri ) public BoltServerAddress( String host, int port ) { - this( host, port, Collections.emptySet() ); + this( host, host, port ); } - public BoltServerAddress( String host, int port, Set resolved ) + public BoltServerAddress( String host, String connectionHost, int port ) { this.host = requireNonNull( host, "host" ); + this.connectionHost = requireNonNull( connectionHost, "connectionHost" ); this.port = requireValidPort( port ); - this.stringValue = String.format( "%s:%d", host, port ); - this.resolved = Collections.unmodifiableSet( new LinkedHashSet<>( resolved ) ); + 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 ) @@ -86,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 @@ -102,18 +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 new InetSocketAddress( host, port ); - } - @Override public String host() { @@ -126,9 +110,21 @@ public int port() return port; } - public Set resolved() + 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 this.resolved; + return Stream.of( this ); } private static String hostFrom( URI uri ) 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 96fd19fb33..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 @@ -27,6 +27,7 @@ import io.netty.resolver.AddressResolverGroup; import java.net.InetSocketAddress; +import java.net.SocketAddress; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; @@ -53,6 +54,7 @@ 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, @@ -73,7 +75,8 @@ public ChannelConnectorImpl( ConnectionSettings connectionSettings, SecurityPlan this.pipelineBuilder = pipelineBuilder; this.logging = requireNonNull( logging ); this.clock = requireNonNull( clock ); - this.addressResolverGroup = new NettyDomainNameResolverGroup( requireNonNull( domainNameResolver ) ); + this.domainNameResolver = requireNonNull( domainNameResolver ); + this.addressResolverGroup = new NettyDomainNameResolverGroup( this.domainNameResolver ); } @Override @@ -83,7 +86,17 @@ public ChannelFuture connect( BoltServerAddress address, Bootstrap bootstrap ) bootstrap.handler( new NettyChannelInitializer( address, securityPlan, connectTimeoutMillis, clock, logging ) ); bootstrap.resolver( addressResolverGroup ); - ChannelFuture channelConnected = bootstrap.connect( address.toSocketAddress() ); + 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( socketAddress ); Channel channel = channelConnected.channel(); ChannelPromise handshakeCompleted = channel.newPromise(); 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 e73d9b74ad..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,10 +21,8 @@ import io.netty.util.concurrent.EventExecutorGroup; import java.net.UnknownHostException; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -32,7 +30,6 @@ import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionStage; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import org.neo4j.driver.Bookmark; import org.neo4j.driver.Logger; @@ -42,6 +39,7 @@ import org.neo4j.driver.exceptions.ServiceUnavailableException; import org.neo4j.driver.internal.BoltServerAddress; 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; @@ -308,7 +306,7 @@ public List resolve() throws UnknownHostException { try { - resolvedAddresses.addAll( resolveAllByDomainName( BoltServerAddress.from( serverAddress ) ) ); + resolveAllByDomainName( serverAddress ).unicastStream().forEach( resolvedAddresses::add ); } catch ( UnknownHostException e ) { @@ -345,10 +343,13 @@ private BoltServerAddress resolveByDomainNameOrThrowCompletionException( BoltSer { try { - Set resolvedAddresses = resolveAllByDomainName( address ); - routingTable.replaceRouterIfPresent( address, new BoltServerAddress( address.host(), address.port(), resolvedAddresses ) ); - return resolvedAddresses.stream().findFirst().orElseThrow( - () -> new IllegalStateException( "Domain name resolution returned empty result set and has not thrown an exception" ) ); + 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 ( Throwable e ) { @@ -356,10 +357,8 @@ private BoltServerAddress resolveByDomainNameOrThrowCompletionException( BoltSer } } - private Set resolveAllByDomainName( BoltServerAddress address ) throws UnknownHostException + private ResolvedBoltServerAddress resolveAllByDomainName( ServerAddress address ) throws UnknownHostException { - return Arrays.stream( domainNameResolver.resolve( address.host() ) ) - .map( inetAddress -> new BoltServerAddress( inetAddress.getHostAddress(), address.port() ) ) - .collect( Collectors.toCollection( LinkedHashSet::new ) ); + return new ResolvedBoltServerAddress( address.host(), address.port(), domainNameResolver.resolve( address.host() ) ); } } 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 7226ce6685..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 @@ -108,20 +108,18 @@ else if ( routingTable.isStaleFor( context.mode() ) ) } } - private synchronized void freshClusterCompositionFetched( ClusterCompositionLookupResult composition ) + private synchronized void freshClusterCompositionFetched( ClusterCompositionLookupResult compositionLookupResult ) { try { - routingTable.update( composition.getClusterComposition() ); + routingTable.update( compositionLookupResult.getClusterComposition() ); routingTableRegistry.removeAged(); Set addressesToRetain = new LinkedHashSet<>(); - for ( BoltServerAddress address : routingTableRegistry.allServers() ) - { - addressesToRetain.add( address ); - addressesToRetain.addAll( address.resolved() ); - } - composition.getResolvedInitialRouters().ifPresent( + routingTableRegistry.allServers().stream() + .flatMap( BoltServerAddress::unicastStream ) + .forEach( addressesToRetain::add ); + compositionLookupResult.getResolvedInitialRouters().ifPresent( addresses -> { resolvedInitialRouters.clear(); 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 80f20af27b..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 @@ -430,7 +430,7 @@ void shouldResolveToIP() throws UnknownHostException verify( resolver, times( 1 ) ).resolve( A ); verify( domainNameResolver, times( 1 ) ).resolve( A.host() ); assertEquals( 1, addresses.size() ); - assertEquals( addresses.get( 0 ), new BoltServerAddress( localhost.getHostAddress(), A.port() ) ); + assertEquals( new BoltServerAddress( A.host(), localhost.getHostAddress(), A.port() ), addresses.get( 0 ) ); } private Rediscovery newRediscovery( BoltServerAddress initialRouter, ClusterCompositionProvider compositionProvider, 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 5f3af8eede..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,7 +20,6 @@ import org.junit.jupiter.api.Test; -import java.net.SocketAddress; import java.net.URI; import org.neo4j.driver.internal.BoltServerAddress; @@ -29,7 +28,6 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.junit.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; @@ -50,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() { 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/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 258dfe2da3..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 @@ -39,9 +39,10 @@ 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.SecurityPlanImpl; +import org.neo4j.driver.internal.security.SecurityPlan; import org.neo4j.driver.net.ServerAddressResolver; @Setter @@ -93,7 +94,7 @@ private ServerAddressResolver callbackResolver( TestkitState testkitState ) ResolverResolutionRequired.ResolverResolutionRequiredBody body = ResolverResolutionRequired.ResolverResolutionRequiredBody.builder() .id( callbackId ) - .address( address.toString() ) + .address( String.format( "%s:%d", address.host(), address.port() ) ) .build(); ResolverResolutionRequired response = ResolverResolutionRequired.builder() @@ -129,8 +130,11 @@ private org.neo4j.driver.Driver driver( URI uri, AuthToken authToken, Config con { 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, SecurityPlanImpl.insecure() ); + .newInstance( uri, authToken, routingSettings, retrySettings, config, securityPlan ); } @Setter From 89314f7754f787c0d4a88e46c2614e67fcdcff5f Mon Sep 17 00:00:00 2001 From: Dmitriy Tverdiakov Date: Mon, 22 Mar 2021 18:44:56 +0000 Subject: [PATCH 8/8] Updated .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) 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/