1 package org.opendaylight.controller.cluster.raft.behaviors;
3 import static org.junit.Assert.assertEquals;
4 import akka.actor.ActorRef;
5 import akka.actor.Props;
6 import akka.testkit.TestActorRef;
7 import java.util.Collections;
8 import java.util.HashMap;
10 import org.junit.After;
11 import org.junit.Before;
12 import org.junit.Test;
13 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
14 import org.opendaylight.controller.cluster.raft.RaftActorContext;
15 import org.opendaylight.controller.cluster.raft.RaftState;
16 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
17 import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
18 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
19 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
20 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
21 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
22 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
23 import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
25 public class CandidateTest extends AbstractRaftActorBehaviorTest {
27 private final TestActorRef<MessageCollectorActor> candidateActor = actorFactory.createTestActor(
28 Props.create(MessageCollectorActor.class), actorFactory.generateActorId("candidate"));
30 private TestActorRef<MessageCollectorActor>[] peerActors;
32 private RaftActorBehavior candidate;
40 public void tearDown() throws Exception {
41 if(candidate != null) {
49 public void testWhenACandidateIsCreatedItIncrementsTheCurrentTermAndVotesForItself(){
50 RaftActorContext raftActorContext = createActorContext();
51 long expectedTerm = raftActorContext.getTermInformation().getCurrentTerm();
53 candidate = new Candidate(raftActorContext);
55 assertEquals("getCurrentTerm", expectedTerm+1, raftActorContext.getTermInformation().getCurrentTerm());
56 assertEquals("getVotedFor", raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor());
60 public void testThatAnElectionTimeoutIsTriggered(){
61 MockRaftActorContext actorContext = createActorContext();
62 candidate = new Candidate(actorContext);
64 MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class,
65 actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis());
69 public void testHandleElectionTimeoutWhenThereAreZeroPeers(){
70 RaftActorContext raftActorContext = createActorContext();
71 candidate = new Candidate(raftActorContext);
73 RaftActorBehavior newBehavior =
74 candidate.handleMessage(candidateActor, new ElectionTimeout());
76 assertEquals("Behavior", RaftState.Leader, newBehavior.state());
80 public void testHandleElectionTimeoutWhenThereAreTwoNodeCluster(){
81 MockRaftActorContext raftActorContext = createActorContext();
82 raftActorContext.setPeerAddresses(setupPeers(1));
83 candidate = new Candidate(raftActorContext);
85 candidate = candidate.handleMessage(candidateActor, new ElectionTimeout());
87 assertEquals("Behavior", RaftState.Candidate, candidate.state());
91 public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodeCluster(){
92 MockRaftActorContext raftActorContext = createActorContext();
93 raftActorContext.setPeerAddresses(setupPeers(2));
94 candidate = new Candidate(raftActorContext);
96 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
98 assertEquals("Behavior", RaftState.Leader, candidate.state());
102 public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster(){
103 MockRaftActorContext raftActorContext = createActorContext();
104 raftActorContext.setPeerAddresses(setupPeers(4));
105 candidate = new Candidate(raftActorContext);
107 // First peers denies the vote.
108 candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false));
110 assertEquals("Behavior", RaftState.Candidate, candidate.state());
112 candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, true));
114 assertEquals("Behavior", RaftState.Candidate, candidate.state());
116 candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
118 assertEquals("Behavior", RaftState.Leader, candidate.state());
122 public void testResponseToHandleAppendEntriesWithLowerTerm() {
123 candidate = new Candidate(createActorContext());
126 candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
127 Collections.<ReplicatedLogEntry>emptyList(), 0, -1));
129 AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
130 peerActors[0], AppendEntriesReply.class);
131 assertEquals("isSuccess", false, reply.isSuccess());
132 assertEquals("getTerm", 2, reply.getTerm());
136 public void testResponseToRequestVoteWithLowerTerm() {
137 candidate = new Candidate(createActorContext());
140 candidate.handleMessage(peerActors[0], new RequestVote(1, "test", 0, 0));
142 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
143 peerActors[0], RequestVoteReply.class);
144 assertEquals("isVoteGranted", false, reply.isVoteGranted());
145 assertEquals("getTerm", 2, reply.getTerm());
149 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForMatches() {
150 MockRaftActorContext context = createActorContext();
151 context.getTermInformation().update(1000, null);
153 // Once a candidate is created it will immediately increment the current term so after
154 // construction the currentTerm should be 1001
155 candidate = new Candidate(context);
158 candidate.handleMessage(peerActors[0], new RequestVote(1001, context.getId(), 10000, 999));
160 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
161 peerActors[0], RequestVoteReply.class);
162 assertEquals("isVoteGranted", true, reply.isVoteGranted());
163 assertEquals("getTerm", 1001, reply.getTerm());
167 public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForDoesNotMatch() {
168 MockRaftActorContext context = createActorContext();
169 context.getTermInformation().update(1000, null);
171 // Once a candidate is created it will immediately increment the current term so after
172 // construction the currentTerm should be 1001
173 candidate = new Candidate(context);
177 // RequestVote candidate ID ("candidate2") does not match this candidate's votedFor
178 // (it votes for itself)
179 candidate.handleMessage(peerActors[0], new RequestVote(1001, "candidate2", 10000, 999));
181 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
182 peerActors[0], RequestVoteReply.class);
183 assertEquals("isVoteGranted", false, reply.isVoteGranted());
184 assertEquals("getTerm", 1001, reply.getTerm());
190 protected RaftActorBehavior createBehavior(RaftActorContext actorContext) {
191 return new Candidate(actorContext);
194 @Override protected MockRaftActorContext createActorContext() {
195 return new MockRaftActorContext("candidate", getSystem(), candidateActor);
198 private Map<String, String> setupPeers(int count) {
199 Map<String, String> peerMap = new HashMap<>();
200 peerActors = new TestActorRef[count];
201 for(int i = 0; i < count; i++) {
202 peerActors[i] = actorFactory.createTestActor(Props.create(MessageCollectorActor.class),
203 actorFactory.generateActorId("peer"));
204 peerMap.put("peer" + (i+1), peerActors[i].path().toString());
211 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(RaftActorContext actorContext,
212 ActorRef actorRef, RaftRPC rpc) throws Exception {
213 super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc);
214 assertEquals("New votedFor", null, actorContext.getTermInformation().getVotedFor());