use of org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply in project controller by opendaylight.
the class LeaderTest method testHandleReplicateMessageWithHigherTermThanPreviousEntry.
@Test
public void testHandleReplicateMessageWithHigherTermThanPreviousEntry() throws Exception {
logStart("testHandleReplicateMessageWithHigherTermThanPreviousEntry");
MockRaftActorContext actorContext = createActorContextWithFollower();
actorContext.setCommitIndex(-1);
actorContext.setLastApplied(-1);
// The raft context is initialized with a couple log entries. However the commitIndex
// is -1, simulating that the leader previously didn't get consensus and thus the log entries weren't
// committed and applied. Now it regains leadership with a higher term (2).
long prevTerm = actorContext.getTermInformation().getCurrentTerm();
long newTerm = prevTerm + 1;
actorContext.getTermInformation().update(newTerm, "");
leader = new Leader(actorContext);
actorContext.setCurrentBehavior(leader);
// Leader will send an immediate heartbeat - ignore it.
MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class);
// The follower replies with the leader's current last index and term, simulating that it is
// up to date with the leader.
long lastIndex = actorContext.getReplicatedLog().lastIndex();
leader.handleMessage(followerActor, new AppendEntriesReply(FOLLOWER_ID, newTerm, true, lastIndex, prevTerm, (short) 0));
// The commit index should not get updated even though consensus was reached. This is b/c the
// last entry's term does match the current term. As per §5.4.1, "Raft never commits log entries
// from previous terms by counting replicas".
assertEquals("Commit Index", -1, actorContext.getCommitIndex());
followerActor.underlyingActor().clear();
// Now replicate a new entry with the new term 2.
long newIndex = lastIndex + 1;
sendReplicate(actorContext, newTerm, newIndex);
AppendEntries appendEntries = MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class);
assertEquals("getPrevLogIndex", lastIndex, appendEntries.getPrevLogIndex());
assertEquals("getPrevLogTerm", prevTerm, appendEntries.getPrevLogTerm());
assertEquals("Entries size", 1, appendEntries.getEntries().size());
assertEquals("Entry getIndex", newIndex, appendEntries.getEntries().get(0).getIndex());
assertEquals("Entry getTerm", newTerm, appendEntries.getEntries().get(0).getTerm());
assertEquals("Entry payload", "foo", appendEntries.getEntries().get(0).getData().toString());
// The follower replies with success. The leader should now update the commit index to the new index
// as per §5.4.1 "once an entry from the current term is committed by counting replicas, then all
// prior entries are committed indirectly".
leader.handleMessage(followerActor, new AppendEntriesReply(FOLLOWER_ID, newTerm, true, newIndex, newTerm, (short) 0));
assertEquals("Commit Index", newIndex, actorContext.getCommitIndex());
}
use of org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply in project controller by opendaylight.
the class LeaderTest method testReplicationConsensusWithNonVotingFollower.
@Test
public void testReplicationConsensusWithNonVotingFollower() {
logStart("testReplicationConsensusWithNonVotingFollower");
MockRaftActorContext leaderActorContext = createActorContextWithFollower();
((DefaultConfigParamsImpl) leaderActorContext.getConfigParams()).setHeartBeatInterval(new FiniteDuration(1000, TimeUnit.SECONDS));
leaderActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().build());
leaderActorContext.setCommitIndex(-1);
leaderActorContext.setLastApplied(-1);
String nonVotingFollowerId = "nonvoting-follower";
ActorRef nonVotingFollowerActor = actorFactory.createActor(MessageCollectorActor.props(), actorFactory.generateActorId(nonVotingFollowerId));
leaderActorContext.addToPeers(nonVotingFollowerId, nonVotingFollowerActor.path().toString(), VotingState.NON_VOTING);
leader = new Leader(leaderActorContext);
leaderActorContext.setCurrentBehavior(leader);
// Ignore initial heartbeats
MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class);
MessageCollectorActor.expectFirstMatching(nonVotingFollowerActor, AppendEntries.class);
MessageCollectorActor.clearMessages(followerActor);
MessageCollectorActor.clearMessages(nonVotingFollowerActor);
MessageCollectorActor.clearMessages(leaderActor);
// Send a Replicate message and wait for AppendEntries.
sendReplicate(leaderActorContext, 0);
MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class);
MessageCollectorActor.expectFirstMatching(nonVotingFollowerActor, AppendEntries.class);
// Send reply only from the voting follower and verify consensus via ApplyState.
leader.handleMessage(leaderActor, new AppendEntriesReply(FOLLOWER_ID, 1, true, 0, 1, (short) 0));
MessageCollectorActor.expectFirstMatching(leaderActor, ApplyState.class);
leader.handleMessage(leaderActor, new AppendEntriesReply(nonVotingFollowerId, 1, true, 0, 1, (short) 0));
MessageCollectorActor.clearMessages(followerActor);
MessageCollectorActor.clearMessages(nonVotingFollowerActor);
MessageCollectorActor.clearMessages(leaderActor);
// Send another Replicate message
sendReplicate(leaderActorContext, 1);
MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class);
AppendEntries appendEntries = MessageCollectorActor.expectFirstMatching(nonVotingFollowerActor, AppendEntries.class);
assertEquals("Log entries size", 1, appendEntries.getEntries().size());
assertEquals("Log entry index", 1, appendEntries.getEntries().get(0).getIndex());
// Send reply only from the non-voting follower and verify no consensus via no ApplyState.
leader.handleMessage(leaderActor, new AppendEntriesReply(nonVotingFollowerId, 1, true, 1, 1, (short) 0));
MessageCollectorActor.assertNoneMatching(leaderActor, ApplyState.class, 500);
// Send reply from the voting follower and verify consensus.
leader.handleMessage(leaderActor, new AppendEntriesReply(FOLLOWER_ID, 1, true, 1, 1, (short) 0));
MessageCollectorActor.expectFirstMatching(leaderActor, ApplyState.class);
}
use of org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply in project controller by opendaylight.
the class RaftActorTest method testFakeSnapshotsForLeaderWithInRealSnapshots.
@Test
public void testFakeSnapshotsForLeaderWithInRealSnapshots() throws Exception {
final String persistenceId = factory.generateActorId("leader-");
final String follower1Id = factory.generateActorId("follower-");
ActorRef followerActor1 = factory.createActor(MessageCollectorActor.props());
DefaultConfigParamsImpl config = new DefaultConfigParamsImpl();
config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS));
config.setIsolatedLeaderCheckInterval(new FiniteDuration(1, TimeUnit.DAYS));
DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class);
Map<String, String> peerAddresses = new HashMap<>();
peerAddresses.put(follower1Id, followerActor1.path().toString());
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId, peerAddresses, config, dataPersistenceProvider), persistenceId);
MockRaftActor leaderActor = mockActorRef.underlyingActor();
leaderActor.getRaftActorContext().setCommitIndex(4);
leaderActor.getRaftActorContext().setLastApplied(4);
leaderActor.getRaftActorContext().getTermInformation().update(1, persistenceId);
leaderActor.waitForInitializeBehaviorComplete();
// create 8 entries in the log - 0 to 4 are applied and will get picked up as part of the capture snapshot
Leader leader = new Leader(leaderActor.getRaftActorContext());
leaderActor.setCurrentBehavior(leader);
assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state());
MockRaftActorContext.MockReplicatedLogBuilder logBuilder = new MockRaftActorContext.MockReplicatedLogBuilder();
leaderActor.getRaftActorContext().setReplicatedLog(logBuilder.createEntries(0, 8, 1).build());
assertEquals(8, leaderActor.getReplicatedLog().size());
leaderActor.getRaftActorContext().getSnapshotManager().capture(new SimpleReplicatedLogEntry(6, 1, new MockRaftActorContext.MockPayload("x")), 4);
verify(leaderActor.snapshotCohortDelegate).createSnapshot(anyObject(), anyObject());
assertEquals(8, leaderActor.getReplicatedLog().size());
assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state());
// fake snapshot on index 5
leaderActor.onReceiveCommand(new AppendEntriesReply(follower1Id, 1, true, 5, 1, (short) 0));
assertEquals(8, leaderActor.getReplicatedLog().size());
// fake snapshot on index 6
assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state());
leaderActor.onReceiveCommand(new AppendEntriesReply(follower1Id, 1, true, 6, 1, (short) 0));
assertEquals(8, leaderActor.getReplicatedLog().size());
assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state());
assertEquals(8, leaderActor.getReplicatedLog().size());
MockSnapshotState snapshotState = new MockSnapshotState(Arrays.asList(new MockRaftActorContext.MockPayload("foo-0"), new MockRaftActorContext.MockPayload("foo-1"), new MockRaftActorContext.MockPayload("foo-2"), new MockRaftActorContext.MockPayload("foo-3"), new MockRaftActorContext.MockPayload("foo-4")));
leaderActor.getRaftActorContext().getSnapshotManager().persist(snapshotState, java.util.Optional.empty(), Runtime.getRuntime().totalMemory());
assertTrue(leaderActor.getRaftActorContext().getSnapshotManager().isCapturing());
// The commit is needed to complete the snapshot creation process
leaderActor.getRaftActorContext().getSnapshotManager().commit(-1, -1);
// capture snapshot reply should remove the snapshotted entries only
assertEquals(3, leaderActor.getReplicatedLog().size());
assertEquals(7, leaderActor.getReplicatedLog().lastIndex());
// add another non-replicated entry
leaderActor.getReplicatedLog().append(new SimpleReplicatedLogEntry(8, 1, new MockRaftActorContext.MockPayload("foo-8")));
// fake snapshot on index 7, since lastApplied = 7 , we would keep the last applied
leaderActor.onReceiveCommand(new AppendEntriesReply(follower1Id, 1, true, 7, 1, (short) 0));
assertEquals(2, leaderActor.getReplicatedLog().size());
assertEquals(8, leaderActor.getReplicatedLog().lastIndex());
}
use of org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply in project controller by opendaylight.
the class AbstractRaftActorBehaviorTest method handleAppendEntriesAddSameEntryToLogReply.
protected void handleAppendEntriesAddSameEntryToLogReply(final ActorRef replyActor) {
AppendEntriesReply reply = MessageCollectorActor.getFirstMatching(replyActor, AppendEntriesReply.class);
Assert.assertNull("Expected no AppendEntriesReply", reply);
}
use of org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply in project controller by opendaylight.
the class FollowerTest method testHandleFirstAppendEntriesWithPrevIndexMinusOne.
@Test
public void testHandleFirstAppendEntriesWithPrevIndexMinusOne() {
logStart("testHandleFirstAppendEntries");
MockRaftActorContext context = createActorContext();
List<ReplicatedLogEntry> entries = Arrays.asList(newReplicatedLogEntry(2, 101, "foo"));
// The new commitIndex is 101
AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 101, 100, (short) 0);
follower = createBehavior(context);
follower.handleMessage(leaderActor, appendEntries);
FollowerInitialSyncUpStatus syncStatus = MessageCollectorActor.expectFirstMatching(followerActor, FollowerInitialSyncUpStatus.class);
AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(leaderActor, AppendEntriesReply.class);
assertFalse(syncStatus.isInitialSyncDone());
assertFalse("append entries reply should be false", reply.isSuccess());
}
Aggregations