Tune replication and stabilize tests
[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, 0, 0, new MockRaftActorContext.MockPayload(""));
162
163             List<ReplicatedLogEntry> entries =
164                 Arrays.asList(
165                     (ReplicatedLogEntry) new MockRaftActorContext.MockReplicatedLogEntry(100, 101,
166                         new MockRaftActorContext.MockPayload("foo"))
167                 );
168
169             // The new commitIndex is 101
170             AppendEntries appendEntries =
171                 new AppendEntries(100, "leader-1", 0, 0, entries, 101);
172
173             RaftState raftState =
174                 createBehavior(context).handleMessage(getRef(), appendEntries);
175
176             assertEquals(101L, context.getLastApplied());
177
178         }};
179     }
180
181     /**
182      * This test verifies that when an AppendEntries is received a specific prevLogTerm
183      * which does not match the term that is in RaftActors log entry at prevLogIndex
184      * then the RaftActor does not change it's state and it returns a failure.
185      *
186      * @throws Exception
187      */
188     @Test
189     public void testHandleAppendEntriesSenderPrevLogTermNotSameAsReceiverPrevLogTerm()
190         throws Exception {
191         new JavaTestKit(getSystem()) {{
192
193             MockRaftActorContext context = (MockRaftActorContext)
194                 createActorContext();
195
196             // First set the receivers term to lower number
197             context.getTermInformation().update(95, "test");
198
199             // Set the last log entry term for the receiver to be greater than
200             // what we will be sending as the prevLogTerm in AppendEntries
201             MockRaftActorContext.SimpleReplicatedLog mockReplicatedLog =
202                 setLastLogEntry(context, 20, 0, new MockRaftActorContext.MockPayload(""));
203
204             // AppendEntries is now sent with a bigger term
205             // this will set the receivers term to be the same as the sender's term
206             AppendEntries appendEntries =
207                 new AppendEntries(100, "leader-1", 0, 0, null, 101);
208
209             RaftActorBehavior behavior = createBehavior(context);
210
211             // Send an unknown message so that the state of the RaftActor remains unchanged
212             RaftState expected = behavior.handleMessage(getRef(), "unknown");
213
214             RaftState raftState =
215                 behavior.handleMessage(getRef(), appendEntries);
216
217             assertEquals(expected, raftState);
218
219             // Also expect an AppendEntriesReply to be sent where success is false
220             final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
221                 "AppendEntriesReply") {
222                 // do not put code outside this method, will run afterwards
223                 protected Boolean match(Object in) {
224                     if (in instanceof AppendEntriesReply) {
225                         AppendEntriesReply reply = (AppendEntriesReply) in;
226                         return reply.isSuccess();
227                     } else {
228                         throw noMatch();
229                     }
230                 }
231             }.get();
232
233             assertEquals(false, out);
234
235
236         }};
237     }
238
239
240
241     /**
242      * This test verifies that when a new AppendEntries message is received with
243      * new entries and the logs of the sender and receiver match that the new
244      * entries get added to the log and the log is incremented by the number of
245      * entries received in appendEntries
246      *
247      * @throws Exception
248      */
249     @Test
250     public void testHandleAppendEntriesAddNewEntries() throws Exception {
251         new JavaTestKit(getSystem()) {{
252
253             MockRaftActorContext context = (MockRaftActorContext)
254                 createActorContext();
255
256             // First set the receivers term to lower number
257             context.getTermInformation().update(1, "test");
258
259             // Prepare the receivers log
260             MockRaftActorContext.SimpleReplicatedLog log =
261                 new MockRaftActorContext.SimpleReplicatedLog();
262             log.append(
263                 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
264             log.append(
265                 new MockRaftActorContext.MockReplicatedLogEntry(1, 1, new MockRaftActorContext.MockPayload("one")));
266             log.append(
267                 new MockRaftActorContext.MockReplicatedLogEntry(1, 2, new MockRaftActorContext.MockPayload("two")));
268
269             context.setReplicatedLog(log);
270
271             // Prepare the entries to be sent with AppendEntries
272             List<ReplicatedLogEntry> entries = new ArrayList<>();
273             entries.add(
274                 new MockRaftActorContext.MockReplicatedLogEntry(1, 3, new MockRaftActorContext.MockPayload("three")));
275             entries.add(
276                 new MockRaftActorContext.MockReplicatedLogEntry(1, 4, new MockRaftActorContext.MockPayload("four")));
277
278             // Send appendEntries with the same term as was set on the receiver
279             // before the new behavior was created (1 in this case)
280             // This will not work for a Candidate because as soon as a Candidate
281             // is created it increments the term
282             AppendEntries appendEntries =
283                 new AppendEntries(1, "leader-1", 2, 1, entries, 4);
284
285             RaftActorBehavior behavior = createBehavior(context);
286
287             // Send an unknown message so that the state of the RaftActor remains unchanged
288             RaftState expected = behavior.handleMessage(getRef(), "unknown");
289
290             RaftState raftState =
291                 behavior.handleMessage(getRef(), appendEntries);
292
293             assertEquals(expected, raftState);
294             assertEquals(5, log.last().getIndex() + 1);
295             assertNotNull(log.get(3));
296             assertNotNull(log.get(4));
297
298             // Also expect an AppendEntriesReply to be sent where success is false
299             final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
300                 "AppendEntriesReply") {
301                 // do not put code outside this method, will run afterwards
302                 protected Boolean match(Object in) {
303                     if (in instanceof AppendEntriesReply) {
304                         AppendEntriesReply reply = (AppendEntriesReply) in;
305                         return reply.isSuccess();
306                     } else {
307                         throw noMatch();
308                     }
309                 }
310             }.get();
311
312             assertEquals(true, out);
313
314
315         }};
316     }
317
318
319
320     /**
321      * This test verifies that when a new AppendEntries message is received with
322      * new entries and the logs of the sender and receiver are out-of-sync that
323      * the log is first corrected by removing the out of sync entries from the
324      * log and then adding in the new entries sent with the AppendEntries message
325      *
326      * @throws Exception
327      */
328     @Test
329     public void testHandleAppendEntriesCorrectReceiverLogEntries()
330         throws Exception {
331         new JavaTestKit(getSystem()) {{
332
333             MockRaftActorContext context = (MockRaftActorContext)
334                 createActorContext();
335
336             // First set the receivers term to lower number
337             context.getTermInformation().update(2, "test");
338
339             // Prepare the receivers log
340             MockRaftActorContext.SimpleReplicatedLog log =
341                 new MockRaftActorContext.SimpleReplicatedLog();
342             log.append(
343                 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
344             log.append(
345                 new MockRaftActorContext.MockReplicatedLogEntry(1, 1, new MockRaftActorContext.MockPayload("one")));
346             log.append(
347                 new MockRaftActorContext.MockReplicatedLogEntry(1, 2, new MockRaftActorContext.MockPayload("two")));
348
349             context.setReplicatedLog(log);
350
351             // Prepare the entries to be sent with AppendEntries
352             List<ReplicatedLogEntry> entries = new ArrayList<>();
353             entries.add(
354                 new MockRaftActorContext.MockReplicatedLogEntry(2, 2, new MockRaftActorContext.MockPayload("two-1")));
355             entries.add(
356                 new MockRaftActorContext.MockReplicatedLogEntry(2, 3, new MockRaftActorContext.MockPayload("three")));
357
358             // Send appendEntries with the same term as was set on the receiver
359             // before the new behavior was created (1 in this case)
360             // This will not work for a Candidate because as soon as a Candidate
361             // is created it increments the term
362             AppendEntries appendEntries =
363                 new AppendEntries(2, "leader-1", 1, 1, entries, 3);
364
365             RaftActorBehavior behavior = createBehavior(context);
366
367             // Send an unknown message so that the state of the RaftActor remains unchanged
368             RaftState expected = behavior.handleMessage(getRef(), "unknown");
369
370             RaftState raftState =
371                 behavior.handleMessage(getRef(), appendEntries);
372
373             assertEquals(expected, raftState);
374
375             // The entry at index 2 will be found out-of-sync with the leader
376             // and will be removed
377             // Then the two new entries will be added to the log
378             // Thus making the log to have 4 entries
379             assertEquals(4, log.last().getIndex() + 1);
380             assertNotNull(log.get(2));
381
382             assertEquals("one", log.get(1).getData().toString());
383
384             // Check that the entry at index 2 has the new data
385             assertEquals("two-1", log.get(2).getData().toString());
386
387             assertEquals("three", log.get(3).getData().toString());
388
389             assertNotNull(log.get(3));
390
391             // Also expect an AppendEntriesReply to be sent where success is false
392             final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
393                 "AppendEntriesReply") {
394                 // do not put code outside this method, will run afterwards
395                 protected Boolean match(Object in) {
396                     if (in instanceof AppendEntriesReply) {
397                         AppendEntriesReply reply = (AppendEntriesReply) in;
398                         return reply.isSuccess();
399                     } else {
400                         throw noMatch();
401                     }
402                 }
403             }.get();
404
405             assertEquals(true, out);
406
407
408         }};
409     }
410
411 }