Improve segmented journal actor metrics
[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
14 import akka.actor.ActorRef;
15 import akka.dispatch.Dispatchers;
16 import akka.testkit.TestActorRef;
17 import com.google.common.base.Stopwatch;
18 import com.google.common.util.concurrent.MoreExecutors;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.concurrent.TimeUnit;
25 import org.junit.After;
26 import org.junit.Before;
27 import org.junit.Test;
28 import org.mockito.Mockito;
29 import org.opendaylight.controller.cluster.NonPersistentDataProvider;
30 import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl;
31 import org.opendaylight.controller.cluster.raft.ElectionTerm;
32 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
33 import org.opendaylight.controller.cluster.raft.RaftActorContext;
34 import org.opendaylight.controller.cluster.raft.RaftActorContextImpl;
35 import org.opendaylight.controller.cluster.raft.RaftState;
36 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
37 import org.opendaylight.controller.cluster.raft.VotingState;
38 import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
39 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
40 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
41 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
42 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
43 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
44 import org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry;
45 import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 public class CandidateTest extends AbstractRaftActorBehaviorTest<Candidate> {
50     static final Logger LOG = LoggerFactory.getLogger(CandidateTest.class);
51
52     private final TestActorRef<MessageCollectorActor> candidateActor = actorFactory.createTestActor(
53             MessageCollectorActor.props().withDispatcher(Dispatchers.DefaultDispatcherId()),
54             actorFactory.generateActorId("candidate"));
55
56     private ActorRef[] peerActors;
57
58     private RaftActorBehavior candidate;
59
60     @Before
61     public void setUp(){
62     }
63
64     @Override
65     @After
66     public void tearDown() {
67         if (candidate != null) {
68             candidate.close();
69         }
70
71         super.tearDown();
72     }
73
74     @Test
75     public void testWhenACandidateIsCreatedItIncrementsTheCurrentTermAndVotesForItself() {
76         RaftActorContext raftActorContext = createActorContext();
77         long expectedTerm = raftActorContext.getTermInformation().getCurrentTerm();
78
79         candidate = new Candidate(raftActorContext);
80
81         assertEquals("getCurrentTerm", expectedTerm + 1, raftActorContext.getTermInformation().getCurrentTerm());
82         assertEquals("getVotedFor", raftActorContext.getId(), raftActorContext.getTermInformation().getVotedFor());
83     }
84
85     @Test
86     public void testThatAnElectionTimeoutIsTriggered() {
87         MockRaftActorContext actorContext = createActorContext();
88         candidate = new Candidate(actorContext);
89
90         MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class,
91                 actorContext.getConfigParams().getElectionTimeOutInterval().$times(6).toMillis());
92     }
93
94     @Test
95     public void testHandleElectionTimeoutWhenThereAreZeroPeers() {
96         RaftActorContext raftActorContext = createActorContext();
97         candidate = new Candidate(raftActorContext);
98
99         RaftActorBehavior newBehavior =
100             candidate.handleMessage(candidateActor, ElectionTimeout.INSTANCE);
101
102         assertEquals("Behavior", RaftState.Leader, newBehavior.state());
103     }
104
105     @Test
106     public void testHandleElectionTimeoutWhenThereAreTwoNodeCluster() {
107         MockRaftActorContext raftActorContext = createActorContext();
108         raftActorContext.setPeerAddresses(setupPeers(1));
109         candidate = new Candidate(raftActorContext);
110
111         candidate = candidate.handleMessage(candidateActor, ElectionTimeout.INSTANCE);
112
113         assertEquals("Behavior", RaftState.Candidate, candidate.state());
114     }
115
116     @Test
117     public void testBecomeLeaderOnReceivingMajorityVotesInThreeNodeCluster() {
118         MockRaftActorContext raftActorContext = createActorContext();
119         raftActorContext.setLastApplied(raftActorContext.getReplicatedLog().lastIndex());
120         raftActorContext.setPeerAddresses(setupPeers(2));
121         candidate = new Candidate(raftActorContext);
122
123         candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
124
125         assertEquals("Behavior", RaftState.Leader, candidate.state());
126     }
127
128     @Test
129     public void testBecomePreLeaderOnReceivingMajorityVotesInThreeNodeCluster() {
130         MockRaftActorContext raftActorContext = createActorContext();
131         raftActorContext.setLastApplied(-1);
132         raftActorContext.setPeerAddresses(setupPeers(2));
133         candidate = new Candidate(raftActorContext);
134
135         candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, true));
136
137         // LastApplied is -1 and behind the last index.
138         assertEquals("Behavior", RaftState.PreLeader, candidate.state());
139     }
140
141     @Test
142     public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster() {
143         MockRaftActorContext raftActorContext = createActorContext();
144         raftActorContext.getTermInformation().update(2L, "other");
145         raftActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder()
146                 .createEntries(0, 5, 1).build());
147         raftActorContext.setCommitIndex(raftActorContext.getReplicatedLog().lastIndex());
148         raftActorContext.setLastApplied(raftActorContext.getReplicatedLog().lastIndex());
149         raftActorContext.setPeerAddresses(setupPeers(4));
150         candidate = new Candidate(raftActorContext);
151
152         RequestVote requestVote = MessageCollectorActor.expectFirstMatching(peerActors[0], RequestVote.class);
153         assertEquals("getTerm", 3L, requestVote.getTerm());
154         assertEquals("getCandidateId", raftActorContext.getId(), requestVote.getCandidateId());
155         assertEquals("getLastLogTerm", 1L, requestVote.getLastLogTerm());
156         assertEquals("getLastLogIndex", 4L, requestVote.getLastLogIndex());
157
158         MessageCollectorActor.expectFirstMatching(peerActors[1], RequestVote.class);
159         MessageCollectorActor.expectFirstMatching(peerActors[2], RequestVote.class);
160         MessageCollectorActor.expectFirstMatching(peerActors[3], RequestVote.class);
161
162         // First peers denies the vote.
163         candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false));
164
165         assertEquals("Behavior", RaftState.Candidate, candidate.state());
166
167         candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, true));
168
169         assertEquals("Behavior", RaftState.Candidate, candidate.state());
170
171         candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
172
173         assertEquals("Behavior", RaftState.Leader, candidate.state());
174     }
175
176     @Test
177     public void testBecomeLeaderOnReceivingMajorityVotesWithNonVotingPeers() {
178         ElectionTerm mockElectionTerm = Mockito.mock(ElectionTerm.class);
179         Mockito.doReturn(1L).when(mockElectionTerm).getCurrentTerm();
180         RaftActorContext raftActorContext = new RaftActorContextImpl(candidateActor, candidateActor.actorContext(),
181                 "candidate", mockElectionTerm, -1, -1, setupPeers(4), new DefaultConfigParamsImpl(),
182                 new NonPersistentDataProvider(Runnable::run), applyState -> { }, LOG,  MoreExecutors.directExecutor());
183         raftActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().build());
184         raftActorContext.getPeerInfo("peer1").setVotingState(VotingState.NON_VOTING);
185         raftActorContext.getPeerInfo("peer4").setVotingState(VotingState.NON_VOTING);
186         candidate = new Candidate(raftActorContext);
187
188         MessageCollectorActor.expectFirstMatching(peerActors[1], RequestVote.class);
189         MessageCollectorActor.expectFirstMatching(peerActors[2], RequestVote.class);
190         MessageCollectorActor.assertNoneMatching(peerActors[0], RequestVote.class, 300);
191         MessageCollectorActor.assertNoneMatching(peerActors[3], RequestVote.class, 100);
192
193         candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, false));
194
195         assertEquals("Behavior", RaftState.Candidate, candidate.state());
196
197         candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
198
199         assertEquals("Behavior", RaftState.Leader, candidate.state());
200     }
201
202     @Test
203     public void testResponseToHandleAppendEntriesWithLowerTerm() {
204         candidate = new Candidate(createActorContext());
205
206         setupPeers(1);
207         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
208                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
209
210         AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
211                 peerActors[0], AppendEntriesReply.class);
212         assertEquals("isSuccess", false, reply.isSuccess());
213         assertEquals("getTerm", 2, reply.getTerm());
214         assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Candidate);
215     }
216
217     @Test
218     public void testResponseToHandleAppendEntriesWithHigherTerm() {
219         candidate = new Candidate(createActorContext());
220
221         setupPeers(1);
222         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(5, "test", 0, 0,
223                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
224
225         assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Follower);
226     }
227
228     @Test
229     public void testResponseToHandleAppendEntriesWithEqualTerm() {
230         MockRaftActorContext actorContext = createActorContext();
231
232         candidate = new Candidate(actorContext);
233
234         setupPeers(1);
235         RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(2, "test", 0, 0,
236                 Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
237
238         assertTrue("New Behavior : " + newBehavior + " term = " + actorContext.getTermInformation().getCurrentTerm(),
239                 newBehavior instanceof Follower);
240     }
241
242
243     @Test
244     public void testResponseToRequestVoteWithLowerTerm() {
245         candidate = new Candidate(createActorContext());
246
247         setupPeers(1);
248         candidate.handleMessage(peerActors[0], new RequestVote(1, "test", 0, 0));
249
250         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
251                 peerActors[0], RequestVoteReply.class);
252         assertEquals("isVoteGranted", false, reply.isVoteGranted());
253         assertEquals("getTerm", 2, reply.getTerm());
254     }
255
256     @Test
257     public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForMatches() {
258         MockRaftActorContext context = createActorContext();
259         context.getTermInformation().update(1000, null);
260
261         // Once a candidate is created it will immediately increment the current term so after
262         // construction the currentTerm should be 1001
263         candidate = new Candidate(context);
264
265         setupPeers(1);
266         candidate.handleMessage(peerActors[0], new RequestVote(1001, context.getId(), 10000, 999));
267
268         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
269                 peerActors[0], RequestVoteReply.class);
270         assertEquals("isVoteGranted", true, reply.isVoteGranted());
271         assertEquals("getTerm", 1001, reply.getTerm());
272     }
273
274     @Test
275     public void testHandleRequestVoteWhenSenderTermEqualToCurrentTermAndVotedForDoesNotMatch() {
276         MockRaftActorContext context = createActorContext();
277         context.getTermInformation().update(1000, null);
278
279         // Once a candidate is created it will immediately increment the current term so after
280         // construction the currentTerm should be 1001
281         candidate = new Candidate(context);
282
283         setupPeers(1);
284
285         // RequestVote candidate ID ("candidate2") does not match this candidate's votedFor
286         // (it votes for itself)
287         candidate.handleMessage(peerActors[0], new RequestVote(1001, "candidate2", 10000, 999));
288
289         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(
290                 peerActors[0], RequestVoteReply.class);
291         assertEquals("isVoteGranted", false, reply.isVoteGranted());
292         assertEquals("getTerm", 1001, reply.getTerm());
293     }
294
295     @Test
296     public void testCandidateSchedulesElectionTimeoutImmediatelyWhenItHasNoPeers() {
297         MockRaftActorContext context = createActorContext();
298
299         Stopwatch stopwatch = Stopwatch.createStarted();
300
301         candidate = createBehavior(context);
302
303         MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class);
304
305         long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
306
307         assertTrue(elapsed < context.getConfigParams().getElectionTimeOutInterval().toMillis());
308     }
309
310     @Test
311     @Override
312     public void testHandleAppendEntriesAddSameEntryToLog() {
313         MockRaftActorContext context = createActorContext();
314
315         context.getTermInformation().update(2, "test");
316
317         // Prepare the receivers log
318         MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
319         setLastLogEntry(context, 2, 0, payload);
320
321         List<ReplicatedLogEntry> entries = new ArrayList<>();
322         entries.add(new SimpleReplicatedLogEntry(0, 2, payload));
323
324         final AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
325
326         behavior = createBehavior(context);
327
328         // Resetting the Candidates term to make sure it will match
329         // the term sent by AppendEntries. If this was not done then
330         // the test will fail because the Candidate will assume that
331         // the message was sent to it from a lower term peer and will
332         // thus respond with a failure
333         context.getTermInformation().update(2, "test");
334
335         // Send an unknown message so that the state of the RaftActor remains unchanged
336         behavior.handleMessage(candidateActor, "unknown");
337
338         RaftActorBehavior raftBehavior = behavior.handleMessage(candidateActor, appendEntries);
339
340         assertEquals("Raft state", RaftState.Follower, raftBehavior.state());
341
342         assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
343
344         handleAppendEntriesAddSameEntryToLogReply(candidateActor);
345     }
346
347     @Override
348     protected Candidate createBehavior(final RaftActorContext actorContext) {
349         return new Candidate(actorContext);
350     }
351
352     @Override
353     protected MockRaftActorContext createActorContext() {
354         return new MockRaftActorContext("candidate", getSystem(), candidateActor);
355     }
356
357     private Map<String, String> setupPeers(final int count) {
358         Map<String, String> peerMap = new HashMap<>();
359         peerActors = new ActorRef[count];
360         for (int i = 0; i < count; i++) {
361             peerActors[i] = actorFactory.createActor(MessageCollectorActor.props(),
362                     actorFactory.generateActorId("peer"));
363             peerMap.put("peer" + (i + 1), peerActors[i].path().toString());
364         }
365
366         return peerMap;
367     }
368
369     @Override
370     protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(final MockRaftActorContext actorContext,
371             final ActorRef actorRef, final RaftRPC rpc) {
372         super.assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, actorRef, rpc);
373         if (rpc instanceof RequestVote) {
374             assertEquals("New votedFor", ((RequestVote)rpc).getCandidateId(),
375                     actorContext.getTermInformation().getVotedFor());
376         } else {
377             assertEquals("New votedFor", null, actorContext.getTermInformation().getVotedFor());
378         }
379     }
380 }