/**
* 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.kernel.ha;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.neo4j.kernel.ha.SlavePriorities.givenOrder;
import static org.neo4j.kernel.ha.SlavePriorities.roundRobin;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.junit.Test;
import org.neo4j.com.ComException;
import org.neo4j.com.ResourceReleaser;
import org.neo4j.com.Response;
import org.neo4j.com.TransactionStream;
import org.neo4j.helpers.Exceptions;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.helpers.collection.Visitor;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.impl.nioneo.store.StoreId;
import org.neo4j.kernel.impl.transaction.xaframework.LogExtractor;
import org.neo4j.kernel.impl.transaction.xaframework.XaConnection;
import org.neo4j.kernel.impl.transaction.xaframework.XaDataSource;
import org.neo4j.kernel.impl.util.StringLogger;
import org.neo4j.test.TargetDirectory;
public class TestMasterCommittingAtSlave
{
private Iterable<Slave> slaves;
private XaDataSource dataSource;
private FakeStringLogger log;
@Test
public void commitSuccessfullyToTheFirstOne() throws Exception
{
MasterTxIdGenerator generator = newGenerator( 3, 1, givenOrder() );
generator.committed( dataSource, 0, 2, null );
assertCalls( (FakeSlave) slaves.iterator().next(), 2l );
assertNoFailureLogs();
}
@Test
public void commitACoupleOfTransactionsSuccessfully() throws Exception
{
MasterTxIdGenerator generator = newGenerator( 3, 1, givenOrder() );
generator.committed( dataSource, 0, 2, null );
generator.committed( dataSource, 0, 3, null );
generator.committed( dataSource, 0, 4, null );
assertCalls( (FakeSlave) slaves.iterator().next(), 2, 3, 4 );
assertNoFailureLogs();
}
@Test
public void commitFailureAtFirstOneShouldMoveOnToNext() throws Exception
{
MasterTxIdGenerator generator = newGenerator( 3, 1, givenOrder(), true );
generator.committed( dataSource, 0, 2, null );
Iterator<Slave> slaveIt = slaves.iterator();
assertCalls( (FakeSlave) slaveIt.next() );
assertCalls( (FakeSlave) slaveIt.next(), 2 );
assertNoFailureLogs();
}
@Test
public void commitSuccessfullyAtThreeSlaves() throws Exception
{
MasterTxIdGenerator generator = newGenerator( 5, 3, givenOrder() );
generator.committed( dataSource, 0, 2, null );
generator.committed( dataSource, 0, 3, 1 );
generator.committed( dataSource, 0, 4, 3 );
Iterator<Slave> slaveIt = slaves.iterator();
assertCalls( (FakeSlave) slaveIt.next(), 2, 3, 4 );
assertCalls( (FakeSlave) slaveIt.next(), 2, 4 );
assertCalls( (FakeSlave) slaveIt.next(), 2, 3 );
assertCalls( (FakeSlave) slaveIt.next() );
assertCalls( (FakeSlave) slaveIt.next() );
assertNoFailureLogs();
}
@Test
public void commitSuccessfullyOnSomeOfThreeSlaves() throws Exception
{
MasterTxIdGenerator generator = newGenerator( 5, 3, givenOrder(), false, true, true );
generator.committed( dataSource, 0, 2, null );
Iterator<Slave> slaveIt = slaves.iterator();
assertCalls( (FakeSlave) slaveIt.next(), 2 );
slaveIt.next();
slaveIt.next();
assertCalls( (FakeSlave) slaveIt.next(), 2 );
assertCalls( (FakeSlave) slaveIt.next(), 2 );
assertNoFailureLogs();
}
@Test
public void roundRobinSingleSlave() throws Exception
{
MasterTxIdGenerator generator = newGenerator( 3, 1, roundRobin() );
for ( long tx = 2; tx <= 6; tx++ )
{
generator.committed( dataSource, 0, tx, null );
}
Iterator<Slave> slaveIt = slaves.iterator();
assertCalls( (FakeSlave) slaveIt.next(), 2, 5 );
assertCalls( (FakeSlave) slaveIt.next(), 3, 6 );
assertCalls( (FakeSlave) slaveIt.next(), 4 );
assertNoFailureLogs();
}
@Test
public void roundRobinSomeFailing() throws Exception
{
MasterTxIdGenerator generator = newGenerator( 4, 2, roundRobin(), false, true );
for ( long tx = 2; tx <= 6; tx++ )
{
generator.committed( dataSource, 0, tx, null );
}
/* SLAVE | TX
* 0 | 2 5 6
* F 1 |
* 2 | 2 3 4 6
* 3 | 3 4 5
*/
Iterator<Slave> slaveIt = slaves.iterator();
assertCalls( (FakeSlave) slaveIt.next(), 2, 5, 6 );
slaveIt.next();
assertCalls( (FakeSlave) slaveIt.next(), 2, 3, 4, 6 );
assertCalls( (FakeSlave) slaveIt.next(), 3, 4, 5 );
assertNoFailureLogs();
}
@Test
public void notEnoughSlavesSuccessful() throws Exception
{
MasterTxIdGenerator generator = newGenerator( 3, 2, givenOrder(), true, true );
generator.committed( dataSource, 0, 2, null );
Iterator<Slave> slaveIt = slaves.iterator();
slaveIt.next();
slaveIt.next();
assertCalls( (FakeSlave) slaveIt.next(), 2 );
assertFailureLogs();
}
@Test
public void testFixedPriorityStrategy()
{
int[] serverIds = new int[]{55, 101, 66};
SlavePriority fixed = SlavePriorities.fixed();
ArrayList<Slave> slaves = new ArrayList<Slave>( 3 );
slaves.add( new FakeSlave( false, serverIds[0] ) );
slaves.add( new FakeSlave( false, serverIds[1] ) );
slaves.add( new FakeSlave( false, serverIds[2] ) );
Iterator<Slave> sortedSlaves = fixed.prioritize( slaves ).iterator();
assertEquals( serverIds[1], sortedSlaves.next().getServerId() );
assertEquals( serverIds[2], sortedSlaves.next().getServerId() );
assertEquals( serverIds[0], sortedSlaves.next().getServerId() );
assertTrue( !sortedSlaves.hasNext() );
}
private void assertNoFailureLogs()
{
assertFalse( "Errors:" + log.errors.toString(), log.anyMessageLogged );
}
private void assertFailureLogs()
{
assertTrue( log.anyMessageLogged );
}
private void assertCalls( FakeSlave slave, long... txs )
{
for ( long tx : txs )
{
Long slaveTx = slave.popCalledTx();
assertNotNull( slaveTx );
assertEquals( (Long) tx, slaveTx );
}
assertFalse( slave.moreTxs() );
}
private MasterTxIdGenerator newGenerator( int slaveCount, int replication, SlavePriority slavePriority,
boolean... failingSlaves ) throws Exception
{
slaves = instantiateSlaves( slaveCount, failingSlaves );
dataSource = new FakeDataSource();
log = new FakeStringLogger();
Config config = new Config( MapUtil.stringMap(
HaSettings.tx_push_factor.name(), "" + replication ) );
MasterTxIdGenerator result = new MasterTxIdGenerator( MasterTxIdGenerator.from( config, slavePriority ),
log, new Slaves()
{
@Override
public Iterable<Slave> getSlaves()
{
return slaves;
}
} );
// Life
try
{
result.init();
result.start();
}
catch ( Throwable e )
{
throw Exceptions.launderedException( e );
}
return result;
}
private Iterable<Slave> instantiateSlaves( int count, boolean[] failingSlaves )
{
List<Slave> slaves = new ArrayList<Slave>();
for ( int i = 0; i < count; i++ )
{
slaves.add( new FakeSlave( i < failingSlaves.length ? failingSlaves[i] : false, i ) );
}
return slaves;
}
private static class FakeDataSource extends XaDataSource
{
private static final byte[] BRANCH = new byte[]{0, 1, 2};
private static final String NAME = "fake";
private final String dir;
FakeDataSource()
{
super( BRANCH, NAME );
this.dir = TargetDirectory.forTest( getClass() ).graphDbDir( true ).getAbsolutePath();
}
@Override
public XaConnection getXaConnection()
{
throw new UnsupportedOperationException();
}
@Override
public LogExtractor getLogExtractor( long startTxId, long endTxIdHint ) throws IOException
{
return LogExtractor.from( dir, startTxId );
}
@Override
public void init() throws Throwable
{
}
@Override
public void start() throws Throwable
{
}
@Override
public void stop() throws Throwable
{
}
@Override
public void shutdown() throws Throwable
{
}
}
private static class FakeSlave implements Slave
{
private volatile Queue<Long> calledWithTxId = new LinkedList<Long>();
private final boolean failing;
private final int serverId;
FakeSlave( boolean failing, int serverId )
{
this.failing = failing;
this.serverId = serverId;
}
@Override
public Response<Void> pullUpdates( String resource, long txId )
{
if ( failing )
{
throw new ComException( "Told to fail" );
}
calledWithTxId.add( txId );
return new Response<Void>( null, new StoreId(), TransactionStream.EMPTY, ResourceReleaser.NO_OP );
}
Long popCalledTx()
{
return calledWithTxId.poll();
}
boolean moreTxs()
{
return !calledWithTxId.isEmpty();
}
@Override
public int getServerId()
{
return serverId;
}
@Override
public String toString()
{
return "FakeSlave[" + serverId + "]";
}
}
private static class FakeStringLogger extends StringLogger
{
private volatile boolean anyMessageLogged;
private StringBuilder errors = new StringBuilder();
@Override
public void logLongMessage( String msg, Visitor<LineLogger> source, boolean flush )
{
addError( msg );
}
private void addError( String msg )
{
anyMessageLogged = true;
errors.append( errors.length() > 0 ? "," : "" ).append( msg );
}
@Override
public void logMessage( String msg, boolean flush )
{
addError( msg );
}
@Override
public void logMessage( String msg, Throwable cause, boolean flush )
{
addError( msg );
}
@Override
public void addRotationListener( Runnable listener )
{
}
@Override
public void flush()
{
}
@Override
public void close()
{
}
@Override
protected void logLine( String line )
{
addError( line );
}
}
}