/**
* 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 static org.neo4j.helpers.collection.Iterables.filter;
import static org.neo4j.helpers.collection.Iterables.map;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.neo4j.cluster.protocol.cluster.ClusterContext;
import org.neo4j.cluster.protocol.heartbeat.HeartbeatContext;
import org.neo4j.helpers.Function;
import org.neo4j.helpers.Specification;
import org.neo4j.helpers.collection.Iterables;
/**
* Context used by {@link ElectionState}.
*/
public class ElectionContext
{
private List<ElectionRole> roles = new ArrayList<ElectionRole>();
private ClusterContext clusterContext;
private HeartbeatContext heartbeatContext;
private Map<String, Election> elections = new HashMap<String, Election>();
private ElectionCredentialsProvider electionCredentialsProvider;
public ElectionContext( Iterable<ElectionRole> roles, ClusterContext clusterContext,
HeartbeatContext heartbeatContext )
{
this.heartbeatContext = heartbeatContext;
Iterables.addAll( this.roles, roles );
this.clusterContext = clusterContext;
}
public void setElectionCredentialsProvider( ElectionCredentialsProvider electionCredentialsProvider )
{
this.electionCredentialsProvider = electionCredentialsProvider;
}
public void created()
{
for ( ElectionRole role : roles )
{
// Elect myself for all roles
clusterContext.elected( role.getName(), clusterContext.getMe() );
}
}
public List<ElectionRole> getPossibleRoles()
{
return roles;
}
/*
* Removes all roles from the provided node. This is expected to be the first call when receiving a demote
* message for a node, since it is the way to ensure that election will happen for each role that node had
*/
public void nodeFailed( URI node )
{
Iterable<String> rolesToDemote = getRoles( node );
for (String role : rolesToDemote)
{
clusterContext.getConfiguration().getRoles().remove( role );
}
}
public Iterable<String> getRoles( URI server )
{
return clusterContext.getConfiguration().getRolesOf( server );
}
public ClusterContext getClusterContext()
{
return clusterContext;
}
public HeartbeatContext getHeartbeatContext()
{
return heartbeatContext;
}
public void unelect( String roleName )
{
clusterContext.getConfiguration().removeElected( roleName );
}
public boolean isElectionProcessInProgress( String role )
{
return elections.containsKey( role );
}
public void startDemotionProcess( String role, final URI demoteNode )
{
elections.put( role, new Election( new WinnerStrategy()
{
@Override
public URI pickWinner( List<Vote> voteList )
{
// Sort based on credentials
// The most suited candidate should come out on top
Collections.sort( voteList );
Collections.reverse( voteList );
for ( Vote vote : voteList )
{
// Don't elect as winner the node we are trying to demote
if ( !vote.getSuggestedNode().equals( demoteNode ) )
{
return vote.getSuggestedNode();
}
}
return null;
}
} ) );
}
public void startPromotionProcess( String role, final URI promoteNode )
{
elections.put( role, new Election( new WinnerStrategy()
{
@Override
public URI pickWinner( List<Vote> voteList )
{
System.out.println( "Potential winners:" + voteList );
System.out.println( "Trying to promote:" + promoteNode );
// Sort based on credentials
// The most suited candidate should come out on top
Collections.sort( voteList );
Collections.reverse( voteList );
for ( Vote vote : voteList )
{
// Don't elect as winner the node we are trying to demote
if ( vote.getSuggestedNode().equals( promoteNode ) )
{
return vote.getSuggestedNode();
}
}
return null;
}
} ) );
}
public void voted( String role, URI suggestedNode, Comparable<Object> suggestionCredentials )
{
if ( isElectionProcessInProgress( role ) )
{
List<Vote> voteList = elections.get( role ).getVotes();
voteList.add( new Vote( suggestedNode, suggestionCredentials ) );
}
}
public URI getElectionWinner( String role )
{
Election election = elections.get( role );
if ( election == null || election.getVotes().isEmpty() )
{
return null;
}
elections.remove( role );
return election.pickWinner();
}
public Comparable<Object> getCredentialsForRole( String role )
{
return electionCredentialsProvider.getCredentials( role );
}
public int getVoteCount( String role )
{
Election election = elections.get( role );
if ( election != null )
{
List<Vote> voteList = election.getVotes();
if ( voteList == null )
{
return 0;
}
return voteList.size();
}
else
{
return 0;
}
}
public int getNeededVoteCount()
{
return clusterContext.getConfiguration().getMembers().size() - heartbeatContext.getFailed().size();
}
public void cancelElection( String role )
{
elections.remove( role );
}
public Iterable<String> getRolesRequiringElection()
{
return filter( new Specification<String>() // Only include roles that are not elected
{
@Override
public boolean satisfiedBy( String role )
{
return clusterContext.getConfiguration().getElected( role ) == null;
}
}, map( new Function<ElectionRole, String>() // Convert ElectionRole to String
{
@Override
public String map( ElectionRole role )
{
return role.getName();
}
}, roles ) );
}
private static class Vote
implements Comparable<Vote>
{
private final URI suggestedNode;
private final Comparable<Object> voteCredentials;
private Vote( URI suggestedNode, Comparable<Object> voteCredentials )
{
this.suggestedNode = suggestedNode;
this.voteCredentials = voteCredentials;
}
public URI getSuggestedNode()
{
return suggestedNode;
}
@Override
public int compareTo( Vote o )
{
return this.voteCredentials.compareTo( o.voteCredentials );
}
@Override
public String toString()
{
return suggestedNode + ":" + voteCredentials;
}
}
private static class Election
{
private WinnerStrategy winnerStrategy;
List<Vote> votes = new ArrayList<Vote>();
private Election( WinnerStrategy winnerStrategy )
{
this.winnerStrategy = winnerStrategy;
}
public List<Vote> getVotes()
{
return votes;
}
public URI pickWinner()
{
return winnerStrategy.pickWinner( votes );
}
}
interface WinnerStrategy
{
URI pickWinner( List<Vote> votes );
}
}