import akka.actor.ActorSelection;
import akka.actor.Cancellable;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Optional;
import com.google.common.base.Stopwatch;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.controller.cluster.raft.base.messages.LeaderTransitioning;
import org.opendaylight.controller.cluster.raft.behaviors.Leader;
import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior;
public class RaftActorLeadershipTransferCohort {
private static final Logger LOG = LoggerFactory.getLogger(RaftActorLeadershipTransferCohort.class);
- private final RaftActor raftActor;
- private Cancellable newLeaderTimer;
+ static final long USE_DEFAULT_LEADER_TIMEOUT = -1;
+
private final List<OnComplete> onCompleteCallbacks = new ArrayList<>();
- private long newLeaderTimeoutInMillis = 2000;
private final Stopwatch transferTimer = Stopwatch.createUnstarted();
+ private final RaftActor raftActor;
+ private final String requestedFollowerId;
+
+ private long newLeaderTimeoutInMillis = 2000;
+ private Cancellable newLeaderTimer;
private boolean isTransferring;
- RaftActorLeadershipTransferCohort(RaftActor raftActor) {
+ RaftActorLeadershipTransferCohort(final RaftActor raftActor) {
+ this(raftActor, null);
+ }
+
+ RaftActorLeadershipTransferCohort(final RaftActor raftActor, final @Nullable String requestedFollowerId) {
this.raftActor = raftActor;
+ this.requestedFollowerId = requestedFollowerId;
+
+ // We'll wait an election timeout period for a new leader to be elected plus some cushion to take into
+ // account the variance.
+ final long electionTimeout = raftActor.getRaftActorContext().getConfigParams()
+ .getElectionTimeOutInterval().toMillis();
+ final int variance = raftActor.getRaftActorContext().getConfigParams().getElectionTimeVariance();
+ newLeaderTimeoutInMillis = 2 * (electionTimeout + variance);
}
void init() {
raftActor.pauseLeader(new TimedRunnable(context.getConfigParams().getElectionTimeOutInterval(), raftActor) {
@Override
protected void doRun() {
+ LOG.debug("{}: pauseLeader successfully completed - doing transfer", raftActor.persistenceId());
doTransfer();
}
@Override
protected void doCancel() {
- LOG.debug("{}: pauseLeader timed out - aborting transfer", raftActor.persistenceId());
- abortTransfer();
+ LOG.debug("{}: pauseLeader timed out - continuing with transfer", raftActor.persistenceId());
+ doTransfer();
}
});
}
void doTransfer() {
RaftActorBehavior behavior = raftActor.getCurrentBehavior();
// Sanity check...
- if (behavior instanceof Leader) {
+ if (behavior instanceof Leader leader) {
isTransferring = true;
- ((Leader)behavior).transferLeadership(this);
+ leader.transferLeadership(this);
} else {
LOG.debug("{}: No longer the leader - skipping transfer", raftActor.persistenceId());
finish(true);
// and convert to follower due to higher term. We should then get an AppendEntries heart
// beat with the new leader id.
- // Add a timer in case we don't get a leader change - 2 sec should be plenty of time if a new
- // leader is elected. Note: the Runnable is sent as a message to the raftActor which executes it
- // safely run on the actor's thread dispatcher.
+ // Add a timer in case we don't get a leader change. Note: the Runnable is sent as a message to the raftActor
+ // which executes it safely run on the actor's thread dispatcher.
FiniteDuration timeout = FiniteDuration.create(newLeaderTimeoutInMillis, TimeUnit.MILLISECONDS);
newLeaderTimer = raftActor.getContext().system().scheduler().scheduleOnce(timeout, raftActor.self(),
(Runnable) () -> {
}, raftActor.getContext().system().dispatcher(), raftActor.self());
}
- void onNewLeader(String newLeader) {
+ void onNewLeader(final String newLeader) {
if (newLeader != null && newLeaderTimer != null) {
LOG.debug("{}: leader changed to {}", raftActor.persistenceId(), newLeader);
newLeaderTimer.cancel();
}
}
- private void finish(boolean success) {
+ private void finish(final boolean success) {
isTransferring = false;
if (transferTimer.isRunning()) {
transferTimer.stop();
raftActor.getLeaderId(), transferTimer);
} else {
LOG.warn("{}: Failed to transfer leadership in {}", raftActor.persistenceId(), transferTimer);
+ raftActor.unpauseLeader();
}
}
}
}
- void addOnComplete(OnComplete onComplete) {
+ void addOnComplete(final OnComplete onComplete) {
onCompleteCallbacks.add(onComplete);
}
return isTransferring;
}
- @VisibleForTesting
- void setNewLeaderTimeoutInMillis(long newLeaderTimeoutInMillis) {
- this.newLeaderTimeoutInMillis = newLeaderTimeoutInMillis;
+ void setNewLeaderTimeoutInMillis(final long newLeaderTimeoutInMillis) {
+ if (newLeaderTimeoutInMillis != USE_DEFAULT_LEADER_TIMEOUT) {
+ this.newLeaderTimeoutInMillis = newLeaderTimeoutInMillis;
+ }
+ }
+
+ public Optional<String> getRequestedFollowerId() {
+ return Optional.ofNullable(requestedFollowerId);
}
interface OnComplete {