Add election info to Snapshot
[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 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;
21 import java.util.Map;
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;
37
38 public class CandidateTest extends AbstractRaftActorBehaviorTest {
39
40     private final TestActorRef<MessageCollectorActor> candidateActor = actorFactory.createTestActor(
41             Props.create(MessageCollectorActor.class), actorFactory.generateActorId("candidate"));
42
43     private TestActorRef<MessageCollectorActor>[] peerActors;
44
45     private RaftActorBehavior candidate;
46
47     @Before
48     public void setUp(){
49     }
50
51     @Override
52     @After
53     public void tearDown() throws Exception {
54         if(candidate != null) {
55             candidate.close();
56         }
57
58         super.tearDown();
59     }
60
61     @Test
62     public void testWhenACandidateIsCreatedItIncrementsTheCurrentTermAndVotesForItself(){
63         RaftActorContext raftActorContext = createActorContext();
64         long expectedTerm = raftActorContext.getTermInformation().getCurrentTerm();
65
66         candidate = new Candidate(raftActorContext);
67
68         assertEquals("getCurrentTerm", expectedTerm+1, raftActorContext.getTermInformation().getCurrentTerm());
69         assertEquals("getVotedFor", raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor());
70     }
71
72     @Test
73     public void testThatAnElectionTimeoutIsTriggered(){
74          MockRaftActorContext actorContext = createActorContext();
75          candidate = new Candidate(actorContext);
76
77          MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class,
78                  actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis());
79     }
80
81     @Test
82     public void testHandleElectionTimeoutWhenThereAreZeroPeers(){
83         RaftActorContext raftActorContext = createActorContext();
84         candidate = new Candidate(raftActorContext);
85
86         RaftActorBehavior newBehavior =
87             candidate.handleMessage(candidateActor, new ElectionTimeout());
88
89         assertEquals("Behavior", RaftState.Leader, newBehavior.state());
90     }
91
92     @Test
93     public void testHandleElectionTimeoutWhenThereAreTwoNodeCluster(){
94         MockRaftActorContext raftActorContext = createActorContext();
95         raftActorContext.setPeerAddresses(setupPeers(1));
96         candidate = new Candidate(raftActorContext);
97
98         candidate = candidate.handleMessage(candidateActor, new ElectionTimeout());
99
100         assertEquals("Behavior", RaftState.Candidate, candidate.state());
101     }
102
103     @Test
104     public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodeCluster(){
105         MockRaftActorContext raftActorContext = createActorContext();
106         raftActorContext.setPeerAddresses(setupPeers(2));
107         candidate = new Candidate(raftActorContext);
108
109         candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
110
111         assertEquals("Behavior", RaftState.Leader, candidate.state());
112     }
113
114     @Test
115     public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster(){
116         MockRaftActorContext raftActorContext = createActorContext();
117         raftActorContext.setPeerAddresses(setupPeers(4));
118         candidate = new Candidate(raftActorContext);
119
120         // First peers denies the vote.
121         candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false));
122
123         assertEquals("Behavior", RaftState.Candidate, candidate.state());
124
125         candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, true));
126
127         assertEquals("Behavior", RaftState.Candidate, candidate.state());
128
129         candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
130
131         assertEquals("Behavior", RaftState.Leader, candidate.state());
132     }
133
134     @Test
135     public void testResponseToHandleAppendEntriesWithLowerTerm() {
136         candidate = new Candidate(createActorContext());
137
138         setupPeers(1);
139         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
140                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
141
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);
147     }
148
149     @Test
150     public void testResponseToHandleAppendEntriesWithHigherTerm() {
151         candidate = new Candidate(createActorContext());
152
153         setupPeers(1);
154         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(5, "test", 0, 0,
155                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
156
157         assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Follower);
158     }
159
160     @Test
161     public void testResponseToHandleAppendEntriesWithEqualTerm() {
162         MockRaftActorContext actorContext = createActorContext();
163
164         candidate = new Candidate(actorContext);
165
166         setupPeers(1);
167         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(2, "test", 0, 0,
168                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
169
170         assertTrue("New Behavior : " + newBehavior + " term = " + actorContext.getTermInformation().getCurrentTerm(),
171                 newBehavior instanceof Follower);
172     }
173
174
175     @Test
176     public void testResponseToRequestVoteWithLowerTerm() {
177         candidate = new Candidate(createActorContext());
178
179         setupPeers(1);
180         candidate.handleMessage(peerActors[0], new RequestVote(1, "test", 0, 0));
181
182         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
183                 peerActors[0], RequestVoteReply.class);
184         assertEquals("isVoteGranted", false, reply.isVoteGranted());
185         assertEquals("getTerm", 2, reply.getTerm());
186     }
187
188     @Test
189     public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForMatches() {
190         MockRaftActorContext context = createActorContext();
191         context.getTermInformation().update(1000, null);
192
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);
196
197         setupPeers(1);
198         candidate.handleMessage(peerActors[0], new RequestVote(1001, context.getId(), 10000, 999));
199
200         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
201                 peerActors[0], RequestVoteReply.class);
202         assertEquals("isVoteGranted", true, reply.isVoteGranted());
203         assertEquals("getTerm", 1001, reply.getTerm());
204     }
205
206     @Test
207     public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForDoesNotMatch() {
208         MockRaftActorContext context = createActorContext();
209         context.getTermInformation().update(1000, null);
210
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);
214
215         setupPeers(1);
216
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));
220
221         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
222                 peerActors[0], RequestVoteReply.class);
223         assertEquals("isVoteGranted", false, reply.isVoteGranted());
224         assertEquals("getTerm", 1001, reply.getTerm());
225     }
226
227     @Test
228     public void testCandidateSchedulesElectionTimeoutImmediatelyWhenItHasNoPeers(){
229         MockRaftActorContext context = createActorContext();
230
231         Stopwatch stopwatch = Stopwatch.createStarted();
232
233         candidate = createBehavior(context);
234
235         MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class);
236
237         long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
238
239         assertTrue(elapsed < context.getConfigParams().getElectionTimeOutInterval().toMillis());
240     }
241
242     @Test
243     @Override
244     public void testHandleAppendEntriesAddSameEntryToLog() throws Exception {
245         MockRaftActorContext context = createActorContext();
246
247         context.getTermInformation().update(2, "test");
248
249         // Prepare the receivers log
250         MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
251         setLastLogEntry(context, 2, 0, payload);
252
253         List<ReplicatedLogEntry> entries = new ArrayList<>();
254         entries.add(new MockRaftActorContext.MockReplicatedLogEntry(2, 0, payload));
255
256         AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
257
258         behavior = createBehavior(context);
259
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");
266
267         // Send an unknown message so that the state of the RaftActor remains unchanged
268         RaftActorBehavior expected = behavior.handleMessage(candidateActor, "unknown");
269
270         RaftActorBehavior raftBehavior = behavior.handleMessage(candidateActor, appendEntries);
271
272         assertEquals("Raft state", RaftState.Follower, raftBehavior.state());
273
274         assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
275
276         handleAppendEntriesAddSameEntryToLogReply(candidateActor);
277     }
278
279     @Override
280     protected RaftActorBehavior createBehavior(RaftActorContext actorContext) {
281         return new Candidate(actorContext);
282     }
283
284     @Override protected MockRaftActorContext createActorContext() {
285         return new MockRaftActorContext("candidate", getSystem(), candidateActor);
286     }
287
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());
295         }
296
297         return peerMap;
298     }
299
300     @Override
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());
305     }
306 }