1 package org.opendaylight.controller.cluster.raft.behaviors;
3 import akka.actor.ActorRef;
4 import akka.actor.Props;
5 import akka.testkit.JavaTestKit;
6 import junit.framework.Assert;
8 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
9 import org.opendaylight.controller.cluster.raft.RaftActorContext;
10 import org.opendaylight.controller.cluster.raft.RaftState;
11 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
12 import org.opendaylight.controller.cluster.raft.internal.messages.ElectionTimeout;
13 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
14 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
15 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
16 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
17 import org.opendaylight.controller.cluster.raft.utils.DoNothingActor;
19 import java.util.ArrayList;
20 import java.util.List;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotNull;
25 public class FollowerTest extends AbstractRaftActorBehaviorTest {
27 private final ActorRef followerActor = getSystem().actorOf(Props.create(
28 DoNothingActor.class));
31 @Override protected RaftActorBehavior createBehavior(RaftActorContext actorContext) {
32 return new Follower(actorContext);
35 @Override protected RaftActorContext createActorContext() {
36 return new MockRaftActorContext("test", getSystem(), followerActor);
40 public void testThatAnElectionTimeoutIsTriggered(){
41 new JavaTestKit(getSystem()) {{
43 new Within(duration("1 seconds")) {
44 protected void run() {
46 Follower follower = new Follower(createActorContext(getTestActor()));
48 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"), "ElectionTimeout") {
49 // do not put code outside this method, will run afterwards
50 protected Boolean match(Object in) {
51 if (in instanceof ElectionTimeout) {
59 assertEquals(true, out);
66 public void testHandleElectionTimeout(){
67 RaftActorContext raftActorContext = createActorContext();
69 new Follower(raftActorContext);
72 follower.handleMessage(followerActor, new ElectionTimeout());
74 Assert.assertEquals(RaftState.Candidate, raftState);
78 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNull(){
79 new JavaTestKit(getSystem()) {{
81 new Within(duration("1 seconds")) {
82 protected void run() {
84 RaftActorContext context = createActorContext(getTestActor());
86 context.getTermInformation().update(1000, null);
88 RaftActorBehavior follower = createBehavior(context);
90 follower.handleMessage(getTestActor(), new RequestVote(1000, "test", 10000, 999));
92 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"), "RequestVoteReply") {
93 // do not put code outside this method, will run afterwards
94 protected Boolean match(Object in) {
95 if (in instanceof RequestVoteReply) {
96 RequestVoteReply reply = (RequestVoteReply) in;
97 return reply.isVoteGranted();
104 assertEquals(true, out);
111 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNotTheSameAsCandidateId(){
112 new JavaTestKit(getSystem()) {{
114 new Within(duration("1 seconds")) {
115 protected void run() {
117 RaftActorContext context = createActorContext(getTestActor());
119 context.getTermInformation().update(1000, "test");
121 RaftActorBehavior follower = createBehavior(context);
123 follower.handleMessage(getTestActor(), new RequestVote(1000, "candidate", 10000, 999));
125 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"), "RequestVoteReply") {
126 // do not put code outside this method, will run afterwards
127 protected Boolean match(Object in) {
128 if (in instanceof RequestVoteReply) {
129 RequestVoteReply reply = (RequestVoteReply) in;
130 return reply.isVoteGranted();
137 assertEquals(false, out);
144 * This test verifies that when an AppendEntries RPC is received by a RaftActor
145 * with a commitIndex that is greater than what has been applied to the
146 * state machine of the RaftActor, the RaftActor applies the state and
147 * sets it current applied state to the commitIndex of the sender.
152 public void testHandleAppendEntriesWithNewerCommitIndex() throws Exception {
153 new JavaTestKit(getSystem()) {{
155 RaftActorContext context =
156 createActorContext();
158 context.setLastApplied(100);
159 setLastLogEntry((MockRaftActorContext) context, 0, 0, "");
161 // The new commitIndex is 101
162 AppendEntries appendEntries =
163 new AppendEntries(100, "leader-1", 0, 0, null, 101);
165 RaftState raftState =
166 createBehavior(context).handleMessage(getRef(), appendEntries);
168 assertEquals(101L, context.getLastApplied());
174 * This test verifies that when an AppendEntries is received a specific prevLogTerm
175 * which does not match the term that is in RaftActors log entry at prevLogIndex
176 * then the RaftActor does not change it's state and it returns a failure.
181 public void testHandleAppendEntriesSenderPrevLogTermNotSameAsReceiverPrevLogTerm()
183 new JavaTestKit(getSystem()) {{
185 MockRaftActorContext context = (MockRaftActorContext)
186 createActorContext();
188 // First set the receivers term to lower number
189 context.getTermInformation().update(95, "test");
191 // Set the last log entry term for the receiver to be greater than
192 // what we will be sending as the prevLogTerm in AppendEntries
193 MockRaftActorContext.MockReplicatedLog mockReplicatedLog =
194 setLastLogEntry(context, 20, 0, "");
196 // Also set the entry at index 0 with term 20 which will be greater
197 // than the prevLogTerm sent by the sender
198 mockReplicatedLog.setReplicatedLogEntry(
199 new MockRaftActorContext.MockReplicatedLogEntry(20, 0, ""));
201 // AppendEntries is now sent with a bigger term
202 // this will set the receivers term to be the same as the sender's term
203 AppendEntries appendEntries =
204 new AppendEntries(100, "leader-1", 0, 0, null, 101);
206 RaftActorBehavior behavior = createBehavior(context);
208 // Send an unknown message so that the state of the RaftActor remains unchanged
209 RaftState expected = behavior.handleMessage(getRef(), "unknown");
211 RaftState raftState =
212 behavior.handleMessage(getRef(), appendEntries);
214 assertEquals(expected, raftState);
216 // Also expect an AppendEntriesReply to be sent where success is false
217 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
218 "AppendEntriesReply") {
219 // do not put code outside this method, will run afterwards
220 protected Boolean match(Object in) {
221 if (in instanceof AppendEntriesReply) {
222 AppendEntriesReply reply = (AppendEntriesReply) in;
223 return reply.isSuccess();
230 assertEquals(false, out);
239 * This test verifies that when a new AppendEntries message is received with
240 * new entries and the logs of the sender and receiver match that the new
241 * entries get added to the log and the log is incremented by the number of
242 * entries received in appendEntries
247 public void testHandleAppendEntriesAddNewEntries() throws Exception {
248 new JavaTestKit(getSystem()) {{
250 MockRaftActorContext context = (MockRaftActorContext)
251 createActorContext();
253 // First set the receivers term to lower number
254 context.getTermInformation().update(1, "test");
256 // Prepare the receivers log
257 MockRaftActorContext.SimpleReplicatedLog log =
258 new MockRaftActorContext.SimpleReplicatedLog();
260 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, "zero"));
262 new MockRaftActorContext.MockReplicatedLogEntry(1, 1, "one"));
264 new MockRaftActorContext.MockReplicatedLogEntry(1, 2, "two"));
266 context.setReplicatedLog(log);
268 // Prepare the entries to be sent with AppendEntries
269 List<ReplicatedLogEntry> entries = new ArrayList<>();
271 new MockRaftActorContext.MockReplicatedLogEntry(1, 3, "three"));
273 new MockRaftActorContext.MockReplicatedLogEntry(1, 4, "four"));
275 // Send appendEntries with the same term as was set on the receiver
276 // before the new behavior was created (1 in this case)
277 // This will not work for a Candidate because as soon as a Candidate
278 // is created it increments the term
279 AppendEntries appendEntries =
280 new AppendEntries(1, "leader-1", 2, 1, entries, 4);
282 RaftActorBehavior behavior = createBehavior(context);
284 // Send an unknown message so that the state of the RaftActor remains unchanged
285 RaftState expected = behavior.handleMessage(getRef(), "unknown");
287 RaftState raftState =
288 behavior.handleMessage(getRef(), appendEntries);
290 assertEquals(expected, raftState);
291 assertEquals(5, log.last().getIndex() + 1);
292 assertNotNull(log.get(3));
293 assertNotNull(log.get(4));
295 // Also expect an AppendEntriesReply to be sent where success is false
296 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
297 "AppendEntriesReply") {
298 // do not put code outside this method, will run afterwards
299 protected Boolean match(Object in) {
300 if (in instanceof AppendEntriesReply) {
301 AppendEntriesReply reply = (AppendEntriesReply) in;
302 return reply.isSuccess();
309 assertEquals(true, out);
318 * This test verifies that when a new AppendEntries message is received with
319 * new entries and the logs of the sender and receiver are out-of-sync that
320 * the log is first corrected by removing the out of sync entries from the
321 * log and then adding in the new entries sent with the AppendEntries message
326 public void testHandleAppendEntriesCorrectReceiverLogEntries()
328 new JavaTestKit(getSystem()) {{
330 MockRaftActorContext context = (MockRaftActorContext)
331 createActorContext();
333 // First set the receivers term to lower number
334 context.getTermInformation().update(2, "test");
336 // Prepare the receivers log
337 MockRaftActorContext.SimpleReplicatedLog log =
338 new MockRaftActorContext.SimpleReplicatedLog();
340 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, "zero"));
342 new MockRaftActorContext.MockReplicatedLogEntry(1, 1, "one"));
344 new MockRaftActorContext.MockReplicatedLogEntry(1, 2, "two"));
346 context.setReplicatedLog(log);
348 // Prepare the entries to be sent with AppendEntries
349 List<ReplicatedLogEntry> entries = new ArrayList<>();
351 new MockRaftActorContext.MockReplicatedLogEntry(2, 2, "two-1"));
353 new MockRaftActorContext.MockReplicatedLogEntry(2, 3, "three"));
355 // Send appendEntries with the same term as was set on the receiver
356 // before the new behavior was created (1 in this case)
357 // This will not work for a Candidate because as soon as a Candidate
358 // is created it increments the term
359 AppendEntries appendEntries =
360 new AppendEntries(2, "leader-1", 1, 1, entries, 3);
362 RaftActorBehavior behavior = createBehavior(context);
364 // Send an unknown message so that the state of the RaftActor remains unchanged
365 RaftState expected = behavior.handleMessage(getRef(), "unknown");
367 RaftState raftState =
368 behavior.handleMessage(getRef(), appendEntries);
370 assertEquals(expected, raftState);
372 // The entry at index 2 will be found out-of-sync with the leader
373 // and will be removed
374 // Then the two new entries will be added to the log
375 // Thus making the log to have 4 entries
376 assertEquals(4, log.last().getIndex() + 1);
377 assertNotNull(log.get(2));
379 // Check that the entry at index 2 has the new data
380 assertEquals("two-1", log.get(2).getData());
381 assertNotNull(log.get(3));
383 // Also expect an AppendEntriesReply to be sent where success is false
384 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
385 "AppendEntriesReply") {
386 // do not put code outside this method, will run afterwards
387 protected Boolean match(Object in) {
388 if (in instanceof AppendEntriesReply) {
389 AppendEntriesReply reply = (AppendEntriesReply) in;
390 return reply.isSuccess();
397 assertEquals(true, out);