/**
* 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.cluster.paxos;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.neo4j.backup.OnlineBackupSettings;
import org.neo4j.cluster.BindingListener;
import org.neo4j.cluster.client.ClusterClient;
import org.neo4j.cluster.protocol.atomicbroadcast.AtomicBroadcastListener;
import org.neo4j.cluster.protocol.atomicbroadcast.AtomicBroadcastSerializer;
import org.neo4j.cluster.protocol.atomicbroadcast.Payload;
import org.neo4j.cluster.protocol.cluster.ClusterConfiguration;
import org.neo4j.cluster.protocol.cluster.ClusterListener;
import org.neo4j.helpers.Listeners;
import org.neo4j.helpers.collection.Iterables;
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.ha.HaSettings;
import org.neo4j.kernel.ha.cluster.AbstractClusterEvents;
import org.neo4j.kernel.ha.cluster.ClusterEventListener;
import org.neo4j.kernel.impl.util.StringLogger;
import org.neo4j.kernel.lifecycle.Lifecycle;
/**
* Paxos based implementation of {@link org.neo4j.kernel.ha.cluster.ClusterEvents}
*/
public class PaxosClusterEvents
extends AbstractClusterEvents
implements Lifecycle
{
public interface Configuration
{
String getHaServer();
int getServerId();
int getBackupPort();
}
public static Configuration adapt( final Config config )
{
return new Configuration()
{
@Override
public int getServerId()
{
return config.get( HaSettings.server_id );
}
@Override
public String getHaServer()
{
return config.get( HaSettings.ha_server );
}
@Override
public int getBackupPort()
{
return config.get( OnlineBackupSettings.online_backup_port );
}
};
}
private URI serverClusterId;
private Configuration config;
private StringLogger logger;
protected AtomicBroadcastSerializer serializer;
private final ClusterClient cluster;
public PaxosClusterEvents( Configuration config, ClusterClient cluster, StringLogger logger )
{
this.config = config;
this.cluster = cluster;
this.logger = logger;
this.cluster.addBindingListener( new BindingListener()
{
@Override
public void listeningAt( URI me )
{
serverClusterId = me;
PaxosClusterEvents.this.logger.logMessage( "Listening at:" + me );
}
} );
}
@Override
public void init()
throws Throwable
{
serializer = new AtomicBroadcastSerializer();
cluster.addClusterListener( new ClusterListener.Adapter()
{
ClusterConfiguration clusterConfiguration;
@Override
public void joinedCluster( URI member )
{
final URI coordinator = clusterConfiguration.getElected( ClusterConfiguration.COORDINATOR );
if ( coordinator.equals( serverClusterId ) )
{
// Reannounce that I am master, for the purpose of the new member to see this
try
{
cluster.broadcast( serializer.broadcast( new MasterIsElected( serverClusterId ) ) );
}
catch ( IOException e )
{
e.printStackTrace();
}
}
}
@Override
public void enteredCluster( ClusterConfiguration clusterConfiguration )
{
this.clusterConfiguration = clusterConfiguration;
}
@Override
public void elected( String role, URI electedMember )
{
try
{
// TODO This seems like double work. We just Paxos-declared an election,
// and then broadcast it again??
if ( electedMember.equals( serverClusterId ) )
{
cluster.broadcast( serializer.broadcast( new MasterIsElected( serverClusterId ) ) );
}
}
catch ( IOException e )
{
e.printStackTrace();
}
}
} );
cluster.addAtomicBroadcastListener( new AtomicBroadcastListener()
{
@Override
public void receive( Payload payload )
{
try
{
final Object value = serializer.receive( payload );
if ( value instanceof MasterIsElected )
{
Listeners.notifyListeners( listeners, new Listeners.Notification<ClusterEventListener>()
{
@Override
public void notify( ClusterEventListener listener )
{
listener.masterIsElected( ((MasterIsElected) value).getMasterUri() );
}
} );
}
else if ( value instanceof MemberIsAvailable )
{
Listeners.notifyListeners( listeners, new Listeners.Notification<ClusterEventListener>()
{
@Override
public void notify( ClusterEventListener listener )
{
MemberIsAvailable memberIsAvailable = (MemberIsAvailable) value;
listener.memberIsAvailable( memberIsAvailable.getRole(),
memberIsAvailable.getClusterUri(),
memberIsAvailable.getInstanceUris() );
}
} );
}
}
catch ( Throwable t )
{
t.printStackTrace();
}
}
} );
}
@Override
public void start()
throws Throwable
{
}
@Override
public void stop()
throws Throwable
{
}
@Override
public void shutdown()
throws Throwable
{
}
@Override
public void memberIsAvailable( String role )
{
try
{
Payload payload = serializer.broadcast( new MemberIsAvailable( role, serverClusterId, Iterables.iterable(
getHaUri( serverClusterId ), getBackupUri( serverClusterId ) ) ) );
serializer.receive( payload );
cluster.broadcast( payload );
}
catch ( Throwable e )
{
logger.warn( "Could not distribute member availability", e );
}
}
private URI getHaUri( URI clusterUri )
{
try
{
// TODO if we don't want this fallback on cluster address, then add this
// logic to the (default) Configuration implementation?
String host = getConfiguredHaAddress( clusterUri );
return new URI( "ha", null, host, portOf( config.getHaServer() ), null,
"serverId=" + config.getServerId(), null );
}
catch ( URISyntaxException e )
{
throw new RuntimeException( e );
}
}
private String getConfiguredHaAddress( URI clusterUri )
{
String host = addressOf( config.getHaServer() );
return host != null ? host : clusterUri.getHost();
}
private int portOf( String addressWithOrWithoutPort )
{
String[] parts = addressWithOrWithoutPort.split( ":" );
return parts.length < 2 ? -1 : Integer.parseInt( parts[1] );
}
private String addressOf( String addressWithOrWithoutPort )
{
String[] parts = addressWithOrWithoutPort.split( ":" );
return parts[0].length() > 0 ? parts[0] : null;
}
private URI getBackupUri( URI clusterUri )
{
try
{
String host = getConfiguredHaAddress( clusterUri );
return new URI( "backup", null, host, config.getBackupPort(), null, null, null );
}
catch ( URISyntaxException e )
{
throw new RuntimeException( e );
}
}
}