2 * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.controller.cluster.raft.behaviors;
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertFalse;
13 import static org.junit.Assert.assertNull;
15 import akka.actor.ActorRef;
16 import akka.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;
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.Payload;
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.persisted.SimpleReplicatedLogEntry;
40 import org.opendaylight.controller.cluster.raft.policy.RaftPolicy;
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;
46 public abstract class AbstractRaftActorBehaviorTest<T extends RaftActorBehavior> extends AbstractActorTest {
48 protected final TestActorFactory actorFactory = new TestActorFactory(getSystem());
50 private final ActorRef behaviorActor = actorFactory.createActor(
51 MessageCollectorActor.props(), actorFactory.generateActorId("behavior"));
53 RaftActorBehavior behavior;
56 public void tearDown() {
57 if (behavior != null) {
63 InMemoryJournal.clear();
64 InMemorySnapshotStore.clear();
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.
72 public void testHandleRaftRPCWithNewerTerm() {
73 MockRaftActorContext actorContext = createActorContext();
75 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
76 createAppendEntriesWithNewerTerm());
78 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
79 createAppendEntriesReplyWithNewerTerm());
81 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
82 createRequestVoteWithNewerTerm());
84 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
85 createRequestVoteReplyWithNewerTerm());
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.
95 public void testHandleAppendEntriesSenderTermLessThanReceiverTerm() {
96 MockRaftActorContext context = createActorContext();
97 short payloadVersion = 5;
98 context.setPayloadVersion(payloadVersion);
100 // First set the receivers term to a high number (1000)
101 context.getTermInformation().update(1000, "test");
103 AppendEntries appendEntries = new AppendEntries(100, "leader-1", 0, 0, Collections.emptyList(), 101, -1,
106 behavior = createBehavior(context);
108 RaftState expected = behavior.state();
110 RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
112 assertEquals("Raft state", expected, raftBehavior.state());
114 // Also expect an AppendEntriesReply to be sent where success is false
116 AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
117 behaviorActor, AppendEntriesReply.class);
119 assertEquals("isSuccess", false, reply.isSuccess());
120 assertEquals("getPayloadVersion", payloadVersion, reply.getPayloadVersion());
125 public void testHandleAppendEntriesAddSameEntryToLog() {
126 MockRaftActorContext context = createActorContext();
128 context.getTermInformation().update(2, "test");
130 // Prepare the receivers log
131 MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
132 setLastLogEntry(context, 2, 0, payload);
134 List<ReplicatedLogEntry> entries = new ArrayList<>();
135 entries.add(new SimpleReplicatedLogEntry(0, 2, payload));
137 final AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
139 behavior = createBehavior(context);
141 assertFalse("This test should be overridden when testing Candidate", behavior instanceof Candidate);
143 RaftState expected = behavior.state();
145 // Check that the behavior does not handle unknwon message
146 assertNull(behavior.handleMessage(behaviorActor, "unknown"));
148 RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
150 assertEquals("Raft state", expected, raftBehavior.state());
152 assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
154 handleAppendEntriesAddSameEntryToLogReply(behaviorActor);
157 protected void handleAppendEntriesAddSameEntryToLogReply(final ActorRef replyActor) {
158 AppendEntriesReply reply = MessageCollectorActor.getFirstMatching(replyActor, AppendEntriesReply.class);
159 Assert.assertNull("Expected no AppendEntriesReply", reply);
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.
168 public void testHandleRequestVoteWhenSenderLogMoreUpToDate() {
169 MockRaftActorContext context = createActorContext();
171 behavior = createBehavior(context);
173 context.getTermInformation().update(1, "test");
175 behavior.handleMessage(behaviorActor, new RequestVote(context.getTermInformation().getCurrentTerm(),
176 "test", 10000, 999));
178 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
179 RequestVoteReply.class);
180 assertEquals("isVoteGranted", true, reply.isVoteGranted());
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.
189 public void testHandleRequestVoteWhenSenderLogLessUptoDate() {
190 MockRaftActorContext context = createActorContext();
192 behavior = createBehavior(context);
194 context.getTermInformation().update(1, "test");
197 setLastLogEntry(context, context.getTermInformation().getCurrentTerm(), index,
198 new MockRaftActorContext.MockPayload(""));
200 behavior.handleMessage(behaviorActor, new RequestVote(
201 context.getTermInformation().getCurrentTerm(), "test",
202 index - 1, context.getTermInformation().getCurrentTerm()));
204 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
205 RequestVoteReply.class);
206 assertEquals("isVoteGranted", false, reply.isVoteGranted());
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.
217 public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() {
218 MockRaftActorContext context = createActorContext();
220 context.getTermInformation().update(1000, null);
222 behavior = createBehavior(context);
224 behavior.handleMessage(behaviorActor, new RequestVote(999, "test", 10000, 999));
226 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
227 RequestVoteReply.class);
228 assertEquals("isVoteGranted", false, reply.isVoteGranted());
232 public void testPerformSnapshot() {
233 MockRaftActorContext context = new MockRaftActorContext("test", getSystem(), behaviorActor);
234 AbstractRaftActorBehavior abstractBehavior = (AbstractRaftActorBehavior) createBehavior(context);
235 if (abstractBehavior instanceof Candidate) {
239 context.getTermInformation().update(1, "test");
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());
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());
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());
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());
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());
279 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(final MockRaftActorContext actorContext,
280 final ActorRef actorRef, final RaftRPC rpc) {
282 Payload payload = new MockRaftActorContext.MockPayload("");
283 setLastLogEntry(actorContext, 1, 0, payload);
284 actorContext.getTermInformation().update(1, "test");
286 RaftActorBehavior origBehavior = createBehavior(actorContext);
287 RaftActorBehavior raftBehavior = origBehavior.handleMessage(actorRef, rpc);
289 assertEquals("New raft state", RaftState.Follower, raftBehavior.state());
290 assertEquals("New election term", rpc.getTerm(), actorContext.getTermInformation().getCurrentTerm());
292 origBehavior.close();
293 raftBehavior.close();
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));
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);
310 protected abstract T createBehavior(RaftActorContext actorContext);
312 protected final T createBehavior(final MockRaftActorContext actorContext) {
313 T ret = createBehavior((RaftActorContext)actorContext);
314 actorContext.setCurrentBehavior(ret);
318 protected RaftActorBehavior createBehavior() {
319 return createBehavior(createActorContext());
322 protected MockRaftActorContext createActorContext() {
323 return new MockRaftActorContext();
326 protected MockRaftActorContext createActorContext(final ActorRef actor) {
327 return new MockRaftActorContext("test", getSystem(), actor);
330 protected AppendEntries createAppendEntriesWithNewerTerm() {
331 return new AppendEntries(100, "leader-1", 0, 0, Collections.emptyList(), 1, -1, (short)0);
334 protected AppendEntriesReply createAppendEntriesReplyWithNewerTerm() {
335 return new AppendEntriesReply("follower-1", 100, false, 100, 100, (short)0);
338 protected RequestVote createRequestVoteWithNewerTerm() {
339 return new RequestVote(100, "candidate-1", 10, 100);
342 protected RequestVoteReply createRequestVoteReplyWithNewerTerm() {
343 return new RequestVoteReply(100, false);
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);
356 protected void logStart(final String name) {
357 LoggerFactory.getLogger(getClass()).info("Starting " + name);
360 protected RaftPolicy createRaftPolicy(final boolean automaticElectionsEnabled,
361 final boolean applyModificationToStateBeforeConsensus) {
362 return new RaftPolicy() {
364 public boolean automaticElectionsEnabled() {
365 return automaticElectionsEnabled;
369 public boolean applyModificationToStateBeforeConsensus() {
370 return applyModificationToStateBeforeConsensus;