Merge "Bug 1637: Change Rpc actor calls to async"
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / test / java / org / opendaylight / controller / cluster / raft / behaviors / FollowerTest.java
1 package org.opendaylight.controller.cluster.raft.behaviors;
2
3 import akka.actor.ActorRef;
4 import akka.actor.Props;
5 import akka.testkit.JavaTestKit;
6 import junit.framework.Assert;
7 import org.junit.Test;
8 import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl;
9 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
10 import org.opendaylight.controller.cluster.raft.RaftActorContext;
11 import org.opendaylight.controller.cluster.raft.RaftState;
12 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
13 import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
14 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
15 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
16 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
17 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
18 import org.opendaylight.controller.cluster.raft.utils.DoNothingActor;
19
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.List;
23
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertNotNull;
26
27 public class FollowerTest extends AbstractRaftActorBehaviorTest {
28
29     private final ActorRef followerActor = getSystem().actorOf(Props.create(
30         DoNothingActor.class));
31
32
33     @Override protected RaftActorBehavior createBehavior(RaftActorContext actorContext) {
34         return new Follower(actorContext);
35     }
36
37     @Override protected RaftActorContext createActorContext() {
38         return new MockRaftActorContext("test", getSystem(), followerActor);
39     }
40
41     @Test
42     public void testThatAnElectionTimeoutIsTriggered(){
43         new JavaTestKit(getSystem()) {{
44
45             new Within(DefaultConfigParamsImpl.HEART_BEAT_INTERVAL.$times(6)) {
46                 protected void run() {
47
48                     Follower follower = new Follower(createActorContext(getTestActor()));
49
50                     final Boolean out = new ExpectMsg<Boolean>(DefaultConfigParamsImpl.HEART_BEAT_INTERVAL.$times(6), "ElectionTimeout") {
51                         // do not put code outside this method, will run afterwards
52                         protected Boolean match(Object in) {
53                             if (in instanceof ElectionTimeout) {
54                                 return true;
55                             } else {
56                                 throw noMatch();
57                             }
58                         }
59                     }.get();
60
61                     assertEquals(true, out);
62                 }
63             };
64         }};
65     }
66
67     @Test
68     public void testHandleElectionTimeout(){
69         RaftActorContext raftActorContext = createActorContext();
70         Follower follower =
71             new Follower(raftActorContext);
72
73         RaftState raftState =
74             follower.handleMessage(followerActor, new ElectionTimeout());
75
76         Assert.assertEquals(RaftState.Candidate, raftState);
77     }
78
79     @Test
80     public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNull(){
81         new JavaTestKit(getSystem()) {{
82
83             new Within(duration("1 seconds")) {
84                 protected void run() {
85
86                     RaftActorContext context = createActorContext(getTestActor());
87
88                     context.getTermInformation().update(1000, null);
89
90                     RaftActorBehavior follower = createBehavior(context);
91
92                     follower.handleMessage(getTestActor(), new RequestVote(1000, "test", 10000, 999));
93
94                     final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"), "RequestVoteReply") {
95                         // do not put code outside this method, will run afterwards
96                         protected Boolean match(Object in) {
97                             if (in instanceof RequestVoteReply) {
98                                 RequestVoteReply reply = (RequestVoteReply) in;
99                                 return reply.isVoteGranted();
100                             } else {
101                                 throw noMatch();
102                             }
103                         }
104                     }.get();
105
106                     assertEquals(true, out);
107                 }
108             };
109         }};
110     }
111
112     @Test
113     public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForIsNotTheSameAsCandidateId(){
114         new JavaTestKit(getSystem()) {{
115
116             new Within(duration("1 seconds")) {
117                 protected void run() {
118
119                     RaftActorContext context = createActorContext(getTestActor());
120
121                     context.getTermInformation().update(1000, "test");
122
123                     RaftActorBehavior follower = createBehavior(context);
124
125                     follower.handleMessage(getTestActor(), new RequestVote(1000, "candidate", 10000, 999));
126
127                     final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"), "RequestVoteReply") {
128                         // do not put code outside this method, will run afterwards
129                         protected Boolean match(Object in) {
130                             if (in instanceof RequestVoteReply) {
131                                 RequestVoteReply reply = (RequestVoteReply) in;
132                                 return reply.isVoteGranted();
133                             } else {
134                                 throw noMatch();
135                             }
136                         }
137                     }.get();
138
139                     assertEquals(false, out);
140                 }
141             };
142         }};
143     }
144
145     /**
146      * This test verifies that when an AppendEntries RPC is received by a RaftActor
147      * with a commitIndex that is greater than what has been applied to the
148      * state machine of the RaftActor, the RaftActor applies the state and
149      * sets it current applied state to the commitIndex of the sender.
150      *
151      * @throws Exception
152      */
153     @Test
154     public void testHandleAppendEntriesWithNewerCommitIndex() throws Exception {
155         new JavaTestKit(getSystem()) {{
156
157             RaftActorContext context =
158                 createActorContext();
159
160             context.setLastApplied(100);
161             setLastLogEntry((MockRaftActorContext) context, 1, 100, new MockRaftActorContext.MockPayload(""));
162             ((MockRaftActorContext) context).getReplicatedLog().setSnapshotIndex(99);
163
164             List<ReplicatedLogEntry> entries =
165                 Arrays.asList(
166                     (ReplicatedLogEntry) new MockRaftActorContext.MockReplicatedLogEntry(2, 101,
167                         new MockRaftActorContext.MockPayload("foo"))
168                 );
169
170             // The new commitIndex is 101
171             AppendEntries appendEntries =
172                 new AppendEntries(2, "leader-1", 100, 1, entries, 101);
173
174             RaftState raftState =
175                 createBehavior(context).handleMessage(getRef(), appendEntries);
176
177             assertEquals(101L, context.getLastApplied());
178
179         }};
180     }
181
182     /**
183      * This test verifies that when an AppendEntries is received a specific prevLogTerm
184      * which does not match the term that is in RaftActors log entry at prevLogIndex
185      * then the RaftActor does not change it's state and it returns a failure.
186      *
187      * @throws Exception
188      */
189     @Test
190     public void testHandleAppendEntriesSenderPrevLogTermNotSameAsReceiverPrevLogTerm()
191         throws Exception {
192         new JavaTestKit(getSystem()) {{
193
194             MockRaftActorContext context = (MockRaftActorContext)
195                 createActorContext();
196
197             // First set the receivers term to lower number
198             context.getTermInformation().update(95, "test");
199
200             // Set the last log entry term for the receiver to be greater than
201             // what we will be sending as the prevLogTerm in AppendEntries
202             MockRaftActorContext.SimpleReplicatedLog mockReplicatedLog =
203                 setLastLogEntry(context, 20, 0, new MockRaftActorContext.MockPayload(""));
204
205             // AppendEntries is now sent with a bigger term
206             // this will set the receivers term to be the same as the sender's term
207             AppendEntries appendEntries =
208                 new AppendEntries(100, "leader-1", 0, 0, null, 101);
209
210             RaftActorBehavior behavior = createBehavior(context);
211
212             // Send an unknown message so that the state of the RaftActor remains unchanged
213             RaftState expected = behavior.handleMessage(getRef(), "unknown");
214
215             RaftState raftState =
216                 behavior.handleMessage(getRef(), appendEntries);
217
218             assertEquals(expected, raftState);
219
220             // Also expect an AppendEntriesReply to be sent where success is false
221             final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
222                 "AppendEntriesReply") {
223                 // do not put code outside this method, will run afterwards
224                 protected Boolean match(Object in) {
225                     if (in instanceof AppendEntriesReply) {
226                         AppendEntriesReply reply = (AppendEntriesReply) in;
227                         return reply.isSuccess();
228                     } else {
229                         throw noMatch();
230                     }
231                 }
232             }.get();
233
234             assertEquals(false, out);
235
236
237         }};
238     }
239
240
241
242     /**
243      * This test verifies that when a new AppendEntries message is received with
244      * new entries and the logs of the sender and receiver match that the new
245      * entries get added to the log and the log is incremented by the number of
246      * entries received in appendEntries
247      *
248      * @throws Exception
249      */
250     @Test
251     public void testHandleAppendEntriesAddNewEntries() throws Exception {
252         new JavaTestKit(getSystem()) {{
253
254             MockRaftActorContext context = (MockRaftActorContext)
255                 createActorContext();
256
257             // First set the receivers term to lower number
258             context.getTermInformation().update(1, "test");
259
260             // Prepare the receivers log
261             MockRaftActorContext.SimpleReplicatedLog log =
262                 new MockRaftActorContext.SimpleReplicatedLog();
263             log.append(
264                 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
265             log.append(
266                 new MockRaftActorContext.MockReplicatedLogEntry(1, 1, new MockRaftActorContext.MockPayload("one")));
267             log.append(
268                 new MockRaftActorContext.MockReplicatedLogEntry(1, 2, new MockRaftActorContext.MockPayload("two")));
269
270             context.setReplicatedLog(log);
271
272             // Prepare the entries to be sent with AppendEntries
273             List<ReplicatedLogEntry> entries = new ArrayList<>();
274             entries.add(
275                 new MockRaftActorContext.MockReplicatedLogEntry(1, 3, new MockRaftActorContext.MockPayload("three")));
276             entries.add(
277                 new MockRaftActorContext.MockReplicatedLogEntry(1, 4, new MockRaftActorContext.MockPayload("four")));
278
279             // Send appendEntries with the same term as was set on the receiver
280             // before the new behavior was created (1 in this case)
281             // This will not work for a Candidate because as soon as a Candidate
282             // is created it increments the term
283             AppendEntries appendEntries =
284                 new AppendEntries(1, "leader-1", 2, 1, entries, 4);
285
286             RaftActorBehavior behavior = createBehavior(context);
287
288             // Send an unknown message so that the state of the RaftActor remains unchanged
289             RaftState expected = behavior.handleMessage(getRef(), "unknown");
290
291             RaftState raftState =
292                 behavior.handleMessage(getRef(), appendEntries);
293
294             assertEquals(expected, raftState);
295             assertEquals(5, log.last().getIndex() + 1);
296             assertNotNull(log.get(3));
297             assertNotNull(log.get(4));
298
299             // Also expect an AppendEntriesReply to be sent where success is false
300             final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
301                 "AppendEntriesReply") {
302                 // do not put code outside this method, will run afterwards
303                 protected Boolean match(Object in) {
304                     if (in instanceof AppendEntriesReply) {
305                         AppendEntriesReply reply = (AppendEntriesReply) in;
306                         return reply.isSuccess();
307                     } else {
308                         throw noMatch();
309                     }
310                 }
311             }.get();
312
313             assertEquals(true, out);
314
315
316         }};
317     }
318
319
320
321     /**
322      * This test verifies that when a new AppendEntries message is received with
323      * new entries and the logs of the sender and receiver are out-of-sync that
324      * the log is first corrected by removing the out of sync entries from the
325      * log and then adding in the new entries sent with the AppendEntries message
326      *
327      * @throws Exception
328      */
329     @Test
330     public void testHandleAppendEntriesCorrectReceiverLogEntries()
331         throws Exception {
332         new JavaTestKit(getSystem()) {{
333
334             MockRaftActorContext context = (MockRaftActorContext)
335                 createActorContext();
336
337             // First set the receivers term to lower number
338             context.getTermInformation().update(2, "test");
339
340             // Prepare the receivers log
341             MockRaftActorContext.SimpleReplicatedLog log =
342                 new MockRaftActorContext.SimpleReplicatedLog();
343             log.append(
344                 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
345             log.append(
346                 new MockRaftActorContext.MockReplicatedLogEntry(1, 1, new MockRaftActorContext.MockPayload("one")));
347             log.append(
348                 new MockRaftActorContext.MockReplicatedLogEntry(1, 2, new MockRaftActorContext.MockPayload("two")));
349
350             context.setReplicatedLog(log);
351
352             // Prepare the entries to be sent with AppendEntries
353             List<ReplicatedLogEntry> entries = new ArrayList<>();
354             entries.add(
355                 new MockRaftActorContext.MockReplicatedLogEntry(2, 2, new MockRaftActorContext.MockPayload("two-1")));
356             entries.add(
357                 new MockRaftActorContext.MockReplicatedLogEntry(2, 3, new MockRaftActorContext.MockPayload("three")));
358
359             // Send appendEntries with the same term as was set on the receiver
360             // before the new behavior was created (1 in this case)
361             // This will not work for a Candidate because as soon as a Candidate
362             // is created it increments the term
363             AppendEntries appendEntries =
364                 new AppendEntries(2, "leader-1", 1, 1, entries, 3);
365
366             RaftActorBehavior behavior = createBehavior(context);
367
368             // Send an unknown message so that the state of the RaftActor remains unchanged
369             RaftState expected = behavior.handleMessage(getRef(), "unknown");
370
371             RaftState raftState =
372                 behavior.handleMessage(getRef(), appendEntries);
373
374             assertEquals(expected, raftState);
375
376             // The entry at index 2 will be found out-of-sync with the leader
377             // and will be removed
378             // Then the two new entries will be added to the log
379             // Thus making the log to have 4 entries
380             assertEquals(4, log.last().getIndex() + 1);
381             assertNotNull(log.get(2));
382
383             assertEquals("one", log.get(1).getData().toString());
384
385             // Check that the entry at index 2 has the new data
386             assertEquals("two-1", log.get(2).getData().toString());
387
388             assertEquals("three", log.get(3).getData().toString());
389
390             assertNotNull(log.get(3));
391
392             // Also expect an AppendEntriesReply to be sent where success is false
393             final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
394                 "AppendEntriesReply") {
395                 // do not put code outside this method, will run afterwards
396                 protected Boolean match(Object in) {
397                     if (in instanceof AppendEntriesReply) {
398                         AppendEntriesReply reply = (AppendEntriesReply) in;
399                         return reply.isSuccess();
400                     } else {
401                         throw noMatch();
402                     }
403                 }
404             }.get();
405
406             assertEquals(true, out);
407
408
409         }};
410     }
411
412 }