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

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.