import org.opendaylight.controller.cluster.raft.Snapshot;
import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot;
import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
+import org.opendaylight.controller.cluster.raft.base.messages.FollowerInitialSyncUpStatus;
import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
import org.opendaylight.controller.cluster.raft.messages.InstallSnapshot;
*/
public class Follower extends AbstractRaftActorBehavior {
+
+
private SnapshotTracker snapshotTracker = null;
+ private final InitialSyncStatusTracker initialSyncStatusTracker;
+
public Follower(RaftActorContext context) {
super(context, RaftState.Follower);
- scheduleElection(electionDuration());
+ initialSyncStatusTracker = new InitialSyncStatusTracker(context.getActor());
+
+ if(context.getPeerAddresses().isEmpty()){
+ actor().tell(ELECTION_TIMEOUT, actor());
+ } else {
+ scheduleElection(electionDuration());
+ }
+
}
private boolean isLogEntryPresent(long index){
return -1;
}
+ private void updateInitialSyncStatus(long currentLeaderCommit, String leaderId){
+ initialSyncStatusTracker.update(leaderId, currentLeaderCommit, context.getCommitIndex());
+ }
+
@Override protected RaftActorBehavior handleAppendEntries(ActorRef sender,
AppendEntries appendEntries) {
// to make it easier to read. Before refactoring ensure tests
// cover the code properly
+ if (snapshotTracker != null) {
+ // if snapshot install is in progress, follower should just acknowledge append entries with a reply.
+ AppendEntriesReply reply = new AppendEntriesReply(context.getId(), currentTerm(), true,
+ lastIndex(), lastTerm(), context.getPayloadVersion());
+
+ if(LOG.isDebugEnabled()) {
+ LOG.debug("{}: snapshot install is in progress, replying immediately with {}", logName(), reply);
+ }
+ sender.tell(reply, actor());
+
+ return this;
+ }
+
// 1. Reply false if term < currentTerm (§5.1)
// This is handled in the appendEntries method of the base class
// If we got here then we do appear to be talking to the leader
leaderId = appendEntries.getLeaderId();
+ setLeaderPayloadVersion(appendEntries.getPayloadVersion());
+
// 2. Reply false if log doesn’t contain an entry at prevLogIndex
// whose term matches prevLogTerm (§5.3)
long prevLogTerm = getLogEntryTerm(appendEntries.getPrevLogIndex());
boolean prevEntryPresent = isLogEntryPresent(appendEntries.getPrevLogIndex());
+ updateInitialSyncStatus(appendEntries.getLeaderCommit(), appendEntries.getLeaderId());
boolean outOfSync = true;
logName(), lastIndex, lastTerm());
sender.tell(new AppendEntriesReply(context.getId(), currentTerm(), false, lastIndex,
- lastTerm()), actor());
+ lastTerm(), context.getPayloadVersion()), actor());
return this;
}
}
AppendEntriesReply reply = new AppendEntriesReply(context.getId(), currentTerm(), true,
- lastIndex, lastTerm());
+ lastIndex, lastTerm(), context.getPayloadVersion());
if(LOG.isTraceEnabled()) {
LOG.trace("{}: handleAppendEntries returning : {}", logName(), reply);
sender.tell(reply, actor());
- if (!context.isSnapshotCaptureInitiated()) {
+ if (!context.getSnapshotManager().isCapturing()) {
super.performSnapshotWithoutCapture(appendEntries.getReplicatedToAllIndex());
}
private void handleInstallSnapshot(ActorRef sender, InstallSnapshot installSnapshot) {
-
LOG.debug("{}: InstallSnapshot received from leader {}, datasize: {} , Chunk: {}/{}",
logName(), installSnapshot.getLeaderId(), installSnapshot.getData().size(),
installSnapshot.getChunkIndex(), installSnapshot.getTotalChunks());
snapshotTracker = new SnapshotTracker(LOG, installSnapshot.getTotalChunks());
}
+ updateInitialSyncStatus(installSnapshot.getLastIncludedIndex(), installSnapshot.getLeaderId());
+
try {
if(snapshotTracker.addChunk(installSnapshot.getChunkIndex(), installSnapshot.getData(),
installSnapshot.getLastChunkHashCode())){
SnapshotTracker getSnapshotTracker(){
return snapshotTracker;
}
+
+ private class InitialSyncStatusTracker {
+
+ private static final long INVALID_LOG_INDEX = -2L;
+ private long initialLeaderCommit = INVALID_LOG_INDEX;
+ private boolean initialSyncUpDone = false;
+ private String syncedLeaderId = null;
+ private final ActorRef actor;
+
+ public InitialSyncStatusTracker(ActorRef actor) {
+ this.actor = actor;
+ }
+
+ public void update(String leaderId, long leaderCommit, long commitIndex){
+
+ if(!leaderId.equals(syncedLeaderId)){
+ initialSyncUpDone = false;
+ initialLeaderCommit = INVALID_LOG_INDEX;
+ syncedLeaderId = leaderId;
+ }
+
+ if(!initialSyncUpDone){
+ if(initialLeaderCommit == INVALID_LOG_INDEX){
+ actor.tell(new FollowerInitialSyncUpStatus(false, getId()), ActorRef.noSender());
+ initialLeaderCommit = leaderCommit;
+ } else if(commitIndex >= initialLeaderCommit){
+ actor.tell(new FollowerInitialSyncUpStatus(true, getId()), ActorRef.noSender());
+ initialSyncUpDone = true;
+ }
+ }
+ }
+ }
}