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;
14 import akka.actor.ActorRef;
15 import akka.actor.Props;
16 import akka.testkit.TestActorRef;
17 import com.google.protobuf.ByteString;
18 import java.io.ByteArrayOutputStream;
19 import java.io.IOException;
20 import java.io.ObjectOutputStream;
21 import java.util.ArrayList;
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.SerializationUtils;
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;
44 public abstract class AbstractRaftActorBehaviorTest<T extends RaftActorBehavior> extends AbstractActorTest {
46 protected final TestActorFactory actorFactory = new TestActorFactory(getSystem());
48 private final TestActorRef<MessageCollectorActor> behaviorActor = actorFactory.createTestActor(
49 Props.create(MessageCollectorActor.class), actorFactory.generateActorId("behavior"));
51 RaftActorBehavior behavior;
54 public void tearDown() throws Exception {
55 if(behavior != null) {
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.
69 public void testHandleRaftRPCWithNewerTerm() throws Exception {
70 MockRaftActorContext actorContext = createActorContext();
72 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
73 createAppendEntriesWithNewerTerm());
75 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
76 createAppendEntriesReplyWithNewerTerm());
78 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
79 createRequestVoteWithNewerTerm());
81 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
82 createRequestVoteReplyWithNewerTerm());
87 * This test verifies that when an AppendEntries is received with a term that
88 * is less that the currentTerm of the RaftActor then the RaftActor does not
89 * change it's state and it responds back with a failure
94 public void testHandleAppendEntriesSenderTermLessThanReceiverTerm() throws Exception {
95 MockRaftActorContext context = createActorContext();
96 short payloadVersion = 5;
97 context.setPayloadVersion(payloadVersion);
99 // First set the receivers term to a high number (1000)
100 context.getTermInformation().update(1000, "test");
102 AppendEntries appendEntries = new AppendEntries(100, "leader-1", 0, 0, null, 101, -1, (short)4);
104 behavior = createBehavior(context);
106 RaftState expected = behavior.state();
108 RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
110 assertEquals("Raft state", expected, raftBehavior.state());
112 // Also expect an AppendEntriesReply to be sent where success is false
114 AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
115 behaviorActor, AppendEntriesReply.class);
117 assertEquals("isSuccess", false, reply.isSuccess());
118 assertEquals("getPayloadVersion", payloadVersion, reply.getPayloadVersion());
123 public void testHandleAppendEntriesAddSameEntryToLog() throws Exception {
124 MockRaftActorContext context = createActorContext();
126 context.getTermInformation().update(2, "test");
128 // Prepare the receivers log
129 MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
130 setLastLogEntry(context, 2, 0, payload);
132 List<ReplicatedLogEntry> entries = new ArrayList<>();
133 entries.add(new MockRaftActorContext.MockReplicatedLogEntry(2, 0, payload));
135 AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
137 behavior = createBehavior(context);
139 assertFalse("This test should be overridden when testing Candidate", behavior instanceof Candidate);
141 RaftState expected = behavior.state();
143 // Check that the behavior does not handle unknwon message
144 assertNull(behavior.handleMessage(behaviorActor, "unknown"));
146 RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
148 assertEquals("Raft state", expected, raftBehavior.state());
150 assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
152 handleAppendEntriesAddSameEntryToLogReply(behaviorActor);
155 protected void handleAppendEntriesAddSameEntryToLogReply(TestActorRef<MessageCollectorActor> replyActor)
157 AppendEntriesReply reply = MessageCollectorActor.getFirstMatching(replyActor, AppendEntriesReply.class);
158 Assert.assertNull("Expected no AppendEntriesReply", reply);
162 * This test verifies that when a RequestVote is received by the RaftActor
163 * with the senders' log is more up to date than the receiver that the receiver grants
164 * the vote to the sender.
167 public void testHandleRequestVoteWhenSenderLogMoreUpToDate() {
168 MockRaftActorContext context = createActorContext();
170 behavior = createBehavior(context);
172 context.getTermInformation().update(1, "test");
174 behavior.handleMessage(behaviorActor, new RequestVote(context.getTermInformation().getCurrentTerm(),
175 "test", 10000, 999));
177 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
178 RequestVoteReply.class);
179 assertEquals("isVoteGranted", true, reply.isVoteGranted());
183 * This test verifies that when a RaftActor receives a RequestVote message
184 * with a term that is greater than it's currentTerm but a less up-to-date
185 * log then the receiving RaftActor will not grant the vote to the sender
188 public void testHandleRequestVoteWhenSenderLogLessUptoDate() {
189 MockRaftActorContext context = createActorContext();
191 behavior = createBehavior(context);
193 context.getTermInformation().update(1, "test");
196 setLastLogEntry(context, context.getTermInformation().getCurrentTerm(), index,
197 new MockRaftActorContext.MockPayload(""));
199 behavior.handleMessage(behaviorActor, new RequestVote(
200 context.getTermInformation().getCurrentTerm(), "test",
201 index - 1, context.getTermInformation().getCurrentTerm()));
203 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
204 RequestVoteReply.class);
205 assertEquals("isVoteGranted", false, reply.isVoteGranted());
211 * This test verifies that the receiving RaftActor will not grant a vote
212 * to a sender if the sender's term is lesser than the currentTerm of the
213 * recipient RaftActor
216 public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() {
217 MockRaftActorContext context = createActorContext();
219 context.getTermInformation().update(1000, null);
221 behavior = createBehavior(context);
223 behavior.handleMessage(behaviorActor, new RequestVote(999, "test", 10000, 999));
225 RequestVoteReply reply = MessageCollectorActor.expectFirstMatching(behaviorActor,
226 RequestVoteReply.class);
227 assertEquals("isVoteGranted", false, reply.isVoteGranted());
231 public void testPerformSnapshot() {
232 MockRaftActorContext context = new MockRaftActorContext("test", getSystem(), behaviorActor);
233 AbstractRaftActorBehavior abstractBehavior = (AbstractRaftActorBehavior) createBehavior(context);
234 if (abstractBehavior instanceof Candidate) {
238 context.getTermInformation().update(1, "test");
240 //log has 1 entry with replicatedToAllIndex = 0, does not do anything, returns the
241 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 1, 1).build());
242 context.setLastApplied(0);
243 abstractBehavior.performSnapshotWithoutCapture(0);
244 assertEquals(-1, abstractBehavior.getReplicatedToAllIndex());
245 assertEquals(1, context.getReplicatedLog().size());
247 //2 entries, lastApplied still 0, no purging.
248 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build());
249 context.setLastApplied(0);
250 abstractBehavior.performSnapshotWithoutCapture(0);
251 assertEquals(-1, abstractBehavior.getReplicatedToAllIndex());
252 assertEquals(2, context.getReplicatedLog().size());
254 //2 entries, lastApplied still 0, no purging.
255 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build());
256 context.setLastApplied(1);
257 abstractBehavior.performSnapshotWithoutCapture(0);
258 assertEquals(0, abstractBehavior.getReplicatedToAllIndex());
259 assertEquals(1, context.getReplicatedLog().size());
261 //5 entries, lastApplied =2 and replicatedIndex = 3, but since we want to keep the lastapplied, indices 0 and 1 will only get purged
262 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 5, 1).build());
263 context.setLastApplied(2);
264 abstractBehavior.performSnapshotWithoutCapture(3);
265 assertEquals(1, abstractBehavior.getReplicatedToAllIndex());
266 assertEquals(3, context.getReplicatedLog().size());
268 // scenario where Last applied > Replicated to all index (becoz of a slow follower)
269 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build());
270 context.setLastApplied(2);
271 abstractBehavior.performSnapshotWithoutCapture(1);
272 assertEquals(1, abstractBehavior.getReplicatedToAllIndex());
273 assertEquals(1, context.getReplicatedLog().size());
277 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(MockRaftActorContext actorContext,
278 ActorRef actorRef, RaftRPC rpc) throws Exception {
280 Payload p = new MockRaftActorContext.MockPayload("");
281 setLastLogEntry(actorContext, 1, 0, p);
282 actorContext.getTermInformation().update(1, "test");
284 RaftActorBehavior origBehavior = createBehavior(actorContext);
285 RaftActorBehavior raftBehavior = origBehavior.handleMessage(actorRef, rpc);
287 assertEquals("New raft state", RaftState.Follower, raftBehavior.state());
288 assertEquals("New election term", rpc.getTerm(), actorContext.getTermInformation().getCurrentTerm());
290 origBehavior.close();
291 raftBehavior.close();
294 protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
295 MockRaftActorContext actorContext, long term, long index, Payload data) {
296 return setLastLogEntry(actorContext,
297 new MockRaftActorContext.MockReplicatedLogEntry(term, index, data));
300 protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(MockRaftActorContext actorContext,
301 ReplicatedLogEntry logEntry) {
302 MockRaftActorContext.SimpleReplicatedLog log = new MockRaftActorContext.SimpleReplicatedLog();
303 log.append(logEntry);
304 actorContext.setReplicatedLog(log);
309 protected abstract T createBehavior(RaftActorContext actorContext);
311 protected final T createBehavior(MockRaftActorContext actorContext) {
312 T ret = createBehavior((RaftActorContext)actorContext);
313 actorContext.setCurrentBehavior(ret);
317 protected RaftActorBehavior createBehavior() {
318 return createBehavior(createActorContext());
321 protected MockRaftActorContext createActorContext() {
322 return new MockRaftActorContext();
325 protected MockRaftActorContext createActorContext(ActorRef actor) {
326 return new MockRaftActorContext("test", getSystem(), actor);
329 protected AppendEntries createAppendEntriesWithNewerTerm() {
330 return new AppendEntries(100, "leader-1", 0, 0, null, 1, -1, (short)0);
333 protected AppendEntriesReply createAppendEntriesReplyWithNewerTerm() {
334 return new AppendEntriesReply("follower-1", 100, false, 100, 100, (short)0);
337 protected RequestVote createRequestVoteWithNewerTerm() {
338 return new RequestVote(100, "candidate-1", 10, 100);
341 protected RequestVoteReply createRequestVoteReplyWithNewerTerm() {
342 return new RequestVoteReply(100, false);
345 protected Object fromSerializableMessage(Object serializable){
346 return SerializationUtils.fromSerializable(serializable);
349 protected ByteString toByteString(Map<String, String> state) {
350 ByteArrayOutputStream bos = new ByteArrayOutputStream();
351 try(ObjectOutputStream oos = new ObjectOutputStream(bos)) {
352 oos.writeObject(state);
353 return ByteString.copyFrom(bos.toByteArray());
354 } catch (IOException e) {
355 throw new AssertionError("IOException occurred converting Map to Bytestring", e);
359 protected void logStart(String name) {
360 LoggerFactory.getLogger(LeaderTest.class).info("Starting " + name);
363 protected RaftPolicy createRaftPolicy(final boolean automaticElectionsEnabled,
364 final boolean applyModificationToStateBeforeConsensus){
365 return new RaftPolicy() {
367 public boolean automaticElectionsEnabled() {
368 return automaticElectionsEnabled;
372 public boolean applyModificationToStateBeforeConsensus() {
373 return applyModificationToStateBeforeConsensus;