2 * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.controller.cluster.raft.behaviors;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertTrue;
13 import akka.actor.ActorRef;
14 import akka.actor.Props;
15 import akka.testkit.TestActorRef;
16 import com.google.common.base.Stopwatch;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
22 import java.util.concurrent.TimeUnit;
23 import org.junit.After;
24 import org.junit.Before;
25 import org.junit.Test;
26 import org.mockito.Mockito;
27 import org.opendaylight.controller.cluster.NonPersistentDataProvider;
28 import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl;
29 import org.opendaylight.controller.cluster.raft.ElectionTerm;
30 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
31 import org.opendaylight.controller.cluster.raft.RaftActorContext;
32 import org.opendaylight.controller.cluster.raft.RaftActorContextImpl;
33 import org.opendaylight.controller.cluster.raft.RaftState;
34 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
35 import org.opendaylight.controller.cluster.raft.VotingState;
36 import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
37 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
38 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
39 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
40 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
41 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
42 import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
46 public class CandidateTest extends AbstractRaftActorBehaviorTest<Candidate> {
47 static final Logger LOG = LoggerFactory.getLogger(CandidateTest.class);
49 private final TestActorRef<MessageCollectorActor> candidateActor = actorFactory.createTestActor(
50 Props.create(MessageCollectorActor.class), actorFactory.generateActorId("candidate"));
52 private TestActorRef<MessageCollectorActor>[] peerActors;
54 private RaftActorBehavior candidate;
62 public void tearDown() throws Exception {
63 if(candidate != null) {
71 public void testWhenACandidateIsCreatedItIncrementsTheCurrentTermAndVotesForItself(){
72 RaftActorContext raftActorContext = createActorContext();
73 long expectedTerm = raftActorContext.getTermInformation().getCurrentTerm();
75 candidate = new Candidate(raftActorContext);
77 assertEquals("getCurrentTerm", expectedTerm+1, raftActorContext.getTermInformation().getCurrentTerm());
78 assertEquals("getVotedFor", raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor());
82 public void testThatAnElectionTimeoutIsTriggered(){
83 MockRaftActorContext actorContext = createActorContext();
84 candidate = new Candidate(actorContext);
86 MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class,
87 actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis());
91 public void testHandleElectionTimeoutWhenThereAreZeroPeers(){
92 RaftActorContext raftActorContext = createActorContext();
93 candidate = new Candidate(raftActorContext);
95 RaftActorBehavior newBehavior =
96 candidate.handleMessage(candidateActor, ElectionTimeout.INSTANCE);
98 assertEquals("Behavior", RaftState.Leader, newBehavior.state());
102 public void testHandleElectionTimeoutWhenThereAreTwoNodeCluster(){
103 MockRaftActorContext raftActorContext = createActorContext();
104 raftActorContext.setPeerAddresses(setupPeers(1));
105 candidate = new Candidate(raftActorContext);
107 candidate = candidate.handleMessage(candidateActor, ElectionTimeout.INSTANCE);
109 assertEquals("Behavior", RaftState.Candidate, candidate.state());
113 public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodeCluster(){
114 MockRaftActorContext raftActorContext = createActorContext();
115 raftActorContext.setLastApplied(raftActorContext.getReplicatedLog().lastIndex());
116 raftActorContext.setPeerAddresses(setupPeers(2));
117 candidate = new Candidate(raftActorContext);
119 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
121 assertEquals("Behavior", RaftState.Leader, candidate.state());
125 public void testBecomePreLeaderOnReceivingMajorityVotesInThreeNodeCluster(){
126 MockRaftActorContext raftActorContext = createActorContext();
127 raftActorContext.setLastApplied(-1);
128 raftActorContext.setPeerAddresses(setupPeers(2));
129 candidate = new Candidate(raftActorContext);
131 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
133 // LastApplied is -1 and behind the last index.
134 assertEquals("Behavior", RaftState.PreLeader, candidate.state());
138 public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster(){
139 MockRaftActorContext raftActorContext = createActorContext();
140 raftActorContext.getTermInformation().update(2L, "other");
141 raftActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().
142 createEntries(0, 5, 1).build());
143 raftActorContext.setCommitIndex(raftActorContext.getReplicatedLog().lastIndex());
144 raftActorContext.setLastApplied(raftActorContext.getReplicatedLog().lastIndex());
145 raftActorContext.setPeerAddresses(setupPeers(4));
146 candidate = new Candidate(raftActorContext);
148 RequestVote requestVote = MessageCollectorActor.expectFirstMatching(peerActors[0], RequestVote.class);
149 assertEquals("getTerm", 3L, requestVote.getTerm());
150 assertEquals("getCandidateId", raftActorContext.getId(), requestVote.getCandidateId());
151 assertEquals("getLastLogTerm", 1L, requestVote.getLastLogTerm());
152 assertEquals("getLastLogIndex", 4L, requestVote.getLastLogIndex());
154 MessageCollectorActor.expectFirstMatching(peerActors[1], RequestVote.class);
155 MessageCollectorActor.expectFirstMatching(peerActors[2], RequestVote.class);
156 MessageCollectorActor.expectFirstMatching(peerActors[3], RequestVote.class);
158 // First peers denies the vote.
159 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false));
161 assertEquals("Behavior", RaftState.Candidate, candidate.state());
163 candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, true));
165 assertEquals("Behavior", RaftState.Candidate, candidate.state());
167 candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
169 assertEquals("Behavior", RaftState.Leader, candidate.state());
173 public void testBecomeLeaderOnReceivingMajorityVotesWithNonVotingPeers(){
174 ElectionTerm mockElectionTerm = Mockito.mock(ElectionTerm.class);
175 Mockito.doReturn(1L).when(mockElectionTerm).getCurrentTerm();
176 RaftActorContext raftActorContext = new RaftActorContextImpl(candidateActor, candidateActor.actorContext(),
177 "candidate", mockElectionTerm, -1, -1, setupPeers(4), new DefaultConfigParamsImpl(),
178 new NonPersistentDataProvider(), LOG);
179 raftActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().build());
180 raftActorContext.getPeerInfo("peer1").setVotingState(VotingState.NON_VOTING);
181 raftActorContext.getPeerInfo("peer4").setVotingState(VotingState.NON_VOTING);
182 candidate = new Candidate(raftActorContext);
184 MessageCollectorActor.expectFirstMatching(peerActors[1], RequestVote.class);
185 MessageCollectorActor.expectFirstMatching(peerActors[2], RequestVote.class);
186 MessageCollectorActor.assertNoneMatching(peerActors[0], RequestVote.class, 300);
187 MessageCollectorActor.assertNoneMatching(peerActors[3], RequestVote.class, 100);
189 candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, false));
191 assertEquals("Behavior", RaftState.Candidate, candidate.state());
193 candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
195 assertEquals("Behavior", RaftState.Leader, candidate.state());
199 public void testResponseToHandleAppendEntriesWithLowerTerm() {
200 candidate = new Candidate(createActorContext());
203 RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
204 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
206 AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
207 peerActors[0], AppendEntriesReply.class);
208 assertEquals("isSuccess", false, reply.isSuccess());
209 assertEquals("getTerm", 2, reply.getTerm());
210 assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Candidate);
214 public void testResponseToHandleAppendEntriesWithHigherTerm() {
215 candidate = new Candidate(createActorContext());
218 RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(5, "test", 0, 0,
219 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
221 assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Follower);
225 public void testResponseToHandleAppendEntriesWithEqualTerm() {
226 MockRaftActorContext actorContext = createActorContext();
228 candidate = new Candidate(actorContext);
231 RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(2, "test", 0, 0,
232 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
234 assertTrue("New Behavior : " + newBehavior + " term = " + actorContext.getTermInformation().getCurrentTerm(),
235 newBehavior instanceof Follower);
240 public void testResponseToRequestVoteWithLowerTerm() {
241 candidate = new Candidate(createActorContext());
244 candidate.handleMessage(peerActors[0], new RequestVote(1, "test", 0, 0));
246 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
247 peerActors[0], RequestVoteReply.class);
248 assertEquals("isVoteGranted", false, reply.isVoteGranted());
249 assertEquals("getTerm", 2, reply.getTerm());
253 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForMatches() {
254 MockRaftActorContext context = createActorContext();
255 context.getTermInformation().update(1000, null);
257 // Once a candidate is created it will immediately increment the current term so after
258 // construction the currentTerm should be 1001
259 candidate = new Candidate(context);
262 candidate.handleMessage(peerActors[0], new RequestVote(1001, context.getId(), 10000, 999));
264 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
265 peerActors[0], RequestVoteReply.class);
266 assertEquals("isVoteGranted", true, reply.isVoteGranted());
267 assertEquals("getTerm", 1001, reply.getTerm());
271 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForDoesNotMatch() {
272 MockRaftActorContext context = createActorContext();
273 context.getTermInformation().update(1000, null);
275 // Once a candidate is created it will immediately increment the current term so after
276 // construction the currentTerm should be 1001
277 candidate = new Candidate(context);
281 // RequestVote candidate ID ("candidate2") does not match this candidate's votedFor
282 // (it votes for itself)
283 candidate.handleMessage(peerActors[0], new RequestVote(1001, "candidate2", 10000, 999));
285 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
286 peerActors[0], RequestVoteReply.class);
287 assertEquals("isVoteGranted", false, reply.isVoteGranted());
288 assertEquals("getTerm", 1001, reply.getTerm());
292 public void testCandidateSchedulesElectionTimeoutImmediatelyWhenItHasNoPeers(){
293 MockRaftActorContext context = createActorContext();
295 Stopwatch stopwatch = Stopwatch.createStarted();
297 candidate = createBehavior(context);
299 MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class);
301 long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
303 assertTrue(elapsed < context.getConfigParams().getElectionTimeOutInterval().toMillis());
308 public void testHandleAppendEntriesAddSameEntryToLog() throws Exception {
309 MockRaftActorContext context = createActorContext();
311 context.getTermInformation().update(2, "test");
313 // Prepare the receivers log
314 MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
315 setLastLogEntry(context, 2, 0, payload);
317 List<ReplicatedLogEntry> entries = new ArrayList<>();
318 entries.add(new MockRaftActorContext.MockReplicatedLogEntry(2, 0, payload));
320 AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
322 behavior = createBehavior(context);
324 // Resetting the Candidates term to make sure it will match
325 // the term sent by AppendEntries. If this was not done then
326 // the test will fail because the Candidate will assume that
327 // the message was sent to it from a lower term peer and will
328 // thus respond with a failure
329 context.getTermInformation().update(2, "test");
331 // Send an unknown message so that the state of the RaftActor remains unchanged
332 behavior.handleMessage(candidateActor, "unknown");
334 RaftActorBehavior raftBehavior = behavior.handleMessage(candidateActor, appendEntries);
336 assertEquals("Raft state", RaftState.Follower, raftBehavior.state());
338 assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
340 handleAppendEntriesAddSameEntryToLogReply(candidateActor);
344 protected Candidate createBehavior(final RaftActorContext actorContext) {
345 return new Candidate(actorContext);
348 @Override protected MockRaftActorContext createActorContext() {
349 return new MockRaftActorContext("candidate", getSystem(), candidateActor);
352 @SuppressWarnings("unchecked")
353 private Map<String, String> setupPeers(final int count) {
354 Map<String, String> peerMap = new HashMap<>();
355 peerActors = new TestActorRef[count];
356 for(int i = 0; i < count; i++) {
357 peerActors[i] = actorFactory.createTestActor(Props.create(MessageCollectorActor.class),
358 actorFactory.generateActorId("peer"));
359 peerMap.put("peer" + (i+1), peerActors[i].path().toString());
366 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(final MockRaftActorContext actorContext,
367 final ActorRef actorRef, final RaftRPC rpc) throws Exception {
368 super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc);
369 if(rpc instanceof RequestVote) {
370 assertEquals("New votedFor", ((RequestVote)rpc).getCandidateId(), actorContext.getTermInformation().getVotedFor());
372 assertEquals("New votedFor", null, actorContext.getTermInformation().getVotedFor());