use of org.opendaylight.controller.cluster.raft.messages.AppendEntries in project controller by opendaylight.
the class IsolationScenarioTest method testLeaderIsolationWithAllPriorEntriesCommitted.
/**
* Isolates the leader after all initial payload entries have been committed and applied on all nodes. While
* isolated, the majority partition elects a new leader and both sides of the partition attempt to commit one entry
* independently. After isolation is removed, the entry will conflict and both sides should reconcile their logs
* appropriately.
*/
@Test
public void testLeaderIsolationWithAllPriorEntriesCommitted() throws Exception {
testLog.info("testLeaderIsolationWithAllPriorEntriesCommitted starting");
createRaftActors();
// Send an initial payloads and verify replication.
final MockPayload payload0 = sendPayloadData(leaderActor, "zero");
final MockPayload payload1 = sendPayloadData(leaderActor, "one");
verifyApplyJournalEntries(leaderCollectorActor, 1);
verifyApplyJournalEntries(follower1CollectorActor, 1);
verifyApplyJournalEntries(follower2CollectorActor, 1);
isolateLeader();
// Send a payload to the isolated leader so it has an uncommitted log entry with index 2.
testLog.info("Sending payload to isolated leader");
final MockPayload isolatedLeaderPayload2 = sendPayloadData(leaderActor, "two");
// Wait for the isolated leader to send AppendEntries to follower1 with the entry at index 2. Note the message
// is collected but not forwarded to the follower RaftActor.
AppendEntries appendEntries = expectFirstMatching(follower1CollectorActor, AppendEntries.class);
assertEquals("getTerm", currentTerm, appendEntries.getTerm());
assertEquals("getLeaderId", leaderId, appendEntries.getLeaderId());
assertEquals("getEntries().size()", 1, appendEntries.getEntries().size());
verifyReplicatedLogEntry(appendEntries.getEntries().get(0), currentTerm, 2, isolatedLeaderPayload2);
// The leader should transition to IsolatedLeader.
expectFirstMatching(leaderNotifierActor, RoleChanged.class, rc -> rc.getNewRole().equals(RaftState.IsolatedLeader.name()));
forceElectionOnFollower1();
// Send a payload to the new leader follower1 with index 2 and verify it's replicated to follower2
// and committed.
testLog.info("Sending payload to new leader");
final MockPayload newLeaderPayload2 = sendPayloadData(follower1Actor, "two-new");
verifyApplyJournalEntries(follower1CollectorActor, 2);
verifyApplyJournalEntries(follower2CollectorActor, 2);
assertEquals("Follower 1 journal last term", currentTerm, follower1Context.getReplicatedLog().lastTerm());
assertEquals("Follower 1 journal last index", 2, follower1Context.getReplicatedLog().lastIndex());
assertEquals("Follower 1 commit index", 2, follower1Context.getCommitIndex());
verifyReplicatedLogEntry(follower1Context.getReplicatedLog().get(2), currentTerm, 2, newLeaderPayload2);
assertEquals("Follower 1 state", Lists.newArrayList(payload0, payload1, newLeaderPayload2), follower1Actor.underlyingActor().getState());
removeIsolation();
// Previous leader should switch to follower b/c it will receive either an AppendEntries or AppendEntriesReply
// with a higher term.
expectFirstMatching(leaderNotifierActor, RoleChanged.class, rc -> rc.getNewRole().equals(RaftState.Follower.name()));
// The previous leader has a conflicting log entry at index 2 with a different term which should get
// replaced by the new leader's index 1 entry.
verifyApplyJournalEntries(leaderCollectorActor, 2);
assertEquals("Prior leader journal last term", currentTerm, leaderContext.getReplicatedLog().lastTerm());
assertEquals("Prior leader journal last index", 2, leaderContext.getReplicatedLog().lastIndex());
assertEquals("Prior leader commit index", 2, leaderContext.getCommitIndex());
verifyReplicatedLogEntry(leaderContext.getReplicatedLog().get(2), currentTerm, 2, newLeaderPayload2);
assertEquals("Prior leader state", Lists.newArrayList(payload0, payload1, newLeaderPayload2), leaderActor.underlyingActor().getState());
testLog.info("testLeaderIsolationWithAllPriorEntriesCommitted ending");
}
use of org.opendaylight.controller.cluster.raft.messages.AppendEntries in project controller by opendaylight.
the class AbstractLeader method sendAppendEntriesToFollower.
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.
// - if the follower isn't active. In this case we don't know the state of the follower and we send an
// empty AppendEntries as a heart beat to prevent election.
// - if we're in the process of installing a snapshot. In this case we don't send any new entries but still
// need to send AppendEntries to prevent election.
// - if we're in the process of slicing an AppendEntries with a large log entry payload. In this case we
// need to send an empty AppendEntries to prevent election.
boolean isInstallingSnaphot = followerLogInformation.getInstallSnapshotState() != null;
long leaderCommitIndex = isInstallingSnaphot || followerLogInformation.isLogEntrySlicingInProgress() || !followerLogInformation.isFollowerActive() ? -1 : context.getCommitIndex();
long followerNextIndex = followerLogInformation.getNextIndex();
AppendEntries appendEntries = new AppendEntries(currentTerm(), context.getId(), getLogEntryIndex(followerNextIndex - 1), getLogEntryTerm(followerNextIndex - 1), entries, leaderCommitIndex, super.getReplicatedToAllIndex(), context.getPayloadVersion());
if (!entries.isEmpty() || log.isTraceEnabled()) {
log.debug("{}: Sending AppendEntries to follower {}: {}", logName(), followerLogInformation.getId(), appendEntries);
}
followerActor.tell(appendEntries, actor());
}
Aggregations