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.Collections;
18 import java.util.HashMap;
20 import java.util.concurrent.TimeUnit;
21 import org.junit.After;
22 import org.junit.Before;
23 import org.junit.Test;
24 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
25 import org.opendaylight.controller.cluster.raft.RaftActorContext;
26 import org.opendaylight.controller.cluster.raft.RaftState;
27 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
28 import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
29 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
30 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
31 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
32 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
33 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
34 import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
36 public class CandidateTest extends AbstractRaftActorBehaviorTest {
38 private final TestActorRef<MessageCollectorActor> candidateActor = actorFactory.createTestActor(
39 Props.create(MessageCollectorActor.class), actorFactory.generateActorId("candidate"));
41 private TestActorRef<MessageCollectorActor>[] peerActors;
43 private RaftActorBehavior candidate;
51 public void tearDown() throws Exception {
52 if(candidate != null) {
60 public void testWhenACandidateIsCreatedItIncrementsTheCurrentTermAndVotesForItself(){
61 RaftActorContext raftActorContext = createActorContext();
62 long expectedTerm = raftActorContext.getTermInformation().getCurrentTerm();
64 candidate = new Candidate(raftActorContext);
66 assertEquals("getCurrentTerm", expectedTerm+1, raftActorContext.getTermInformation().getCurrentTerm());
67 assertEquals("getVotedFor", raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor());
71 public void testThatAnElectionTimeoutIsTriggered(){
72 MockRaftActorContext actorContext = createActorContext();
73 candidate = new Candidate(actorContext);
75 MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class,
76 actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis());
80 public void testHandleElectionTimeoutWhenThereAreZeroPeers(){
81 RaftActorContext raftActorContext = createActorContext();
82 candidate = new Candidate(raftActorContext);
84 RaftActorBehavior newBehavior =
85 candidate.handleMessage(candidateActor, new ElectionTimeout());
87 assertEquals("Behavior", RaftState.Leader, newBehavior.state());
91 public void testHandleElectionTimeoutWhenThereAreTwoNodeCluster(){
92 MockRaftActorContext raftActorContext = createActorContext();
93 raftActorContext.setPeerAddresses(setupPeers(1));
94 candidate = new Candidate(raftActorContext);
96 candidate = candidate.handleMessage(candidateActor, new ElectionTimeout());
98 assertEquals("Behavior", RaftState.Candidate, candidate.state());
102 public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodeCluster(){
103 MockRaftActorContext raftActorContext = createActorContext();
104 raftActorContext.setPeerAddresses(setupPeers(2));
105 candidate = new Candidate(raftActorContext);
107 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
109 assertEquals("Behavior", RaftState.Leader, candidate.state());
113 public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster(){
114 MockRaftActorContext raftActorContext = createActorContext();
115 raftActorContext.setPeerAddresses(setupPeers(4));
116 candidate = new Candidate(raftActorContext);
118 // First peers denies the vote.
119 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false));
121 assertEquals("Behavior", RaftState.Candidate, candidate.state());
123 candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, true));
125 assertEquals("Behavior", RaftState.Candidate, candidate.state());
127 candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
129 assertEquals("Behavior", RaftState.Leader, candidate.state());
133 public void testResponseToHandleAppendEntriesWithLowerTerm() {
134 candidate = new Candidate(createActorContext());
137 candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
138 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short)0));
140 AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
141 peerActors[0], AppendEntriesReply.class);
142 assertEquals("isSuccess", false, reply.isSuccess());
143 assertEquals("getTerm", 2, reply.getTerm());
147 public void testResponseToRequestVoteWithLowerTerm() {
148 candidate = new Candidate(createActorContext());
151 candidate.handleMessage(peerActors[0], new RequestVote(1, "test", 0, 0));
153 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
154 peerActors[0], RequestVoteReply.class);
155 assertEquals("isVoteGranted", false, reply.isVoteGranted());
156 assertEquals("getTerm", 2, reply.getTerm());
160 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForMatches() {
161 MockRaftActorContext context = createActorContext();
162 context.getTermInformation().update(1000, null);
164 // Once a candidate is created it will immediately increment the current term so after
165 // construction the currentTerm should be 1001
166 candidate = new Candidate(context);
169 candidate.handleMessage(peerActors[0], new RequestVote(1001, context.getId(), 10000, 999));
171 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
172 peerActors[0], RequestVoteReply.class);
173 assertEquals("isVoteGranted", true, reply.isVoteGranted());
174 assertEquals("getTerm", 1001, reply.getTerm());
178 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForDoesNotMatch() {
179 MockRaftActorContext context = createActorContext();
180 context.getTermInformation().update(1000, null);
182 // Once a candidate is created it will immediately increment the current term so after
183 // construction the currentTerm should be 1001
184 candidate = new Candidate(context);
188 // RequestVote candidate ID ("candidate2") does not match this candidate's votedFor
189 // (it votes for itself)
190 candidate.handleMessage(peerActors[0], new RequestVote(1001, "candidate2", 10000, 999));
192 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
193 peerActors[0], RequestVoteReply.class);
194 assertEquals("isVoteGranted", false, reply.isVoteGranted());
195 assertEquals("getTerm", 1001, reply.getTerm());
199 public void testCandidateSchedulesElectionTimeoutImmediatelyWhenItHasNoPeers(){
200 MockRaftActorContext context = createActorContext();
202 Stopwatch stopwatch = Stopwatch.createStarted();
204 candidate = createBehavior(context);
206 MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class);
208 long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
210 assertTrue(elapsed < context.getConfigParams().getElectionTimeOutInterval().toMillis());
215 protected RaftActorBehavior createBehavior(RaftActorContext actorContext) {
216 return new Candidate(actorContext);
219 @Override protected MockRaftActorContext createActorContext() {
220 return new MockRaftActorContext("candidate", getSystem(), candidateActor);
223 private Map<String, String> setupPeers(int count) {
224 Map<String, String> peerMap = new HashMap<>();
225 peerActors = new TestActorRef[count];
226 for(int i = 0; i < count; i++) {
227 peerActors[i] = actorFactory.createTestActor(Props.create(MessageCollectorActor.class),
228 actorFactory.generateActorId("peer"));
229 peerMap.put("peer" + (i+1), peerActors[i].path().toString());
236 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(RaftActorContext actorContext,
237 ActorRef actorRef, RaftRPC rpc) throws Exception {
238 super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc);
239 assertEquals("New votedFor", null, actorContext.getTermInformation().getVotedFor());