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