LOG.debug("{}: In requestVote: {}", logName(), requestVote);
+ boolean grantVote = canGrantVote(requestVote);
+
+ if(grantVote) {
+ context.getTermInformation().updateAndPersist(requestVote.getTerm(), requestVote.getCandidateId());
+ }
+
+ RequestVoteReply reply = new RequestVoteReply(currentTerm(), grantVote);
+
+ LOG.debug("{}: requestVote returning: {}", logName(), reply);
+
+ sender.tell(reply, actor());
+
+ return this;
+ }
+
+ protected boolean canGrantVote(RequestVote requestVote){
boolean grantVote = false;
// Reply false if term < currentTerm (§5.1)
// If votedFor is null or candidateId, and candidate’s log is at
// least as up-to-date as receiver’s log, grant vote (§5.2, §5.4)
} else if (votedFor() == null || votedFor()
- .equals(requestVote.getCandidateId())) {
+ .equals(requestVote.getCandidateId())) {
boolean candidateLatest = false;
if (requestVote.getLastLogTerm() > lastTerm()) {
candidateLatest = true;
} else if ((requestVote.getLastLogTerm() == lastTerm())
- && requestVote.getLastLogIndex() >= lastIndex()) {
+ && requestVote.getLastLogIndex() >= lastIndex()) {
candidateLatest = true;
}
if (candidateLatest) {
grantVote = true;
- context.getTermInformation().updateAndPersist(requestVote.getTerm(),
- requestVote.getCandidateId());
}
}
-
- RequestVoteReply reply = new RequestVoteReply(currentTerm(), grantVote);
-
- LOG.debug("{}: requestVote returning: {}", logName(), reply);
-
- sender.tell(reply, actor());
-
- return this;
+ return grantVote;
}
/**
import org.opendaylight.controller.cluster.raft.messages.InstallSnapshot;
import org.opendaylight.controller.cluster.raft.messages.InstallSnapshotReply;
import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
+import org.opendaylight.controller.cluster.raft.messages.RequestVote;
import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
/**
*/
public class Follower extends AbstractRaftActorBehavior {
-
-
private SnapshotTracker snapshotTracker = null;
private final InitialSyncStatusTracker initialSyncStatusTracker;
handleInstallSnapshot(sender, installSnapshot);
}
- scheduleElection(electionDuration());
+ if(message instanceof RaftRPC && (!(message instanceof RequestVote) || (canGrantVote((RequestVote) message)))){
+ scheduleElection(electionDuration());
+ }
return super.handleMessage(sender, message);
}
@Override
protected RaftActorBehavior createBehavior(RaftActorContext actorContext) {
- return new Follower(actorContext);
+ return new TestFollower(actorContext);
}
@Override
return context;
}
+ private int getElectionTimeoutCount(RaftActorBehavior follower){
+ if(follower instanceof TestFollower){
+ return ((TestFollower) follower).getElectionTimeoutCount();
+ }
+ return -1;
+ }
+
@Test
public void testThatAnElectionTimeoutIsTriggered(){
MockRaftActorContext actorContext = createActorContext();
assertEquals("isVoteGranted", true, reply.isVoteGranted());
assertEquals("getTerm", term, reply.getTerm());
+ assertEquals("schedule election", 1, getElectionTimeoutCount(follower));
}
@Test
RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(leaderActor, RequestVoteReply.class);
assertEquals("isVoteGranted", false, reply.isVoteGranted());
+ assertEquals("schedule election", 0, getElectionTimeoutCount(follower));
}
MockRaftActorContext context = createActorContext();
context.getReplicatedLog().clear(0,2);
- context.getReplicatedLog().append(newReplicatedLogEntry(1,100, "bar"));
+ context.getReplicatedLog().append(newReplicatedLogEntry(1, 100, "bar"));
context.getReplicatedLog().setSnapshotIndex(99);
List<ReplicatedLogEntry> entries = Arrays.asList(
MessageCollectorActor.assertNoneMatching(followerActor, ElectionTimeout.class, 500);
}
+ @Test
+ public void testElectionScheduledWhenAnyRaftRPCReceived(){
+ MockRaftActorContext context = createActorContext();
+ follower = createBehavior(context);
+ follower.handleMessage(leaderActor, new RaftRPC() {
+ @Override
+ public long getTerm() {
+ return 100;
+ }
+ });
+ assertEquals("schedule election", 1, getElectionTimeoutCount(follower));
+ }
+
+ @Test
+ public void testElectionNotScheduledWhenNonRaftRPCMessageReceived(){
+ MockRaftActorContext context = createActorContext();
+ follower = createBehavior(context);
+ follower.handleMessage(leaderActor, "non-raft-rpc");
+ assertEquals("schedule election", 0, getElectionTimeoutCount(follower));
+ }
public ByteString getNextChunk (ByteString bs, int offset, int chunkSize){
int snapshotLength = bs.size();
AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(replyActor, AppendEntriesReply.class);
assertEquals("isSuccess", true, reply.isSuccess());
}
+
+ private static class TestFollower extends Follower {
+
+ int electionTimeoutCount = 0;
+
+ public TestFollower(RaftActorContext context) {
+ super(context);
+ }
+
+ @Override
+ protected void scheduleElection(FiniteDuration interval) {
+ electionTimeoutCount++;
+ super.scheduleElection(interval);
+ }
+
+ public int getElectionTimeoutCount() {
+ return electionTimeoutCount;
+ }
+ }
}