/** * 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 java.lang.System.currentTimeMillis; import static java.util.concurrent.TimeUnit.SECONDS; import static junit.framework.Assert.assertEquals; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.factory.HighlyAvailableGraphDatabaseFactory; import org.neo4j.ha.StartInstanceInAnotherJvm; import org.neo4j.helpers.Predicate; import org.neo4j.helpers.collection.IteratorUtil; import org.neo4j.helpers.collection.MapUtil; import org.neo4j.kernel.GraphDatabaseAPI; import org.neo4j.test.TargetDirectory; // TODO This needs to be fixed @Ignore public class TestTxPushStrategyConfig { private GraphDatabaseAPI master; private List<GraphDatabaseAPI> slaves = new ArrayList<GraphDatabaseAPI>(); private TargetDirectory dir; // private Slaves slavesList; @Before public void before() throws Exception { dir = TargetDirectory.forTest( getClass() ); } @After public void after() throws Exception { for ( GraphDatabaseService slave : slaves ) { slave.shutdown(); } if ( master != null ) master.shutdown(); } GraphDatabaseService startMaster( int pushFactor, String pushStrategy, int basePort ) { master = (GraphDatabaseAPI) new HighlyAvailableGraphDatabaseFactory().newHighlyAvailableDatabaseBuilder( dir .directory( "1", true ).getAbsolutePath() ) .setConfig( HaSettings.server_id, "1" ) .setConfig( HaSettings.tx_push_factor, "" + pushFactor ) .setConfig( HaSettings.cluster_discovery_enabled, "false" ) .setConfig( HaSettings.tx_push_strategy, pushStrategy ) .setConfig( HaSettings.cluster_server, "localhost:"+basePort ) .newGraphDatabase(); return master; } void addSlaves( int count, int basePort ) { for ( int i = 1; i <= count; i++ ) { GraphDatabaseAPI newSlave = (GraphDatabaseAPI) new HighlyAvailableGraphDatabaseFactory() .newHighlyAvailableDatabaseBuilder( dir.directory( "" + (1 + i), true ).getAbsolutePath() ) .setConfig( HaSettings.server_id, "" + (i + 1) ) .setConfig( HaSettings.ha_server, ":" + (6361 + i) ) .setConfig( HaSettings.cluster_discovery_enabled, "false" ) .setConfig( HaSettings.cluster_server, "localhost:" + (basePort + i) ) .setConfig( HaSettings.initial_hosts, "localhost:"+basePort ) .newGraphDatabase(); slaves.add( newSlave ); } awaitSlaveList( master, slaveCount( this.slaves.size() ) ); } Process addRemoteSlave() throws Exception { int i = slaves.size()+1; return StartInstanceInAnotherJvm.start( dir.directory( "" + (1 + i), true ).getAbsolutePath(), MapUtil.stringMap( HaSettings.server_id.name(), "" + (i + 1), HaSettings.ha_server.name(), ":" + (6361 + i), HaSettings.cluster_discovery_enabled.name(), "false", HaSettings.cluster_server.name(), "localhost:" + (5001 + i), HaSettings.initial_hosts.name(), "localhost:5001" ) ); } private void awaitSlaveList( GraphDatabaseAPI db, Predicate<Slaves> predicate ) { Slaves slavesList = db.getDependencyResolver().resolveDependency( Slaves.class ); long end = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis( 200 ); while ( !predicate.accept( slavesList ) && System.currentTimeMillis() < end ) { try { Thread.sleep( 100 ); } catch ( InterruptedException e ) { // Ignore } } if ( System.currentTimeMillis() > end ) throw new RuntimeException( "Didn't meet slave list requirement" ); } private Predicate<Slaves> slaveCount( final int count ) { return new Predicate<Slaves>() { @Override public boolean accept( Slaves slaves ) { return IteratorUtil.count( slaves.getSlaves() ) == count; } }; } @Test public void twoFixed() throws Exception { startMaster( 2, "fixed", 5001 ); addSlaves( 3, 5001 ); for ( int i = 0; i < 5; i++ ) { createTransactionOnMaster(); assertLastTxId( 2 + i, 4 ); assertLastTxId( 2 + i, 3 ); assertLastTxId( 1, 2 ); } } @Test public void twoRoundRobin() throws Exception { startMaster( 2, "round_robin",5001 ); addSlaves( 4, 5001 ); createTransactionOnMaster(); assertLastTxId( 2, 2 ); assertLastTxId( 2, 3 ); assertLastTxId( 1, 4 ); assertLastTxId( 1, 5 ); createTransactionOnMaster(); assertLastTxId( 2, 2 ); assertLastTxId( 3, 3 ); assertLastTxId( 3, 4 ); assertLastTxId( 1, 5 ); createTransactionOnMaster(); assertLastTxId( 2, 2 ); assertLastTxId( 3, 3 ); assertLastTxId( 4, 4 ); assertLastTxId( 4, 5 ); createTransactionOnMaster(); assertLastTxId( 5, 2 ); assertLastTxId( 3, 3 ); assertLastTxId( 4, 4 ); assertLastTxId( 5, 5 ); } @Test public void twoFixedFromSlaveCommit() throws Exception { startMaster( 2, "fixed", 5001 ); addSlaves( 3, 5001 ); createTransactionOnSlave( 2 ); assertLastTxId( 2, 4 ); assertLastTxId( 1, 3 ); assertLastTxId( 2, 2 ); assertLastTxId( 2, 1 ); createTransactionOnSlave( 3 ); assertLastTxId( 3, 4 ); assertLastTxId( 3, 3 ); assertLastTxId( 2, 2 ); assertLastTxId( 3, 1 ); createTransactionOnSlave( 4 ); assertLastTxId( 4, 4 ); assertLastTxId( 4, 3 ); assertLastTxId( 2, 2 ); assertLastTxId( 4, 1 ); } @Test public void slavesListGetsUpdatedWhenSlaveLeavesNicely() throws Exception { startMaster( 1, "fixed", 5030 ); addSlaves( 2, 5030 ); GraphDatabaseAPI db = slaves.remove( slaves.size() - 1 ); db.shutdown(); awaitSlaveList( master, slaveCount( slaves.size() ) ); } @Test public void slaveListIsCorrectAfterMasterSwitch() throws Exception { startMaster( 1, "fixed", 5040 ); addSlaves( 2, 5040 ); master.shutdown(); master = null; GraphDatabaseAPI newMaster = awaitNewMaster(); awaitSlaveList( newMaster, slaveCount( 1 ) ); createTransaction( newMaster ); assertLastTxId( 2, 2 ); assertLastTxId( 2, 3 ); } private GraphDatabaseAPI awaitNewMaster() { long end = currentTimeMillis() + SECONDS.toMillis( 10 ); while ( currentTimeMillis() < end ) { for ( GraphDatabaseAPI db : slaves ) { if ( ((HighlyAvailableGraphDatabase)db).isMaster() ) return db; } } fail( "No new master chosen" ); return null; // fail will throw exception } @Test @Ignore public void slavesListGetsUpdatedWhenSlaveRageQuits() throws Exception { startMaster( 1, "fixed", 5050 ); addSlaves( 1, 5050 ); Process remoteSlave = addRemoteSlave(); awaitSlaveList( master, slaveCount( 2 ) ); remoteSlave.destroy(); remoteSlave.waitFor(); awaitSlaveList( master, slaveCount( 1 ) ); } private void assertLastTxId( long tx, int serverId ) { GraphDatabaseAPI db = serverId == 1 ? master : getSlave( serverId ); assertEquals( tx, db.getXaDataSourceManager().getNeoStoreDataSource().getLastCommittedTxId() ); } private GraphDatabaseAPI getSlave( int serverId ) { return slaves.get( serverId - 2 ); } private void createTransactionOnMaster() { createTransaction( master ); } private void createTransactionOnSlave( int serverId ) { System.out.println( "Creating tx on " + serverId ); createTransaction( getSlave( serverId ) ); } private void createTransaction( GraphDatabaseAPI db ) { Transaction tx = db.beginTx(); try { db.createNode(); tx.success(); } catch ( RuntimeException e ) { e.printStackTrace(); throw e; } finally { tx.finish(); } } }