/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.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 org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.logging.ConsoleLogging;
import org.neo4j.driver.internal.retry.RetrySettings;
import org.neo4j.driver.internal.util.DriverFactoryWithClock;
import org.neo4j.driver.internal.util.DriverFactoryWithFixedRetryLogic;
import org.neo4j.driver.internal.util.SleeplessClock;
import org.neo4j.driver.v1.AccessMode;
import org.neo4j.driver.v1.AuthToken;
import org.neo4j.driver.v1.AuthTokens;
import org.neo4j.driver.v1.Config;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.Transaction;
import org.neo4j.driver.v1.TransactionWork;
import org.neo4j.driver.v1.exceptions.ServiceUnavailableException;
import org.neo4j.driver.v1.exceptions.SessionExpiredException;
import org.neo4j.driver.v1.util.Function;
import org.neo4j.driver.v1.util.StubServer;
import static java.util.Arrays.asList;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class RoutingDriverBoltKitTest
{
@Rule
public ExpectedException exception = ExpectedException.none();
private static final Config config = Config.build()
.withoutEncryption()
.withLogging( new ConsoleLogging( Level.INFO ) ).toConfig();
@Test
public void shouldHandleAcquireReadSession() throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a read server
StubServer readServer = StubServer.start( "read_server.script", 9005 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session( AccessMode.READ ) )
{
List<String> result = session.run( "MATCH (n) RETURN n.name" ).list( new Function<Record,String>()
{
@Override
public String apply( Record record )
{
return record.get( "n.name" ).asString();
}
} );
assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) );
}
// Finally
assertThat( server.exitStatus(), equalTo( 0 ) );
assertThat( readServer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldHandleAcquireReadSessionPlusTransaction()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a read server
StubServer readServer = StubServer.start( "read_server.script", 9005 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session( AccessMode.READ );
Transaction tx = session.beginTransaction() )
{
List<String> result = tx.run( "MATCH (n) RETURN n.name" ).list( new Function<Record,String>()
{
@Override
public String apply( Record record )
{
return record.get( "n.name" ).asString();
}
} );
assertThat( result, equalTo( asList( "Bob", "Alice", "Tina" ) ) );
}
// Finally
assertThat( server.exitStatus(), equalTo( 0 ) );
assertThat( readServer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldRoundRobinReadServers() throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START two read servers
StubServer readServer1 = StubServer.start( "read_server.script", 9005 );
StubServer readServer2 = StubServer.start( "read_server.script", 9006 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
try ( Driver driver = GraphDatabase.driver( uri, config ) )
{
// Run twice, one on each read server
for ( int i = 0; i < 2; i++ )
{
try ( Session session = driver.session( AccessMode.READ ) )
{
assertThat( session.run( "MATCH (n) RETURN n.name" ).list( new Function<Record,String>()
{
@Override
public String apply( Record record )
{
return 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
public void shouldRoundRobinReadServersWhenUsingTransaction()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START two read servers
StubServer readServer1 = StubServer.start( "read_server.script", 9005 );
StubServer readServer2 = StubServer.start( "read_server.script", 9006 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
try ( Driver driver = GraphDatabase.driver( uri, config ) )
{
// Run twice, one on each read server
for ( int i = 0; i < 2; i++ )
{
try ( Session session = driver.session( AccessMode.READ );
Transaction tx = session.beginTransaction() )
{
assertThat( tx.run( "MATCH (n) RETURN n.name" ).list( new Function<Record,String>()
{
@Override
public String apply( Record record )
{
return 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
public void shouldThrowSessionExpiredIfReadServerDisappears()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a read server
StubServer.start( "dead_read_server.script", 9005 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
//Expect
exception.expect( SessionExpiredException.class );
exception.expectMessage( "Server at 127.0.0.1:9005 is no longer available" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session( AccessMode.READ ) )
{
session.run( "MATCH (n) RETURN n.name" );
}
finally
{
assertThat( server.exitStatus(), equalTo( 0 ) );
}
}
@Test
public void shouldThrowSessionExpiredIfReadServerDisappearsWhenUsingTransaction()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a read server
StubServer.start( "dead_read_server.script", 9005 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
//Expect
exception.expect( SessionExpiredException.class );
exception.expectMessage( "Server at 127.0.0.1:9005 is no longer available" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session( AccessMode.READ );
Transaction tx = session.beginTransaction() )
{
tx.run( "MATCH (n) RETURN n.name" );
tx.success();
}
finally
{
assertThat( server.exitStatus(), equalTo( 0 ) );
}
}
@Test
public void shouldThrowSessionExpiredIfWriteServerDisappears()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a dead write servers
StubServer.start( "dead_read_server.script", 9007 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
//Expect
exception.expect( SessionExpiredException.class );
//exception.expectMessage( "Server at 127.0.0.1:9006 is no longer available" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session( AccessMode.WRITE ) )
{
session.run( "MATCH (n) RETURN n.name" ).consume();
}
finally
{
assertThat( server.exitStatus(), equalTo( 0 ) );
}
}
@Test
public void shouldThrowSessionExpiredIfWriteServerDisappearsWhenUsingTransaction()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a dead write servers
StubServer.start( "dead_read_server.script", 9007 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
//Expect
exception.expect( SessionExpiredException.class );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session( AccessMode.WRITE );
Transaction tx = session.beginTransaction() )
{
tx.run( "MATCH (n) RETURN n.name" ).consume();
tx.success();
}
finally
{
assertThat( server.exitStatus(), equalTo( 0 ) );
}
}
@Test
public void shouldHandleAcquireWriteSession() throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a write server
StubServer writeServer = StubServer.start( "write_server.script", 9007 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session( AccessMode.WRITE ) )
{
session.run( "CREATE (n {name:'Bob'})" );
}
// Finally
assertThat( server.exitStatus(), equalTo( 0 ) );
assertThat( writeServer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldHandleAcquireWriteSessionAndTransaction()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a write server
StubServer writeServer = StubServer.start( "write_server.script", 9007 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session( AccessMode.WRITE );
Transaction tx = session.beginTransaction() )
{
tx.run( "CREATE (n {name:'Bob'})" );
tx.success();
}
// Finally
assertThat( server.exitStatus(), equalTo( 0 ) );
assertThat( writeServer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldRoundRobinWriteSessions() throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a write server
StubServer writeServer1 = StubServer.start( "write_server.script", 9007 );
StubServer writeServer2 = StubServer.start( "write_server.script", 9008 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
try ( Driver driver = GraphDatabase.driver( uri, 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
public void shouldRoundRobinWriteSessionsInTransaction() throws Exception
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a write server
StubServer writeServer1 = StubServer.start( "write_server.script", 9007 );
StubServer writeServer2 = StubServer.start( "write_server.script", 9008 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
try ( Driver driver = GraphDatabase.driver( uri, config ) )
{
for ( int i = 0; i < 2; i++ )
{
try ( Session session = driver.session();
Transaction tx = session.beginTransaction() )
{
tx.run( "CREATE (n {name:'Bob'})" );
tx.success();
}
}
}
// Finally
assertThat( server.exitStatus(), equalTo( 0 ) );
assertThat( writeServer1.exitStatus(), equalTo( 0 ) );
assertThat( writeServer2.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldFailOnNonDiscoverableServer() throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer.start( "non_discovery_server.script", 9001 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
//Expect
exception.expect( ServiceUnavailableException.class );
// When
GraphDatabase.driver( uri, config );
}
@Test
public void shouldFailRandomFailureInGetServers() throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer.start( "failed_discovery.script", 9001 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
//Expect
exception.expect( ServiceUnavailableException.class );
// When
GraphDatabase.driver( uri, config );
}
@Test
public void shouldHandleLeaderSwitchWhenWriting()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a write server that doesn't accept writes
StubServer.start( "not_able_to_write_server.script", 9007 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
Driver driver = GraphDatabase.driver( uri, config );
boolean failed = false;
try ( Session session = driver.session( AccessMode.WRITE ) )
{
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
public void shouldHandleLeaderSwitchWhenWritingWithoutConsuming()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a write server that doesn't accept writes
StubServer.start( "not_able_to_write_server.script", 9007 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
Driver driver = GraphDatabase.driver( uri, config );
boolean failed = false;
try ( Session session = driver.session( AccessMode.WRITE ) )
{
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
public void shouldHandleLeaderSwitchWhenWritingInTransaction()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a write server that doesn't accept writes
StubServer.start( "not_able_to_write_server.script", 9007 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
Driver driver = GraphDatabase.driver( uri, config );
boolean failed = false;
try ( Session session = driver.session( AccessMode.WRITE );
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
public void shouldHandleLeaderSwitchWhenWritingInTransactionWithoutConsuming()
throws IOException, InterruptedException, StubServer.ForceKilled
{
// Given
StubServer server = StubServer.start( "acquire_endpoints.script", 9001 );
//START a write server that doesn't accept writes
StubServer.start( "not_able_to_write_server.script", 9007 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001" );
Driver driver = GraphDatabase.driver( uri, config );
boolean failed = false;
try ( Session session = driver.session( AccessMode.WRITE );
Transaction tx = session.beginTransaction() )
{
tx.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
public void shouldSendAndReceiveBookmark() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer writer = StubServer.start( "write_tx_with_bookmarks.script", 9007 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9001", config );
Session session = driver.session( "OldBookmark" ) )
{
try ( Transaction tx = session.beginTransaction() )
{
tx.run( "CREATE (n {name:'Bob'})" );
tx.success();
}
assertEquals( "NewBookmark", session.lastBookmark() );
}
assertThat( router.exitStatus(), equalTo( 0 ) );
assertThat( writer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldSendInitialBookmark() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer writer = StubServer.start( "write_tx_with_bookmarks.script", 9007 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9001", config );
Session session = driver.session( "OldBookmark" ) )
{
try ( Transaction tx = session.beginTransaction() )
{
tx.run( "CREATE (n {name:'Bob'})" );
tx.success();
}
assertEquals( "NewBookmark", session.lastBookmark() );
}
assertThat( router.exitStatus(), equalTo( 0 ) );
assertThat( writer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldUseWriteSessionModeAndInitialBookmark() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer writer = StubServer.start( "write_tx_with_bookmarks.script", 9008 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9001", config );
Session session = driver.session( AccessMode.WRITE, "OldBookmark" ) )
{
try ( Transaction tx = session.beginTransaction() )
{
tx.run( "CREATE (n {name:'Bob'})" );
tx.success();
}
assertEquals( "NewBookmark", session.lastBookmark() );
}
assertThat( router.exitStatus(), equalTo( 0 ) );
assertThat( writer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldUseReadSessionModeAndInitialBookmark() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer writer = StubServer.start( "read_tx_with_bookmarks.script", 9005 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9001", config );
Session session = driver.session( AccessMode.READ, "OldBookmark" ) )
{
try ( Transaction tx = session.beginTransaction() )
{
List<Record> 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.success();
}
assertEquals( "NewBookmark", session.lastBookmark() );
}
assertThat( router.exitStatus(), equalTo( 0 ) );
assertThat( writer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldPassBookmarkFromTransactionToTransaction() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer writer = StubServer.start( "write_read_tx_with_bookmarks.script", 9007 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9001", config );
Session session = driver.session( "BookmarkA" ) )
{
try ( Transaction tx = session.beginTransaction() )
{
tx.run( "CREATE (n {name:'Bob'})" );
tx.success();
}
assertEquals( "BookmarkB", session.lastBookmark() );
try ( Transaction tx = session.beginTransaction() )
{
List<Record> records = tx.run( "MATCH (n) RETURN n.name AS name" ).list();
assertEquals( 1, records.size() );
assertEquals( "Bob", records.get( 0 ).get( "name" ).asString() );
tx.success();
}
assertEquals( "BookmarkC", session.lastBookmark() );
}
assertThat( router.exitStatus(), equalTo( 0 ) );
assertThat( writer.exitStatus(), equalTo( 0 ) );
}
@Test
public void shouldRetryReadTransactionUntilSuccess() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer brokenReader = StubServer.start( "dead_read_server.script", 9005 );
StubServer reader = StubServer.start( "read_server.script", 9006 );
try ( Driver driver = newDriverWithSleeplessClock( "bolt+routing://127.0.0.1:9001" );
Session session = driver.session() )
{
AtomicInteger invocations = new AtomicInteger();
List<Record> 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
public void shouldRetryWriteTransactionUntilSuccess() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer brokenWriter = StubServer.start( "dead_write_server.script", 9007 );
StubServer writer = StubServer.start( "write_server.script", 9008 );
try ( Driver driver = newDriverWithSleeplessClock( "bolt+routing://127.0.0.1:9001" );
Session session = driver.session() )
{
AtomicInteger invocations = new AtomicInteger();
List<Record> 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
public void shouldRetryReadTransactionUntilFailure() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer brokenReader1 = StubServer.start( "dead_read_server.script", 9005 );
StubServer brokenReader2 = StubServer.start( "dead_read_server.script", 9006 );
try ( Driver driver = newDriverWithFixedRetries( "bolt+routing://127.0.0.1:9001", 1 );
Session session = driver.session() )
{
AtomicInteger invocations = new AtomicInteger();
try
{
session.readTransaction( queryWork( "MATCH (n) RETURN n.name", invocations ) );
fail( "Exception expected" );
}
catch ( Exception e )
{
assertThat( e, instanceOf( SessionExpiredException.class ) );
}
assertEquals( 2, invocations.get() );
}
finally
{
assertEquals( 0, router.exitStatus() );
assertEquals( 0, brokenReader1.exitStatus() );
assertEquals( 0, brokenReader2.exitStatus() );
}
}
@Test
public void shouldRetryWriteTransactionUntilFailure() throws Exception
{
StubServer router = StubServer.start( "acquire_endpoints.script", 9001 );
StubServer brokenWriter1 = StubServer.start( "dead_write_server.script", 9007 );
StubServer brokenWriter2 = StubServer.start( "dead_write_server.script", 9008 );
try ( Driver driver = newDriverWithFixedRetries( "bolt+routing://127.0.0.1:9001", 1 );
Session session = driver.session() )
{
AtomicInteger invocations = new AtomicInteger();
try
{
session.writeTransaction( queryWork( "CREATE (n {name:'Bob'})", invocations ) );
fail( "Exception expected" );
}
catch ( Exception e )
{
assertThat( e, instanceOf( SessionExpiredException.class ) );
}
assertEquals( 2, invocations.get() );
}
finally
{
assertEquals( 0, router.exitStatus() );
assertEquals( 0, brokenWriter1.exitStatus() );
assertEquals( 0, brokenWriter2.exitStatus() );
}
}
@Test
public void shouldRetryReadTransactionAndPerformRediscoveryUntilSuccess() throws Exception
{
StubServer router1 = StubServer.start( "acquire_endpoints.script", 9010 );
StubServer brokenReader1 = StubServer.start( "dead_read_server.script", 9005 );
StubServer brokenReader2 = StubServer.start( "dead_read_server.script", 9006 );
StubServer router2 = StubServer.start( "discover_servers.script", 9003 );
StubServer reader = StubServer.start( "read_server.script", 9004 );
try ( Driver driver = newDriverWithSleeplessClock( "bolt+routing://127.0.0.1:9010" );
Session session = driver.session() )
{
AtomicInteger invocations = new AtomicInteger();
List<Record> 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
public void shouldRetryWriteTransactionAndPerformRediscoveryUntilSuccess() throws Exception
{
StubServer router1 = StubServer.start( "acquire_endpoints.script", 9010 );
StubServer brokenWriter1 = StubServer.start( "dead_write_server.script", 9007 );
StubServer brokenWriter2 = StubServer.start( "dead_write_server.script", 9008 );
StubServer router2 = StubServer.start( "discover_servers.script", 9002 );
StubServer writer = StubServer.start( "write_server.script", 9001 );
try ( Driver driver = newDriverWithSleeplessClock( "bolt+routing://127.0.0.1:9010" );
Session session = driver.session() )
{
AtomicInteger invocations = new AtomicInteger();
List<Record> 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, brokenWriter2.exitStatus() );
assertEquals( 0, router2.exitStatus() );
assertEquals( 0, writer.exitStatus() );
}
}
@Test
public void shouldUseInitialRouterForRediscoveryWhenAllOtherRoutersAreDead() throws Exception
{
// initial router does not have itself in the returned set of routers
StubServer router = StubServer.start( "acquire_endpoints.script", 9010 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9010", config ) )
{
try ( Session session = driver.session( AccessMode.READ ) )
{
// restart router on the same port with different script that contains itself as reader
assertEquals( 0, router.exitStatus() );
router = StubServer.start( "rediscover_using_initial_router.script", 9010 );
List<String> names = readStrings( "MATCH (n) RETURN n.name AS name", session );
assertEquals( asList( "Bob", "Alice" ), names );
}
}
assertEquals( 0, router.exitStatus() );
}
@Test
public void shouldInvokeProcedureGetRoutingTableWhenServerVersionPermits() throws Exception
{
// stub server is both a router and reader
StubServer server = StubServer.start( "get_routing_table.script", 9001 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9001", config );
Session session = driver.session() )
{
List<Record> 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
public void shouldSendRoutingContextToServer() throws Exception
{
// stub server is both a router and reader
StubServer server = StubServer.start( "get_routing_table_with_context.script", 9001 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001/?policy=my_policy®ion=china" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session() )
{
List<Record> 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
public void shouldIgnoreRoutingContextWhenServerDoesNotSupportIt() throws Exception
{
// stub server is both a router and reader
StubServer server = StubServer.start( "rediscover_and_read_with_init.script", 9001 );
URI uri = URI.create( "bolt+routing://127.0.0.1:9001/?policy=my_policy" );
try ( Driver driver = GraphDatabase.driver( uri, config );
Session session = driver.session() )
{
List<Record> records = session.run( "MATCH (n) RETURN n.name" ).list();
assertEquals( 2, records.size() );
assertEquals( "Bob", records.get( 0 ).get( 0 ).asString() );
assertEquals( "Tina", records.get( 1 ).get( 0 ).asString() );
}
finally
{
assertEquals( 0, server.exitStatus() );
}
}
@Test
public void shouldServeReadsButFailWritesWhenNoWritersAvailable() throws Exception
{
StubServer router1 = StubServer.start( "discover_no_writers.script", 9010 );
StubServer router2 = StubServer.start( "discover_no_writers.script", 9004 );
StubServer reader = StubServer.start( "read_server.script", 9003 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9010", config );
Session session = driver.session() )
{
assertEquals( asList( "Bob", "Alice", "Tina" ), readStrings( "MATCH (n) RETURN n.name", session ) );
try
{
session.run( "CREATE (n {name:'Bob'})" ).consume();
fail( "Exception expected" );
}
catch ( Exception e )
{
assertThat( e, instanceOf( SessionExpiredException.class ) );
}
}
finally
{
assertEquals( 0, router1.exitStatus() );
assertEquals( 0, router2.exitStatus() );
assertEquals( 0, reader.exitStatus() );
}
}
@Test
public 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 = StubServer.start( "discover_no_writers.script", 9010 );
StubServer router2 = null;
StubServer reader = StubServer.start( "read_server.script", 9003 );
StubServer writer = StubServer.start( "write_server.script", 9007 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9010", config );
Session session = driver.session() )
{
// start another router which knows about writes, use same address as the initial router
router2 = StubServer.start( "acquire_endpoints.script", 9010 );
assertEquals( asList( "Bob", "Alice", "Tina" ), readStrings( "MATCH (n) RETURN n.name", session ) );
StatementResult 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
public void shouldTreatRoutingTableWithSingleRouterAsValid() throws Exception
{
StubServer router = StubServer.start( "discover_one_router.script", 9010 );
StubServer reader1 = StubServer.start( "read_server.script", 9003 );
StubServer reader2 = StubServer.start( "read_server.script", 9004 );
try ( Driver driver = GraphDatabase.driver( "bolt+routing://127.0.0.1:9010", config );
Session session = driver.session( AccessMode.READ ) )
{
// returned routing table contains only one router, this should be fine and we should be able to
// read multiple times without additional rediscovery
StatementResult readResult1 = session.run( "MATCH (n) RETURN n.name" );
assertEquals( "127.0.0.1:9003", readResult1.summary().server().address() );
assertEquals( 3, readResult1.list().size() );
StatementResult readResult2 = session.run( "MATCH (n) RETURN n.name" );
assertEquals( "127.0.0.1:9004", readResult2.summary().server().address() );
assertEquals( 3, readResult2.list().size() );
}
finally
{
assertEquals( 0, router.exitStatus() );
assertEquals( 0, reader1.exitStatus() );
assertEquals( 0, reader2.exitStatus() );
}
}
private static Driver newDriverWithSleeplessClock( String uriString )
{
DriverFactory driverFactory = new DriverFactoryWithClock( new SleeplessClock() );
return newDriver( uriString, driverFactory );
}
private static Driver newDriverWithFixedRetries( String uriString, int retries )
{
DriverFactory driverFactory = new DriverFactoryWithFixedRetryLogic( retries );
return newDriver( uriString, driverFactory );
}
private static Driver newDriver( String uriString, DriverFactory driverFactory )
{
URI uri = URI.create( uriString );
RoutingSettings routingConf = new RoutingSettings( 1, 1, null );
AuthToken auth = AuthTokens.none();
return driverFactory.newInstance( uri, auth, routingConf, RetrySettings.DEFAULT, config );
}
private static TransactionWork<List<Record>> queryWork( final String query, final AtomicInteger invocations )
{
return new TransactionWork<List<Record>>()
{
@Override
public List<Record> execute( Transaction tx )
{
invocations.incrementAndGet();
return tx.run( query ).list();
}
};
}
private static List<String> readStrings( final String query, Session session )
{
return session.readTransaction( new TransactionWork<List<String>>()
{
@Override
public List<String> execute( Transaction tx )
{
List<Record> records = tx.run( query ).list();
List<String> names = new ArrayList<>( records.size() );
for ( Record record : records )
{
names.add( record.get( 0 ).asString() );
}
return names;
}
} );
}
}