1 package org.opendaylight.controller.cluster.raft.behaviors;
3 import akka.actor.ActorRef;
4 import akka.actor.Props;
5 import akka.testkit.JavaTestKit;
7 import org.opendaylight.controller.cluster.raft.AbstractActorTest;
8 import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
9 import org.opendaylight.controller.cluster.raft.RaftActorContext;
10 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
11 import org.opendaylight.controller.cluster.raft.SerializationUtils;
12 import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
13 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
14 import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
15 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
16 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
17 import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload;
18 import org.opendaylight.controller.cluster.raft.utils.DoNothingActor;
20 import java.util.ArrayList;
21 import java.util.List;
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertTrue;
26 public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest {
28 private final ActorRef behaviorActor = getSystem().actorOf(Props.create(
29 DoNothingActor.class));
32 * This test checks that when a new Raft RPC message is received with a newer
33 * term the RaftActor gets into the Follower state.
38 public void testHandleRaftRPCWithNewerTerm() throws Exception {
39 new JavaTestKit(getSystem()) {{
41 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(),
42 createAppendEntriesWithNewerTerm());
44 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(),
45 createAppendEntriesReplyWithNewerTerm());
47 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(),
48 createRequestVoteWithNewerTerm());
50 assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(getTestActor(),
51 createRequestVoteReplyWithNewerTerm());
59 * This test verifies that when an AppendEntries is received with a term that
60 * is less that the currentTerm of the RaftActor then the RaftActor does not
61 * change it's state and it responds back with a failure
66 public void testHandleAppendEntriesSenderTermLessThanReceiverTerm()
68 new JavaTestKit(getSystem()) {{
70 MockRaftActorContext context = (MockRaftActorContext)
73 // First set the receivers term to a high number (1000)
74 context.getTermInformation().update(1000, "test");
76 AppendEntries appendEntries =
77 new AppendEntries(100, "leader-1", 0, 0, null, 101, -1);
79 RaftActorBehavior behavior = createBehavior(context);
81 // Send an unknown message so that the state of the RaftActor remains unchanged
82 RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown");
84 RaftActorBehavior raftBehavior =
85 behavior.handleMessage(getRef(), appendEntries);
87 assertEquals(expected, raftBehavior);
89 // Also expect an AppendEntriesReply to be sent where success is false
90 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
91 "AppendEntriesReply") {
92 // do not put code outside this method, will run afterwards
93 protected Boolean match(Object in) {
94 if (in instanceof AppendEntriesReply) {
95 AppendEntriesReply reply = (AppendEntriesReply) in;
96 return reply.isSuccess();
103 assertEquals(false, out);
111 public void testHandleAppendEntriesAddSameEntryToLog(){
112 new JavaTestKit(getSystem()) {
115 MockRaftActorContext context = (MockRaftActorContext)
116 createActorContext();
118 // First set the receivers term to lower number
119 context.getTermInformation().update(2, "test");
121 // Prepare the receivers log
122 MockRaftActorContext.SimpleReplicatedLog log =
123 new MockRaftActorContext.SimpleReplicatedLog();
125 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
127 context.setReplicatedLog(log);
129 List<ReplicatedLogEntry> entries = new ArrayList<>();
131 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
133 AppendEntries appendEntries =
134 new AppendEntries(2, "leader-1", -1, 1, entries, 0, -1);
136 RaftActorBehavior behavior = createBehavior(context);
138 if (AbstractRaftActorBehaviorTest.this instanceof CandidateTest) {
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");
147 // Send an unknown message so that the state of the RaftActor remains unchanged
148 RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown");
150 RaftActorBehavior raftBehavior =
151 behavior.handleMessage(getRef(), appendEntries);
153 assertEquals(expected, raftBehavior);
155 assertEquals(1, log.size());
162 * This test verifies that when a RequestVote is received by the RaftActor
163 * with a term which is greater than the RaftActors' currentTerm and the
164 * senders' log is more upto date than the receiver that the receiver grants
165 * the vote to the sender
168 public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermAndSenderLogMoreUpToDate() {
169 new JavaTestKit(getSystem()) {{
171 new Within(duration("1 seconds")) {
172 protected void run() {
174 RaftActorBehavior behavior = createBehavior(
175 createActorContext(behaviorActor));
177 RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(),
178 new RequestVote(1000, "test", 10000, 999));
180 if(!(behavior instanceof Follower)){
181 assertTrue(raftBehavior instanceof Follower);
185 new ExpectMsg<Boolean>(duration("1 seconds"),
186 "RequestVoteReply") {
187 // do not put code outside this method, will run afterwards
188 protected Boolean match(Object in) {
189 if (in instanceof RequestVoteReply) {
190 RequestVoteReply reply =
191 (RequestVoteReply) in;
192 return reply.isVoteGranted();
199 assertEquals(true, out);
207 * This test verifies that when a RaftActor receives a RequestVote message
208 * with a term that is greater than it's currentTerm but a less up-to-date
209 * log then the receiving RaftActor will not grant the vote to the sender
212 public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermButSenderLogLessUptoDate() {
213 new JavaTestKit(getSystem()) {{
215 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
241 protected Boolean match(Object in) {
242 if (in instanceof RequestVoteReply) {
243 RequestVoteReply reply =
244 (RequestVoteReply) in;
245 return reply.isVoteGranted();
252 assertEquals(false, out);
262 * This test verifies that the receiving RaftActor will not grant a vote
263 * to a sender if the sender's term is lesser than the currentTerm of the
264 * recipient RaftActor
267 public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() {
268 new JavaTestKit(getSystem()) {{
270 new Within(duration("1 seconds")) {
271 protected void run() {
273 RaftActorContext context =
274 createActorContext(behaviorActor);
276 context.getTermInformation().update(1000, null);
278 RaftActorBehavior follower = createBehavior(context);
280 follower.handleMessage(getTestActor(),
281 new RequestVote(999, "test", 10000, 999));
284 new ExpectMsg<Boolean>(duration("1 seconds"),
285 "RequestVoteReply") {
286 // do not put code outside this method, will run afterwards
287 protected Boolean match(Object in) {
288 if (in instanceof RequestVoteReply) {
289 RequestVoteReply reply =
290 (RequestVoteReply) in;
291 return reply.isVoteGranted();
298 assertEquals(false, out);
305 public void testFakeSnapshots() {
306 MockRaftActorContext context = new MockRaftActorContext("test", getSystem(), behaviorActor);
307 AbstractRaftActorBehavior behavior = new Leader(context);
308 context.getTermInformation().update(1, "leader");
310 //entry with 1 index=0 entry with replicatedToAllIndex = 0, does not do anything, returns the
311 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 1, 1).build());
312 context.setLastApplied(0);
313 assertEquals(-1, behavior.fakeSnapshot(0, -1));
314 assertEquals(1, context.getReplicatedLog().size());
316 //2 entries, lastApplied still 0, no purging.
317 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0,2,1).build());
318 context.setLastApplied(0);
319 assertEquals(-1, behavior.fakeSnapshot(0, -1));
320 assertEquals(2, context.getReplicatedLog().size());
322 //2 entries, lastApplied still 0, no purging.
323 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0,2,1).build());
324 context.setLastApplied(1);
325 assertEquals(0, behavior.fakeSnapshot(0, -1));
326 assertEquals(1, context.getReplicatedLog().size());
328 //5 entries, lastApplied =2 and replicatedIndex = 3, but since we want to keep the lastapplied, indices 0 and 1 will only get purged
329 context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0,5,1).build());
330 context.setLastApplied(2);
331 assertEquals(1, behavior.fakeSnapshot(3, 1));
332 assertEquals(3, context.getReplicatedLog().size());
337 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(
338 ActorRef actorRef, RaftRPC rpc) {
340 RaftActorContext actorContext = createActorContext();
341 Payload p = new MockRaftActorContext.MockPayload("");
343 (MockRaftActorContext) actorContext, 0, 0, p);
345 RaftActorBehavior raftBehavior = createBehavior(actorContext)
346 .handleMessage(actorRef, rpc);
348 assertTrue(raftBehavior instanceof Follower);
351 protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
352 MockRaftActorContext actorContext, long term, long index, Payload data) {
353 return setLastLogEntry(actorContext,
354 new MockRaftActorContext.MockReplicatedLogEntry(term, index, data));
357 protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
358 MockRaftActorContext actorContext, ReplicatedLogEntry logEntry) {
359 MockRaftActorContext.SimpleReplicatedLog
360 log = new MockRaftActorContext.SimpleReplicatedLog();
361 log.append(logEntry);
362 actorContext.setReplicatedLog(log);
367 protected abstract RaftActorBehavior createBehavior(
368 RaftActorContext actorContext);
370 protected RaftActorBehavior createBehavior() {
371 return createBehavior(createActorContext());
374 protected RaftActorContext createActorContext() {
375 return new MockRaftActorContext();
378 protected RaftActorContext createActorContext(ActorRef actor) {
379 return new MockRaftActorContext("test", getSystem(), actor);
382 protected AppendEntries createAppendEntriesWithNewerTerm() {
383 return new AppendEntries(100, "leader-1", 0, 0, null, 1, -1);
386 protected AppendEntriesReply createAppendEntriesReplyWithNewerTerm() {
387 return new AppendEntriesReply("follower-1", 100, false, 100, 100);
390 protected RequestVote createRequestVoteWithNewerTerm() {
391 return new RequestVote(100, "candidate-1", 10, 100);
394 protected RequestVoteReply createRequestVoteReplyWithNewerTerm() {
395 return new RequestVoteReply(100, false);
398 protected Object fromSerializableMessage(Object serializable){
399 return SerializationUtils.fromSerializable(serializable);