/** * 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.cluster.protocol.election; import java.net.URI; import org.neo4j.cluster.com.message.Message; import org.neo4j.cluster.com.message.MessageProcessor; import org.neo4j.cluster.protocol.atomicbroadcast.multipaxos.ProposerMessage; import org.neo4j.cluster.protocol.cluster.ClusterMessage; import org.neo4j.cluster.statemachine.State; /** * State machine that implements the {@link Election} API. */ public enum ElectionState implements State<ElectionContext, ElectionMessage> { start { @Override public State<?, ?> handle( ElectionContext context, Message<ElectionMessage> message, MessageProcessor outgoing ) throws Throwable { switch ( message.getMessageType() ) { case created: { context.created(); return election; } case join: { return election; } } return this; } }, election { @Override public State<?, ?> handle( ElectionContext context, Message<ElectionMessage> message, MessageProcessor outgoing ) throws Throwable { switch ( message.getMessageType() ) { case demote: { URI demoteNode = message.getPayload(); // TODO Could perhaps be done better? context.nodeFailed( demoteNode ); if ( context.getClusterContext().isInCluster() ) { int remainingDelays; if ( message.hasHeader( "delays" ) ) { // Check how many more times we should delay before proceeding remainingDelays = Integer.parseInt( message.getHeader( "delays" ) ); } else { // Should we delay this so that someone else can give it a go? remainingDelays = context.getClusterContext().getConfiguration().getMembers() .indexOf( context.getClusterContext().getMe() ); if ( remainingDelays > 0 ) { context.getClusterContext().getLogger().debug( "Delay demotion of " + demoteNode .toString() + " " + remainingDelays + " times" ); } } if ( remainingDelays > 0 ) { context.getClusterContext() .timeouts .setTimeout( "delayed-demote-" + demoteNode.toString(), Message.timeout( ElectionMessage.demote, message, demoteNode ).setHeader( "delays", (remainingDelays - 1) + "" ) ); } else // Start election process for all roles that are currently unassigned { Iterable<String> rolesRequiringElection = context.getRolesRequiringElection(); for ( String role : rolesRequiringElection ) { if ( !context.isElectionProcessInProgress( role ) ) { context.getClusterContext().getLogger().debug( "Starting election process" + " for role " + role ); int voterCount = 0; context.startDemotionProcess( role, demoteNode ); // Allow other live nodes to vote which one should take over for ( URI uri : context.getClusterContext().getConfiguration().getMembers () ) { if ( !context.getHeartbeatContext().getFailed().contains( uri ) ) { // This is a candidate - allow it to vote itself for promotion outgoing.process( Message.to( ElectionMessage.vote, uri, role ) ); voterCount++; } } context.getClusterContext() .timeouts .setTimeout( "election-" + role, Message.timeout( ElectionMessage.electionTimeout, message, role ) ); } else { context.getClusterContext().getLogger().debug( "Election already in " + "progress for role " + role ); } } } } break; } case promote: { Object[] args = message.getPayload(); URI promoteNode = (URI) args[0]; String role = (String) args[1]; if ( context.getClusterContext().isInCluster() ) { // Start election process for coordinator role if ( !context.isElectionProcessInProgress( role ) ) { context.startPromotionProcess( role, promoteNode ); // Allow other live nodes to vote which one should take over for ( URI uri : context.getClusterContext().getConfiguration().getMembers() ) { if ( !context.getHeartbeatContext().getFailed().contains( uri ) ) { // This is a candidate - allow it to vote itself for promotion outgoing.process( Message.to( ElectionMessage.vote, uri, role ) ); } } context.getClusterContext() .timeouts .setTimeout( "election-" + role, Message.timeout( ElectionMessage .electionTimeout, message, role ) ); } } break; } case vote: { String role = message.getPayload(); outgoing.process( Message.respond( ElectionMessage.voted, message, new ElectionMessage.VotedData( role, context .getCredentialsForRole( role ) ) ) ); break; } case voted: { ElectionMessage.VotedData data = message.getPayload(); context.voted( data.getRole(), new URI( message.getHeader( Message.FROM ) ), data.getVoteCredentials() ); if ( context.getVoteCount( data.getRole() ) == context.getNeededVoteCount() ) { // We have all votes now URI winner = context.getElectionWinner( data.getRole() ); if ( winner != null ) { context.getClusterContext().getLogger().debug( "Elected " + winner + " as " + data.getRole() ); // Broadcast this ClusterMessage.ConfigurationChangeState configurationChangeState = new ClusterMessage.ConfigurationChangeState(); configurationChangeState.elected( data.getRole(), winner ); outgoing.process( Message.internal( ProposerMessage.propose, configurationChangeState ) ); } else { context.getClusterContext().getLogger().warn( "Election could not " + "pick a " + "winner" ); } context.getClusterContext().timeouts.cancelTimeout( "election-" + data.getRole() ); } break; } case electionTimeout: { // Something was lost context.getClusterContext().getLogger().warn( "Election timed out" ); context.cancelElection( (String) message.getPayload() ); break; } case leave: { return start; } } return this; } } }