diff --git a/driver/pom.xml b/driver/pom.xml index c0ac6cbec1..881fad3aeb 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -82,6 +82,16 @@ reactor-test test + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + neo4j + test + diff --git a/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java b/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java index f807aa372a..5b66037340 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java +++ b/driver/src/test/java/org/neo4j/driver/stress/AbstractStressTestBase.java @@ -21,7 +21,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -88,7 +87,6 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.neo4j.driver.SessionConfig.builder; -@ExtendWith( DumpLogsOnFailureWatcher.class ) abstract class AbstractStressTestBase { private static final int THREAD_COUNT = Integer.getInteger( "threadCount", 8 ); @@ -202,8 +200,6 @@ private void runStressTest( Function>> threadLauncher ) throws verifyResults( context, resourcesInfo ); } - abstract void dumpLogs(); - abstract URI databaseUri(); abstract AuthToken authToken(); diff --git a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringStressIT.java b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringStressIT.java index 133fb67271..ff4fd85000 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringStressIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/CausalClusteringStressIT.java @@ -19,6 +19,7 @@ package org.neo4j.driver.stress; import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.junit.jupiter.Testcontainers; import java.net.URI; import java.util.Arrays; @@ -30,6 +31,7 @@ import org.neo4j.driver.exceptions.SessionExpiredException; import org.neo4j.driver.util.cc.LocalOrRemoteClusterExtension; +@Testcontainers( disabledWithoutDocker = true ) class CausalClusteringStressIT extends AbstractStressTestBase { @RegisterExtension @@ -84,12 +86,6 @@ void printStats( Context context ) System.out.println( "Bookmark failures: " + context.getBookmarkFailures() ); } - @Override - void dumpLogs() - { - clusterRule.dumpClusterLogs(); - } - @Override List> createTestSpecificBlockingCommands() { @@ -112,5 +108,4 @@ int getLeaderSwitchCount() return leaderSwitches.get(); } } - } diff --git a/driver/src/test/java/org/neo4j/driver/stress/DumpLogsOnFailureWatcher.java b/driver/src/test/java/org/neo4j/driver/stress/DumpLogsOnFailureWatcher.java deleted file mode 100644 index 949c018927..0000000000 --- a/driver/src/test/java/org/neo4j/driver/stress/DumpLogsOnFailureWatcher.java +++ /dev/null @@ -1,55 +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.stress; - -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.TestWatcher; - -import java.util.Optional; - -public class DumpLogsOnFailureWatcher implements TestWatcher -{ - @Override - public void testDisabled( ExtensionContext context, Optional reason ) - { - // do nothing - } - - @Override - public void testSuccessful( ExtensionContext context ) - { - // do nothing - } - - @Override - public void testAborted( ExtensionContext context, Throwable cause ) - { - // do nothing - } - - @Override - public void testFailed( ExtensionContext context, Throwable cause ) - { - if ( context.getTestInstance().isPresent() && context.getTestInstance().get() instanceof AbstractStressTestBase) - { - AbstractStressTestBase clusterTest = (AbstractStressTestBase) context.getTestInstance().get(); - clusterTest.dumpLogs(); - } - } -} diff --git a/driver/src/test/java/org/neo4j/driver/stress/SingleInstanceStressIT.java b/driver/src/test/java/org/neo4j/driver/stress/SingleInstanceStressIT.java index 9c128467ab..63038d1cc0 100644 --- a/driver/src/test/java/org/neo4j/driver/stress/SingleInstanceStressIT.java +++ b/driver/src/test/java/org/neo4j/driver/stress/SingleInstanceStressIT.java @@ -145,10 +145,4 @@ List> createTestSpecificRxCommands() static class Context extends AbstractContext { } - - @Override - void dumpLogs() - { - neo4j.dumpLogs(); - } } 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 deleted file mode 100644 index 81079cce95..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/Cluster.java +++ /dev/null @@ -1,385 +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.util.cc; - -import java.io.FileNotFoundException; -import java.net.URI; -import java.nio.file.Path; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; - -import org.neo4j.driver.Bookmark; -import org.neo4j.driver.Driver; -import org.neo4j.driver.internal.BoltServerAddress; -import org.neo4j.driver.util.TestUtil; -import org.neo4j.driver.util.cc.ClusterMemberRoleDiscoveryFactory.ClusterMemberRoleDiscovery; - -import static java.util.Collections.emptySet; -import static java.util.Collections.unmodifiableSet; -import static org.neo4j.driver.util.TestUtil.sleep; - -public class Cluster implements AutoCloseable -{ - private static final String ADMIN_USER = "neo4j"; - private static final int STARTUP_TIMEOUT_SECONDS = 120; - private static final int ONLINE_MEMBERS_CHECK_SLEEP_MS = 500; - - private final Path path; - private final Set members; - private final Set offlineMembers; - private final ClusterDrivers clusterDrivers; - - public Cluster( Path path, String password ) - { - this( path, emptySet(), new ClusterDrivers( ADMIN_USER, password ) ); - } - - private Cluster( Path path, Set members, ClusterDrivers clusterDrivers ) - { - this.path = path; - this.members = members; - this.offlineMembers = new HashSet<>(); - this.clusterDrivers = clusterDrivers; - } - - Cluster withMembers( Set newMembers ) throws ClusterUnavailableException - { - waitForMembersToBeOnline( newMembers, clusterDrivers ); - return new Cluster( path, newMembers, clusterDrivers ); - } - - public URI getRoutingUri() - { - return randomOf( cores() ).getRoutingUri(); - } - - public Path getPath() - { - return path; - } - - public void deleteData() - { - // execute write query to remove all nodes and retrieve bookmark - Driver driverToLeader = clusterDrivers.getDriver( leader() ); - Bookmark bookmark = TestUtil.cleanDb( driverToLeader ); - if ( bookmark == null ) - { - throw new IllegalStateException( "Cleanup of the database did not produce a bookmark" ); - } - - // ensure that every cluster member is up-to-date and contains no nodes - for ( ClusterMember member : members ) - { - Driver driver = clusterDrivers.getDriver( member ); - long nodeCount = TestUtil.countNodes( driver, bookmark ); - if ( nodeCount != 0 ) - { - throw new IllegalStateException( "Not all nodes have been deleted. " + nodeCount + " still there somehow" ); - } - } - } - - public Set members() - { - return unmodifiableSet( members ); - } - - public ClusterMember leader() - { - Set leaders = membersWithRole( ClusterMemberRole.LEADER ); - if ( leaders.size() != 1 ) - { - throw new IllegalStateException( "Single leader expected. " + leaders ); - } - return leaders.iterator().next(); - } - - public ClusterMember anyFollower() - { - return randomOf( followers() ); - } - - public Set followers() - { - return membersWithRole( ClusterMemberRole.FOLLOWER ); - } - - public ClusterMember anyReadReplica() - { - return randomOf( readReplicas() ); - } - - public Set cores() - { - Set readReplicas = membersWithRole( ClusterMemberRole.READ_REPLICA ); - Set cores = new HashSet<>( members ); - cores.removeAll( readReplicas ); - return cores; - } - - public Set readReplicas() - { - return membersWithRole( ClusterMemberRole.READ_REPLICA ); - } - - public void start( ClusterMember member ) - { - startNoWait( member ); - waitForMembersToBeOnline(); - } - - public Driver getDirectDriver( ClusterMember member ) - { - return clusterDrivers.getDriver( member ); - } - - public void dumpClusterDebugLog() - { - for ( ClusterMember member : members ) - { - - System.out.println( "Debug log for: " + member.getPath().toString() ); - try - { - member.dumpDebugLog(); - } - catch ( FileNotFoundException e ) - { - System.out.println("Unable to find debug log file for: " + member.getPath().toString()); - e.printStackTrace(); - } - } - } - - @Override - public void close() - { - clusterDrivers.close(); - } - - @Override - public String toString() - { - return "Cluster{" + - "path=" + path + - ", members=" + members + - "}"; - } - - private void addOfflineMember( ClusterMember member ) - { - if ( !offlineMembers.remove( member ) ) - { - throw new IllegalArgumentException( "Cluster member is not offline: " + member ); - } - members.add( member ); - } - - private void startNoWait( ClusterMember member ) - { - addOfflineMember( member ); - SharedCluster.start( member ); - } - - private Set membersWithRole( ClusterMemberRole role ) - { - Set membersWithRole = new HashSet<>(); - int retryCount = 0; - - while ( membersWithRole.isEmpty() && retryCount < 10 ) - { - Driver driver = driverToAnyCore( members, clusterDrivers ); - final ClusterMemberRoleDiscovery discovery = clusterDrivers.getDiscovery(); - final Map clusterOverview = discovery.findClusterOverview( driver ); - for ( BoltServerAddress boltAddress : clusterOverview.keySet() ) - { - if ( role == clusterOverview.get( boltAddress ) ) - { - ClusterMember member = findByBoltAddress( boltAddress, members ); - if ( member == null ) - { - throw new IllegalStateException( "Unknown cluster member: '" + boltAddress + "'\n" + this ); - } - membersWithRole.add( member ); - } - } - retryCount++; - - if ( !membersWithRole.isEmpty() ) - { - break; - } - else - { - try - { - // give some time for cluster to stabilise - Thread.sleep( 2000 ); - } - catch ( InterruptedException ignored ) - { - } - } - } - - if ( membersWithRole.isEmpty() ) - { - throw new IllegalStateException( "No cluster members with role '" + role + " " + this ); - } - - return membersWithRole; - } - - private void waitForMembersToBeOnline() - { - try - { - waitForMembersToBeOnline( members, clusterDrivers ); - } - catch ( ClusterUnavailableException e ) - { - throw new RuntimeException( e ); - } - } - - private static void waitForMembersToBeOnline( Set members, ClusterDrivers clusterDrivers ) - throws ClusterUnavailableException - { - if ( members.isEmpty() ) - { - throw new IllegalArgumentException( "No members to wait for" ); - } - - Set expectedOnlineAddresses = extractBoltAddresses( members ); - Set actualOnlineAddresses = emptySet(); - - long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis( STARTUP_TIMEOUT_SECONDS ); - Throwable error = null; - - while ( !expectedOnlineAddresses.equals( actualOnlineAddresses ) ) - { - sleep( ONLINE_MEMBERS_CHECK_SLEEP_MS ); - assertDeadlineNotReached( deadline, expectedOnlineAddresses, actualOnlineAddresses, error ); - - Driver driver = driverToAnyCore( members, clusterDrivers ); - final ClusterMemberRoleDiscovery discovery = clusterDrivers.getDiscovery(); - try - { - final Map clusterOverview = discovery.findClusterOverview( driver ); - actualOnlineAddresses = clusterOverview.keySet(); - } - catch ( Throwable t ) - { - t.printStackTrace(); - - if ( error == null ) - { - error = t; - } - else - { - error.addSuppressed( t ); - } - } - } - } - - private static Driver driverToAnyCore( Set members, ClusterDrivers clusterDrivers ) - { - if ( members.isEmpty() ) - { - throw new IllegalArgumentException( "No members, can't create driver" ); - } - - for ( ClusterMember member : members ) - { - Driver driver = clusterDrivers.getDriver( member ); - final ClusterMemberRoleDiscovery discovery = clusterDrivers.getDiscovery(); - if ( discovery.isCoreMember( driver ) ) - { - return driver; - } - } - - throw new IllegalStateException( "No core members found among: " + members ); - } - - private static void assertDeadlineNotReached( long deadline, Set expectedAddresses, Set actualAddresses, - Throwable error ) throws ClusterUnavailableException - { - if ( System.currentTimeMillis() > deadline ) - { - String baseMessage = "Cluster did not become available in " + STARTUP_TIMEOUT_SECONDS + " seconds.\n"; - String errorMessage = error == null ? "" : "There were errors checking cluster members.\n"; - String expectedAddressesMessage = "Expected online addresses: " + expectedAddresses + "\n"; - String actualAddressesMessage = "Actual last seen online addresses: " + actualAddresses + "\n"; - String message = baseMessage + errorMessage + expectedAddressesMessage + actualAddressesMessage; - - ClusterUnavailableException clusterUnavailable = new ClusterUnavailableException( message ); - - if ( error != null ) - { - clusterUnavailable.addSuppressed( error ); - } - - throw clusterUnavailable; - } - } - - private static Set extractBoltAddresses( Set members ) - { - Set addresses = new HashSet<>(); - for ( ClusterMember member : members ) - { - addresses.add( member.getBoltAddress() ); - } - return addresses; - } - - private static ClusterMember findByBoltAddress( BoltServerAddress boltAddress, Set members ) - { - for ( ClusterMember member : members ) - { - if ( member.getBoltAddress().equals( boltAddress ) ) - { - return member; - } - } - return null; - } - - private static ClusterMember randomOf( Set members ) - { - int randomIndex = ThreadLocalRandom.current().nextInt( members.size() ); - int currentIndex = 0; - for ( ClusterMember member : members ) - { - if ( currentIndex == randomIndex ) - { - return member; - } - currentIndex++; - } - throw new AssertionError(); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterControl.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterControl.java deleted file mode 100644 index ae6d1329c0..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterControl.java +++ /dev/null @@ -1,60 +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.util.cc; - -import java.nio.file.Path; - -import static org.neo4j.driver.util.cc.CommandLineUtil.executeCommand; - -final class ClusterControl -{ - private ClusterControl() - { - } - - static void installCluster( String neo4jVersion, int cores, int readReplicas, String password, int port, - Path path ) - { - executeCommand( "neoctrl-cluster", "install", - "--cores", String.valueOf( cores ), "--read-replicas", String.valueOf( readReplicas ), - "--password", password, "--initial-port", String.valueOf( port ), - neo4jVersion, path.toString() ); - } - - static String startCluster( Path path ) - { - return executeCommand( "neoctrl-cluster", "start", path.toString() ); - } - - static String startClusterMember( Path path ) - { - return executeCommand( "neoctrl-start", path.toString() ); - } - - static void stopCluster( Path path ) - { - executeCommand( "neoctrl-cluster", "stop", path.toString() ); - } - - static void killCluster( Path path ) - { - executeCommand( "neoctrl-cluster", "stop", "--kill", path.toString() ); - } - -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterDrivers.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterDrivers.java deleted file mode 100644 index 1d68da6c5c..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterDrivers.java +++ /dev/null @@ -1,97 +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.util.cc; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.Config; -import org.neo4j.driver.Driver; -import org.neo4j.driver.GraphDatabase; -import org.neo4j.driver.Session; -import org.neo4j.driver.internal.messaging.BoltProtocolVersion; -import org.neo4j.driver.util.cc.ClusterMemberRoleDiscoveryFactory.ClusterMemberRoleDiscovery; - -import static org.neo4j.driver.internal.logging.DevNullLogging.DEV_NULL_LOGGING; - -public class ClusterDrivers implements AutoCloseable -{ - private final String user; - private final String password; - private final Map membersWithDrivers; - private ClusterMemberRoleDiscovery discovery; - - public ClusterDrivers( String user, String password ) - { - this.user = user; - this.password = password; - this.membersWithDrivers = new ConcurrentHashMap<>(); - } - - public Driver getDriver( ClusterMember member ) - { - final Driver driver = membersWithDrivers.computeIfAbsent( member, this::createDriver ); - if ( discovery == null ) - { - try ( Session session = driver.session() ) - { - String version = session.readTransaction( tx -> tx.run( "RETURN 1" ).consume().server().protocolVersion() ); - List versionParts = Arrays.stream( version.split( "\\." ) ).map( Integer::parseInt ).collect( Collectors.toList() ); - BoltProtocolVersion protocolVersion = new BoltProtocolVersion( versionParts.get( 0 ), versionParts.get( 1 ) ); - discovery = ClusterMemberRoleDiscoveryFactory.newInstance( protocolVersion ); - } - } - return driver; - } - - public ClusterMemberRoleDiscovery getDiscovery() - { - return discovery; - } - - @Override - public void close() - { - for ( Driver driver : membersWithDrivers.values() ) - { - driver.close(); - } - } - - private Driver createDriver( ClusterMember member ) - { - return GraphDatabase.driver( member.getBoltUri(), AuthTokens.basic( user, password ), driverConfig() ); - } - - private static Config driverConfig() - { - return Config.builder() - .withLogging( DEV_NULL_LOGGING ) - .withoutEncryption() - .withMaxConnectionPoolSize( 1 ) - .withConnectionLivenessCheckTimeout( 0, TimeUnit.MILLISECONDS ) - .withEventLoopThreads( 1 ) - .build(); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterExtension.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterExtension.java deleted file mode 100644 index add7438fc0..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterExtension.java +++ /dev/null @@ -1,154 +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.util.cc; - -import org.junit.jupiter.api.extension.AfterAllCallback; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeAllCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; - -import org.neo4j.driver.AuthToken; -import org.neo4j.driver.AuthTokens; -import org.neo4j.driver.util.Neo4jRunner; - -import static org.junit.jupiter.api.Assumptions.assumeTrue; -import static org.neo4j.driver.util.Neo4jRunner.PASSWORD; -import static org.neo4j.driver.util.Neo4jRunner.TARGET_DIR; -import static org.neo4j.driver.util.Neo4jRunner.USER; -import static org.neo4j.driver.util.cc.CommandLineUtil.boltKitAvailable; - -public class ClusterExtension implements BeforeAllCallback, AfterEachCallback, AfterAllCallback -{ - private static final Path CLUSTER_DIR = Paths.get( TARGET_DIR, "test-cluster" ).toAbsolutePath(); - private static final int INITIAL_PORT = 20_000; - - public static final int CORE_COUNT = 3; - public static final int READ_REPLICA_COUNT = 2; - - public Cluster getCluster() - { - return SharedCluster.get(); - } - - public AuthToken getDefaultAuthToken() - { - return AuthTokens.basic( USER, PASSWORD ); - } - - @Override - public void beforeAll( ExtensionContext context ) throws Exception - { - assumeTrue( boltKitAvailable(), "BoltKit cluster support unavailable" ); - - stopSingleInstanceDatabase(); - - if ( !SharedCluster.exists() ) - { - SharedCluster.install( parseNeo4jVersion(), - CORE_COUNT, READ_REPLICA_COUNT, PASSWORD, INITIAL_PORT, CLUSTER_DIR ); - - try - { - SharedCluster.start(); - } - catch ( Throwable startError ) - { - try - { - SharedCluster.kill(); - } - catch ( Throwable killError ) - { - startError.addSuppressed( killError ); - } - finally - { - SharedCluster.remove(); - } - throw startError; - } - finally - { - addShutdownHookToStopCluster(); - } - } - - getCluster().deleteData(); - } - - @Override - public void afterEach( ExtensionContext context ) - { - Cluster cluster = getCluster(); - cluster.deleteData(); - } - - @Override - public void afterAll( ExtensionContext context ) - { - if ( SharedCluster.exists() ) - { - try - { - SharedCluster.stop(); - } - finally - { - SharedCluster.remove(); - } - } - } - - private static String parseNeo4jVersion() - { - String[] split = Neo4jRunner.NEOCTRL_ARGS.split( "\\s+" ); - return split[split.length - 1]; - } - - private static void stopSingleInstanceDatabase() throws IOException - { - if ( Neo4jRunner.globalRunnerExists() ) - { - Neo4jRunner.getOrCreateGlobalRunner().stopNeo4j(); - } - } - - private static void addShutdownHookToStopCluster() - { - Runtime.getRuntime().addShutdownHook( new Thread( () -> - { - try - { - if ( SharedCluster.exists() ) - { - SharedCluster.kill(); - } - } - catch ( Throwable t ) - { - System.err.println( "Cluster stopping shutdown hook failed" ); - t.printStackTrace(); - } - } ) ); - } -} 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 deleted file mode 100644 index 68ec80aa1f..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMember.java +++ /dev/null @@ -1,122 +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.util.cc; - -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; -import java.util.Objects; -import java.util.Scanner; - -import org.neo4j.driver.internal.BoltServerAddress; - -import static java.util.Objects.requireNonNull; - -public class ClusterMember -{ - public static final String SIMPLE_SCHEME = "bolt://"; - public static final String ROUTING_SCHEME = "neo4j://"; - - private final URI boltUri; - private final BoltServerAddress boltAddress; - private final Path path; - - public ClusterMember( URI boltUri, Path path ) - { - this.boltUri = requireNonNull( boltUri ); - this.boltAddress = newBoltServerAddress( boltUri ); - this.path = requireNonNull( path ); - } - - public URI getBoltUri() - { - return boltUri; - } - - public URI getRoutingUri() - { - return URI.create( boltUri.toString().replace( SIMPLE_SCHEME, ROUTING_SCHEME ) ); - } - - public BoltServerAddress getBoltAddress() - { - return boltAddress; - } - - public Path getPath() - { - return path; - } - - public void dumpDebugLog() throws FileNotFoundException - { - Scanner input = new Scanner( new File( path.toAbsolutePath().toString() + "/logs/debug.log" )); - - while (input.hasNextLine()) - { - System.out.println(input.nextLine()); - } - } - - @Override - public boolean equals( Object o ) - { - if ( this == o ) - { - return true; - } - if ( o == null || getClass() != o.getClass() ) - { - return false; - } - ClusterMember that = (ClusterMember) o; - return Objects.equals( boltAddress, that.boltAddress ); - } - - @Override - public int hashCode() - { - return Objects.hash( boltAddress ); - } - - @Override - public String toString() - { - return "ClusterMember{" + - "boltUri=" + boltUri + - ", boltAddress=" + boltAddress + - ", path=" + path + - '}'; - } - - private static BoltServerAddress newBoltServerAddress( URI uri ) - { - try - { - return new BoltServerAddress( InetAddress.getByName( uri.getHost() ).getHostAddress(), uri.getPort() ); - } - catch ( UnknownHostException e ) - { - throw new RuntimeException( e ); - } - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRole.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRole.java deleted file mode 100644 index bd81c9fd79..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRole.java +++ /dev/null @@ -1,27 +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.util.cc; - -public enum ClusterMemberRole -{ - LEADER, - FOLLOWER, - READ_REPLICA, - UNKNOWN -} 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 deleted file mode 100644 index ece7025c8c..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterMemberRoleDiscoveryFactory.java +++ /dev/null @@ -1,162 +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.util.cc; - -import java.net.InetAddress; -import java.net.URI; -import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.neo4j.driver.AccessMode; -import org.neo4j.driver.Driver; -import org.neo4j.driver.Record; -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.messaging.BoltProtocolVersion; - -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 -{ - public static ClusterMemberRoleDiscovery newInstance( BoltProtocolVersion version ) - { - if ( version.getMajorVersion() >= 4 ) - { - return new SimpleClusterMemberRoleDiscovery(); - } - else - { - return new LegacyClusterMemberRoleDiscovery(); - } - } - - public interface ClusterMemberRoleDiscovery - { - boolean isCoreMember( Driver driver ); - - Map findClusterOverview( Driver driver ); - } - - public static class LegacyClusterMemberRoleDiscovery implements ClusterMemberRoleDiscovery - { - @Override - public boolean isCoreMember( Driver driver ) - { - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - Record record = single( session.run( "CALL dbms.cluster.role()" ).list() ); - ClusterMemberRole role = extractRole( record ); - return role == ClusterMemberRole.LEADER || role == ClusterMemberRole.FOLLOWER; - } - } - - @Override - public Map findClusterOverview( Driver driver ) - { - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.WRITE ).build() ) ) - { - Result result = session.run( "CALL dbms.cluster.overview()" ); - Map overview = new HashMap<>(); - for ( Record record : result.list() ) - { - final BoltServerAddress address = extractBoltAddress( record ); - final ClusterMemberRole role = extractRole( record ); - overview.put( address, role ); - } - return overview; - } - } - } - - public static class SimpleClusterMemberRoleDiscovery implements ClusterMemberRoleDiscovery - { - private static final String DEFAULT_DATABASE = "neo4j"; - - @Override - public boolean isCoreMember( Driver driver ) - { - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - Record record = single( session.run( "CALL dbms.cluster.role($database)", - parameters( "database", DEFAULT_DATABASE ) ).list() ); - ClusterMemberRole role = extractRole( record ); - return role == ClusterMemberRole.LEADER || role == ClusterMemberRole.FOLLOWER; - } - } - - @Override - public Map findClusterOverview( Driver driver ) - { - try ( Session session = driver.session( builder().withDefaultAccessMode( AccessMode.READ ).build() ) ) - { - Result result = session.run( "CALL dbms.cluster.overview()" ); - Map overview = new HashMap<>(); - for ( Record record : result.list() ) - { - final BoltServerAddress address = extractBoltAddress( record ); - final ClusterMemberRole role = extractRoleForDatabase( record, DEFAULT_DATABASE ); - if ( role != ClusterMemberRole.UNKNOWN ) // the unknown ones has not fully come online - { - overview.put( address, role ); - } - } - return overview; - } - } - } - - private static ClusterMemberRole extractRoleForDatabase( Record record, String database ) - { - final Map databases = record.get( "databases" ).asMap( Values.ofString() ); - final String roleString = databases.get( database ); - return ClusterMemberRole.valueOf( roleString.toUpperCase() ); - } - - private static BoltServerAddress extractBoltAddress( Record record ) - { - List addresses = record.get( "addresses" ).asList(); - String boltUriString = (String) addresses.get( 0 ); - URI boltUri = URI.create( boltUriString ); - return newBoltServerAddress( boltUri ); - } - - private static BoltServerAddress newBoltServerAddress( URI uri ) - { - try - { - return new BoltServerAddress( InetAddress.getByName( uri.getHost() ).getHostAddress(), uri.getPort() ); - } - catch ( UnknownHostException e ) - { - throw new RuntimeException( "Unable to resolve host to IP in URI: '" + uri + "'" ); - } - } - - private static ClusterMemberRole extractRole( Record record ) - { - String roleString = record.get( "role" ).asString(); - return ClusterMemberRole.valueOf( roleString.toUpperCase() ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterUnavailableException.java b/driver/src/test/java/org/neo4j/driver/util/cc/ClusterUnavailableException.java deleted file mode 100644 index 675dc383ea..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/ClusterUnavailableException.java +++ /dev/null @@ -1,27 +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.util.cc; - -class ClusterUnavailableException extends Exception -{ - ClusterUnavailableException( String message ) - { - super( message ); - } -} diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java b/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java index 0de95e7318..21c11b756e 100644 --- a/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java +++ b/driver/src/test/java/org/neo4j/driver/util/cc/LocalOrRemoteClusterExtension.java @@ -22,8 +22,10 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import org.testcontainers.containers.Neo4jContainer; import java.net.URI; +import java.util.Optional; import org.neo4j.driver.AuthToken; import org.neo4j.driver.AuthTokens; @@ -37,7 +39,7 @@ public class LocalOrRemoteClusterExtension implements BeforeAllCallback, AfterEa private static final String CLUSTER_URI_SYSTEM_PROPERTY_NAME = "externalClusterUri"; private static final String NEO4J_USER_PASSWORD_PROPERTY_NAME = "neo4jUserPassword"; - private ClusterExtension localClusterExtension; + private Neo4jContainer neo4jContainer; private URI clusterUri; public LocalOrRemoteClusterExtension() @@ -56,36 +58,33 @@ public AuthToken getAuthToken() { return AuthTokens.basic( "neo4j", neo4jUserPasswordFromSystemProperty() ); } - return localClusterExtension.getDefaultAuthToken(); + return AuthTokens.basic( "neo4j", neo4jContainer.getAdminPassword() ); } @Override - public void beforeAll( ExtensionContext context ) throws Exception + public void beforeAll( ExtensionContext context ) { if ( remoteClusterExists() ) { clusterUri = remoteClusterUriFromSystemProperty(); - deleteDataInRemoteCluster(); + cleanDb(); } else { - localClusterExtension = new ClusterExtension(); - localClusterExtension.beforeAll( context ); - clusterUri = localClusterExtension.getCluster().getRoutingUri(); + String neo4JVersion = Optional.ofNullable( System.getenv( "NEO4J_VERSION" ) ) + .orElse( "4.4" ); + neo4jContainer = new Neo4jContainer<>( String.format( "neo4j:%s-enterprise", neo4JVersion ) ) + .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" ); + neo4jContainer.start(); + + clusterUri = URI.create( neo4jContainer.getBoltUrl().replace( "bolt://", "neo4j://" ) ); } } @Override public void afterEach( ExtensionContext context ) { - if ( remoteClusterExists() ) - { - deleteDataInRemoteCluster(); - } - else - { - localClusterExtension.afterEach( context ); - } + cleanDb(); } @Override @@ -93,19 +92,11 @@ public void afterAll( ExtensionContext context ) { if ( !remoteClusterExists() ) { - localClusterExtension.afterAll( context ); - } - } - - public void dumpClusterLogs() - { - if ( localClusterExtension != null ) - { - localClusterExtension.getCluster().dumpClusterDebugLog(); + neo4jContainer.stop(); } } - private void deleteDataInRemoteCluster() + private void cleanDb() { Config.ConfigBuilder builder = Config.builder(); builder.withEventLoopThreads( 1 ); diff --git a/driver/src/test/java/org/neo4j/driver/util/cc/SharedCluster.java b/driver/src/test/java/org/neo4j/driver/util/cc/SharedCluster.java deleted file mode 100644 index c032dbde1f..0000000000 --- a/driver/src/test/java/org/neo4j/driver/util/cc/SharedCluster.java +++ /dev/null @@ -1,162 +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.util.cc; - -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashSet; -import java.util.Set; - -import static java.lang.System.lineSeparator; -import static org.neo4j.driver.util.Neo4jRunner.debug; - -final class SharedCluster -{ - private static Cluster clusterInstance; - - private SharedCluster() - { - } - - static Cluster get() - { - assertClusterExists(); - return clusterInstance; - } - - static void remove() - { - assertClusterExists(); - clusterInstance.close(); - clusterInstance = null; - } - - static boolean exists() - { - return clusterInstance != null; - } - - static void install( String neo4jVersion, int cores, int readReplicas, String password, int port, Path path ) - { - assertClusterDoesNotExist(); - if ( Files.isDirectory( path ) ) - { - debug( "Found and using cluster installed at `%s`.", path ); - } - else - { - ClusterControl.installCluster( neo4jVersion, cores, readReplicas, password, port, path ); - debug( "Downloaded cluster at `%s`.", path ); - } - clusterInstance = new Cluster( path, password ); - } - - static void start() throws ClusterUnavailableException - { - assertClusterExists(); - String output = ClusterControl.startCluster( clusterInstance.getPath() ); - Set members = parseStartCommandOutput( output ); - - try - { - clusterInstance = clusterInstance.withMembers( members ); - debug( "Cluster started: %s.", members ); - } - catch ( ClusterUnavailableException e ) - { - kill(); - throw e; - } - } - - static void start( ClusterMember member ) - { - assertClusterExists(); - ClusterControl.startClusterMember( member.getPath() ); - debug( "Cluster member at `%s` started.", member ); - } - - static void stop() - { - assertClusterExists(); - ClusterControl.stopCluster( clusterInstance.getPath() ); - debug( "Cluster at `%s` stopped.", clusterInstance.getPath() ); - } - - static void kill() - { - assertClusterExists(); - ClusterControl.killCluster( clusterInstance.getPath() ); - debug( "Cluster at `%s` killed.", clusterInstance.getPath() ); - } - - private static Set parseStartCommandOutput( String output ) - { - Set result = new HashSet<>(); - - String[] lines = output.split( lineSeparator() ); - for ( int i = 0; i < lines.length; i++ ) - { - String line = lines[i].trim(); - if( line.isEmpty() ) - { - // skip any empty lines - continue; - } - String[] clusterMemberSplit = line.split( " " ); - if ( clusterMemberSplit.length != 3 ) - { - throw new IllegalArgumentException( String.format( - "Wrong start command output found at line [%s]. " + - "Expected to have 'http_uri bolt_uri path' on each nonempty line. " + - "Command output:%n`%s`", i + 1, output ) ); - } - - URI boltUri = URI.create( clusterMemberSplit[1] ); - Path path = Paths.get( clusterMemberSplit[2] ); - - result.add( new ClusterMember( boltUri, path ) ); - } - - if ( result.isEmpty() ) - { - throw new IllegalStateException( "No cluster members" ); - } - - return result; - } - - private static void assertClusterExists() - { - if ( clusterInstance == null ) - { - throw new IllegalStateException( "Shared cluster does not exist" ); - } - } - - private static void assertClusterDoesNotExist() - { - if ( clusterInstance != null ) - { - throw new IllegalStateException( "Shared cluster already exists" ); - } - } -} diff --git a/examples/pom.xml b/examples/pom.xml index e7fdf24a9a..72a7d30f30 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -64,6 +64,16 @@ ch.qos.logback logback-classic + + org.testcontainers + junit-jupiter + test + + + org.testcontainers + neo4j + test + diff --git a/examples/src/test/java/org/neo4j/docs/driver/RoutingExamplesIT.java b/examples/src/test/java/org/neo4j/docs/driver/RoutingExamplesIT.java index 200acc99a3..e37d7bddaa 100644 --- a/examples/src/test/java/org/neo4j/docs/driver/RoutingExamplesIT.java +++ b/examples/src/test/java/org/neo4j/docs/driver/RoutingExamplesIT.java @@ -19,27 +19,37 @@ package org.neo4j.docs.driver; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.Neo4jContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; import java.net.URI; +import java.util.Optional; +import org.neo4j.driver.AuthTokens; import org.neo4j.driver.net.ServerAddress; -import org.neo4j.driver.util.cc.ClusterExtension; import static org.junit.jupiter.api.Assertions.assertTrue; +@Testcontainers( disabledWithoutDocker = true ) class RoutingExamplesIT { - @RegisterExtension - static final ClusterExtension neo4j = new ClusterExtension(); + private static final String NEO4J_VERSION = Optional.ofNullable( System.getenv( "NEO4J_VERSION" ) ) + .orElse( "4.4" ); + + @Container + private static final Neo4jContainer NEO4J_CONTAINER = new Neo4jContainer<>( String.format( "neo4j:%s-enterprise", NEO4J_VERSION ) ) + .withEnv( "NEO4J_ACCEPT_LICENSE_AGREEMENT", "yes" ) + .withAdminPassword( null ); @Test void testShouldRunConfigCustomResolverExample() throws Exception { // Given - URI uri = neo4j.getCluster().getRoutingUri(); - try ( ConfigCustomResolverExample example = new ConfigCustomResolverExample( "neo4j://x.example.com", neo4j.getDefaultAuthToken(), - ServerAddress.of( uri.getHost(), uri.getPort() ) ) ) + URI boltUri = URI.create( NEO4J_CONTAINER.getBoltUrl() ); + String neo4jUrl = String.format( "neo4j://%s:%d", boltUri.getHost(), boltUri.getPort() ); + try ( ConfigCustomResolverExample example = new ConfigCustomResolverExample( neo4jUrl, AuthTokens.none(), + ServerAddress.of( boltUri.getHost(), boltUri.getPort() ) ) ) { // Then assertTrue( example.canConnect() ); diff --git a/pom.xml b/pom.xml index 66266cad0e..a337ee581e 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,7 @@ 1.18.22 21.3.1 1.8.3 + 1.17.1 @@ -170,6 +171,13 @@ ${logback-classic.version} test + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + pom + import +