/**
* Copyright (c) 2002-2011 "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.management.ManagementFactory.getPlatformMBeanServer;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.management.MBeanServerInvocationHandler;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import org.apache.zookeeper.server.quorum.QuorumMXBean;
import org.apache.zookeeper.server.quorum.QuorumPeerMain;
import org.jboss.netty.handler.timeout.TimeoutException;
import org.junit.Ignore;
import org.neo4j.test.SubProcess;
import org.neo4j.test.TargetDirectory;
@Ignore
public final class LocalhostZooKeeperCluster
{
private final ZooKeeper[] keeper;
private final String connection;
public LocalhostZooKeeperCluster( Class<?> owningTest, int... ports )
{
this( TargetDirectory.forTest( owningTest ), ports );
}
public LocalhostZooKeeperCluster( TargetDirectory target, int... ports )
{
keeper = new ZooKeeper[ports.length];
boolean success = false;
try
{
ZooKeeperProcess subprocess = new ZooKeeperProcess( null );
StringBuilder connection = new StringBuilder();
for ( int i = 0; i < keeper.length; i++ )
{
keeper[i] = subprocess.start( new String[] { config( target, i + 1, ports[i] ) } );
if ( connection.length() > 0 ) connection.append( "," );
connection.append( "localhost:" + ports[i] );
}
this.connection = connection.toString();
await( keeper, 10, TimeUnit.SECONDS );
success = true;
}
finally
{
if ( !success ) shutdown();
}
}
private static void await( ZooKeeper[] keepers, long timeout, TimeUnit unit )
{
timeout = System.currentTimeMillis() + unit.toMillis( timeout );
boolean done;
do
{
done = true;
for ( ZooKeeper keeper : keepers )
{
if ( keeper.getQuorumSize() != keepers.length ) done = false;
}
if ( System.currentTimeMillis() > timeout )
throw new TimeoutException( "waiting for ZooKeeper cluster to start" );
try
{
Thread.sleep( 10 );
}
catch ( InterruptedException e )
{
throw new TimeoutException( "waiting for ZooKeeper cluster to start", e );
}
}
while ( !done );
}
@Override
public String toString()
{
return getClass().getSimpleName() + "[" + getConnectionString() + "]";
}
public synchronized String getConnectionString()
{
return connection;
}
String getStatus()
{
StringBuilder result = new StringBuilder();
String prefix = "";
for ( ZooKeeper zk : keeper )
{
result.append( prefix ).append( zk ).append( ": " ).append( zk.getStatus() );
prefix = "; ";
}
return result.toString();
}
private String config( TargetDirectory target, int id, int port )
{
File config = target.file( "zookeeper" + id + ".cfg" );
File dataDir = target.directory( "zk" + id + "data", true );
try
{
PrintWriter conf = new PrintWriter( config );
try
{
conf.println( "tickTime=2000" );
conf.println( "initLimit=10" );
conf.println( "syncLimit=5" );
conf.println( "dataDir=" + dataDir.getAbsolutePath() );
conf.println( "clientPort=" + port );
for ( int j = 0; j < keeper.length; j++ )
{
conf.println( "server." + ( j + 1 ) + "=localhost:" + ( 2888 + j ) + ":"
+ ( 3888 + j ) );
}
}
finally
{
conf.close();
}
PrintWriter myid = new PrintWriter( new File( dataDir, "myid" ) );
try
{
myid.println( Integer.toString( id ) );
}
finally
{
myid.close();
}
}
catch ( IOException e )
{
throw new IllegalStateException( "Could not write ZooKeeper configuration", e );
}
return config.getAbsolutePath();
}
public synchronized void shutdown()
{
if ( keeper.length > 0 && keeper[0] == null ) return;
for ( ZooKeeper zk : keeper )
{
if ( zk != null ) SubProcess.stop( zk );
}
Arrays.fill( keeper, null );
}
public static void main( String[] args ) throws Exception
{
LocalhostZooKeeperCluster cluster = new LocalhostZooKeeperCluster( ZooKeeperProcess.class,
2181, 2182, 2183 );
try
{
System.out.println( "press return to exit" );
System.in.read();
}
finally
{
cluster.shutdown();
}
}
public interface ZooKeeper
{
int getQuorumSize();
String getStatus();
}
private static class ZooKeeperProcess extends SubProcess<ZooKeeper, String[]> implements
ZooKeeper
{
private final String name;
ZooKeeperProcess( String name )
{
this.name = name;
}
@Override
protected void startup( String[] parameters )
{
System.out.println( "parameters=" + Arrays.toString( parameters ) );
QuorumPeerMain.main( parameters );
}
@Override
public String toString()
{
if ( name != null )
{
return super.toString() + ":" + name;
}
else
{
return super.toString();
}
}
public int getQuorumSize()
{
try
{
return quorumBean().getQuorumSize();
}
catch ( Exception e )
{
return 0;
}
}
public String getStatus()
{
try
{
return status( quorumBean() );
}
catch ( Exception e )
{
return "-down-";
}
}
private QuorumMXBean quorumBean() throws MalformedObjectNameException
{
Set<ObjectName> names = getPlatformMBeanServer().queryNames(
new ObjectName( "org.apache.ZooKeeperService:name0=ReplicatedServer_id*" ),
null );
QuorumMXBean quorum = MBeanServerInvocationHandler.newProxyInstance(
getPlatformMBeanServer(), names.iterator().next(), QuorumMXBean.class, false );
return quorum;
}
@SuppressWarnings( "boxing" )
private String status( QuorumMXBean quorumBean )
{
long time = System.currentTimeMillis();
String name = quorumBean.getName();
int size = quorumBean.getQuorumSize();
return String.format( "name=%s, size=%s, time=%s (+%sms)", name, size, format( time ),
System.currentTimeMillis() - time );
}
private String format( long time )
{
return new SimpleDateFormat( "[HH:mm:ss:SS] " ).format( new Date( time ) );
}
}
}