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.SimpleReplicatedLog mockReplicatedLog =
194 setLastLogEntry(context, 20, 0, "");
196 // AppendEntries is now sent with a bigger term
197 // this will set the receivers term to be the same as the sender's term
198 AppendEntries appendEntries =
199 new AppendEntries(100, "leader-1", 0, 0, null, 101);
201 RaftActorBehavior behavior = createBehavior(context);
203 // Send an unknown message so that the state of the RaftActor remains unchanged
204 RaftState expected = behavior.handleMessage(getRef(), "unknown");
206 RaftState raftState =
207 behavior.handleMessage(getRef(), appendEntries);
209 assertEquals(expected, raftState);
211 // Also expect an AppendEntriesReply to be sent where success is false
212 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
213 "AppendEntriesReply") {
214 // do not put code outside this method, will run afterwards
215 protected Boolean match(Object in) {
216 if (in instanceof AppendEntriesReply) {
217 AppendEntriesReply reply = (AppendEntriesReply) in;
218 return reply.isSuccess();
225 assertEquals(false, out);
234 * This test verifies that when a new AppendEntries message is received with
235 * new entries and the logs of the sender and receiver match that the new
236 * entries get added to the log and the log is incremented by the number of
237 * entries received in appendEntries
242 public void testHandleAppendEntriesAddNewEntries() throws Exception {
243 new JavaTestKit(getSystem()) {{
245 MockRaftActorContext context = (MockRaftActorContext)
246 createActorContext();
248 // First set the receivers term to lower number
249 context.getTermInformation().update(1, "test");
251 // Prepare the receivers log
252 MockRaftActorContext.SimpleReplicatedLog log =
253 new MockRaftActorContext.SimpleReplicatedLog();
255 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, "zero"));
257 new MockRaftActorContext.MockReplicatedLogEntry(1, 1, "one"));
259 new MockRaftActorContext.MockReplicatedLogEntry(1, 2, "two"));
261 context.setReplicatedLog(log);
263 // Prepare the entries to be sent with AppendEntries
264 List<ReplicatedLogEntry> entries = new ArrayList<>();
266 new MockRaftActorContext.MockReplicatedLogEntry(1, 3, "three"));
268 new MockRaftActorContext.MockReplicatedLogEntry(1, 4, "four"));
270 // Send appendEntries with the same term as was set on the receiver
271 // before the new behavior was created (1 in this case)
272 // This will not work for a Candidate because as soon as a Candidate
273 // is created it increments the term
274 AppendEntries appendEntries =
275 new AppendEntries(1, "leader-1", 2, 1, entries, 4);
277 RaftActorBehavior behavior = createBehavior(context);
279 // Send an unknown message so that the state of the RaftActor remains unchanged
280 RaftState expected = behavior.handleMessage(getRef(), "unknown");
282 RaftState raftState =
283 behavior.handleMessage(getRef(), appendEntries);
285 assertEquals(expected, raftState);
286 assertEquals(5, log.last().getIndex() + 1);
287 assertNotNull(log.get(3));
288 assertNotNull(log.get(4));
290 // Also expect an AppendEntriesReply to be sent where success is false
291 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
292 "AppendEntriesReply") {
293 // do not put code outside this method, will run afterwards
294 protected Boolean match(Object in) {
295 if (in instanceof AppendEntriesReply) {
296 AppendEntriesReply reply = (AppendEntriesReply) in;
297 return reply.isSuccess();
304 assertEquals(true, out);
313 * This test verifies that when a new AppendEntries message is received with
314 * new entries and the logs of the sender and receiver are out-of-sync that
315 * the log is first corrected by removing the out of sync entries from the
316 * log and then adding in the new entries sent with the AppendEntries message
321 public void testHandleAppendEntriesCorrectReceiverLogEntries()
323 new JavaTestKit(getSystem()) {{
325 MockRaftActorContext context = (MockRaftActorContext)
326 createActorContext();
328 // First set the receivers term to lower number
329 context.getTermInformation().update(2, "test");
331 // Prepare the receivers log
332 MockRaftActorContext.SimpleReplicatedLog log =
333 new MockRaftActorContext.SimpleReplicatedLog();
335 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, "zero"));
337 new MockRaftActorContext.MockReplicatedLogEntry(1, 1, "one"));
339 new MockRaftActorContext.MockReplicatedLogEntry(1, 2, "two"));
341 context.setReplicatedLog(log);
343 // Prepare the entries to be sent with AppendEntries
344 List<ReplicatedLogEntry> entries = new ArrayList<>();
346 new MockRaftActorContext.MockReplicatedLogEntry(2, 2, "two-1"));
348 new MockRaftActorContext.MockReplicatedLogEntry(2, 3, "three"));
350 // Send appendEntries with the same term as was set on the receiver
351 // before the new behavior was created (1 in this case)
352 // This will not work for a Candidate because as soon as a Candidate
353 // is created it increments the term
354 AppendEntries appendEntries =
355 new AppendEntries(2, "leader-1", 1, 1, entries, 3);
357 RaftActorBehavior behavior = createBehavior(context);
359 // Send an unknown message so that the state of the RaftActor remains unchanged
360 RaftState expected = behavior.handleMessage(getRef(), "unknown");
362 RaftState raftState =
363 behavior.handleMessage(getRef(), appendEntries);
365 assertEquals(expected, raftState);
367 // The entry at index 2 will be found out-of-sync with the leader
368 // and will be removed
369 // Then the two new entries will be added to the log
370 // Thus making the log to have 4 entries
371 assertEquals(4, log.last().getIndex() + 1);
372 assertNotNull(log.get(2));
374 // Check that the entry at index 2 has the new data
375 assertEquals("two-1", log.get(2).getData());
376 assertNotNull(log.get(3));
378 // Also expect an AppendEntriesReply to be sent where success is false
379 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
380 "AppendEntriesReply") {
381 // do not put code outside this method, will run afterwards
382 protected Boolean match(Object in) {
383 if (in instanceof AppendEntriesReply) {
384 AppendEntriesReply reply = (AppendEntriesReply) in;
385 return reply.isSuccess();
392 assertEquals(true, out);