1 package org.opendaylight.controller.cluster.raft.behaviors;
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.assertTrue;
5 import akka.actor.ActorRef;
6 import akka.actor.Props;
7 import akka.testkit.JavaTestKit;
8 import java.util.ArrayList;
10 import org.junit.Test;
11 import org.opendaylight.controller.cluster.raft.AbstractActorTest;
12 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
13 import org.opendaylight.controller.cluster.raft.RaftActorContext;
14 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
15 import org.opendaylight.controller.cluster.raft.SerializationUtils;
16 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
17 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
18 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
19 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
20 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
21 import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload;
22 import org.opendaylight.controller.cluster.raft.utils.DoNothingActor;
24 public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest {
26 private final ActorRef behaviorActor = getSystem().actorOf(Props.create(
27 DoNothingActor.class));
30 * This test checks that when a new Raft RPC message is received with a newer
31 * term the RaftActor gets into the Follower state.
36 public void testHandleRaftRPCWithNewerTerm() throws Exception {
37 new JavaTestKit(getSystem()) {{
39 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(),
40 createAppendEntriesWithNewerTerm());
42 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(),
43 createAppendEntriesReplyWithNewerTerm());
45 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(),
46 createRequestVoteWithNewerTerm());
48 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(),
49 createRequestVoteReplyWithNewerTerm());
57 * This test verifies that when an AppendEntries is received with a term that
58 * is less that the currentTerm of the RaftActor then the RaftActor does not
59 * change it's state and it responds back with a failure
64 public void testHandleAppendEntriesSenderTermLessThanReceiverTerm()
66 new JavaTestKit(getSystem()) {{
68 MockRaftActorContext context = createActorContext();
70 // First set the receivers term to a high number (1000)
71 context.getTermInformation().update(1000, "test");
73 AppendEntries appendEntries =
74 new AppendEntries(100, "leader-1", 0, 0, null, 101, -1);
76 RaftActorBehavior behavior = createBehavior(context);
78 // Send an unknown message so that the state of the RaftActor remains unchanged
79 RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown");
81 RaftActorBehavior raftBehavior =
82 behavior.handleMessage(getRef(), appendEntries);
84 assertEquals(expected, raftBehavior);
86 // Also expect an AppendEntriesReply to be sent where success is false
87 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
88 "AppendEntriesReply") {
89 // do not put code outside this method, will run afterwards
91 protected Boolean match(Object in) {
92 if (in instanceof AppendEntriesReply) {
93 AppendEntriesReply reply = (AppendEntriesReply) in;
94 return reply.isSuccess();
101 assertEquals(false, out);
109 public void testHandleAppendEntriesAddSameEntryToLog(){
110 new JavaTestKit(getSystem()) {
113 MockRaftActorContext context = createActorContext();
115 // First set the receivers term to lower number
116 context.getTermInformation().update(2, "test");
118 // Prepare the receivers log
119 MockRaftActorContext.SimpleReplicatedLog log =
120 new MockRaftActorContext.SimpleReplicatedLog();
122 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
124 context.setReplicatedLog(log);
126 List<ReplicatedLogEntry> entries = new ArrayList<>();
128 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
130 AppendEntries appendEntries =
131 new AppendEntries(2, "leader-1", -1, 1, entries, 0, -1);
133 RaftActorBehavior behavior = createBehavior(context);
135 if (AbstractRaftActorBehaviorTest.this instanceof CandidateTest) {
136 // Resetting the Candidates term to make sure it will match
137 // the term sent by AppendEntries. If this was not done then
138 // the test will fail because the Candidate will assume that
139 // the message was sent to it from a lower term peer and will
140 // thus respond with a failure
141 context.getTermInformation().update(2, "test");
144 // Send an unknown message so that the state of the RaftActor remains unchanged
145 RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown");
147 RaftActorBehavior raftBehavior =
148 behavior.handleMessage(getRef(), appendEntries);
150 assertEquals(expected, raftBehavior);
152 assertEquals(1, log.size());
159 * This test verifies that when a RequestVote is received by the RaftActor
160 * with a term which is greater than the RaftActors' currentTerm and the
161 * senders' log is more upto date than the receiver that the receiver grants
162 * the vote to the sender
165 public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermAndSenderLogMoreUpToDate() {
166 new JavaTestKit(getSystem()) {{
168 new Within(duration("1 seconds")) {
170 protected void run() {
172 RaftActorBehavior behavior = createBehavior(
173 createActorContext(behaviorActor));
175 RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(),
176 new RequestVote(1000, "test", 10000, 999));
178 if(!(behavior instanceof Follower)){
179 assertTrue(raftBehavior instanceof Follower);
183 new ExpectMsg<Boolean>(duration("1 seconds"),
184 "RequestVoteReply") {
185 // do not put code outside this method, will run afterwards
187 protected Boolean match(Object in) {
188 if (in instanceof RequestVoteReply) {
189 RequestVoteReply reply =
190 (RequestVoteReply) in;
191 return reply.isVoteGranted();
198 assertEquals(true, out);
206 * This test verifies that when a RaftActor receives a RequestVote message
207 * with a term that is greater than it's currentTerm but a less up-to-date
208 * log then the receiving RaftActor will not grant the vote to the sender
211 public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermButSenderLogLessUptoDate() {
212 new JavaTestKit(getSystem()) {{
214 new Within(duration("1 seconds")) {
216 protected void run() {
218 RaftActorContext actorContext =
219 createActorContext(behaviorActor);
221 MockRaftActorContext.SimpleReplicatedLog
222 log = new MockRaftActorContext.SimpleReplicatedLog();
224 new MockRaftActorContext.MockReplicatedLogEntry(20000,
225 1000000, new MockRaftActorContext.MockPayload("")));
227 ((MockRaftActorContext) actorContext).setReplicatedLog(log);
229 RaftActorBehavior behavior = createBehavior(actorContext);
231 RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(),
232 new RequestVote(1000, "test", 10000, 999));
234 if(!(behavior instanceof Follower)){
235 assertTrue(raftBehavior instanceof Follower);
238 new ExpectMsg<Boolean>(duration("1 seconds"),
239 "RequestVoteReply") {
240 // do not put code outside this method, will run afterwards
242 protected Boolean match(Object in) {
243 if (in instanceof RequestVoteReply) {
244 RequestVoteReply reply =
245 (RequestVoteReply) in;
246 return reply.isVoteGranted();
253 assertEquals(false, out);
263 * This test verifies that the receiving RaftActor will not grant a vote
264 * to a sender if the sender's term is lesser than the currentTerm of the
265 * recipient RaftActor
268 public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() {
269 new JavaTestKit(getSystem()) {{
271 new Within(duration("1 seconds")) {
273 protected void run() {
275 RaftActorContext context =
276 createActorContext(behaviorActor);
278 context.getTermInformation().update(1000, null);
280 RaftActorBehavior follower = createBehavior(context);
282 follower.handleMessage(getTestActor(),
283 new RequestVote(999, "test", 10000, 999));
286 new ExpectMsg<Boolean>(duration("1 seconds"),
287 "RequestVoteReply") {
288 // do not put code outside this method, will run afterwards
290 protected Boolean match(Object in) {
291 if (in instanceof RequestVoteReply) {
292 RequestVoteReply reply =
293 (RequestVoteReply) in;
294 return reply.isVoteGranted();
301 assertEquals(false, out);
308 public void testPerformSnapshot() {
309 MockRaftActorContext context = new MockRaftActorContext("test", getSystem(), behaviorActor);
310 AbstractRaftActorBehavior abstractBehavior = (AbstractRaftActorBehavior) createBehavior(context);
311 if (abstractBehavior instanceof Candidate) {
315 context.getTermInformation().update(1, "test");
317 //log has 1 entry with replicatedToAllIndex = 0, does not do anything, returns the
318 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 1, 1).build());
319 context.setLastApplied(0);
320 abstractBehavior.performSnapshotWithoutCapture(0);
321 assertEquals(-1, abstractBehavior.getReplicatedToAllIndex());
322 assertEquals(1, context.getReplicatedLog().size());
324 //2 entries, lastApplied still 0, no purging.
325 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build());
326 context.setLastApplied(0);
327 abstractBehavior.performSnapshotWithoutCapture(0);
328 assertEquals(-1, abstractBehavior.getReplicatedToAllIndex());
329 assertEquals(2, context.getReplicatedLog().size());
331 //2 entries, lastApplied still 0, no purging.
332 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build());
333 context.setLastApplied(1);
334 abstractBehavior.performSnapshotWithoutCapture(0);
335 assertEquals(0, abstractBehavior.getReplicatedToAllIndex());
336 assertEquals(1, context.getReplicatedLog().size());
338 //5 entries, lastApplied =2 and replicatedIndex = 3, but since we want to keep the lastapplied, indices 0 and 1 will only get purged
339 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 5, 1).build());
340 context.setLastApplied(2);
341 abstractBehavior.performSnapshotWithoutCapture(3);
342 assertEquals(1, abstractBehavior.getReplicatedToAllIndex());
343 assertEquals(3, context.getReplicatedLog().size());
345 // scenario where Last applied > Replicated to all index (becoz of a slow follower)
346 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build());
347 context.setLastApplied(2);
348 abstractBehavior.performSnapshotWithoutCapture(1);
349 assertEquals(1, abstractBehavior.getReplicatedToAllIndex());
350 assertEquals(1, context.getReplicatedLog().size());
354 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(
355 ActorRef actorRef, RaftRPC rpc) {
357 RaftActorContext actorContext = createActorContext();
358 Payload p = new MockRaftActorContext.MockPayload("");
360 (MockRaftActorContext) actorContext, 0, 0, p);
362 RaftActorBehavior raftBehavior = createBehavior(actorContext)
363 .handleMessage(actorRef, rpc);
365 assertTrue(raftBehavior instanceof Follower);
368 protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
369 MockRaftActorContext actorContext, long term, long index, Payload data) {
370 return setLastLogEntry(actorContext,
371 new MockRaftActorContext.MockReplicatedLogEntry(term, index, data));
374 protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
375 MockRaftActorContext actorContext, ReplicatedLogEntry logEntry) {
376 MockRaftActorContext.SimpleReplicatedLog
377 log = new MockRaftActorContext.SimpleReplicatedLog();
378 log.append(logEntry);
379 actorContext.setReplicatedLog(log);
384 protected abstract RaftActorBehavior createBehavior(
385 RaftActorContext actorContext);
387 protected RaftActorBehavior createBehavior() {
388 return createBehavior(createActorContext());
391 protected MockRaftActorContext createActorContext() {
392 return new MockRaftActorContext();
395 protected MockRaftActorContext createActorContext(ActorRef actor) {
396 return new MockRaftActorContext("test", getSystem(), actor);
399 protected AppendEntries createAppendEntriesWithNewerTerm() {
400 return new AppendEntries(100, "leader-1", 0, 0, null, 1, -1);
403 protected AppendEntriesReply createAppendEntriesReplyWithNewerTerm() {
404 return new AppendEntriesReply("follower-1", 100, false, 100, 100);
407 protected RequestVote createRequestVoteWithNewerTerm() {
408 return new RequestVote(100, "candidate-1", 10, 100);
411 protected RequestVoteReply createRequestVoteReplyWithNewerTerm() {
412 return new RequestVoteReply(100, false);
415 protected Object fromSerializableMessage(Object serializable){
416 return SerializationUtils.fromSerializable(serializable);