2 * Copyright (c) 2015 Brocade Communications 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
8 package org.opendaylight.controller.cluster.raft.behaviors;
10 import static org.junit.Assert.assertEquals;
12 import akka.actor.ActorRef;
13 import com.google.common.collect.ImmutableMap;
14 import org.junit.Test;
15 import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl;
16 import org.opendaylight.controller.cluster.raft.RaftState;
17 import org.opendaylight.controller.cluster.raft.base.messages.TimeoutNow;
18 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
19 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
20 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
23 * A leader election scenario test that delays various messages to behaviors to simulate network delays.
25 * @author Thomas Pantelis
27 public class DelayedMessagesElectionScenarioTest extends AbstractLeaderElectionScenarioTest {
30 public void runTest() throws Exception {
31 testLog.info("DelayedMessagesElectionScenarioTest starting");
33 setupInitialMemberBehaviors();
35 sendInitialElectionTimeoutToFollowerMember2();
37 forwardDelayedRequestVotesToLeaderMember1AndFollowerMember3();
39 sendElectionTimeoutToFollowerMember3();
41 forwardDelayedRequestVoteReplyFromOriginalFollowerMember3ToMember2();
43 testLog.info("DelayedMessagesElectionScenarioTest ending");
46 private void forwardDelayedRequestVoteReplyFromOriginalFollowerMember3ToMember2() throws Exception {
47 testLog.info("forwardDelayedRequestVoteReplyFromOriginalFollowerMember3ToMember2 starting");
49 // Now forward the original delayed RequestVoteReply from member 3 to member 2 that granted
50 // the vote. Since member 2 is now a Follower, the RequestVoteReply should be ignored.
52 member2Actor.clearDropMessagesToBehavior();
53 member2Actor.forwardCapturedMessageToBehavior(RequestVoteReply.class, member3ActorRef);
55 member2Actor.waitForExpectedMessages(RequestVoteReply.class);
57 verifyBehaviorState("member 1", member1Actor, RaftState.Follower);
58 verifyBehaviorState("member 2", member2Actor, RaftState.Follower);
59 verifyBehaviorState("member 3", member3Actor, RaftState.Leader);
61 assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm());
62 assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm());
63 assertEquals("member 3 election term", 3, member3Context.getTermInformation().getCurrentTerm());
65 testLog.info("forwardDelayedRequestVoteReplyFromOriginalFollowerMember3ToMember2 ending");
68 private void sendElectionTimeoutToFollowerMember3() throws Exception {
69 testLog.info("sendElectionTimeoutToFollowerMember3 starting");
71 // Send ElectionTimeout to member 3 to simulate missing heartbeat from a Leader. member 3
72 // should switch to Candidate and send out RequestVote messages. member 1 should grant the
73 // vote and send a reply. After receiving the RequestVoteReply, member 3 should switch to leader.
75 member2Actor.expectBehaviorStateChange();
77 member3Actor.expectMessageClass(RequestVoteReply.class, 1);
78 member3Actor.expectMessageClass(AppendEntriesReply.class, 2);
80 member3ActorRef.tell(TimeoutNow.INSTANCE, ActorRef.noSender());
82 member3Actor.waitForExpectedMessages(RequestVoteReply.class);
84 RequestVoteReply requestVoteReply = member3Actor.getCapturedMessage(RequestVoteReply.class);
85 assertEquals("getTerm", member3Context.getTermInformation().getCurrentTerm(), requestVoteReply.getTerm());
86 assertEquals("isVoteGranted", true, requestVoteReply.isVoteGranted());
88 verifyBehaviorState("member 3", member3Actor, RaftState.Leader);
90 // member 2 should've switched to Follower as member 3's RequestVote term (3) was greater
91 // than member 2's term (2).
93 member2Actor.waitForBehaviorStateChange();
94 verifyBehaviorState("member 2", member2Actor, RaftState.Follower);
96 // The switch to leader should cause an immediate AppendEntries heartbeat from member 3.
98 member3Actor.waitForExpectedMessages(AppendEntriesReply.class);
100 assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm());
101 assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm());
102 assertEquals("member 3 election term", 3, member3Context.getTermInformation().getCurrentTerm());
104 testLog.info("sendElectionTimeoutToFollowerMember3 ending");
107 private void forwardDelayedRequestVotesToLeaderMember1AndFollowerMember3() throws Exception {
108 testLog.info("forwardDelayedRequestVotesToLeaderMember1AndFollowerMember3 starting");
110 // At this point member 1 and 3 actors have captured the RequestVote messages. First
111 // forward the RequestVote message to member 1's behavior. Since the RequestVote term
112 // is greater than member 1's term and member 1 is a Leader, member 1 should switch to Follower
113 // without replying to RequestVote and update its term to 2.
115 member1Actor.clearDropMessagesToBehavior();
116 member1Actor.expectBehaviorStateChange();
117 member1Actor.forwardCapturedMessageToBehavior(RequestVote.class, member2ActorRef);
118 member1Actor.waitForExpectedMessages(RequestVote.class);
120 member1Actor.waitForBehaviorStateChange();
121 verifyBehaviorState("member 1", member1Actor, RaftState.Follower);
123 // Now forward member 3's captured RequestVote message to its behavior. Since member 3 is
124 // already a Follower, it should update its term to 2 and send a RequestVoteReply back to
125 // member 2 granting the vote b/c the RequestVote's term, lastLogTerm, and lastLogIndex
126 // should satisfy the criteria for granting the vote. However, we'll delay sending the
127 // RequestVoteReply to member 2's behavior to simulate network latency.
129 member2Actor.dropMessagesToBehavior(RequestVoteReply.class);
131 member3Actor.clearDropMessagesToBehavior();
132 member3Actor.expectMessageClass(RequestVote.class, 1);
133 member3Actor.forwardCapturedMessageToBehavior(RequestVote.class, member2ActorRef);
134 member3Actor.waitForExpectedMessages(RequestVote.class);
135 verifyBehaviorState("member 3", member3Actor, RaftState.Follower);
137 assertEquals("member 1 election term", 2, member1Context.getTermInformation().getCurrentTerm());
138 assertEquals("member 2 election term", 2, member2Context.getTermInformation().getCurrentTerm());
139 assertEquals("member 3 election term", 2, member3Context.getTermInformation().getCurrentTerm());
141 testLog.info("forwardDelayedRequestVotesToLeaderMember1AndFollowerMember3 ending");
144 private void sendInitialElectionTimeoutToFollowerMember2() {
145 testLog.info("sendInitialElectionTimeoutToFollowerMember2 starting");
147 // Send ElectionTimeout to member 2 to simulate missing heartbeat from the Leader. member 2
148 // should switch to Candidate and send out RequestVote messages. Set member 1 and 3 actors
149 // to capture RequestVote but not to forward to the behavior just yet as we want to
150 // control the order of RequestVote messages to member 1 and 3.
152 member1Actor.dropMessagesToBehavior(RequestVote.class);
154 member2Actor.expectBehaviorStateChange();
156 member3Actor.dropMessagesToBehavior(RequestVote.class);
158 member2ActorRef.tell(TimeoutNow.INSTANCE, ActorRef.noSender());
160 member1Actor.waitForExpectedMessages(RequestVote.class);
161 member3Actor.waitForExpectedMessages(RequestVote.class);
163 member2Actor.waitForBehaviorStateChange();
164 verifyBehaviorState("member 2", member2Actor, RaftState.Candidate);
166 assertEquals("member 1 election term", 1, member1Context.getTermInformation().getCurrentTerm());
167 assertEquals("member 2 election term", 2, member2Context.getTermInformation().getCurrentTerm());
168 assertEquals("member 3 election term", 1, member3Context.getTermInformation().getCurrentTerm());
170 testLog.info("sendInitialElectionTimeoutToFollowerMember2 ending");
173 private void setupInitialMemberBehaviors() throws Exception {
174 testLog.info("setupInitialMemberBehaviors starting");
176 // Create member 2's behavior initially as Follower
178 member2Context = newRaftActorContext("member2", member2ActorRef,
179 ImmutableMap.<String,String>builder()
180 .put("member1", member1ActorRef.path().toString())
181 .put("member3", member3ActorRef.path().toString()).build());
183 DefaultConfigParamsImpl member2ConfigParams = newConfigParams();
184 member2Context.setConfigParams(member2ConfigParams);
186 member2Actor.behavior = new Follower(member2Context);
187 member2Context.setCurrentBehavior(member2Actor.behavior);
189 // Create member 3's behavior initially as Follower
191 member3Context = newRaftActorContext("member3", member3ActorRef,
192 ImmutableMap.<String,String>builder()
193 .put("member1", member1ActorRef.path().toString())
194 .put("member2", member2ActorRef.path().toString()).build());
196 DefaultConfigParamsImpl member3ConfigParams = newConfigParams();
197 member3Context.setConfigParams(member3ConfigParams);
199 member3Actor.behavior = new Follower(member3Context);
200 member3Context.setCurrentBehavior(member3Actor.behavior);
202 // Create member 1's behavior initially as Leader
204 member1Context = newRaftActorContext("member1", member1ActorRef,
205 ImmutableMap.<String,String>builder()
206 .put("member2", member2ActorRef.path().toString())
207 .put("member3", member3ActorRef.path().toString()).build());
209 DefaultConfigParamsImpl member1ConfigParams = newConfigParams();
210 member1Context.setConfigParams(member1ConfigParams);
212 initializeLeaderBehavior(member1Actor, member1Context, 2);
214 member2Actor.clear();
215 member3Actor.clear();
217 testLog.info("setupInitialMemberBehaviors ending");