import akka.actor.ActorSelection;
import akka.actor.Cancellable;
import com.google.common.base.Preconditions;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.LinkedList;
-import java.util.List;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
-import org.opendaylight.controller.cluster.raft.ServerConfigurationPayload.ServerInfo;
+import javax.annotation.Nullable;
import org.opendaylight.controller.cluster.raft.base.messages.ApplyState;
import org.opendaylight.controller.cluster.raft.base.messages.SnapshotComplete;
import org.opendaylight.controller.cluster.raft.behaviors.AbstractLeader;
import org.opendaylight.controller.cluster.raft.messages.AddServer;
import org.opendaylight.controller.cluster.raft.messages.AddServerReply;
import org.opendaylight.controller.cluster.raft.messages.FollowerCatchUpTimeout;
+import org.opendaylight.controller.cluster.raft.messages.RemoveServer;
+import org.opendaylight.controller.cluster.raft.messages.RemoveServerReply;
import org.opendaylight.controller.cluster.raft.messages.ServerChangeStatus;
+import org.opendaylight.controller.cluster.raft.messages.ServerRemoved;
import org.opendaylight.controller.cluster.raft.messages.UnInitializedFollowerSnapshotReply;
import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload;
import org.slf4j.Logger;
boolean handleMessage(Object message, RaftActor raftActor, ActorRef sender) {
if(message instanceof AddServer) {
- onAddServer((AddServer)message, raftActor, sender);
+ onAddServer((AddServer) message, raftActor, sender);
+ return true;
+ } else if(message instanceof RemoveServer) {
+ onRemoveServer((RemoveServer) message, raftActor, sender);
return true;
} else if (message instanceof FollowerCatchUpTimeout) {
- currentOperationState.onFollowerCatchupTimeout(raftActor, (FollowerCatchUpTimeout)message);
+ currentOperationState.onFollowerCatchupTimeout(raftActor, (FollowerCatchUpTimeout) message);
return true;
} else if (message instanceof UnInitializedFollowerSnapshotReply) {
currentOperationState.onUnInitializedFollowerSnapshotReply(raftActor,
- (UnInitializedFollowerSnapshotReply)message);
+ (UnInitializedFollowerSnapshotReply) message);
return true;
} else if(message instanceof ApplyState) {
return onApplyState((ApplyState) message, raftActor);
}
}
+ private void onRemoveServer(RemoveServer removeServer, RaftActor raftActor, ActorRef sender) {
+ if(removeServer.getServerId().equals(raftActor.getLeaderId())){
+ // Removing current leader is not supported yet
+ // TODO: To properly support current leader removal we need to first implement transfer of leadership
+ LOG.debug("Cannot remove {} replica because it is the Leader", removeServer.getServerId());
+ sender.tell(new RemoveServerReply(ServerChangeStatus.NOT_SUPPORTED, raftActor.getLeaderId()), raftActor.getSelf());
+ } else if(!raftContext.getPeerIds().contains(removeServer.getServerId())) {
+ sender.tell(new RemoveServerReply(ServerChangeStatus.DOES_NOT_EXIST, raftActor.getLeaderId()), raftActor.getSelf());
+ } else {
+ onNewOperation(raftActor, new RemoveServerContext(removeServer, raftContext.getPeerAddress(removeServer.getServerId()), sender));
+ }
+ }
+
private boolean onApplyState(ApplyState applyState, RaftActor raftActor) {
Payload data = applyState.getReplicatedLogEntry().getData();
if(data instanceof ServerConfigurationPayload) {
}
protected void persistNewServerConfiguration(RaftActor raftActor, ServerOperationContext<?> operationContext){
- Collection<PeerInfo> peers = raftContext.getPeers();
- List<ServerInfo> newConfig = new ArrayList<>(peers.size() + 1);
- for(PeerInfo peer: peers) {
- newConfig.add(new ServerInfo(peer.getId(), peer.isVoting()));
- }
-
- newConfig.add(new ServerInfo(raftContext.getId(), true));
-
- LOG.debug("{}: New server configuration : {}", raftContext.getId(), newConfig);
-
- ServerConfigurationPayload payload = new ServerConfigurationPayload(newConfig);
+ raftContext.setDynamicServerConfigurationInUse();
+ ServerConfigurationPayload payload = raftContext.getPeerServerInfo();
+ LOG.debug("{}: New server configuration : {}", raftContext.getId(), payload.getServerConfig());
raftActor.persistData(operationContext.getClientRequestor(), operationContext.getContextId(), payload);
currentOperationState = new Persisting(operationContext);
+
+ sendReply(raftActor, operationContext, ServerChangeStatus.OK);
}
protected void operationComplete(RaftActor raftActor, ServerOperationContext<?> operationContext,
- ServerChangeStatus status) {
-
- LOG.debug("{}: Returning {} for operation {}", raftContext.getId(), status, operationContext.getOperation());
+ @Nullable ServerChangeStatus replyStatus) {
+ if(replyStatus != null) {
+ sendReply(raftActor, operationContext, replyStatus);
+ }
- operationContext.getClientRequestor().tell(operationContext.newReply(status, raftActor.getLeaderId()),
- raftActor.self());
+ operationContext.operationComplete(raftActor, replyStatus);
currentOperationState = IDLE;
}
}
+ private void sendReply(RaftActor raftActor, ServerOperationContext<?> operationContext,
+ ServerChangeStatus status) {
+ LOG.debug("{}: Returning {} for operation {}", raftContext.getId(), status, operationContext.getOperation());
+
+ operationContext.getClientRequestor().tell(operationContext.newReply(status, raftActor.getLeaderId()),
+ raftActor.self());
+ }
+
@Override
public String toString() {
return getClass().getSimpleName();
// Sanity check - we could get an ApplyState from a previous operation that timed out so make
// sure it's meant for us.
if(operationContext.getContextId().equals(applyState.getIdentifier())) {
- LOG.info("{}: {} has been successfully replicated to a majority of followers",
+ LOG.info("{}: {} has been successfully replicated to a majority of followers", raftActor.getId(),
applyState.getReplicatedLogEntry().getData());
- operationComplete(raftActor, operationContext, ServerChangeStatus.OK);
+ operationComplete(raftActor, operationContext, null);
}
}
}
operationComplete(raftActor, getAddServerContext(),
isLeader ? ServerChangeStatus.TIMEOUT : ServerChangeStatus.NO_LEADER);
}
+
}
/**
@Override
public void initiate(RaftActor raftActor) {
AbstractLeader leader = (AbstractLeader) raftActor.getCurrentBehavior();
-
AddServer addServer = getAddServerContext().getOperation();
LOG.debug("{}: Initiating {}", raftContext.getId(), addServer);
+ if(raftContext.getPeerInfo(addServer.getNewServerId()) != null) {
+ operationComplete(raftActor, getAddServerContext(), ServerChangeStatus.ALREADY_EXISTS);
+ return;
+ }
+
VotingState votingState = addServer.isVotingMember() ? VotingState.VOTING_NOT_INITIALIZED :
VotingState.NON_VOTING;
raftContext.addToPeers(addServer.getNewServerId(), addServer.getNewServerAddress(), votingState);
abstract Object newReply(ServerChangeStatus status, String leaderId);
abstract InitialOperationState newInitialOperationState(RaftActorServerConfigurationSupport support);
+
+ abstract void operationComplete(RaftActor raftActor, ServerChangeStatus serverChangeStatus);
}
/**
InitialOperationState newInitialOperationState(RaftActorServerConfigurationSupport support) {
return support.new InitialAddServerState(this);
}
+
+ @Override
+ void operationComplete(RaftActor raftActor, ServerChangeStatus serverChangeStatus) {
+
+ }
+ }
+
+ private abstract class RemoveServerState extends AbstractOperationState {
+ private final RemoveServerContext removeServerContext;
+
+
+ protected RemoveServerState(RemoveServerContext removeServerContext) {
+ this.removeServerContext = Preconditions.checkNotNull(removeServerContext);
+
+ }
+
+ public RemoveServerContext getRemoveServerContext() {
+ return removeServerContext;
+ }
+
+ }
+
+ private class InitialRemoveServerState extends RemoveServerState implements InitialOperationState{
+
+ protected InitialRemoveServerState(RemoveServerContext removeServerContext) {
+ super(removeServerContext);
+ }
+
+ @Override
+ public void initiate(RaftActor raftActor) {
+ raftContext.removePeer(getRemoveServerContext().getOperation().getServerId());
+ persistNewServerConfiguration(raftActor, getRemoveServerContext());
+ }
+ }
+
+ private static class RemoveServerContext extends ServerOperationContext<RemoveServer> {
+ private final String peerAddress;
+
+ RemoveServerContext(RemoveServer operation, String peerAddress, ActorRef clientRequestor) {
+ super(operation, clientRequestor);
+ this.peerAddress = peerAddress;
+ }
+
+ @Override
+ Object newReply(ServerChangeStatus status, String leaderId) {
+ return new RemoveServerReply(status, leaderId);
+ }
+
+ @Override
+ InitialOperationState newInitialOperationState(RaftActorServerConfigurationSupport support) {
+ return support.new InitialRemoveServerState(this);
+ }
+
+ @Override
+ void operationComplete(RaftActor raftActor, ServerChangeStatus serverChangeStatus) {
+ if(peerAddress != null) {
+ raftActor.context().actorSelection(peerAddress).tell(new ServerRemoved(getOperation().getServerId()), raftActor.getSelf());
+ }
+ }
+
}
}