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