+ @Test
+ public void testFollowerRecoveryAfterInstallSnapshot() throws Exception {
+
+ send2InitialPayloads();
+
+ leader = leaderActor.underlyingActor().getCurrentBehavior();
+
+ follower2Actor = newTestRaftActor(follower2Id, ImmutableMap.of(leaderId, testActorPath(leaderId)),
+ newFollowerConfigParams());
+ follower2CollectorActor = follower2Actor.underlyingActor().collectorActor();
+
+ leaderActor.tell(new SetPeerAddress(follower2Id, follower2Actor.path().toString()), ActorRef.noSender());
+
+ MockPayload payload2 = sendPayloadData(leaderActor, "two");
+
+ // Verify the leader applies the 3rd payload state.
+ MessageCollectorActor.expectMatching(leaderCollectorActor, ApplyJournalEntries.class, 1);
+
+ MessageCollectorActor.expectMatching(follower2CollectorActor, ApplyJournalEntries.class, 1);
+
+ assertEquals("Leader commit index", 2, leaderContext.getCommitIndex());
+ assertEquals("Leader last applied", 2, leaderContext.getLastApplied());
+ assertEquals("Leader snapshot index", 1, leaderContext.getReplicatedLog().getSnapshotIndex());
+ assertEquals("Leader replicatedToAllIndex", 1, leader.getReplicatedToAllIndex());
+
+ killActor(follower2Actor);
+
+ InMemoryJournal.clear();
+
+ follower2Actor = newTestRaftActor(follower2Id, ImmutableMap.of(leaderId, testActorPath(leaderId)),
+ newFollowerConfigParams());
+ TestRaftActor follower2Underlying = follower2Actor.underlyingActor();
+ follower2CollectorActor = follower2Underlying.collectorActor();
+ follower2Context = follower2Underlying.getRaftActorContext();
+
+ leaderActor.tell(new SetPeerAddress(follower2Id, follower2Actor.path().toString()), ActorRef.noSender());
+
+ // The leader should install a snapshot so wait for the follower to receive ApplySnapshot.
+ MessageCollectorActor.expectFirstMatching(follower2CollectorActor, ApplySnapshot.class);
+
+ // Wait for the follower to persist the snapshot.
+ MessageCollectorActor.expectFirstMatching(follower2CollectorActor, SaveSnapshotSuccess.class);
+
+ // The last applied entry on the leader is included in the snapshot but is also sent in a subsequent
+ // AppendEntries because the InstallSnapshot message lastIncludedIndex field is set to the leader's
+ // snapshotIndex and not the actual last index included in the snapshot.
+ // FIXME? - is this OK?
+ MessageCollectorActor.expectFirstMatching(follower2CollectorActor, ApplyState.class);
+ List<MockPayload> expFollowerState = Arrays.asList(payload0, payload1, payload2, payload2);
+
+ assertEquals("Follower commit index", 2, follower2Context.getCommitIndex());
+ assertEquals("Follower last applied", 2, follower2Context.getLastApplied());
+ assertEquals("Follower snapshot index", 1, follower2Context.getReplicatedLog().getSnapshotIndex());
+ assertEquals("Follower state", expFollowerState, follower2Underlying.getState());
+
+ killActor(follower2Actor);
+
+ follower2Actor = newTestRaftActor(follower2Id, ImmutableMap.of(leaderId, testActorPath(leaderId)),
+ newFollowerConfigParams());
+
+ follower2Underlying = follower2Actor.underlyingActor();
+ follower2Underlying.waitForRecoveryComplete();
+ follower2Context = follower2Underlying.getRaftActorContext();
+
+ assertEquals("Follower commit index", 2, follower2Context.getCommitIndex());
+ assertEquals("Follower last applied", 2, follower2Context.getLastApplied());
+ assertEquals("Follower snapshot index", 1, follower2Context.getReplicatedLog().getSnapshotIndex());
+ assertEquals("Follower state", expFollowerState, follower2Underlying.getState());
+ }
+