Fix intermittent PreLeaderScenarioTest failure
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / test / java / org / opendaylight / controller / cluster / raft / behaviors / AbstractRaftActorBehaviorTest.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.assertFalse;
13 import static org.junit.Assert.assertNull;
14
15 import akka.actor.ActorRef;
16 import com.google.protobuf.ByteString;
17 import java.io.ByteArrayOutputStream;
18 import java.io.IOException;
19 import java.io.ObjectOutputStream;
20 import java.util.ArrayList;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.Map;
24 import org.junit.After;
25 import org.junit.Assert;
26 import org.junit.Test;
27 import org.opendaylight.controller.cluster.raft.AbstractActorTest;
28 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
29 import org.opendaylight.controller.cluster.raft.RaftActorContext;
30 import org.opendaylight.controller.cluster.raft.RaftState;
31 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
32 import org.opendaylight.controller.cluster.raft.TestActorFactory;
33 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
34 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
35 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
36 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
37 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
38 import org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry;
39 import org.opendaylight.controller.cluster.raft.policy.RaftPolicy;
40 import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload;
41 import org.opendaylight.controller.cluster.raft.utils.InMemoryJournal;
42 import org.opendaylight.controller.cluster.raft.utils.InMemorySnapshotStore;
43 import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
44 import org.slf4j.LoggerFactory;
45
46 public abstract class AbstractRaftActorBehaviorTest<T extends RaftActorBehavior> extends AbstractActorTest {
47
48     protected final TestActorFactory actorFactory = new TestActorFactory(getSystem());
49
50     private final ActorRef behaviorActor = actorFactory.createActor(
51             MessageCollectorActor.props(), actorFactory.generateActorId("behavior"));
52
53     RaftActorBehavior behavior;
54
55     @After
56     public void tearDown() {
57         if (behavior != null) {
58             behavior.close();
59         }
60
61         actorFactory.close();
62
63         InMemoryJournal.clear();
64         InMemorySnapshotStore.clear();
65     }
66
67     /**
68      * This test checks that when a new Raft RPC message is received with a newer
69      * term the RaftActor gets into the Follower state.
70      */
71     @Test
72     public void testHandleRaftRPCWithNewerTerm() {
73         MockRaftActorContext actorContext = createActorContext();
74
75         assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
76                 createAppendEntriesWithNewerTerm());
77
78         assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
79                 createAppendEntriesReplyWithNewerTerm());
80
81         assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
82                 createRequestVoteWithNewerTerm());
83
84         assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
85                 createRequestVoteReplyWithNewerTerm());
86     }
87
88
89     /**
90      * This test verifies that when an AppendEntries is received with a term that
91      * is less that the currentTerm of the RaftActor then the RaftActor does not
92      * change it's state and it responds back with a failure.
93      */
94     @Test
95     public void testHandleAppendEntriesSenderTermLessThanReceiverTerm() {
96         MockRaftActorContext context = createActorContext();
97         short payloadVersion = 5;
98         context.setPayloadVersion(payloadVersion);
99
100         // First set the receivers term to a high number (1000)
101         context.getTermInformation().update(1000, "test");
102
103         AppendEntries appendEntries = new AppendEntries(100, "leader-1", 0, 0, Collections.emptyList(), 101, -1,
104                 (short)4);
105
106         behavior = createBehavior(context);
107
108         RaftState expected = behavior.state();
109
110         RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
111
112         assertEquals("Raft state", expected, raftBehavior.state());
113
114         // Also expect an AppendEntriesReply to be sent where success is false
115
116         AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
117                 behaviorActor, AppendEntriesReply.class);
118
119         assertEquals("isSuccess", false, reply.isSuccess());
120         assertEquals("getPayloadVersion", payloadVersion, reply.getPayloadVersion());
121     }
122
123
124     @Test
125     public void testHandleAppendEntriesAddSameEntryToLog() {
126         MockRaftActorContext context = createActorContext();
127
128         context.getTermInformation().update(2, "test");
129
130         // Prepare the receivers log
131         MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
132         setLastLogEntry(context, 2, 0, payload);
133
134         List<ReplicatedLogEntry> entries = new ArrayList<>();
135         entries.add(new SimpleReplicatedLogEntry(0, 2, payload));
136
137         final AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
138
139         behavior = createBehavior(context);
140
141         assertFalse("This test should be overridden when testing Candidate", behavior instanceof Candidate);
142
143         RaftState expected = behavior.state();
144
145         // Check that the behavior does not handle unknwon message
146         assertNull(behavior.handleMessage(behaviorActor, "unknown"));
147
148         RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
149
150         assertEquals("Raft state", expected, raftBehavior.state());
151
152         assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
153
154         handleAppendEntriesAddSameEntryToLogReply(behaviorActor);
155     }
156
157     protected void handleAppendEntriesAddSameEntryToLogReply(final ActorRef replyActor) {
158         AppendEntriesReply reply = MessageCollectorActor.getFirstMatching(replyActor, AppendEntriesReply.class);
159         Assert.assertNull("Expected no AppendEntriesReply", reply);
160     }
161
162     /**
163      * This test verifies that when a RequestVote is received by the RaftActor
164      * with the senders' log is more up to date than the receiver that the receiver grants
165      * the vote to the sender.
166      */
167     @Test
168     public void testHandleRequestVoteWhenSenderLogMoreUpToDate() {
169         MockRaftActorContext context = createActorContext();
170
171         behavior = createBehavior(context);
172
173         context.getTermInformation().update(1, "test");
174
175         behavior.handleMessage(behaviorActor, new RequestVote(context.getTermInformation().getCurrentTerm(),
176                 "test", 10000, 999));
177
178         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
179                 RequestVoteReply.class);
180         assertEquals("isVoteGranted", true, reply.isVoteGranted());
181     }
182
183     /**
184      * This test verifies that when a RaftActor receives a RequestVote message
185      * with a term that is greater than it's currentTerm but a less up-to-date
186      * log then the receiving RaftActor will not grant the vote to the sender.
187      */
188     @Test
189     public void testHandleRequestVoteWhenSenderLogLessUptoDate() {
190         MockRaftActorContext context = createActorContext();
191
192         behavior = createBehavior(context);
193
194         context.getTermInformation().update(1, "test");
195
196         int index = 2000;
197         setLastLogEntry(context, context.getTermInformation().getCurrentTerm(), index,
198                 new MockRaftActorContext.MockPayload(""));
199
200         behavior.handleMessage(behaviorActor, new RequestVote(
201                 context.getTermInformation().getCurrentTerm(), "test",
202                 index - 1, context.getTermInformation().getCurrentTerm()));
203
204         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
205                 RequestVoteReply.class);
206         assertEquals("isVoteGranted", false, reply.isVoteGranted());
207     }
208
209
210
211     /**
212      * This test verifies that the receiving RaftActor will not grant a vote
213      * to a sender if the sender's term is lesser than the currentTerm of the
214      * recipient RaftActor.
215      */
216     @Test
217     public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() {
218         MockRaftActorContext context = createActorContext();
219
220         context.getTermInformation().update(1000, null);
221
222         behavior = createBehavior(context);
223
224         behavior.handleMessage(behaviorActor, new RequestVote(999, "test", 10000, 999));
225
226         RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
227                 RequestVoteReply.class);
228         assertEquals("isVoteGranted", false, reply.isVoteGranted());
229     }
230
231     @Test
232     public void testPerformSnapshot() {
233         MockRaftActorContext context = new MockRaftActorContext("test", getSystem(), behaviorActor);
234         AbstractRaftActorBehavior abstractBehavior =  (AbstractRaftActorBehavior) createBehavior(context);
235         if (abstractBehavior instanceof Candidate) {
236             return;
237         }
238
239         context.getTermInformation().update(1, "test");
240
241         //log has 1 entry with replicatedToAllIndex = 0, does not do anything, returns the
242         context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 1, 1).build());
243         context.setLastApplied(0);
244         abstractBehavior.performSnapshotWithoutCapture(0);
245         assertEquals(-1, abstractBehavior.getReplicatedToAllIndex());
246         assertEquals(1, context.getReplicatedLog().size());
247
248         //2 entries, lastApplied still 0, no purging.
249         context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build());
250         context.setLastApplied(0);
251         abstractBehavior.performSnapshotWithoutCapture(0);
252         assertEquals(-1, abstractBehavior.getReplicatedToAllIndex());
253         assertEquals(2, context.getReplicatedLog().size());
254
255         //2 entries, lastApplied still 0, no purging.
256         context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build());
257         context.setLastApplied(1);
258         abstractBehavior.performSnapshotWithoutCapture(0);
259         assertEquals(0, abstractBehavior.getReplicatedToAllIndex());
260         assertEquals(1, context.getReplicatedLog().size());
261
262         // 5 entries, lastApplied =2 and replicatedIndex = 3, but since we want to keep the lastapplied, indices 0 and
263         // 1 will only get purged
264         context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 5, 1).build());
265         context.setLastApplied(2);
266         abstractBehavior.performSnapshotWithoutCapture(3);
267         assertEquals(1, abstractBehavior.getReplicatedToAllIndex());
268         assertEquals(3, context.getReplicatedLog().size());
269
270         // scenario where Last applied > Replicated to all index (becoz of a slow follower)
271         context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build());
272         context.setLastApplied(2);
273         abstractBehavior.performSnapshotWithoutCapture(1);
274         assertEquals(1, abstractBehavior.getReplicatedToAllIndex());
275         assertEquals(1, context.getReplicatedLog().size());
276     }
277
278
279     protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(final MockRaftActorContext actorContext,
280             final ActorRef actorRef, final RaftRPC rpc) {
281
282         Payload payload = new MockRaftActorContext.MockPayload("");
283         setLastLogEntry(actorContext, 1, 0, payload);
284         actorContext.getTermInformation().update(1, "test");
285
286         RaftActorBehavior origBehavior = createBehavior(actorContext);
287         RaftActorBehavior raftBehavior = origBehavior.handleMessage(actorRef, rpc);
288
289         assertEquals("New raft state", RaftState.Follower, raftBehavior.state());
290         assertEquals("New election term", rpc.getTerm(), actorContext.getTermInformation().getCurrentTerm());
291
292         origBehavior.close();
293         raftBehavior.close();
294     }
295
296     protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
297         final MockRaftActorContext actorContext, final long term, final long index, final Payload data) {
298         return setLastLogEntry(actorContext, new SimpleReplicatedLogEntry(index, term, data));
299     }
300
301     protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(final MockRaftActorContext actorContext,
302             final ReplicatedLogEntry logEntry) {
303         MockRaftActorContext.SimpleReplicatedLog log = new MockRaftActorContext.SimpleReplicatedLog();
304         log.append(logEntry);
305         actorContext.setReplicatedLog(log);
306
307         return log;
308     }
309
310     protected abstract T createBehavior(RaftActorContext actorContext);
311
312     protected final T createBehavior(final MockRaftActorContext actorContext) {
313         T ret = createBehavior((RaftActorContext)actorContext);
314         actorContext.setCurrentBehavior(ret);
315         return ret;
316     }
317
318     protected RaftActorBehavior createBehavior() {
319         return createBehavior(createActorContext());
320     }
321
322     protected MockRaftActorContext createActorContext() {
323         return new MockRaftActorContext();
324     }
325
326     protected MockRaftActorContext createActorContext(final ActorRef actor) {
327         return new MockRaftActorContext("test", getSystem(), actor);
328     }
329
330     protected AppendEntries createAppendEntriesWithNewerTerm() {
331         return new AppendEntries(100, "leader-1", 0, 0, Collections.emptyList(), 1, -1, (short)0);
332     }
333
334     protected AppendEntriesReply createAppendEntriesReplyWithNewerTerm() {
335         return new AppendEntriesReply("follower-1", 100, false, 100, 100, (short)0);
336     }
337
338     protected RequestVote createRequestVoteWithNewerTerm() {
339         return new RequestVote(100, "candidate-1", 10, 100);
340     }
341
342     protected RequestVoteReply createRequestVoteReplyWithNewerTerm() {
343         return new RequestVoteReply(100, false);
344     }
345
346     protected ByteString toByteString(final Map<String, String> state) {
347         ByteArrayOutputStream bos = new ByteArrayOutputStream();
348         try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
349             oos.writeObject(state);
350             return ByteString.copyFrom(bos.toByteArray());
351         } catch (IOException e) {
352             throw new AssertionError("IOException occurred converting Map to Bytestring", e);
353         }
354     }
355
356     protected void logStart(final String name) {
357         LoggerFactory.getLogger(getClass()).info("Starting " + name);
358     }
359
360     protected RaftPolicy createRaftPolicy(final boolean automaticElectionsEnabled,
361                                           final boolean applyModificationToStateBeforeConsensus) {
362         return new RaftPolicy() {
363             @Override
364             public boolean automaticElectionsEnabled() {
365                 return automaticElectionsEnabled;
366             }
367
368             @Override
369             public boolean applyModificationToStateBeforeConsensus() {
370                 return applyModificationToStateBeforeConsensus;
371             }
372         };
373     }
374 }