import org.junit.Before;
import org.junit.Test;
import org.opendaylight.controller.cluster.NonPersistentDataProvider;
-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.ApplySnapshot;
import org.opendaylight.controller.cluster.raft.base.messages.ApplyState;
import org.opendaylight.controller.cluster.raft.base.messages.CaptureSnapshotReply;
import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
import org.opendaylight.controller.cluster.raft.base.messages.InitiateCaptureSnapshot;
-import org.opendaylight.controller.cluster.raft.base.messages.SnapshotComplete;
-import org.opendaylight.controller.cluster.raft.base.messages.UpdateElectionTerm;
+import org.opendaylight.controller.cluster.raft.base.messages.TimeoutNow;
import org.opendaylight.controller.cluster.raft.behaviors.AbstractLeader;
import org.opendaylight.controller.cluster.raft.behaviors.Follower;
import org.opendaylight.controller.cluster.raft.behaviors.Leader;
import org.opendaylight.controller.cluster.raft.messages.ServerChangeStatus;
import org.opendaylight.controller.cluster.raft.messages.ServerRemoved;
import org.opendaylight.controller.cluster.raft.messages.UnInitializedFollowerSnapshotReply;
+import org.opendaylight.controller.cluster.raft.persisted.ApplyJournalEntries;
+import org.opendaylight.controller.cluster.raft.persisted.ServerConfigurationPayload;
+import org.opendaylight.controller.cluster.raft.persisted.ServerInfo;
+import org.opendaylight.controller.cluster.raft.persisted.UpdateElectionTerm;
import org.opendaylight.controller.cluster.raft.policy.DisableElectionsRaftPolicy;
import org.opendaylight.controller.cluster.raft.utils.ForwardMessageToBehaviorActor;
import org.opendaylight.controller.cluster.raft.utils.InMemoryJournal;
assertEquals("New follower commit index", 3, newFollowerActorContext.getCommitIndex());
assertEquals("New follower last applied index", 3, newFollowerActorContext.getLastApplied());
- List<ReplicatedLogImplEntry> persistedLogEntries = InMemoryJournal.get(LEADER_ID, ReplicatedLogImplEntry.class);
- assertEquals("Leader ReplicatedLogImplEntry entries", 1, persistedLogEntries.size());
- ReplicatedLogImplEntry logEntry = persistedLogEntries.get(0);
- assertEquals("Leader ReplicatedLogImplEntry getTerm", 1, logEntry.getTerm());
- assertEquals("Leader ReplicatedLogImplEntry getIndex", 3, logEntry.getIndex());
- assertEquals("Leader ReplicatedLogImplEntry getData", ServerConfigurationPayload.class, logEntry.getData().getClass());
-
- persistedLogEntries = InMemoryJournal.get(NEW_SERVER_ID, ReplicatedLogImplEntry.class);
- assertEquals("New follower ReplicatedLogImplEntry entries", 1, persistedLogEntries.size());
- logEntry = persistedLogEntries.get(0);
- assertEquals("New follower ReplicatedLogImplEntry getTerm", 1, logEntry.getTerm());
- assertEquals("New follower ReplicatedLogImplEntry getIndex", 3, logEntry.getIndex());
- assertEquals("New follower ReplicatedLogImplEntry getData", ServerConfigurationPayload.class,
- logEntry.getData().getClass());
+ assertEquals("Leader persisted ReplicatedLogImplEntry entries", 0,
+ InMemoryJournal.get(LEADER_ID, ReplicatedLogImplEntry.class).size());
+ assertEquals("Leader persisted ServerConfigurationPayload entries", 1,
+ InMemoryJournal.get(LEADER_ID, ServerConfigurationPayload.class).size());
+
+ assertEquals("New follower persisted ReplicatedLogImplEntry entries", 0,
+ InMemoryJournal.get(NEW_SERVER_ID, ReplicatedLogImplEntry.class).size());
+ assertEquals("New follower persisted ServerConfigurationPayload entries", 1,
+ InMemoryJournal.get(NEW_SERVER_ID, ServerConfigurationPayload.class).size());
LOG.info("testAddServerWithExistingFollower ending");
}
followerActor.underlyingActor().setBehavior(newFollower2);
MockNewFollowerRaftActor newFollowerRaftActorInstance = newFollowerRaftActor.underlyingActor();
- newFollowerRaftActorInstance.setDropMessageOfType(InstallSnapshot.SERIALIZABLE_CLASS);
+ newFollowerRaftActorInstance.setDropMessageOfType(InstallSnapshot.class);
leaderActor.tell(new AddServer(NEW_SERVER_ID, newFollowerRaftActor.path().toString(), true), testKit.getRef());
((DefaultConfigParamsImpl)leaderActorContext.getConfigParams()).setElectionTimeoutFactor(1);
// Drop the InstallSnapshot message so it times out
- newFollowerRaftActor.underlyingActor().setDropMessageOfType(InstallSnapshot.SERIALIZABLE_CLASS);
+ newFollowerRaftActor.underlyingActor().setDropMessageOfType(InstallSnapshot.class);
leaderActor.tell(new AddServer(NEW_SERVER_ID, newFollowerRaftActor.path().toString(), true), testKit.getRef());
LOG.info("testChangeLeaderToNonVoting ending");
}
+ @Test
+ public void testChangeLeaderToNonVotingInSingleNode() {
+ LOG.info("testChangeLeaderToNonVotingInSingleNode starting");
+
+ TestActorRef<MockLeaderRaftActor> leaderActor = actorFactory.createTestActor(
+ MockLeaderRaftActor.props(ImmutableMap.of(), new MockRaftActorContext()).
+ withDispatcher(Dispatchers.DefaultDispatcherId()), actorFactory.generateActorId(LEADER_ID));
+
+ leaderActor.tell(new ChangeServersVotingStatus(ImmutableMap.of(LEADER_ID, false)), testKit.getRef());
+ ServerChangeReply reply = testKit.expectMsgClass(JavaTestKit.duration("5 seconds"), ServerChangeReply.class);
+ assertEquals("getStatus", ServerChangeStatus.INVALID_REQUEST, reply.getStatus());
+
+ LOG.info("testChangeLeaderToNonVotingInSingleNode ending");
+ }
+
@Test
public void testChangeToVotingWithNoLeader() {
LOG.info("testChangeToVotingWithNoLeader starting");
DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl();
configParams.setHeartBeatInterval(new FiniteDuration(100, TimeUnit.MILLISECONDS));
+ configParams.setElectionTimeoutFactor(5);
final String node1ID = "node1";
final String node2ID = "node2";
InMemoryJournal.addEntry(node1ID, 1, new UpdateElectionTerm(1, "downNode1"));
InMemoryJournal.addEntry(node1ID, 2, persistedServerConfigEntry);
+ InMemoryJournal.addEntry(node1ID, 3, new ApplyJournalEntries(0));
InMemoryJournal.addEntry(node2ID, 1, new UpdateElectionTerm(1, "downNode2"));
InMemoryJournal.addEntry(node2ID, 2, persistedServerConfigEntry);
+ InMemoryJournal.addEntry(node2ID, 3, new ApplyJournalEntries(0));
TestActorRef<MessageCollectorActor> node1Collector = actorFactory.createTestActor(
MessageCollectorActor.props().withDispatcher(Dispatchers.DefaultDispatcherId()),
PERSISTENT, node2Collector).withDispatcher(Dispatchers.DefaultDispatcherId()), node2ID);
CollectingMockRaftActor node2RaftActor = node2RaftActorRef.underlyingActor();
- // Wait for snapshot after recovery
- MessageCollectorActor.expectFirstMatching(node1Collector, SnapshotComplete.class);
+ node1RaftActor.waitForInitializeBehaviorComplete();
+ node2RaftActor.waitForInitializeBehaviorComplete();
// Verify the intended server config was loaded and applied.
verifyServerConfigurationPayloadEntry(node1RaftActor.getRaftActorContext().getReplicatedLog(),
assertEquals("getRaftState", RaftState.Follower, node1RaftActor.getRaftState());
assertEquals("getLeaderId", null, node1RaftActor.getLeaderId());
- MessageCollectorActor.expectFirstMatching(node2Collector, SnapshotComplete.class);
+ verifyServerConfigurationPayloadEntry(node2RaftActor.getRaftActorContext().getReplicatedLog(),
+ nonVotingServer(node1ID), nonVotingServer(node2ID), votingServer("downNode1"),
+ votingServer("downNode2"));
assertEquals("isVotingMember", false, node2RaftActor.getRaftActorContext().isVotingMember());
// For the test, we send a ChangeServersVotingStatus message to node1 to flip the voting states for
ServerChangeReply reply = testKit.expectMsgClass(JavaTestKit.duration("5 seconds"), ServerChangeReply.class);
assertEquals("getStatus", ServerChangeStatus.NO_LEADER, reply.getStatus());
+ // Send an AppendEntries so node1 has a leaderId
+
+ MessageCollectorActor.clearMessages(node1Collector);
+
+ long term = node1RaftActor.getRaftActorContext().getTermInformation().getCurrentTerm();
+ node1RaftActorRef.tell(new AppendEntries(term, "downNode1", -1L, -1L,
+ Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short)1), ActorRef.noSender());
+
+ // Wait for the ElectionTimeout to clear the leaderId. he leaderId must be null so on the
+ // ChangeServersVotingStatus message, it will try to elect a leader.
+
+ MessageCollectorActor.expectFirstMatching(node1Collector, ElectionTimeout.class);
+
// Update node2's peer address and send the message again
node1RaftActor.setPeerAddress(node2ID, node2RaftActorRef.path().toString());
PERSISTENT, node2Collector).withDispatcher(Dispatchers.DefaultDispatcherId()), node2ID);
CollectingMockRaftActor node2RaftActor = node2RaftActorRef.underlyingActor();
- // Wait for snapshot after recovery
- MessageCollectorActor.expectFirstMatching(node1Collector, SnapshotComplete.class);
-
// Send a ChangeServersVotingStatus message to node1 to change mode1 to voting. This should cause
// node1 to try to elect itself as leader in order to apply the new server config. But we'll drop
// RequestVote messages in node2 which should cause node1 to time out and revert back to the previous
InMemoryJournal.addEntry(node2ID, 2, persistedServerConfigEntry);
InMemoryJournal.addEntry(node2ID, 3, new ReplicatedLogImplEntry(1, 1,
new MockRaftActorContext.MockPayload("2")));
+ InMemoryJournal.addEntry(node2ID, 4, new ApplyJournalEntries(1));
TestActorRef<MessageCollectorActor> node1Collector = actorFactory.createTestActor(
MessageCollectorActor.props().withDispatcher(Dispatchers.DefaultDispatcherId()),
PERSISTENT, node2Collector).withDispatcher(Dispatchers.DefaultDispatcherId()), node2ID);
CollectingMockRaftActor node2RaftActor = node2RaftActorRef.underlyingActor();
- // Wait for snapshot after recovery
- MessageCollectorActor.expectFirstMatching(node1Collector, SnapshotComplete.class);
-
// Send a ChangeServersVotingStatus message to node1 to change mode1 to voting. This should cause
// node1 to try to elect itself as leader in order to apply the new server config. However node1's log
// is behind node2's so node2 should not grant node1's vote. This should cause node1 to time out and
PERSISTENT, node2Collector).withDispatcher(Dispatchers.DefaultDispatcherId()), node2ID);
CollectingMockRaftActor node2RaftActor = node2RaftActorRef.underlyingActor();
- // Wait for snapshot after recovery
- MessageCollectorActor.expectFirstMatching(node1Collector, SnapshotComplete.class);
-
// Send a ChangeServersVotingStatus message to node1 to change node1 to voting. This should cause
// node1 to try to elect itself as leader in order to apply the new server config. But we'll drop
// RequestVote messages in node2 and make it the leader so node1 should forward the server change
MessageCollectorActor.expectFirstMatching(node2Collector, RequestVote.class);
- node2RaftActorRef.tell(ElectionTimeout.INSTANCE, ActorRef.noSender());
+ node2RaftActorRef.tell(TimeoutNow.INSTANCE, ActorRef.noSender());
ServerChangeReply reply = testKit.expectMsgClass(JavaTestKit.duration("5 seconds"), ServerChangeReply.class);
assertEquals("getStatus", ServerChangeStatus.OK, reply.getStatus());