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.RaftState;
11 import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
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.utils.DoNothingActor;
19 import java.util.ArrayList;
20 import java.util.List;
22 import static org.junit.Assert.assertEquals;
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 = (MockRaftActorContext)
71 // First set the receivers term to a high number (1000)
72 context.getTermInformation().update(1000, "test");
74 AppendEntries appendEntries =
75 new AppendEntries(100, "leader-1", 0, 0, null, 101);
77 RaftActorBehavior behavior = createBehavior(context);
79 // Send an unknown message so that the state of the RaftActor remains unchanged
80 RaftState expected = behavior.handleMessage(getRef(), "unknown");
83 behavior.handleMessage(getRef(), appendEntries);
85 assertEquals(expected, raftState);
87 // Also expect an AppendEntriesReply to be sent where success is false
88 final Boolean out = new ExpectMsg<Boolean>(duration("1 seconds"),
89 "AppendEntriesReply") {
90 // 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 = (MockRaftActorContext)
114 createActorContext();
116 // First set the receivers term to lower number
117 context.getTermInformation().update(2, "test");
119 // Prepare the receivers log
120 MockRaftActorContext.SimpleReplicatedLog log =
121 new MockRaftActorContext.SimpleReplicatedLog();
123 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, "zero"));
125 context.setReplicatedLog(log);
127 List<ReplicatedLogEntry> entries = new ArrayList<>();
129 new MockRaftActorContext.MockReplicatedLogEntry(1, 0, "zero"));
131 AppendEntries appendEntries =
132 new AppendEntries(2, "leader-1", -1, 1, entries, 0);
134 RaftActorBehavior behavior = createBehavior(context);
136 if (AbstractRaftActorBehaviorTest.this instanceof CandidateTest) {
137 // Resetting the Candidates term to make sure it will match
138 // the term sent by AppendEntries. If this was not done then
139 // the test will fail because the Candidate will assume that
140 // the message was sent to it from a lower term peer and will
141 // thus respond with a failure
142 context.getTermInformation().update(2, "test");
145 // Send an unknown message so that the state of the RaftActor remains unchanged
146 RaftState expected = behavior.handleMessage(getRef(), "unknown");
148 RaftState raftState =
149 behavior.handleMessage(getRef(), appendEntries);
151 assertEquals(expected, raftState);
153 assertEquals(1, log.size());
160 * This test verifies that when a RequestVote is received by the RaftActor
161 * with a term which is greater than the RaftActors' currentTerm and the
162 * senders' log is more upto date than the receiver that the receiver grants
163 * the vote to the sender
166 public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermAndSenderLogMoreUpToDate() {
167 new JavaTestKit(getSystem()) {{
169 new Within(duration("1 seconds")) {
170 protected void run() {
172 RaftActorBehavior behavior = createBehavior(
173 createActorContext(behaviorActor));
175 RaftState raftState = behavior.handleMessage(getTestActor(),
176 new RequestVote(1000, "test", 10000, 999));
178 if(behavior.state() != RaftState.Follower){
179 assertEquals(RaftState.Follower, raftState);
183 new ExpectMsg<Boolean>(duration("1 seconds"),
184 "RequestVoteReply") {
185 // do not put code outside this method, will run afterwards
186 protected Boolean match(Object in) {
187 if (in instanceof RequestVoteReply) {
188 RequestVoteReply reply =
189 (RequestVoteReply) in;
190 return reply.isVoteGranted();
197 assertEquals(true, out);
205 * This test verifies that when a RaftActor receives a RequestVote message
206 * with a term that is greater than it's currentTerm but a less up-to-date
207 * log then the receiving RaftActor will not grant the vote to the sender
210 public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermButSenderLogLessUptoDate() {
211 new JavaTestKit(getSystem()) {{
213 new Within(duration("1 seconds")) {
214 protected void run() {
216 RaftActorContext actorContext =
217 createActorContext(behaviorActor);
219 MockRaftActorContext.SimpleReplicatedLog
220 log = new MockRaftActorContext.SimpleReplicatedLog();
222 new MockRaftActorContext.MockReplicatedLogEntry(20000,
225 ((MockRaftActorContext) actorContext).setReplicatedLog(log);
227 RaftActorBehavior behavior = createBehavior(actorContext);
229 RaftState raftState = behavior.handleMessage(getTestActor(),
230 new RequestVote(1000, "test", 10000, 999));
232 if(behavior.state() != RaftState.Follower){
233 assertEquals(RaftState.Follower, raftState);
236 new ExpectMsg<Boolean>(duration("1 seconds"),
237 "RequestVoteReply") {
238 // do not put code outside this method, will run afterwards
239 protected Boolean match(Object in) {
240 if (in instanceof RequestVoteReply) {
241 RequestVoteReply reply =
242 (RequestVoteReply) in;
243 return reply.isVoteGranted();
250 assertEquals(false, out);
260 * This test verifies that the receiving RaftActor will not grant a vote
261 * to a sender if the sender's term is lesser than the currentTerm of the
262 * recipient RaftActor
265 public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() {
266 new JavaTestKit(getSystem()) {{
268 new Within(duration("1 seconds")) {
269 protected void run() {
271 RaftActorContext context =
272 createActorContext(behaviorActor);
274 context.getTermInformation().update(1000, null);
276 RaftActorBehavior follower = createBehavior(context);
278 follower.handleMessage(getTestActor(),
279 new RequestVote(999, "test", 10000, 999));
282 new ExpectMsg<Boolean>(duration("1 seconds"),
283 "RequestVoteReply") {
284 // do not put code outside this method, will run afterwards
285 protected Boolean match(Object in) {
286 if (in instanceof RequestVoteReply) {
287 RequestVoteReply reply =
288 (RequestVoteReply) in;
289 return reply.isVoteGranted();
296 assertEquals(false, out);
302 protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(
303 ActorRef actorRef, RaftRPC rpc) {
305 RaftActorContext actorContext = createActorContext();
307 (MockRaftActorContext) actorContext, 0, 0, "");
309 RaftState raftState = createBehavior(actorContext)
310 .handleMessage(actorRef, rpc);
312 assertEquals(RaftState.Follower, raftState);
315 protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
316 MockRaftActorContext actorContext, long term, long index, Object data) {
317 return setLastLogEntry(actorContext,
318 new MockRaftActorContext.MockReplicatedLogEntry(term, index, data));
321 protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
322 MockRaftActorContext actorContext, ReplicatedLogEntry logEntry) {
323 MockRaftActorContext.SimpleReplicatedLog
324 log = new MockRaftActorContext.SimpleReplicatedLog();
325 log.append(logEntry);
326 actorContext.setReplicatedLog(log);
331 protected abstract RaftActorBehavior createBehavior(
332 RaftActorContext actorContext);
334 protected RaftActorBehavior createBehavior() {
335 return createBehavior(createActorContext());
338 protected RaftActorContext createActorContext() {
339 return new MockRaftActorContext();
342 protected RaftActorContext createActorContext(ActorRef actor) {
343 return new MockRaftActorContext("test", getSystem(), actor);
346 protected AppendEntries createAppendEntriesWithNewerTerm() {
347 return new AppendEntries(100, "leader-1", 0, 0, null, 1);
350 protected AppendEntriesReply createAppendEntriesReplyWithNewerTerm() {
351 return new AppendEntriesReply("follower-1", 100, false, 100, 100);
354 protected RequestVote createRequestVoteWithNewerTerm() {
355 return new RequestVote(100, "candidate-1", 10, 100);
358 protected RequestVoteReply createRequestVoteReplyWithNewerTerm() {
359 return new RequestVoteReply(100, false);