Fix intermittent PreLeaderScenarioTest failure
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / test / java / org / opendaylight / controller / cluster / raft / behaviors / CandidateTest.java
1 /*
2  * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.cluster.raft.behaviors;
10
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertTrue;
13
14 import akka.actor.ActorRef;
15 import akka.dispatch.Dispatchers;
16 import akka.testkit.TestActorRef;
17 import com.google.common.base.Stopwatch;
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.concurrent.TimeUnit;
24 import org.junit.After;
25 import org.junit.Before;
26 import org.junit.Test;
27 import org.mockito.Mockito;
28 import org.opendaylight.controller.cluster.NonPersistentDataProvider;
29 import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl;
30 import org.opendaylight.controller.cluster.raft.ElectionTerm;
31 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
32 import org.opendaylight.controller.cluster.raft.RaftActorContext;
33 import org.opendaylight.controller.cluster.raft.RaftActorContextImpl;
34 import org.opendaylight.controller.cluster.raft.RaftState;
35 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
36 import org.opendaylight.controller.cluster.raft.VotingState;
37 import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
38 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
39 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
40 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
41 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
42 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
43 import org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry;
44 import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 public class CandidateTest extends AbstractRaftActorBehaviorTest<Candidate> {
49     static final Logger LOG = LoggerFactory.getLogger(CandidateTest.class);
50
51     private final TestActorRef<MessageCollectorActor> candidateActor = actorFactory.createTestActor(
52             MessageCollectorActor.props().withDispatcher(Dispatchers.DefaultDispatcherId()),
53             actorFactory.generateActorId("candidate"));
54
55     private ActorRef[] peerActors;
56
57     private RaftActorBehavior candidate;
58
59     @Before
60     public void setUp(){
61     }
62
63     @Override
64     @After
65     public void tearDown() {
66         if (candidate != null) {
67             candidate.close();
68         }
69
70         super.tearDown();
71     }
72
73     @Test
74     public void testWhenACandidateIsCreatedItIncrementsTheCurrentTermAndVotesForItself() {
75         RaftActorContext raftActorContext = createActorContext();
76         long expectedTerm = raftActorContext.getTermInformation().getCurrentTerm();
77
78         candidate = new Candidate(raftActorContext);
79
80         assertEquals("getCurrentTerm", expectedTerm + 1, raftActorContext.getTermInformation().getCurrentTerm());
81         assertEquals("getVotedFor", raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor());
82     }
83
84     @Test
85     public void testThatAnElectionTimeoutIsTriggered() {
86         MockRaftActorContext actorContext = createActorContext();
87         candidate = new Candidate(actorContext);
88
89         MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class,
90                 actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis());
91     }
92
93     @Test
94     public void testHandleElectionTimeoutWhenThereAreZeroPeers() {
95         RaftActorContext raftActorContext = createActorContext();
96         candidate = new Candidate(raftActorContext);
97
98         RaftActorBehavior newBehavior =
99             candidate.handleMessage(candidateActor, ElectionTimeout.INSTANCE);
100
101         assertEquals("Behavior", RaftState.Leader, newBehavior.state());
102     }
103
104     @Test
105     public void testHandleElectionTimeoutWhenThereAreTwoNodeCluster() {
106         MockRaftActorContext raftActorContext = createActorContext();
107         raftActorContext.setPeerAddresses(setupPeers(1));
108         candidate = new Candidate(raftActorContext);
109
110         candidate = candidate.handleMessage(candidateActor, ElectionTimeout.INSTANCE);
111
112         assertEquals("Behavior", RaftState.Candidate, candidate.state());
113     }
114
115     @Test
116     public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodeCluster() {
117         MockRaftActorContext raftActorContext = createActorContext();
118         raftActorContext.setLastApplied(raftActorContext.getReplicatedLog().lastIndex());
119         raftActorContext.setPeerAddresses(setupPeers(2));
120         candidate = new Candidate(raftActorContext);
121
122         candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
123
124         assertEquals("Behavior", RaftState.Leader, candidate.state());
125     }
126
127     @Test
128     public void testBecomePreLeaderOnReceivingMajorityVotesInThreeNodeCluster() {
129         MockRaftActorContext raftActorContext = createActorContext();
130         raftActorContext.setLastApplied(-1);
131         raftActorContext.setPeerAddresses(setupPeers(2));
132         candidate = new Candidate(raftActorContext);
133
134         candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
135
136         // LastApplied is -1 and behind the last index.
137         assertEquals("Behavior", RaftState.PreLeader, candidate.state());
138     }
139
140     @Test
141     public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster() {
142         MockRaftActorContext raftActorContext = createActorContext();
143         raftActorContext.getTermInformation().update(2L, "other");
144         raftActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder()
145                 .createEntries(0, 5, 1).build());
146         raftActorContext.setCommitIndex(raftActorContext.getReplicatedLog().lastIndex());
147         raftActorContext.setLastApplied(raftActorContext.getReplicatedLog().lastIndex());
148         raftActorContext.setPeerAddresses(setupPeers(4));
149         candidate = new Candidate(raftActorContext);
150
151         RequestVote requestVote = MessageCollectorActor.expectFirstMatching(peerActors[0], RequestVote.class);
152         assertEquals("getTerm", 3L, requestVote.getTerm());
153         assertEquals("getCandidateId", raftActorContext.getId(), requestVote.getCandidateId());
154         assertEquals("getLastLogTerm", 1L, requestVote.getLastLogTerm());
155         assertEquals("getLastLogIndex", 4L, requestVote.getLastLogIndex());
156
157         MessageCollectorActor.expectFirstMatching(peerActors[1], RequestVote.class);
158         MessageCollectorActor.expectFirstMatching(peerActors[2], RequestVote.class);
159         MessageCollectorActor.expectFirstMatching(peerActors[3], RequestVote.class);
160
161         // First peers denies the vote.
162         candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false));
163
164         assertEquals("Behavior", RaftState.Candidate, candidate.state());
165
166         candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, true));
167
168         assertEquals("Behavior", RaftState.Candidate, candidate.state());
169
170         candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
171
172         assertEquals("Behavior", RaftState.Leader, candidate.state());
173     }
174
175     @Test
176     public void testBecomeLeaderOnReceivingMajorityVotesWithNonVotingPeers() {
177         ElectionTerm mockElectionTerm = Mockito.mock(ElectionTerm.class);
178         Mockito.doReturn(1L).when(mockElectionTerm).getCurrentTerm();
179         RaftActorContext raftActorContext = new RaftActorContextImpl(candidateActor, candidateActor.actorContext(),
180                 "candidate", mockElectionTerm, -1, -1, setupPeers(4), new DefaultConfigParamsImpl(),
181                 new NonPersistentDataProvider(Runnable::run), applyState -> { }, LOG);
182         raftActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().build());
183         raftActorContext.getPeerInfo("peer1").setVotingState(VotingState.NON_VOTING);
184         raftActorContext.getPeerInfo("peer4").setVotingState(VotingState.NON_VOTING);
185         candidate = new Candidate(raftActorContext);
186
187         MessageCollectorActor.expectFirstMatching(peerActors[1], RequestVote.class);
188         MessageCollectorActor.expectFirstMatching(peerActors[2], RequestVote.class);
189         MessageCollectorActor.assertNoneMatching(peerActors[0], RequestVote.class, 300);
190         MessageCollectorActor.assertNoneMatching(peerActors[3], RequestVote.class, 100);
191
192         candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, false));
193
194         assertEquals("Behavior", RaftState.Candidate, candidate.state());
195
196         candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
197
198         assertEquals("Behavior", RaftState.Leader, candidate.state());
199     }
200
201     @Test
202     public void testResponseToHandleAppendEntriesWithLowerTerm() {
203         candidate = new Candidate(createActorContext());
204
205         setupPeers(1);
206         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
207                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
208
209         AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
210                 peerActors[0], AppendEntriesReply.class);
211         assertEquals("isSuccess", false, reply.isSuccess());
212         assertEquals("getTerm", 2, reply.getTerm());
213         assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Candidate);
214     }
215
216     @Test
217     public void testResponseToHandleAppendEntriesWithHigherTerm() {
218         candidate = new Candidate(createActorContext());
219
220         setupPeers(1);
221         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(5, "test", 0, 0,
222                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
223
224         assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Follower);
225     }
226
227     @Test
228     public void testResponseToHandleAppendEntriesWithEqualTerm() {
229         MockRaftActorContext actorContext = createActorContext();
230
231         candidate = new Candidate(actorContext);
232
233         setupPeers(1);
234         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(2, "test", 0, 0,
235                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
236
237         assertTrue("New Behavior : " + newBehavior + " term = " + actorContext.getTermInformation().getCurrentTerm(),
238                 newBehavior instanceof Follower);
239     }
240
241
242     @Test
243     public void testResponseToRequestVoteWithLowerTerm() {
244         candidate = new Candidate(createActorContext());
245
246         setupPeers(1);
247         candidate.handleMessage(peerActors[0], new RequestVote(1, "test", 0, 0));
248
249         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
250                 peerActors[0], RequestVoteReply.class);
251         assertEquals("isVoteGranted", false, reply.isVoteGranted());
252         assertEquals("getTerm", 2, reply.getTerm());
253     }
254
255     @Test
256     public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForMatches() {
257         MockRaftActorContext context = createActorContext();
258         context.getTermInformation().update(1000, null);
259
260         // Once a candidate is created it will immediately increment the current term so after
261         // construction the currentTerm should be 1001
262         candidate = new Candidate(context);
263
264         setupPeers(1);
265         candidate.handleMessage(peerActors[0], new RequestVote(1001, context.getId(), 10000, 999));
266
267         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
268                 peerActors[0], RequestVoteReply.class);
269         assertEquals("isVoteGranted", true, reply.isVoteGranted());
270         assertEquals("getTerm", 1001, reply.getTerm());
271     }
272
273     @Test
274     public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForDoesNotMatch() {
275         MockRaftActorContext context = createActorContext();
276         context.getTermInformation().update(1000, null);
277
278         // Once a candidate is created it will immediately increment the current term so after
279         // construction the currentTerm should be 1001
280         candidate = new Candidate(context);
281
282         setupPeers(1);
283
284         // RequestVote candidate ID ("candidate2") does not match this candidate's votedFor
285         // (it votes for itself)
286         candidate.handleMessage(peerActors[0], new RequestVote(1001, "candidate2", 10000, 999));
287
288         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
289                 peerActors[0], RequestVoteReply.class);
290         assertEquals("isVoteGranted", false, reply.isVoteGranted());
291         assertEquals("getTerm", 1001, reply.getTerm());
292     }
293
294     @Test
295     public void testCandidateSchedulesElectionTimeoutImmediatelyWhenItHasNoPeers() {
296         MockRaftActorContext context = createActorContext();
297
298         Stopwatch stopwatch = Stopwatch.createStarted();
299
300         candidate = createBehavior(context);
301
302         MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class);
303
304         long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
305
306         assertTrue(elapsed < context.getConfigParams().getElectionTimeOutInterval().toMillis());
307     }
308
309     @Test
310     @Override
311     public void testHandleAppendEntriesAddSameEntryToLog() {
312         MockRaftActorContext context = createActorContext();
313
314         context.getTermInformation().update(2, "test");
315
316         // Prepare the receivers log
317         MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
318         setLastLogEntry(context, 2, 0, payload);
319
320         List<ReplicatedLogEntry> entries = new ArrayList<>();
321         entries.add(new SimpleReplicatedLogEntry(0, 2, payload));
322
323         final AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
324
325         behavior = createBehavior(context);
326
327         // Resetting the Candidates term to make sure it will match
328         // the term sent by AppendEntries. If this was not done then
329         // the test will fail because the Candidate will assume that
330         // the message was sent to it from a lower term peer and will
331         // thus respond with a failure
332         context.getTermInformation().update(2, "test");
333
334         // Send an unknown message so that the state of the RaftActor remains unchanged
335         behavior.handleMessage(candidateActor, "unknown");
336
337         RaftActorBehavior raftBehavior = behavior.handleMessage(candidateActor, appendEntries);
338
339         assertEquals("Raft state", RaftState.Follower, raftBehavior.state());
340
341         assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
342
343         handleAppendEntriesAddSameEntryToLogReply(candidateActor);
344     }
345
346     @Override
347     protected Candidate createBehavior(final RaftActorContext actorContext) {
348         return new Candidate(actorContext);
349     }
350
351     @Override
352     protected MockRaftActorContext createActorContext() {
353         return new MockRaftActorContext("candidate", getSystem(), candidateActor);
354     }
355
356     private Map<String, String> setupPeers(final int count) {
357         Map<String, String> peerMap = new HashMap<>();
358         peerActors = new ActorRef[count];
359         for (int i = 0; i < count; i++) {
360             peerActors[i] = actorFactory.createActor(MessageCollectorActor.props(),
361                     actorFactory.generateActorId("peer"));
362             peerMap.put("peer" + (i + 1), peerActors[i].path().toString());
363         }
364
365         return peerMap;
366     }
367
368     @Override
369     protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(final MockRaftActorContext actorContext,
370             final ActorRef actorRef, final RaftRPC rpc) {
371         super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc);
372         if (rpc instanceof RequestVote) {
373             assertEquals("New votedFor", ((RequestVote)rpc).getCandidateId(),
374                     actorContext.getTermInformation().getVotedFor());
375         } else {
376             assertEquals("New votedFor", null, actorContext.getTermInformation().getVotedFor());
377         }
378     }
379 }