/** * 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.ha; import static java.lang.System.currentTimeMillis; import static org.junit.Assert.assertTrue; import static org.neo4j.test.TargetDirectory.forTest; import java.net.URI; import java.util.concurrent.CountDownLatch; import org.junit.After; import org.junit.Test; import org.neo4j.cluster.protocol.cluster.ClusterConfiguration; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.factory.HighlyAvailableGraphDatabaseFactory; import org.neo4j.kernel.ha.HaSettings; import org.neo4j.kernel.ha.HighlyAvailableGraphDatabase; import org.neo4j.kernel.ha.cluster.ClusterEventListener; import org.neo4j.kernel.ha.cluster.ClusterEvents; import org.neo4j.shell.ShellClient; import org.neo4j.shell.ShellException; import org.neo4j.shell.ShellLobby; import org.neo4j.shell.ShellSettings; import org.neo4j.test.TargetDirectory; public class TestPullUpdates { private final TargetDirectory dir = forTest( getClass() ); private HighlyAvailableGraphDatabase[] dbs; private static final int PULL_INTERVAL = 100; private static final int SHELL_PORT = 6370; private void startDbs( int size, int pullInterval ) throws Exception { dbs = new HighlyAvailableGraphDatabase[size]; for ( int i = 0; i < dbs.length; i++ ) dbs[i] = newDb( i, pullInterval ); } private HighlyAvailableGraphDatabase newDb( int i, int pullInterval ) { HighlyAvailableGraphDatabase db = (HighlyAvailableGraphDatabase) new HighlyAvailableGraphDatabaseFactory(). newHighlyAvailableDatabaseBuilder( dir.directory( "" + (i + 1), true ).getAbsolutePath() ). setConfig( HaSettings.server_id, "" + (i + 1) ). setConfig( HaSettings.ha_server, "127.0.0.1:" + (6361 + i) ). setConfig( HaSettings.cluster_server, "127.0.0.1:" + (5001 + i) ). setConfig( HaSettings.initial_hosts, "127.0.0.1:5001,127.0.0.1:5002,127.0.0.1:5003" ). setConfig( HaSettings.pull_interval, pullInterval + "ms" ). setConfig( HaSettings.tx_push_factor, "0" ). setConfig( ShellSettings.remote_shell_enabled, "true" ). setConfig( ShellSettings.remote_shell_port, "" + (SHELL_PORT+1) ). newGraphDatabase(); Transaction tx = db.beginTx(); tx.finish(); return db; } @After public void doAfter() throws Exception { for ( HighlyAvailableGraphDatabase db : dbs ) { if ( db != null ) { db.shutdown(); } } } @Test public void makeSureUpdatePullerGetsGoingAfterMasterSwitch() throws Exception { startDbs( 3, PULL_INTERVAL ); int master = getCurrentMaster(); setProperty( master, 1 ); awaitPropagation( 1 ); awaitNewMaster( (master + 1) % dbs.length ); kill( master ); masterElectedLatch.await(); setProperty( getCurrentMaster(), 2 ); awaitNewMaster( (master + 1) % dbs.length ); start( master, PULL_INTERVAL ); masterElectedLatch.await(); awaitPropagation( 2 ); } @Test public void pullupdatesShellAppPullsUpdates() throws Exception { startDbs( 2, 0 ); int master = getCurrentMaster(); setProperty( master, 1 ); callPullUpdatesViaShell( (master+1)%dbs.length ); awaitPropagation( 1 ); } private void callPullUpdatesViaShell( int i ) throws ShellException { HighlyAvailableGraphDatabase db = dbs[i]; ShellClient client = ShellLobby.newClient( SHELL_PORT+i ); client.evaluate( "pullupdates" ); } private void awaitNewMaster( int master ) { masterElectedLatch = new CountDownLatch( 1 ); final ClusterEvents events = dbs[master].getDependencyResolver().resolveDependency( ClusterEvents.class ); events.addClusterEventListener( new ClusterEventListener.Adapter() { @Override public void memberIsAvailable( String role, URI instanceClusterUri, Iterable<URI> instanceUris ) { if ( role.equals( ClusterConfiguration.COORDINATOR ) ) { masterElectedLatch.countDown(); events.removeClusterEventListener( this ); } } } ); } private void powerNap() throws InterruptedException { Thread.sleep( 50 ); } private CountDownLatch masterElectedLatch; private void start( int master, int pullInterval ) { dbs[master] = newDb( master, pullInterval ); } private void kill( int master ) { dbs[master].shutdown(); dbs[master] = null; } private void awaitPropagation( int i ) throws Exception { long endTime = currentTimeMillis() + 10000; boolean ok = false; while ( !ok && currentTimeMillis() < endTime ) { ok = true; for ( HighlyAvailableGraphDatabase db : dbs ) { Object value = db.getReferenceNode().getProperty( "i", null ); if ( value == null || ((Integer) value).intValue() != i ) { ok = false; } } if ( !ok ) { powerNap(); } } assertTrue( "Change wasn't propagated by pulling updates", ok ); } private void setProperty( int dbId, int i ) throws Exception { HighlyAvailableGraphDatabase db = dbs[dbId]; Transaction tx = db.beginTx(); try { db.getReferenceNode().setProperty( "i", i ); tx.success(); } finally { tx.finish(); } } private int getCurrentMaster() throws Exception { for ( int i = 0; i < dbs.length; i++ ) { HighlyAvailableGraphDatabase db = dbs[i]; if ( db != null && db.isMaster() ) { return i; } } return -1; } }