/**
* 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.backup;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.neo4j.backup.BackupExtensionService;
import org.neo4j.backup.OnlineBackupSettings;
import org.neo4j.cluster.ClusterSettings;
import org.neo4j.cluster.client.ClusterClient;
import org.neo4j.cluster.protocol.cluster.ClusterConfiguration;
import org.neo4j.helpers.Args;
import org.neo4j.helpers.Service;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.configuration.ConfigurationDefaults;
import org.neo4j.kernel.ha.HaSettings;
import org.neo4j.kernel.ha.cluster.ClusterEventListener;
import org.neo4j.kernel.ha.cluster.ClusterEvents;
import org.neo4j.kernel.ha.cluster.paxos.PaxosClusterEvents;
import org.neo4j.kernel.impl.util.StringLogger;
import org.neo4j.kernel.lifecycle.LifeSupport;
import org.neo4j.kernel.logging.Logging;
@Service.Implementation(BackupExtensionService.class)
public final class HaBackupProvider extends BackupExtensionService
{
public HaBackupProvider()
{
super( "ha" );
}
@Override
public URI resolve( URI address, Args args, Logging logging )
{
String master = null;
logging.getLogger( "ha.backup" ).logMessage( "Asking cluster member at '" + address
+ "' for master" );
String clusterName = args.get( ClusterSettings.cluster_name.name(), null );
if ( clusterName == null )
{
clusterName = args.get( ClusterSettings.cluster_name.name(),
ConfigurationDefaults.getDefault( ClusterSettings.cluster_name, ClusterSettings.class ) );
}
master = getMasterServerInCluster( address.getSchemeSpecificPart().substring(
2 ), clusterName, logging ); // skip the "//" part
logging.getLogger( "ha.backup" ).logMessage( "Found master '" + master + "' in cluster" );
URI toReturn = null;
try
{
toReturn = new URI( master );
}
catch ( URISyntaxException e )
{
// no way
}
return toReturn;
}
private static String getMasterServerInCluster( String from, String clusterName, final Logging logging )
{
LifeSupport life = new LifeSupport();
Map<String, String> params = new ConfigurationDefaults( ClusterSettings.class, OnlineBackupSettings.class ).apply( new HashMap<String, String>() );
params.put( HaSettings.server_id.name(), "-1" );
params.put( ClusterSettings.cluster_name.name(), clusterName );
params.put( HaSettings.initial_hosts.name(), from );
params.put( HaSettings.cluster_discovery_enabled.name(), "false" );
final Config config = new Config( params );
ClusterClient clusterClient = life.add( new ClusterClient( ClusterClient.adapt( config, new BackupElectionCredentialsProvider() ), logging ) );
ClusterEvents events = life.add( new PaxosClusterEvents( PaxosClusterEvents.adapt( config ), clusterClient, StringLogger.SYSTEM ) );
final Semaphore infoReceivedLatch = new Semaphore( 0 );
final ClusterInfoHolder addresses = new ClusterInfoHolder();
events.addClusterEventListener( new ClusterEventListener()
{
@Override
public void masterIsElected( URI masterUri )
{
}
@Override
public void memberIsAvailable( String role, URI masterClusterUri, Iterable<URI> masterURIs )
{
if ( ClusterConfiguration.COORDINATOR.equals( role ) )
{
addresses.held = masterURIs;
infoReceivedLatch.release();
}
}
} );
life.start();
try
{
if ( !infoReceivedLatch.tryAcquire( 10, TimeUnit.SECONDS ) )
{
throw new RuntimeException( "Could not find master in cluster " + clusterName + " at " + from + ", " +
"operation timed out" );
}
}
catch ( InterruptedException e )
{
throw new RuntimeException( e );
}
finally
{
life.shutdown();
}
String backupAddress = null;
for ( URI uri : addresses.held )
{
if ( "backup".equals( uri.getScheme() ) )
{
backupAddress = uri.toString();
break;
}
}
return backupAddress;
}
private static final class ClusterInfoHolder
{
public Iterable<URI> held;
}
}