/**
* Copyright (c) 2002-2012 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.com;
import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.yield;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.neo4j.com.MadeUpServer.FRAME_LENGTH;
import static org.neo4j.com.TxChecksumVerifier.ALWAYS_MATCH;
import static org.neo4j.kernel.impl.nioneo.store.CommonAbstractStore.ALL_STORES_VERSION;
import static org.neo4j.kernel.impl.nioneo.store.NeoStore.versionStringToLong;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.neo4j.kernel.impl.nioneo.store.StoreId;
import org.neo4j.kernel.lifecycle.LifeSupport;
public class TestCommunication
{
private static final byte INTERNAL_PROTOCOL_VERSION = 0;
private static final byte APPLICATION_PROTOCOL_VERSION = 0;
private static final int PORT = 1234;
private StoreId storeIdToUse;
private LifeSupport life = new LifeSupport();
private Builder builder;
@Before
public void doBefore()
{
storeIdToUse = new StoreId();
builder = new Builder();
}
@After
public void shutdownLife()
{
life.shutdown();
}
@Test
public void clientGetResponseFromServerViaComLayer() throws Throwable
{
MadeUpServerImplementation serverImplementation = new MadeUpServerImplementation( storeIdToUse );
MadeUpServer server = builder.server( serverImplementation );
MadeUpClient client = builder.client();
life.add( server );
life.add( client );
life.start();
int value1 = 10;
int value2 = 5;
Response<Integer> response = client.multiply( 10, 5 );
waitUntilResponseHasBeenWritten( server, 1000 );
assertEquals( (Integer) (value1 * value2), response.response() );
assertTrue( serverImplementation.gotCalled() );
assertTrue( server.responseHasBeenWritten() );
}
private void waitUntilResponseHasBeenWritten( MadeUpServer server, int maxTime ) throws Exception
{
long time = currentTimeMillis();
while ( !server.responseHasBeenWritten() && currentTimeMillis()-time < maxTime )
{
Thread.sleep( 50 );
}
}
@Test
public void makeSureClientStoreIdsMustMatch() throws Throwable
{
MadeUpServer server = builder.server();
MadeUpClient client = builder.storeId( new StoreId( 10, 10, versionStringToLong( ALL_STORES_VERSION ) ) ).client();
life.add( server );
life.add( client );
life.start();
try
{
client.multiply( 1, 2 );
fail();
}
catch ( ComException e )
{
// Good
}
}
@Test
public void makeSureServerStoreIdsMustMatch() throws Throwable
{
MadeUpServer server = builder.storeId( new StoreId( 10, 10, versionStringToLong( ALL_STORES_VERSION ) ) ).server();
MadeUpClient client = builder.client();
life.add( server );
life.add( client );
life.start();
try
{
client.multiply( 1, 2 );
fail();
}
catch ( ComException e )
{
// Good
}
}
@Test
public void makeSureClientCanStreamBigData() throws Throwable
{
MadeUpServer server = builder.server();
MadeUpClient client = builder.client();
life.add( server );
life.add( client );
life.start();
client.fetchDataStream( new ToAssertionWriter(), FRAME_LENGTH*3 );
}
@Test
public void clientThrowsServerSideErrorMidwayThroughStreaming() throws Throwable
{
final String failureMessage = "Just failing";
MadeUpServerImplementation serverImplementation = new MadeUpServerImplementation( storeIdToUse )
{
@Override
public Response<Void> fetchDataStream( MadeUpWriter writer, int dataSize )
{
writer.write( new FailingByteChannel( dataSize, failureMessage ) );
return new Response<Void>( null, storeIdToUse,
TransactionStream.EMPTY, ResourceReleaser.NO_OP );
}
};
MadeUpServer server = builder.server( serverImplementation );
MadeUpClient client = builder.client();
life.add( server );
life.add( client );
life.start();
try
{
client.fetchDataStream( new ToAssertionWriter(), FRAME_LENGTH*2 );
fail( "Should have thrown " + MadeUpException.class.getSimpleName() );
}
catch ( MadeUpException e )
{
assertEquals( failureMessage, e.getMessage() );
}
}
@Test
public void communicateBetweenJvms() throws Throwable
{
ServerInterface server = builder.serverInOtherJvm();
server.awaitStarted();
MadeUpClient client = builder.port( MadeUpServerProcess.PORT ).client();
life.add( client );
life.start();
assertEquals( (Integer)(9*5), client.multiply( 9, 5 ).response() );
client.fetchDataStream( new ToAssertionWriter(), 1024*1024*3 );
server.shutdown();
}
@Test
public void throwingServerSideExceptionBackToClient() throws Throwable
{
MadeUpServer server = builder.server();
MadeUpClient client = builder.client();
life.add( server );
life.add( client );
life.start();
String exceptionMessage = "The message";
try
{
client.throwException( exceptionMessage );
fail( "Should have thrown " + MadeUpException.class.getSimpleName() );
}
catch ( MadeUpException e )
{ // Good
assertEquals( exceptionMessage, e.getMessage() );
}
}
@Test
public void applicationProtocolVersionsMustMatch() throws Throwable
{
MadeUpServer server = builder.applicationProtocolVersion( (byte) (APPLICATION_PROTOCOL_VERSION+1) ).server();
MadeUpClient client = builder.client();
life.add( server );
life.add( client );
life.start();
try
{
client.multiply( 10, 20 );
fail( "Shouldn't be able to communicate with different application protocol versions" );
}
catch ( IllegalProtocolVersionException e ) { /* Good */ }
}
@Test
public void applicationProtocolVersionsMustMatchMultiJvm() throws Throwable
{
ServerInterface server = builder.applicationProtocolVersion( (byte)(APPLICATION_PROTOCOL_VERSION+1) ).serverInOtherJvm();
server.awaitStarted();
MadeUpClient client = builder.port( MadeUpServerProcess.PORT ).client();
life.add( client );
life.start();
try
{
client.multiply( 10, 20 );
fail( "Shouldn't be able to communicate with different application protocol versions" );
}
catch ( IllegalProtocolVersionException e ) { /* Good */ }
server.shutdown();
}
@Test
public void internalProtocolVersionsMustMatch() throws Throwable
{
MadeUpServer server = builder.internalProtocolVersion( (byte) 1 ).server();
MadeUpClient client = builder.internalProtocolVersion( (byte) 2 ).client();
life.add( server );
life.add( client );
life.start();
try
{
client.multiply( 10, 20 );
fail( "Shouldn't be able to communicate with different application protocol versions" );
}
catch ( IllegalProtocolVersionException e ) { /* Good */ }
}
@Test
public void internalProtocolVersionsMustMatchMultiJvm() throws Throwable
{
ServerInterface server = builder.internalProtocolVersion( (byte) 1 ).serverInOtherJvm();
server.awaitStarted();
MadeUpClient client = builder.port( MadeUpServerProcess.PORT ).internalProtocolVersion( (byte) 2 ).client();
life.add( client );
life.start();
try
{
client.multiply( 10, 20 );
fail( "Shouldn't be able to communicate with different application protocol versions" );
}
catch ( IllegalProtocolVersionException e ) { /* Good */ }
server.shutdown();
}
@Test
@Ignore("getting build back to green")
public void serverStopsStreamingToDeadClient() throws Throwable
{
MadeUpServer server = builder.server();
MadeUpClient client = builder.client();
life.add( server );
life.add( client );
life.start();
int failAtSize = FRAME_LENGTH*2;
ClientCrashingWriter writer = new ClientCrashingWriter( client, failAtSize );
try
{
client.fetchDataStream( writer, FRAME_LENGTH*10 );
fail( "Should fail in the middle" );
}
catch ( ComException e )
{ // Expected
}
assertTrue( writer.getSizeRead() >= failAtSize );
long maxWaitUntil = System.currentTimeMillis()+2*1000;
while ( !server.responseFailureEncountered() && System.currentTimeMillis() < maxWaitUntil ) yield();
assertTrue( "Failure writing the response should have been encountered", server.responseFailureEncountered() );
assertFalse( "Response shouldn't have been successful", server.responseHasBeenWritten() );
}
@Test
public void serverContextVerificationCanThrowException() throws Throwable
{
final String failureMessage = "I'm failing";
TxChecksumVerifier failingVerifier = new TxChecksumVerifier()
{
@Override
public void assertMatch( long txId, int masterId, long checksum )
{
throw new FailingException( failureMessage );
}
};
MadeUpServer server = builder.verifier( failingVerifier ).server();
MadeUpClient client = builder.client();
life.add( server );
life.add( client );
life.start();
try
{
client.multiply( 10, 5 );
fail( "Should have failed" );
}
catch ( Exception e )
{ // Good
// TODO catch FailingException instead of Exception and make Server throw the proper
// one instead of getting a "channel closed".
}
}
@Test
public void clientCanReadChunkSizeBiggerThanItsOwn() throws Throwable
{ // Given that frameLength is the same for both client and server.
int serverChunkSize = 20000;
int clientChunkSize = serverChunkSize/10;
MadeUpServer server = builder.chunkSize( serverChunkSize ).server();
MadeUpClient client = builder.chunkSize( clientChunkSize ).client();
life.add( server );
life.add( client );
life.start();
// Tell server to stream data occupying roughly two chunks. The chunks
// from server are 10 times bigger than the clients chunk size.
client.fetchDataStream( new ToAssertionWriter(), serverChunkSize*2 );
}
@Test
public void serverCanReadChunkSizeBiggerThanItsOwn() throws Throwable
{ // Given that frameLength is the same for both client and server.
int serverChunkSize = 1000;
int clientChunkSize = serverChunkSize*10;
MadeUpServer server = builder.chunkSize( serverChunkSize ).server();
MadeUpClient client = builder.chunkSize( clientChunkSize ).client();
life.add( server );
life.add( client );
life.start();
// Tell server to stream data occupying roughly two chunks. The chunks
// from server are 10 times bigger than the clients chunk size.
client.sendDataStream( new DataProducer( clientChunkSize*2 ) );
}
@Test
public void impossibleToHaveBiggerChunkSizeThanFrameSize() throws Throwable
{
Builder myBuilder = builder.chunkSize( MadeUpServer.FRAME_LENGTH+10 );
try
{
myBuilder.server().start();
fail( "Shouldn't be possible" );
}
catch ( IllegalArgumentException e )
{ // Good
}
try
{
myBuilder.client();
fail( "Shouldn't be possible" );
}
catch ( IllegalArgumentException e )
{ // Good
}
}
class Builder
{
private final int port;
private final int chunkSize;
private final byte internalProtocolVersion;
private final byte applicationProtocolVersion;
private final TxChecksumVerifier verifier;
private final StoreId storeId;
public Builder()
{
this( PORT, FRAME_LENGTH, INTERNAL_PROTOCOL_VERSION, APPLICATION_PROTOCOL_VERSION,
ALWAYS_MATCH, storeIdToUse );
}
public Builder( int port, int chunkSize, byte internalProtocolVersion, byte applicationProtocolVersion,
TxChecksumVerifier verifier, StoreId storeId )
{
this.port = port;
this.chunkSize = chunkSize;
this.internalProtocolVersion = internalProtocolVersion;
this.applicationProtocolVersion = applicationProtocolVersion;
this.verifier = verifier;
this.storeId = storeId;
}
public Builder port( int port )
{
return new Builder( port, chunkSize, internalProtocolVersion, applicationProtocolVersion, verifier, storeId );
}
public Builder chunkSize( int chunkSize )
{
return new Builder( port, chunkSize, internalProtocolVersion, applicationProtocolVersion, verifier, storeId );
}
public Builder internalProtocolVersion( byte internalProtocolVersion )
{
return new Builder( port, chunkSize, internalProtocolVersion, applicationProtocolVersion, verifier, storeId );
}
public Builder applicationProtocolVersion( byte applicationProtocolVersion )
{
return new Builder( port, chunkSize, internalProtocolVersion, applicationProtocolVersion, verifier, storeId );
}
public Builder verifier( TxChecksumVerifier verifier )
{
return new Builder( port, chunkSize, internalProtocolVersion, applicationProtocolVersion, verifier, storeId );
}
public Builder storeId( StoreId storeId )
{
return new Builder( port, chunkSize, internalProtocolVersion, applicationProtocolVersion, verifier, storeId );
}
public MadeUpServer server()
{
return new MadeUpServer( new MadeUpServerImplementation( storeId ), port,
internalProtocolVersion, applicationProtocolVersion, verifier, chunkSize );
}
public MadeUpServer server( MadeUpCommunicationInterface target )
{
return new MadeUpServer( target, port, internalProtocolVersion, applicationProtocolVersion, verifier, chunkSize );
}
public MadeUpClient client()
{
return new MadeUpClient( port, storeId, internalProtocolVersion, applicationProtocolVersion, chunkSize );
}
public ServerInterface serverInOtherJvm()
{
ServerInterface server = new MadeUpServerProcess().start( new StartupData(
storeId.getCreationTime(), storeId.getRandomId(),
storeId.getStoreVersion(), internalProtocolVersion,
applicationProtocolVersion, chunkSize ) );
server.awaitStarted();
return server;
}
}
}