/**
* 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;
import java.net.URI;
import org.neo4j.cluster.protocol.cluster.ClusterConfiguration;
import org.neo4j.com.ServerUtil;
import org.neo4j.helpers.Listeners;
import org.neo4j.kernel.ha.InstanceAccessGuard;
import org.neo4j.kernel.impl.util.StringLogger;
import org.neo4j.kernel.lifecycle.LifecycleAdapter;
/**
* State machine that listens for global cluster events, and coordinates
* the internal transitions between ClusterMemberStates. Internal services
* that wants to know what is going on should register ClusterMemberListener implementations
* which will receive callbacks on state changes.
*/
public class ClusterMemberStateMachine extends LifecycleAdapter
{
private final ClusterMemberContext context;
private final InstanceAccessGuard accessGuard;
private final ClusterEvents clusterEvents;
private StringLogger logger;
private Iterable<ClusterMemberListener> clusterMemberListeners = Listeners.newListeners();
private ClusterMemberState state;
public ClusterMemberStateMachine( ClusterMemberContext context, InstanceAccessGuard accessGuard,
ClusterEvents clusterEvents, StringLogger logger )
{
this.context = context;
this.accessGuard = accessGuard;
this.clusterEvents = clusterEvents;
this.logger = logger;
clusterEvents.addClusterEventListener( new StateMachineClusterEventListener() );
state = ClusterMemberState.PENDING;
}
@Override
public void stop() throws Throwable
{
ClusterMemberState oldState = state;
state = ClusterMemberState.PENDING;
final ClusterMemberChangeEvent event = new ClusterMemberChangeEvent( oldState, state, null, null );
Listeners.notifyListeners( clusterMemberListeners, new Listeners.Notification<ClusterMemberListener>()
{
@Override
public void notify( ClusterMemberListener listener )
{
listener.instanceStops( event );
}
} );
accessGuard.setState( state );
}
public void addClusterMemberListener( ClusterMemberListener toAdd )
{
clusterMemberListeners = Listeners.addListener( toAdd, clusterMemberListeners );
}
public ClusterMemberState getCurrentState()
{
return state;
}
private class StateMachineClusterEventListener implements ClusterEventListener
{
@Override
public void masterIsElected( URI masterUri )
{
try
{
ClusterMemberState oldState = state;
URI previousElected = context.getElectedMasterId();
String msg = "";
if ( oldState.equals( ClusterMemberState.MASTER ) && masterUri.equals( context.getMyId() ) )
{
clusterEvents.memberIsAvailable( ClusterConfiguration.COORDINATOR );
msg = "(Sent masterIsAvailable) ";
}
else //if ( !masterUri.equals( context.getMyId() ) )
{
state = state.masterIsElected( context, masterUri );
context.setElectedMasterId( masterUri );
final ClusterMemberChangeEvent event = new ClusterMemberChangeEvent( oldState, state, masterUri,
null );
Listeners.notifyListeners( clusterMemberListeners,
new Listeners.Notification<ClusterMemberListener>()
{
@Override
public void notify( ClusterMemberListener listener )
{
listener.masterIsElected( event );
}
} );
context.setAvailableHaMasterId( null );
accessGuard.setState( state );
}
logger.debug( msg + "Got masterIsElected(" + masterUri + "), moved to " + state + " from " +
oldState + ". Previous elected master is " + previousElected );
}
catch ( Throwable t )
{
throw new RuntimeException( t );
}
}
@Override
public void memberIsAvailable( String role, URI instanceClusterUri, Iterable<URI> instanceUris )
{
try
{
if ( role.equals( ClusterConfiguration.COORDINATOR ) )
{
URI masterHaUri = ServerUtil.getUriForScheme( "ha", instanceUris );
if ( !masterHaUri.equals( context.getAvailableHaMaster() ) )
{
ClusterMemberState oldState = state;
context.setAvailableHaMasterId( masterHaUri );
state = state.masterIsAvailable( context, instanceClusterUri, masterHaUri );
logger.debug( "Got masterIsAvailable(" + instanceClusterUri + ", moved to " + state + " from " +
oldState );
final ClusterMemberChangeEvent event = new ClusterMemberChangeEvent( oldState, state,
instanceClusterUri,
masterHaUri );
Listeners.notifyListeners( clusterMemberListeners,
new Listeners.Notification<ClusterMemberListener>()
{
@Override
public void notify( ClusterMemberListener listener )
{
listener.masterIsAvailable( event );
}
} );
accessGuard.setState( state );
}
}
else if ( role.equals( ClusterConfiguration.SLAVE ) )
{
URI slaveHaUri = ServerUtil.getUriForScheme( "ha", instanceUris );
ClusterMemberState oldState = state;
state = state.slaveIsAvailable( context, instanceClusterUri );
logger.debug( "Got slaveIsAvailable(" + instanceClusterUri + "), " +
"moved to " + state + " from " + oldState );
final ClusterMemberChangeEvent event = new ClusterMemberChangeEvent( oldState, state,
instanceClusterUri,
slaveHaUri );
Listeners.notifyListeners( clusterMemberListeners,
new Listeners.Notification<ClusterMemberListener>()
{
@Override
public void notify( ClusterMemberListener listener )
{
listener.slaveIsAvailable( event );
}
} );
accessGuard.setState( state );
}
}
catch ( Throwable throwable )
{
logger.warn( "Exception while receiving member availability notification", throwable );
}
}
}
}