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.opendaylight.controller.cluster.raft.MockRaftActorContext;
27 import org.opendaylight.controller.cluster.raft.RaftActorContext;
28 import org.opendaylight.controller.cluster.raft.RaftState;
29 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
30 import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
31 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
32 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
33 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
34 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
35 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
36 import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
38 public class CandidateTest extends AbstractRaftActorBehaviorTest {
40 private final TestActorRef<MessageCollectorActor> candidateActor = actorFactory.createTestActor(
41 Props.create(MessageCollectorActor.class), actorFactory.generateActorId("candidate"));
43 private TestActorRef<MessageCollectorActor>[] peerActors;
45 private RaftActorBehavior candidate;
53 public void tearDown() throws Exception {
54 if(candidate != null) {
62 public void testWhenACandidateIsCreatedItIncrementsTheCurrentTermAndVotesForItself(){
63 RaftActorContext raftActorContext = createActorContext();
64 long expectedTerm = raftActorContext.getTermInformation().getCurrentTerm();
66 candidate = new Candidate(raftActorContext);
68 assertEquals("getCurrentTerm", expectedTerm+1, raftActorContext.getTermInformation().getCurrentTerm());
69 assertEquals("getVotedFor", raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor());
73 public void testThatAnElectionTimeoutIsTriggered(){
74 MockRaftActorContext actorContext = createActorContext();
75 candidate = new Candidate(actorContext);
77 MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class,
78 actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis());
82 public void testHandleElectionTimeoutWhenThereAreZeroPeers(){
83 RaftActorContext raftActorContext = createActorContext();
84 candidate = new Candidate(raftActorContext);
86 RaftActorBehavior newBehavior =
87 candidate.handleMessage(candidateActor, new ElectionTimeout());
89 assertEquals("Behavior", RaftState.Leader, newBehavior.state());
93 public void testHandleElectionTimeoutWhenThereAreTwoNodeCluster(){
94 MockRaftActorContext raftActorContext = createActorContext();
95 raftActorContext.setPeerAddresses(setupPeers(1));
96 candidate = new Candidate(raftActorContext);
98 candidate = candidate.handleMessage(candidateActor, new ElectionTimeout());
100 assertEquals("Behavior", RaftState.Candidate, candidate.state());
104 public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodeCluster(){
105 MockRaftActorContext raftActorContext = createActorContext();
106 raftActorContext.setPeerAddresses(setupPeers(2));
107 candidate = new Candidate(raftActorContext);
109 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
111 assertEquals("Behavior", RaftState.Leader, candidate.state());
115 public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster(){
116 MockRaftActorContext raftActorContext = createActorContext();
117 raftActorContext.setPeerAddresses(setupPeers(4));
118 candidate = new Candidate(raftActorContext);
120 // First peers denies the vote.
121 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false));
123 assertEquals("Behavior", RaftState.Candidate, candidate.state());
125 candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, true));
127 assertEquals("Behavior", RaftState.Candidate, candidate.state());
129 candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
131 assertEquals("Behavior", RaftState.Leader, candidate.state());
135 public void testResponseToHandleAppendEntriesWithLowerTerm() {
136 candidate = new Candidate(createActorContext());
139 RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
140 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
142 AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
143 peerActors[0], AppendEntriesReply.class);
144 assertEquals("isSuccess", false, reply.isSuccess());
145 assertEquals("getTerm", 2, reply.getTerm());
146 assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Candidate);
150 public void testResponseToHandleAppendEntriesWithHigherTerm() {
151 candidate = new Candidate(createActorContext());
154 RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(5, "test", 0, 0,
155 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
157 assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Follower);
161 public void testResponseToHandleAppendEntriesWithEqualTerm() {
162 MockRaftActorContext actorContext = createActorContext();
164 candidate = new Candidate(actorContext);
167 RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(2, "test", 0, 0,
168 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
170 assertTrue("New Behavior : " + newBehavior + " term = " + actorContext.getTermInformation().getCurrentTerm(),
171 newBehavior instanceof Follower);
176 public void testResponseToRequestVoteWithLowerTerm() {
177 candidate = new Candidate(createActorContext());
180 candidate.handleMessage(peerActors[0], new RequestVote(1, "test", 0, 0));
182 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
183 peerActors[0], RequestVoteReply.class);
184 assertEquals("isVoteGranted", false, reply.isVoteGranted());
185 assertEquals("getTerm", 2, reply.getTerm());
189 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForMatches() {
190 MockRaftActorContext context = createActorContext();
191 context.getTermInformation().update(1000, null);
193 // Once a candidate is created it will immediately increment the current term so after
194 // construction the currentTerm should be 1001
195 candidate = new Candidate(context);
198 candidate.handleMessage(peerActors[0], new RequestVote(1001, context.getId(), 10000, 999));
200 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
201 peerActors[0], RequestVoteReply.class);
202 assertEquals("isVoteGranted", true, reply.isVoteGranted());
203 assertEquals("getTerm", 1001, reply.getTerm());
207 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForDoesNotMatch() {
208 MockRaftActorContext context = createActorContext();
209 context.getTermInformation().update(1000, null);
211 // Once a candidate is created it will immediately increment the current term so after
212 // construction the currentTerm should be 1001
213 candidate = new Candidate(context);
217 // RequestVote candidate ID ("candidate2") does not match this candidate's votedFor
218 // (it votes for itself)
219 candidate.handleMessage(peerActors[0], new RequestVote(1001, "candidate2", 10000, 999));
221 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
222 peerActors[0], RequestVoteReply.class);
223 assertEquals("isVoteGranted", false, reply.isVoteGranted());
224 assertEquals("getTerm", 1001, reply.getTerm());
228 public void testCandidateSchedulesElectionTimeoutImmediatelyWhenItHasNoPeers(){
229 MockRaftActorContext context = createActorContext();
231 Stopwatch stopwatch = Stopwatch.createStarted();
233 candidate = createBehavior(context);
235 MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class);
237 long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
239 assertTrue(elapsed < context.getConfigParams().getElectionTimeOutInterval().toMillis());
244 public void testHandleAppendEntriesAddSameEntryToLog() throws Exception {
245 MockRaftActorContext context = createActorContext();
247 context.getTermInformation().update(2, "test");
249 // Prepare the receivers log
250 MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
251 setLastLogEntry(context, 2, 0, payload);
253 List<ReplicatedLogEntry> entries = new ArrayList<>();
254 entries.add(new MockRaftActorContext.MockReplicatedLogEntry(2, 0, payload));
256 AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
258 behavior = createBehavior(context);
260 // Resetting the Candidates term to make sure it will match
261 // the term sent by AppendEntries. If this was not done then
262 // the test will fail because the Candidate will assume that
263 // the message was sent to it from a lower term peer and will
264 // thus respond with a failure
265 context.getTermInformation().update(2, "test");
267 // Send an unknown message so that the state of the RaftActor remains unchanged
268 RaftActorBehavior expected = behavior.handleMessage(candidateActor, "unknown");
270 RaftActorBehavior raftBehavior = behavior.handleMessage(candidateActor, appendEntries);
272 assertEquals("Raft state", RaftState.Follower, raftBehavior.state());
274 assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
276 handleAppendEntriesAddSameEntryToLogReply(candidateActor);
280 protected RaftActorBehavior createBehavior(RaftActorContext actorContext) {
281 return new Candidate(actorContext);
284 @Override protected MockRaftActorContext createActorContext() {
285 return new MockRaftActorContext("candidate", getSystem(), candidateActor);
288 private Map<String, String> setupPeers(int count) {
289 Map<String, String> peerMap = new HashMap<>();
290 peerActors = new TestActorRef[count];
291 for(int i = 0; i < count; i++) {
292 peerActors[i] = actorFactory.createTestActor(Props.create(MessageCollectorActor.class),
293 actorFactory.generateActorId("peer"));
294 peerMap.put("peer" + (i+1), peerActors[i].path().toString());
301 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(RaftActorContext actorContext,
302 ActorRef actorRef, RaftRPC rpc) throws Exception {
303 super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc);
304 assertEquals("New votedFor", null, actorContext.getTermInformation().getVotedFor());