import org.opendaylight.controller.cluster.raft.ClientRequestTracker;
import org.opendaylight.controller.cluster.raft.ClientRequestTrackerImpl;
import org.opendaylight.controller.cluster.raft.FollowerLogInformation;
-import org.opendaylight.controller.cluster.raft.FollowerLogInformationImpl;
import org.opendaylight.controller.cluster.raft.PeerInfo;
import org.opendaylight.controller.cluster.raft.RaftActorContext;
import org.opendaylight.controller.cluster.raft.RaftState;
import scala.concurrent.duration.FiniteDuration;
/**
- * The behavior of a RaftActor when it is in the Leader state
+ * The behavior of a RaftActor when it is in the Leader state.
*
* <p>
* Leaders:
trackers.addAll(initializeFromLeader.trackers);
} else {
for (PeerInfo peerInfo: context.getPeers()) {
- FollowerLogInformation followerLogInformation = new FollowerLogInformationImpl(peerInfo, -1, context);
+ FollowerLogInformation followerLogInformation = new FollowerLogInformation(peerInfo, context);
followerToLog.put(peerInfo.getId(), followerLogInformation);
}
}
}
public void addFollower(final String followerId) {
- FollowerLogInformation followerLogInformation = new FollowerLogInformationImpl(
- context.getPeerInfo(followerId), -1, context);
+ FollowerLogInformation followerLogInformation = new FollowerLogInformation(context.getPeerInfo(followerId),
+ context);
followerToLog.put(followerId, followerLogInformation);
if (heartbeatSchedule == null) {
}
@VisibleForTesting
- void setSnapshot(@Nullable final SnapshotHolder snapshotHolder) {
+ void setSnapshotHolder(@Nullable final SnapshotHolder snapshotHolder) {
this.snapshotHolder = Optional.fromNullable(snapshotHolder);
}
return this;
}
- if (followerLogInformation.timeSinceLastActivity()
- > context.getConfigParams().getElectionTimeOutInterval().toMillis()) {
+ final long lastActivityNanos = followerLogInformation.nanosSinceLastActivity();
+ if (lastActivityNanos > context.getConfigParams().getElectionTimeOutInterval().toNanos()) {
log.warn("{} : handleAppendEntriesReply delayed beyond election timeout, "
+ "appendEntriesReply : {}, timeSinceLastActivity : {}, lastApplied : {}, commitIndex : {}",
- logName(), appendEntriesReply, followerLogInformation.timeSinceLastActivity(),
+ logName(), appendEntriesReply, TimeUnit.NANOSECONDS.toMillis(lastActivityNanos),
context.getLastApplied(), context.getCommitIndex());
}
followerLogInformation.markFollowerActive();
followerLogInformation.setPayloadVersion(appendEntriesReply.getPayloadVersion());
followerLogInformation.setRaftVersion(appendEntriesReply.getRaftVersion());
+ followerLogInformation.setNeedsLeaderAddress(appendEntriesReply.isNeedsLeaderAddress());
long followerLastLogIndex = appendEntriesReply.getLogLastIndex();
- long followersLastLogTermInLeadersLog = getLogEntryTerm(followerLastLogIndex);
boolean updated = false;
if (appendEntriesReply.getLogLastIndex() > context.getReplicatedLog().lastIndex()) {
// The follower's log is actually ahead of the leader's log. Normally this doesn't happen
// However in this case the log terms won't match and the logs will conflict - this is handled
// elsewhere.
log.info("{}: handleAppendEntriesReply: follower {} lastIndex {} is ahead of our lastIndex {} "
- + "(snapshotIndex {}) - forcing install snaphot", logName(), followerLogInformation.getId(),
- appendEntriesReply.getLogLastIndex(), context.getReplicatedLog().lastIndex(),
- context.getReplicatedLog().getSnapshotIndex());
+ + "(snapshotIndex {}, snapshotTerm {}) - forcing install snaphot", logName(),
+ followerLogInformation.getId(), appendEntriesReply.getLogLastIndex(),
+ context.getReplicatedLog().lastIndex(), context.getReplicatedLog().getSnapshotIndex(),
+ context.getReplicatedLog().getSnapshotTerm());
followerLogInformation.setMatchIndex(-1);
followerLogInformation.setNextIndex(-1);
updated = true;
} else if (appendEntriesReply.isSuccess()) {
+ long followersLastLogTermInLeadersLog = getLogEntryTerm(followerLastLogIndex);
if (followerLastLogIndex >= 0 && followersLastLogTermInLeadersLog >= 0
&& followersLastLogTermInLeadersLog != appendEntriesReply.getLogLastTerm()) {
// The follower's last entry is present in the leader's journal but the terms don't match so the
updated = updateFollowerLogInformation(followerLogInformation, appendEntriesReply);
}
} else {
- log.info("{}: handleAppendEntriesReply - received unsuccessful reply: {}, leader snapshotIndex: {}",
- logName(), appendEntriesReply, context.getReplicatedLog().getSnapshotIndex());
+ log.info("{}: handleAppendEntriesReply - received unsuccessful reply: {}, leader snapshotIndex: {}, "
+ + "snapshotTerm: {}, replicatedToAllIndex: {}", logName(), appendEntriesReply,
+ context.getReplicatedLog().getSnapshotIndex(), context.getReplicatedLog().getSnapshotTerm(),
+ getReplicatedToAllIndex());
+ long followersLastLogTermInLeadersLogOrSnapshot = getLogEntryOrSnapshotTerm(followerLastLogIndex);
if (appendEntriesReply.isForceInstallSnapshot()) {
// Reset the followers match and next index. This is to signal that this follower has nothing
// in common with this Leader and so would require a snapshot to be installed
// Force initiate a snapshot capture
initiateCaptureSnapshot(followerId);
- } else if (followerLastLogIndex < 0 || followersLastLogTermInLeadersLog >= 0
- && followersLastLogTermInLeadersLog == appendEntriesReply.getLogLastTerm()) {
- // The follower's log is empty or the last entry is present in the leader's journal
- // and the terms match so the follower is just behind the leader's journal from
- // the last snapshot, if any. We'll catch up the follower quickly by starting at the
- // follower's last log index.
+ } else if (followerLastLogIndex < 0 || followersLastLogTermInLeadersLogOrSnapshot >= 0
+ && followersLastLogTermInLeadersLogOrSnapshot == appendEntriesReply.getLogLastTerm()) {
+ // The follower's log is empty or the follower's last entry is present in the leader's journal or
+ // snapshot and the terms match so the follower is just behind the leader's journal from the last
+ // snapshot, if any. We'll catch up the follower quickly by starting at the follower's last log index.
updated = updateFollowerLogInformation(followerLogInformation, appendEntriesReply);
log.info("{}: follower {} appears to be behind the leader from the last snapshot - "
+ "updated: matchIndex: {}, nextIndex: {}", logName(), followerId,
- appendEntriesReply.getLogLastTerm(), followerLogInformation.getMatchIndex(),
- followerLogInformation.getNextIndex());
+ followerLogInformation.getMatchIndex(), followerLogInformation.getNextIndex());
} else {
// The follower's log conflicts with leader's log so decrement follower's next index by 1
// in an attempt to find where the logs match.
log.info("{}: follower {} last log term {} conflicts with the leader's {} - dec next index to {}",
logName(), followerId, appendEntriesReply.getLogLastTerm(),
- followersLastLogTermInLeadersLog, followerLogInformation.getNextIndex());
+ followersLastLogTermInLeadersLogOrSnapshot, followerLogInformation.getNextIndex());
}
}
}
scheduleHeartBeat(context.getConfigParams().getHeartBeatInterval());
} else if (message instanceof SendInstallSnapshot) {
SendInstallSnapshot sendInstallSnapshot = (SendInstallSnapshot) message;
- setSnapshot(new SnapshotHolder(sendInstallSnapshot.getSnapshot(), sendInstallSnapshot.getSnapshotBytes()));
+ setSnapshotHolder(new SnapshotHolder(sendInstallSnapshot.getSnapshot(),
+ sendInstallSnapshot.getSnapshotBytes()));
sendInstallSnapshot();
} else if (message instanceof Replicate) {
replicate((Replicate) message);
if (!anyFollowersInstallingSnapshot()) {
// once there are no pending followers receiving snapshots
// we can remove snapshot from the memory
- setSnapshot(null);
+ setSnapshotHolder(null);
}
wasLastChunk = true;
}
}
- protected void sendAppendEntries(final long timeSinceLastActivityInterval, final boolean isHeartbeat) {
+ protected void sendAppendEntries(final long timeSinceLastActivityIntervalNanos, final boolean isHeartbeat) {
// Send an AppendEntries to all followers
for (Entry<String, FollowerLogInformation> e : followerToLog.entrySet()) {
final String followerId = e.getKey();
final FollowerLogInformation followerLogInformation = e.getValue();
// This checks helps not to send a repeat message to the follower
if (!followerLogInformation.isFollowerActive()
- || followerLogInformation.timeSinceLastActivity() >= timeSinceLastActivityInterval) {
+ || followerLogInformation.nanosSinceLastActivity() >= timeSinceLastActivityIntervalNanos) {
sendUpdatesToFollower(followerId, followerLogInformation, true, isHeartbeat);
}
}
}
}
- private List<ReplicatedLogEntry> getEntriesToSend(FollowerLogInformation followerLogInfo,
- ActorSelection followerActor) {
+ private List<ReplicatedLogEntry> getEntriesToSend(final FollowerLogInformation followerLogInfo,
+ final ActorSelection followerActor) {
// Try to get all the entries in the journal but not exceeding the max data size for a single AppendEntries
// message.
int maxEntries = (int) context.getReplicatedLog().size();
return Collections.emptyList();
}
- private void sendAppendEntriesToFollower(ActorSelection followerActor, List<ReplicatedLogEntry> entries,
- FollowerLogInformation followerLogInformation) {
+ private void sendAppendEntriesToFollower(final ActorSelection followerActor, final List<ReplicatedLogEntry> entries,
+ final FollowerLogInformation followerLogInformation) {
// In certain cases outlined below we don't want to send the actual commit index to prevent the follower from
// possibly committing and applying conflicting entries (those with same index, different term) from a prior
// term that weren't replicated to a majority, which would be a violation of raft.
AppendEntries appendEntries = new AppendEntries(currentTerm(), context.getId(),
getLogEntryIndex(followerNextIndex - 1),
getLogEntryTerm(followerNextIndex - 1), entries,
- leaderCommitIndex, super.getReplicatedToAllIndex(), context.getPayloadVersion());
+ leaderCommitIndex, super.getReplicatedToAllIndex(), context.getPayloadVersion(),
+ followerLogInformation.getRaftVersion(), followerLogInformation.needsLeaderAddress(getId()));
if (!entries.isEmpty() || log.isTraceEnabled()) {
log.debug("{}: Sending AppendEntries to follower {}: {}", logName(), followerLogInformation.getId(),
private void sendHeartBeat() {
if (!followerToLog.isEmpty()) {
log.trace("{}: Sending heartbeat", logName());
- sendAppendEntries(context.getConfigParams().getHeartBeatInterval().toMillis(), true);
+ sendAppendEntries(context.getConfigParams().getHeartBeatInterval().toNanos(), true);
appendEntriesMessageSlicer.checkExpiredSlicedMessageState();
}