package org.opendaylight.controller.cluster.raft;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import akka.actor.ActorRef;
import akka.actor.PoisonPill;
import akka.actor.Props;
+import akka.actor.Status.Failure;
import akka.actor.Terminated;
import akka.dispatch.Dispatchers;
import akka.japi.Procedure;
-import akka.persistence.RecoveryCompleted;
import akka.persistence.SaveSnapshotFailure;
import akka.persistence.SaveSnapshotSuccess;
import akka.persistence.SnapshotMetadata;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.commons.lang3.SerializationUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.opendaylight.controller.cluster.DataPersistenceProvider;
import org.opendaylight.controller.cluster.NonPersistentDataProvider;
import org.opendaylight.controller.cluster.PersistentDataProvider;
import org.opendaylight.controller.cluster.notifications.LeaderStateChanged;
import org.opendaylight.controller.cluster.notifications.RoleChanged;
+import org.opendaylight.controller.cluster.raft.MockRaftActorContext.MockPayload;
+import org.opendaylight.controller.cluster.raft.ServerConfigurationPayload.ServerInfo;
import org.opendaylight.controller.cluster.raft.base.messages.ApplyJournalEntries;
import org.opendaylight.controller.cluster.raft.base.messages.ApplyLogEntries;
import org.opendaylight.controller.cluster.raft.base.messages.ApplySnapshot;
import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshot;
import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply;
import org.opendaylight.controller.cluster.raft.base.messages.DeleteEntries;
+import org.opendaylight.controller.cluster.raft.base.messages.LeaderTransitioning;
import org.opendaylight.controller.cluster.raft.base.messages.SendHeartBeat;
import org.opendaylight.controller.cluster.raft.base.messages.SwitchBehavior;
import org.opendaylight.controller.cluster.raft.base.messages.UpdateElectionTerm;
import org.opendaylight.controller.cluster.raft.behaviors.Follower;
import org.opendaylight.controller.cluster.raft.behaviors.Leader;
import org.opendaylight.controller.cluster.raft.behaviors.RaftActorBehavior;
+import org.opendaylight.controller.cluster.raft.client.messages.GetSnapshot;
+import org.opendaylight.controller.cluster.raft.client.messages.GetSnapshotReply;
import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
+import org.opendaylight.controller.cluster.raft.policy.DisableElectionsRaftPolicy;
import org.opendaylight.controller.cluster.raft.utils.InMemoryJournal;
import org.opendaylight.controller.cluster.raft.utils.InMemorySnapshotStore;
import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import scala.concurrent.duration.Duration;
import scala.concurrent.duration.FiniteDuration;
public class RaftActorTest extends AbstractActorTest {
kit.waitUntilLeader();
}
+
@Test
public void testRaftActorRecoveryWithPersistenceEnabled() throws Exception {
TEST_LOG.info("testRaftActorRecoveryWithPersistenceEnabled starting");
ImmutableMap<String, String> peerAddresses = ImmutableMap.<String, String>builder().put("member1", "address").build();
ActorRef followerActor = factory.createActor(MockRaftActor.props(persistenceId,
- peerAddresses, Optional.<ConfigParams>of(config)), persistenceId);
+ peerAddresses, config), persistenceId);
watch(followerActor);
//reinstate the actor
TestActorRef<MockRaftActor> ref = factory.createTestActor(
- MockRaftActor.props(persistenceId, peerAddresses, Optional.<ConfigParams>of(config)));
+ MockRaftActor.props(persistenceId, peerAddresses, config));
MockRaftActor mockRaftActor = ref.underlyingActor();
TestActorRef<MockRaftActor> ref = factory.createTestActor(MockRaftActor.props(persistenceId,
ImmutableMap.<String, String>builder().put("member1", "address").build(),
- Optional.<ConfigParams>of(config), new NonPersistentDataProvider()), persistenceId);
+ config, new NonPersistentDataProvider()), persistenceId);
MockRaftActor mockRaftActor = ref.underlyingActor();
TestActorRef<MockRaftActor> ref = factory.createTestActor(MockRaftActor.props(persistenceId,
ImmutableMap.<String, String>builder().put("member1", "address").build(),
- Optional.<ConfigParams>of(config), new NonPersistentDataProvider()).
+ config, new NonPersistentDataProvider()).
withDispatcher(Dispatchers.DefaultDispatcherId()), persistenceId);
InMemoryJournal.waitForWriteMessagesComplete(persistenceId);
config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS));
ref = factory.createTestActor(MockRaftActor.props(persistenceId,
- ImmutableMap.<String, String>builder().put("member1", "address").build(),
- Optional.<ConfigParams>of(config), new NonPersistentDataProvider()).
+ ImmutableMap.<String, String>builder().put("member1", "address").build(), config,
+ new NonPersistentDataProvider()).
withDispatcher(Dispatchers.DefaultDispatcherId()),
factory.generateActorId("follower-"));
config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS));
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId,
- Collections.<String, String>emptyMap(), Optional.<ConfigParams>of(config)), persistenceId);
+ Collections.<String, String>emptyMap(), config), persistenceId);
MockRaftActor mockRaftActor = mockActorRef.underlyingActor();
RaftActorSnapshotMessageSupport mockSupport = mock(RaftActorSnapshotMessageSupport.class);
- TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId,
- Collections.<String, String>emptyMap(), Optional.<ConfigParams>of(config), mockSupport), persistenceId);
+ TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(MockRaftActor.builder().id(persistenceId).
+ config(config).snapshotMessageSupport(mockSupport).props());
MockRaftActor mockRaftActor = mockActorRef.underlyingActor();
mockRaftActor.waitForRecoveryComplete();
ApplySnapshot applySnapshot = new ApplySnapshot(mock(Snapshot.class));
- doReturn(true).when(mockSupport).handleSnapshotMessage(same(applySnapshot));
+ doReturn(true).when(mockSupport).handleSnapshotMessage(same(applySnapshot), any(ActorRef.class));
mockRaftActor.handleCommand(applySnapshot);
CaptureSnapshot captureSnapshot = new CaptureSnapshot(1, 1, 1, 1, 0, 1, null);
- doReturn(true).when(mockSupport).handleSnapshotMessage(same(captureSnapshot));
+ doReturn(true).when(mockSupport).handleSnapshotMessage(same(captureSnapshot), any(ActorRef.class));
mockRaftActor.handleCommand(captureSnapshot);
CaptureSnapshotReply captureSnapshotReply = new CaptureSnapshotReply(new byte[0]);
- doReturn(true).when(mockSupport).handleSnapshotMessage(same(captureSnapshotReply));
+ doReturn(true).when(mockSupport).handleSnapshotMessage(same(captureSnapshotReply), any(ActorRef.class));
mockRaftActor.handleCommand(captureSnapshotReply);
SaveSnapshotSuccess saveSnapshotSuccess = new SaveSnapshotSuccess(mock(SnapshotMetadata.class));
- doReturn(true).when(mockSupport).handleSnapshotMessage(same(saveSnapshotSuccess));
+ doReturn(true).when(mockSupport).handleSnapshotMessage(same(saveSnapshotSuccess), any(ActorRef.class));
mockRaftActor.handleCommand(saveSnapshotSuccess);
SaveSnapshotFailure saveSnapshotFailure = new SaveSnapshotFailure(mock(SnapshotMetadata.class), new Throwable());
- doReturn(true).when(mockSupport).handleSnapshotMessage(same(saveSnapshotFailure));
+ doReturn(true).when(mockSupport).handleSnapshotMessage(same(saveSnapshotFailure), any(ActorRef.class));
mockRaftActor.handleCommand(saveSnapshotFailure);
- doReturn(true).when(mockSupport).handleSnapshotMessage(same(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT));
+ doReturn(true).when(mockSupport).handleSnapshotMessage(same(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT),
+ any(ActorRef.class));
mockRaftActor.handleCommand(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT);
- verify(mockSupport).handleSnapshotMessage(same(applySnapshot));
- verify(mockSupport).handleSnapshotMessage(same(captureSnapshot));
- verify(mockSupport).handleSnapshotMessage(same(captureSnapshotReply));
- verify(mockSupport).handleSnapshotMessage(same(saveSnapshotSuccess));
- verify(mockSupport).handleSnapshotMessage(same(saveSnapshotFailure));
- verify(mockSupport).handleSnapshotMessage(same(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT));
+ doReturn(true).when(mockSupport).handleSnapshotMessage(same(GetSnapshot.INSTANCE), any(ActorRef.class));
+ mockRaftActor.handleCommand(GetSnapshot.INSTANCE);
+
+ verify(mockSupport).handleSnapshotMessage(same(applySnapshot), any(ActorRef.class));
+ verify(mockSupport).handleSnapshotMessage(same(captureSnapshot), any(ActorRef.class));
+ verify(mockSupport).handleSnapshotMessage(same(captureSnapshotReply), any(ActorRef.class));
+ verify(mockSupport).handleSnapshotMessage(same(saveSnapshotSuccess), any(ActorRef.class));
+ verify(mockSupport).handleSnapshotMessage(same(saveSnapshotFailure), any(ActorRef.class));
+ verify(mockSupport).handleSnapshotMessage(same(RaftActorSnapshotMessageSupport.COMMIT_SNAPSHOT),
+ any(ActorRef.class));
+ verify(mockSupport).handleSnapshotMessage(same(GetSnapshot.INSTANCE), any(ActorRef.class));
}
@Test
DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class);
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId,
- Collections.<String, String>emptyMap(), Optional.<ConfigParams>of(config), dataPersistenceProvider), persistenceId);
+ Collections.<String, String>emptyMap(), config, dataPersistenceProvider), persistenceId);
MockRaftActor mockRaftActor = mockActorRef.underlyingActor();
DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class);
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(MockRaftActor.props(persistenceId,
- Collections.<String, String>emptyMap(), Optional.<ConfigParams>of(config), dataPersistenceProvider), persistenceId);
+ Collections.<String, String>emptyMap(), config, dataPersistenceProvider), persistenceId);
MockRaftActor mockRaftActor = mockActorRef.underlyingActor();
String persistenceId = factory.generateActorId("notifier-");
- TestActorRef<MockRaftActor> raftActorRef = factory.createTestActor(MockRaftActor.props(persistenceId,
- Collections.<String, String>emptyMap(), Optional.<ConfigParams>of(config), notifierActor,
- new NonPersistentDataProvider()).withDispatcher(Dispatchers.DefaultDispatcherId()), persistenceId);
+ TestActorRef<MockRaftActor> raftActorRef = factory.createTestActor(MockRaftActor.builder().id(persistenceId).
+ config(config).roleChangeNotifier(notifierActor).dataPersistenceProvider(
+ new NonPersistentDataProvider()).props().withDispatcher(Dispatchers.DefaultDispatcherId()),
+ persistenceId);
List<RoleChanged> matches = MessageCollectorActor.expectMatching(notifierActor, RoleChanged.class, 3);
String persistenceId = factory.generateActorId("notifier-");
- factory.createActor(MockRaftActor.props(persistenceId,
- ImmutableMap.of("leader", "fake/path"), Optional.<ConfigParams>of(config), notifierActor), persistenceId);
+ factory.createActor(MockRaftActor.builder().id(persistenceId).
+ peerAddresses(ImmutableMap.of("leader", "fake/path")).
+ config(config).roleChangeNotifier(notifierActor).props());
List<RoleChanged> matches = null;
for(int i = 0; i < 5000 / heartBeatInterval; i++) {
peerAddresses.put(follower1Id, followerActor1.path().toString());
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(
- MockRaftActor.props(persistenceId, peerAddresses,
- Optional.<ConfigParams>of(config), dataPersistenceProvider), persistenceId);
+ MockRaftActor.props(persistenceId, peerAddresses, config, dataPersistenceProvider), persistenceId);
MockRaftActor leaderActor = mockActorRef.underlyingActor();
peerAddresses.put(leaderId, leaderActor1.path().toString());
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(
- MockRaftActor.props(persistenceId, peerAddresses,
- Optional.<ConfigParams>of(config), dataPersistenceProvider), persistenceId);
+ MockRaftActor.props(persistenceId, peerAddresses, config, dataPersistenceProvider), persistenceId);
MockRaftActor followerActor = mockActorRef.underlyingActor();
followerActor.getRaftActorContext().setCommitIndex(4);
peerAddresses.put(follower2Id, followerActor2.path().toString());
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(
- MockRaftActor.props(persistenceId, peerAddresses,
- Optional.<ConfigParams>of(config), dataPersistenceProvider), persistenceId);
+ MockRaftActor.props(persistenceId, peerAddresses, config, dataPersistenceProvider), persistenceId);
MockRaftActor leaderActor = mockActorRef.underlyingActor();
leaderActor.getRaftActorContext().setCommitIndex(9);
Map<String, String> peerAddresses = ImmutableMap.<String, String>builder().put("member1", "address").build();
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(
- MockRaftActor.props(persistenceId, peerAddresses,
- Optional.<ConfigParams>of(config), dataPersistenceProvider), persistenceId);
+ MockRaftActor.props(persistenceId, peerAddresses, config, dataPersistenceProvider), persistenceId);
MockRaftActor leaderActor = mockActorRef.underlyingActor();
leaderActor.getRaftActorContext().setCommitIndex(3);
Map<String, String> peerAddresses = ImmutableMap.<String, String>builder().put("member1", "address").build();
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(
- MockRaftActor.props(persistenceId, peerAddresses,
- Optional.<ConfigParams>of(config), dataPersistenceProvider), persistenceId);
+ MockRaftActor.props(persistenceId, peerAddresses, config, dataPersistenceProvider), persistenceId);
MockRaftActor leaderActor = mockActorRef.underlyingActor();
leaderActor.getRaftActorContext().setCommitIndex(3);
}};
}
+ @Test
+ public void testRaftActorOnRecoverySnapshot() throws Exception {
+ TEST_LOG.info("testRaftActorOnRecoverySnapshot");
+
+ new JavaTestKit(getSystem()) {{
+ String persistenceId = factory.generateActorId("follower-");
+
+ DefaultConfigParamsImpl config = new DefaultConfigParamsImpl();
+
+ // Set the heartbeat interval high to essentially disable election otherwise the test
+ // may fail if the actor is switched to Leader
+ config.setHeartBeatInterval(new FiniteDuration(1, TimeUnit.DAYS));
+
+ ImmutableMap<String, String> peerAddresses = ImmutableMap.<String, String>builder().put("member1", "address").build();
+
+ // Create mock ReplicatedLogEntry
+ ReplicatedLogEntry replLogEntry = new MockRaftActorContext.MockReplicatedLogEntry(1,1,
+ new MockRaftActorContext.MockPayload("F", 1));
+
+ InMemoryJournal.addEntry(persistenceId, 1, replLogEntry);
+
+ TestActorRef<MockRaftActor> ref = factory.createTestActor(
+ MockRaftActor.props(persistenceId, peerAddresses, config));
+
+ MockRaftActor mockRaftActor = ref.underlyingActor();
+
+ mockRaftActor.waitForRecoveryComplete();
+
+ mockRaftActor.waitForInitializeBehaviorComplete();
+
+ verify(mockRaftActor.snapshotCohortDelegate, timeout(5000)).createSnapshot(any(ActorRef.class));
+ }};
+ }
+
@Test
public void testSwitchBehavior(){
String persistenceId = factory.generateActorId("leader-");
Map<String, String> peerAddresses = ImmutableMap.<String, String>builder().build();
TestActorRef<MockRaftActor> mockActorRef = factory.createTestActor(
- MockRaftActor.props(persistenceId, peerAddresses,
- Optional.<ConfigParams>of(config), dataPersistenceProvider), persistenceId);
+ MockRaftActor.props(persistenceId, peerAddresses, config, dataPersistenceProvider), persistenceId);
MockRaftActor leaderActor = mockActorRef.underlyingActor();
- leaderActor.handleRecover(RecoveryCompleted.getInstance());
+ leaderActor.waitForRecoveryComplete();
leaderActor.handleCommand(new SwitchBehavior(RaftState.Follower, 100));
assertEquals(110, leaderActor.getRaftActorContext().getTermInformation().getCurrentTerm());
assertEquals(RaftState.Leader, leaderActor.getCurrentBehavior().state());
-
-
}
public static ByteString fromObject(Object snapshot) throws Exception {
}
}
+ @Test
+ public void testUpdateConfigParam() throws Exception {
+ DefaultConfigParamsImpl emptyConfig = new DefaultConfigParamsImpl();
+ String persistenceId = factory.generateActorId("follower-");
+ ImmutableMap<String, String> peerAddresses =
+ ImmutableMap.<String, String>builder().put("member1", "address").build();
+ DataPersistenceProvider dataPersistenceProvider = mock(DataPersistenceProvider.class);
+
+ TestActorRef<MockRaftActor> actorRef = factory.createTestActor(
+ MockRaftActor.props(persistenceId, peerAddresses, emptyConfig, dataPersistenceProvider), persistenceId);
+ MockRaftActor mockRaftActor = actorRef.underlyingActor();
+ mockRaftActor.waitForInitializeBehaviorComplete();
+
+ RaftActorBehavior behavior = mockRaftActor.getCurrentBehavior();
+ mockRaftActor.updateConfigParams(emptyConfig);
+ assertSame("Same Behavior", behavior, mockRaftActor.getCurrentBehavior());
+ assertEquals("Behavior State", RaftState.Follower,
+ mockRaftActor.getCurrentBehavior().state());
+
+ DefaultConfigParamsImpl disableConfig = new DefaultConfigParamsImpl();
+ disableConfig.setCustomRaftPolicyImplementationClass(
+ "org.opendaylight.controller.cluster.raft.policy.DisableElectionsRaftPolicy");
+ mockRaftActor.updateConfigParams(disableConfig);
+ assertNotSame("Different Behavior", behavior, mockRaftActor.getCurrentBehavior());
+ assertEquals("Behavior State", RaftState.Follower,
+ mockRaftActor.getCurrentBehavior().state());
+
+ behavior = mockRaftActor.getCurrentBehavior();
+ mockRaftActor.updateConfigParams(disableConfig);
+ assertSame("Same Behavior", behavior, mockRaftActor.getCurrentBehavior());
+ assertEquals("Behavior State", RaftState.Follower,
+ mockRaftActor.getCurrentBehavior().state());
+
+ DefaultConfigParamsImpl defaultConfig = new DefaultConfigParamsImpl();
+ defaultConfig.setCustomRaftPolicyImplementationClass(
+ "org.opendaylight.controller.cluster.raft.policy.DefaultRaftPolicy");
+ mockRaftActor.updateConfigParams(defaultConfig);
+ assertNotSame("Different Behavior", behavior, mockRaftActor.getCurrentBehavior());
+ assertEquals("Behavior State", RaftState.Follower,
+ mockRaftActor.getCurrentBehavior().state());
+
+ behavior = mockRaftActor.getCurrentBehavior();
+ mockRaftActor.updateConfigParams(defaultConfig);
+ assertSame("Same Behavior", behavior, mockRaftActor.getCurrentBehavior());
+ assertEquals("Behavior State", RaftState.Follower,
+ mockRaftActor.getCurrentBehavior().state());
+ }
+
+ @Test
+ public void testGetSnapshot() throws Exception {
+ TEST_LOG.info("testGetSnapshot starting");
+
+ JavaTestKit kit = new JavaTestKit(getSystem());
+
+ String persistenceId = factory.generateActorId("test-actor-");
+ DefaultConfigParamsImpl config = new DefaultConfigParamsImpl();
+ config.setCustomRaftPolicyImplementationClass(DisableElectionsRaftPolicy.class.getName());
+
+ long term = 3;
+ long seqN = 1;
+ InMemoryJournal.addEntry(persistenceId, seqN++, new UpdateElectionTerm(term, "member-1"));
+ InMemoryJournal.addEntry(persistenceId, seqN++, new MockRaftActorContext.MockReplicatedLogEntry(term, 0,
+ new MockRaftActorContext.MockPayload("A")));
+ InMemoryJournal.addEntry(persistenceId, seqN++, new MockRaftActorContext.MockReplicatedLogEntry(term, 1,
+ new MockRaftActorContext.MockPayload("B")));
+ InMemoryJournal.addEntry(persistenceId, seqN++, new ApplyJournalEntries(1));
+ InMemoryJournal.addEntry(persistenceId, seqN++, new MockRaftActorContext.MockReplicatedLogEntry(term, 2,
+ new MockRaftActorContext.MockPayload("C")));
+
+ TestActorRef<MockRaftActor> raftActorRef = factory.createTestActor(MockRaftActor.props(persistenceId,
+ ImmutableMap.<String, String>builder().put("member1", "address").build(), config).
+ withDispatcher(Dispatchers.DefaultDispatcherId()), persistenceId);
+ MockRaftActor mockRaftActor = raftActorRef.underlyingActor();
+
+ mockRaftActor.waitForRecoveryComplete();
+
+ // Wait for snapshot after recovery
+ verify(mockRaftActor.snapshotCohortDelegate, timeout(5000)).createSnapshot(any(ActorRef.class));
+
+ mockRaftActor.snapshotCohortDelegate = mock(RaftActorSnapshotCohort.class);
+
+ raftActorRef.tell(GetSnapshot.INSTANCE, kit.getRef());
+
+ ArgumentCaptor<ActorRef> replyActor = ArgumentCaptor.forClass(ActorRef.class);
+ verify(mockRaftActor.snapshotCohortDelegate, timeout(5000)).createSnapshot(replyActor.capture());
+
+ byte[] stateSnapshot = new byte[]{1,2,3};
+ replyActor.getValue().tell(new CaptureSnapshotReply(stateSnapshot), ActorRef.noSender());
+
+ GetSnapshotReply reply = kit.expectMsgClass(GetSnapshotReply.class);
+
+ assertEquals("getId", persistenceId, reply.getId());
+ Snapshot replySnapshot = SerializationUtils.deserialize(reply.getSnapshot());
+ assertEquals("getElectionTerm", term, replySnapshot.getElectionTerm());
+ assertEquals("getElectionVotedFor", "member-1", replySnapshot.getElectionVotedFor());
+ assertEquals("getLastAppliedIndex", 1L, replySnapshot.getLastAppliedIndex());
+ assertEquals("getLastAppliedTerm", term, replySnapshot.getLastAppliedTerm());
+ assertEquals("getLastIndex", 2L, replySnapshot.getLastIndex());
+ assertEquals("getLastTerm", term, replySnapshot.getLastTerm());
+ assertArrayEquals("getState", stateSnapshot, replySnapshot.getState());
+ assertEquals("getUnAppliedEntries size", 1, replySnapshot.getUnAppliedEntries().size());
+ assertEquals("UnApplied entry index ", 2L, replySnapshot.getUnAppliedEntries().get(0).getIndex());
+
+ // Test with timeout
+
+ mockRaftActor.getSnapshotMessageSupport().setSnapshotReplyActorTimeout(Duration.create(200, TimeUnit.MILLISECONDS));
+ reset(mockRaftActor.snapshotCohortDelegate);
+
+ raftActorRef.tell(GetSnapshot.INSTANCE, kit.getRef());
+ Failure failure = kit.expectMsgClass(akka.actor.Status.Failure.class);
+ assertEquals("Failure cause type", TimeoutException.class, failure.cause().getClass());
+
+ mockRaftActor.getSnapshotMessageSupport().setSnapshotReplyActorTimeout(Duration.create(30, TimeUnit.SECONDS));
+
+ // Test with persistence disabled.
+
+ mockRaftActor.setPersistence(false);
+ reset(mockRaftActor.snapshotCohortDelegate);
+
+ raftActorRef.tell(GetSnapshot.INSTANCE, kit.getRef());
+ reply = kit.expectMsgClass(GetSnapshotReply.class);
+ verify(mockRaftActor.snapshotCohortDelegate, never()).createSnapshot(any(ActorRef.class));
+
+ assertEquals("getId", persistenceId, reply.getId());
+ replySnapshot = SerializationUtils.deserialize(reply.getSnapshot());
+ assertEquals("getElectionTerm", term, replySnapshot.getElectionTerm());
+ assertEquals("getElectionVotedFor", "member-1", replySnapshot.getElectionVotedFor());
+ assertEquals("getLastAppliedIndex", -1L, replySnapshot.getLastAppliedIndex());
+ assertEquals("getLastAppliedTerm", -1L, replySnapshot.getLastAppliedTerm());
+ assertEquals("getLastIndex", -1L, replySnapshot.getLastIndex());
+ assertEquals("getLastTerm", -1L, replySnapshot.getLastTerm());
+ assertEquals("getState length", 0, replySnapshot.getState().length);
+ assertEquals("getUnAppliedEntries size", 0, replySnapshot.getUnAppliedEntries().size());
+
+ TEST_LOG.info("testGetSnapshot ending");
+ }
+
+ @Test
+ public void testRestoreFromSnapshot() throws Exception {
+ TEST_LOG.info("testRestoreFromSnapshot starting");
+
+ String persistenceId = factory.generateActorId("test-actor-");
+ DefaultConfigParamsImpl config = new DefaultConfigParamsImpl();
+ config.setCustomRaftPolicyImplementationClass(DisableElectionsRaftPolicy.class.getName());
+
+ List<ReplicatedLogEntry> snapshotUnappliedEntries = new ArrayList<>();
+ snapshotUnappliedEntries.add(new MockRaftActorContext.MockReplicatedLogEntry(1, 4,
+ new MockRaftActorContext.MockPayload("E")));
+
+ int snapshotLastApplied = 3;
+ int snapshotLastIndex = 4;
+
+ List<MockPayload> state = Arrays.asList(
+ new MockRaftActorContext.MockPayload("A"),
+ new MockRaftActorContext.MockPayload("B"),
+ new MockRaftActorContext.MockPayload("C"),
+ new MockRaftActorContext.MockPayload("D"));
+ ByteString stateBytes = fromObject(state);
+
+ Snapshot snapshot = Snapshot.create(stateBytes.toByteArray(), snapshotUnappliedEntries,
+ snapshotLastIndex, 1, snapshotLastApplied, 1, 1, "member-1");
+
+ InMemorySnapshotStore.addSnapshotSavedLatch(persistenceId);
+
+ TestActorRef<MockRaftActor> raftActorRef = factory.createTestActor(MockRaftActor.builder().id(persistenceId).
+ config(config).restoreFromSnapshot(SerializationUtils.serialize(snapshot)).props().
+ withDispatcher(Dispatchers.DefaultDispatcherId()), persistenceId);
+ MockRaftActor mockRaftActor = raftActorRef.underlyingActor();
+
+ mockRaftActor.waitForRecoveryComplete();
+
+ Snapshot savedSnapshot = InMemorySnapshotStore.waitForSavedSnapshot(persistenceId, Snapshot.class);
+ assertEquals("getElectionTerm", snapshot.getElectionTerm(), savedSnapshot.getElectionTerm());
+ assertEquals("getElectionVotedFor", snapshot.getElectionVotedFor(), savedSnapshot.getElectionVotedFor());
+ assertEquals("getLastAppliedIndex", snapshot.getLastAppliedIndex(), savedSnapshot.getLastAppliedIndex());
+ assertEquals("getLastAppliedTerm", snapshot.getLastAppliedTerm(), savedSnapshot.getLastAppliedTerm());
+ assertEquals("getLastIndex", snapshot.getLastIndex(), savedSnapshot.getLastIndex());
+ assertEquals("getLastTerm", snapshot.getLastTerm(), savedSnapshot.getLastTerm());
+ assertArrayEquals("getState", snapshot.getState(), savedSnapshot.getState());
+ assertEquals("getUnAppliedEntries", snapshot.getUnAppliedEntries(), savedSnapshot.getUnAppliedEntries());
+
+ verify(mockRaftActor.snapshotCohortDelegate, timeout(5000)).applySnapshot(any(byte[].class));
+
+ RaftActorContext context = mockRaftActor.getRaftActorContext();
+ assertEquals("Journal log size", 1, context.getReplicatedLog().size());
+ assertEquals("Last index", snapshotLastIndex, context.getReplicatedLog().lastIndex());
+ assertEquals("Last applied", snapshotLastApplied, context.getLastApplied());
+ assertEquals("Commit index", snapshotLastApplied, context.getCommitIndex());
+ assertEquals("Recovered state", state, mockRaftActor.getState());
+ assertEquals("Current term", 1L, context.getTermInformation().getCurrentTerm());
+ assertEquals("Voted for", "member-1", context.getTermInformation().getVotedFor());
+
+ // Test with data persistence disabled
+
+ snapshot = Snapshot.create(new byte[0], Collections.<ReplicatedLogEntry>emptyList(),
+ -1, -1, -1, -1, 5, "member-1");
+
+ persistenceId = factory.generateActorId("test-actor-");
+
+ raftActorRef = factory.createTestActor(MockRaftActor.builder().id(persistenceId).
+ config(config).restoreFromSnapshot(SerializationUtils.serialize(snapshot)).
+ persistent(Optional.of(Boolean.FALSE)).props().
+ withDispatcher(Dispatchers.DefaultDispatcherId()), persistenceId);
+ mockRaftActor = raftActorRef.underlyingActor();
+
+ mockRaftActor.waitForRecoveryComplete();
+ assertEquals("snapshot committed", true,
+ Uninterruptibles.awaitUninterruptibly(mockRaftActor.snapshotCommitted, 5, TimeUnit.SECONDS));
+
+ context = mockRaftActor.getRaftActorContext();
+ assertEquals("Current term", 5L, context.getTermInformation().getCurrentTerm());
+ assertEquals("Voted for", "member-1", context.getTermInformation().getVotedFor());
+
+ TEST_LOG.info("testRestoreFromSnapshot ending");
+ }
+
+ @Test
+ public void testRestoreFromSnapshotWithRecoveredData() throws Exception {
+ TEST_LOG.info("testRestoreFromSnapshotWithRecoveredData starting");
+
+ String persistenceId = factory.generateActorId("test-actor-");
+ DefaultConfigParamsImpl config = new DefaultConfigParamsImpl();
+ config.setCustomRaftPolicyImplementationClass(DisableElectionsRaftPolicy.class.getName());
+
+ List<MockPayload> state = Arrays.asList(new MockRaftActorContext.MockPayload("A"));
+ Snapshot snapshot = Snapshot.create(fromObject(state).toByteArray(), Arrays.<ReplicatedLogEntry>asList(),
+ 5, 2, 5, 2, 2, "member-1");
+
+ InMemoryJournal.addEntry(persistenceId, 1, new MockRaftActorContext.MockReplicatedLogEntry(1, 0,
+ new MockRaftActorContext.MockPayload("B")));
+
+ TestActorRef<MockRaftActor> raftActorRef = factory.createTestActor(MockRaftActor.builder().id(persistenceId).
+ config(config).restoreFromSnapshot(SerializationUtils.serialize(snapshot)).props().
+ withDispatcher(Dispatchers.DefaultDispatcherId()), persistenceId);
+ MockRaftActor mockRaftActor = raftActorRef.underlyingActor();
+
+ mockRaftActor.waitForRecoveryComplete();
+
+ verify(mockRaftActor.snapshotCohortDelegate, timeout(500).never()).applySnapshot(any(byte[].class));
+
+ RaftActorContext context = mockRaftActor.getRaftActorContext();
+ assertEquals("Journal log size", 1, context.getReplicatedLog().size());
+ assertEquals("Last index", 0, context.getReplicatedLog().lastIndex());
+ assertEquals("Last applied", -1, context.getLastApplied());
+ assertEquals("Commit index", -1, context.getCommitIndex());
+ assertEquals("Current term", 0, context.getTermInformation().getCurrentTerm());
+ assertEquals("Voted for", null, context.getTermInformation().getVotedFor());
+
+ TEST_LOG.info("testRestoreFromSnapshotWithRecoveredData ending");
+ }
+
+ @Test
+ public void testNonVotingOnRecovery() throws Exception {
+ TEST_LOG.info("testNonVotingOnRecovery starting");
+
+ DefaultConfigParamsImpl config = new DefaultConfigParamsImpl();
+ config.setElectionTimeoutFactor(1);
+ config.setHeartBeatInterval(FiniteDuration.create(1, TimeUnit.MILLISECONDS));
+
+ String persistenceId = factory.generateActorId("test-actor-");
+ InMemoryJournal.addEntry(persistenceId, 1, new MockRaftActorContext.MockReplicatedLogEntry(1, 0,
+ new ServerConfigurationPayload(Arrays.asList(new ServerInfo(persistenceId, false)))));
+
+ TestActorRef<MockRaftActor> raftActorRef = factory.createTestActor(MockRaftActor.builder().id(persistenceId).
+ config(config).props().withDispatcher(Dispatchers.DefaultDispatcherId()), persistenceId);
+ MockRaftActor mockRaftActor = raftActorRef.underlyingActor();
+
+ mockRaftActor.waitForInitializeBehaviorComplete();
+
+ // Sleep a bit and verify it didn't get an election timeout and schedule an election.
+
+ Uninterruptibles.sleepUninterruptibly(400, TimeUnit.MILLISECONDS);
+ assertEquals("getRaftState", RaftState.Follower, mockRaftActor.getRaftState());
+
+ TEST_LOG.info("testNonVotingOnRecovery ending");
+ }
+
+ @Test
+ public void testLeaderTransitioning() throws Exception {
+ TEST_LOG.info("testLeaderTransitioning starting");
+
+ TestActorRef<MessageCollectorActor> notifierActor = factory.createTestActor(
+ Props.create(MessageCollectorActor.class));
+
+ DefaultConfigParamsImpl config = new DefaultConfigParamsImpl();
+ config.setCustomRaftPolicyImplementationClass(DisableElectionsRaftPolicy.class.getName());
+
+ String persistenceId = factory.generateActorId("test-actor-");
+
+ TestActorRef<MockRaftActor> raftActorRef = factory.createTestActor(MockRaftActor.builder().id(persistenceId).
+ config(config).roleChangeNotifier(notifierActor).props().withDispatcher(Dispatchers.DefaultDispatcherId()), persistenceId);
+ MockRaftActor mockRaftActor = raftActorRef.underlyingActor();
+
+ mockRaftActor.waitForInitializeBehaviorComplete();
+
+ raftActorRef.tell(new AppendEntries(1L, "leader", 0L, 1L, Collections.<ReplicatedLogEntry>emptyList(),
+ 0L, -1L, (short)1), ActorRef.noSender());
+ LeaderStateChanged leaderStateChange = MessageCollectorActor.expectFirstMatching(
+ notifierActor, LeaderStateChanged.class);
+ assertEquals("getLeaderId", "leader", leaderStateChange.getLeaderId());
+
+ MessageCollectorActor.clearMessages(notifierActor);
+
+ raftActorRef.tell(new LeaderTransitioning(), ActorRef.noSender());
+
+ leaderStateChange = MessageCollectorActor.expectFirstMatching(notifierActor, LeaderStateChanged.class);
+ assertEquals("getMemberId", persistenceId, leaderStateChange.getMemberId());
+ assertEquals("getLeaderId", null, leaderStateChange.getLeaderId());
+
+ TEST_LOG.info("testLeaderTransitioning ending");
+ }
}