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;
11 import akka.actor.ActorRef;
12 import com.google.common.collect.ImmutableMap;
13 import org.junit.Test;
14 import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl;
15 import org.opendaylight.controller.cluster.raft.RaftState;
16 import org.opendaylight.controller.cluster.raft.base.messages.TimeoutNow;
17 import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
18 import org.opendaylight.controller.cluster.raft.messages.RequestVote;
19 import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
22 * A leader election scenario test that delays various messages to behaviors to simulate network delays.
24 * @author Thomas Pantelis
26 public class DelayedMessagesElectionScenarioTest extends AbstractLeaderElectionScenarioTest {
29 public void runTest() throws Exception {
30 testLog.info("DelayedMessagesElectionScenarioTest starting");
32 setupInitialMemberBehaviors();
34 sendInitialElectionTimeoutToFollowerMember2();
36 forwardDelayedRequestVotesToLeaderMember1AndFollowerMember3();
38 sendElectionTimeoutToFollowerMember3();
40 forwardDelayedRequestVoteReplyFromOriginalFollowerMember3ToMember2();
42 testLog.info("DelayedMessagesElectionScenarioTest ending");
45 private void forwardDelayedRequestVoteReplyFromOriginalFollowerMember3ToMember2() throws Exception {
46 testLog.info("forwardDelayedRequestVoteReplyFromOriginalFollowerMember3ToMember2 starting");
48 // Now forward the original delayed RequestVoteReply from member 3 to member 2 that granted
49 // the vote. Since member 2 is now a Follower, the RequestVoteReply should be ignored.
51 member2Actor.clearDropMessagesToBehavior();
52 member2Actor.forwardCapturedMessageToBehavior(RequestVoteReply.class, member3ActorRef);
54 member2Actor.waitForExpectedMessages(RequestVoteReply.class);
56 verifyBehaviorState("member 1", member1Actor, RaftState.Follower);
57 verifyBehaviorState("member 2", member2Actor, RaftState.Follower);
58 verifyBehaviorState("member 3", member3Actor, RaftState.Leader);
60 assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm());
61 assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm());
62 assertEquals("member 3 election term", 3, member3Context.getTermInformation().getCurrentTerm());
64 testLog.info("forwardDelayedRequestVoteReplyFromOriginalFollowerMember3ToMember2 ending");
67 private void sendElectionTimeoutToFollowerMember3() throws Exception {
68 testLog.info("sendElectionTimeoutToFollowerMember3 starting");
70 // Send ElectionTimeout to member 3 to simulate missing heartbeat from a Leader. member 3
71 // should switch to Candidate and send out RequestVote messages. member 1 should grant the
72 // vote and send a reply. After receiving the RequestVoteReply, member 3 should switch to leader.
74 member2Actor.expectBehaviorStateChange();
76 member3Actor.expectMessageClass(RequestVoteReply.class, 1);
77 member3Actor.expectMessageClass(AppendEntriesReply.class, 2);
79 member3ActorRef.tell(TimeoutNow.INSTANCE, ActorRef.noSender());
81 member3Actor.waitForExpectedMessages(RequestVoteReply.class);
83 RequestVoteReply requestVoteReply = member3Actor.getCapturedMessage(RequestVoteReply.class);
84 assertEquals("getTerm", member3Context.getTermInformation().getCurrentTerm(), requestVoteReply.getTerm());
85 assertEquals("isVoteGranted", true, requestVoteReply.isVoteGranted());
87 verifyBehaviorState("member 3", member3Actor, RaftState.Leader);
89 // member 2 should've switched to Follower as member 3's RequestVote term (3) was greater
90 // than member 2's term (2).
92 member2Actor.waitForBehaviorStateChange();
93 verifyBehaviorState("member 2", member2Actor, RaftState.Follower);
95 // The switch to leader should cause an immediate AppendEntries heartbeat from member 3.
97 member3Actor.waitForExpectedMessages(AppendEntriesReply.class);
99 assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm());
100 assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm());
101 assertEquals("member 3 election term", 3, member3Context.getTermInformation().getCurrentTerm());
103 testLog.info("sendElectionTimeoutToFollowerMember3 ending");
106 private void forwardDelayedRequestVotesToLeaderMember1AndFollowerMember3() throws Exception {
107 testLog.info("forwardDelayedRequestVotesToLeaderMember1AndFollowerMember3 starting");
109 // At this point member 1 and 3 actors have captured the RequestVote messages. First
110 // forward the RequestVote message to member 1's behavior. Since the RequestVote term
111 // is greater than member 1's term and member 1 is a Leader, member 1 should switch to Follower
112 // without replying to RequestVote and update its term to 2.
114 member1Actor.clearDropMessagesToBehavior();
115 member1Actor.expectBehaviorStateChange();
116 member1Actor.forwardCapturedMessageToBehavior(RequestVote.class, member2ActorRef);
117 member1Actor.waitForExpectedMessages(RequestVote.class);
119 member1Actor.waitForBehaviorStateChange();
120 verifyBehaviorState("member 1", member1Actor, RaftState.Follower);
122 // Now forward member 3's captured RequestVote message to its behavior. Since member 3 is
123 // already a Follower, it should update its term to 2 and send a RequestVoteReply back to
124 // member 2 granting the vote b/c the RequestVote's term, lastLogTerm, and lastLogIndex
125 // should satisfy the criteria for granting the vote. However, we'll delay sending the
126 // RequestVoteReply to member 2's behavior to simulate network latency.
128 member2Actor.dropMessagesToBehavior(RequestVoteReply.class);
130 member3Actor.clearDropMessagesToBehavior();
131 member3Actor.expectMessageClass(RequestVote.class, 1);
132 member3Actor.forwardCapturedMessageToBehavior(RequestVote.class, member2ActorRef);
133 member3Actor.waitForExpectedMessages(RequestVote.class);
134 verifyBehaviorState("member 3", member3Actor, RaftState.Follower);
136 assertEquals("member 1 election term", 2, member1Context.getTermInformation().getCurrentTerm());
137 assertEquals("member 2 election term", 2, member2Context.getTermInformation().getCurrentTerm());
138 assertEquals("member 3 election term", 2, member3Context.getTermInformation().getCurrentTerm());
140 testLog.info("forwardDelayedRequestVotesToLeaderMember1AndFollowerMember3 ending");
143 private void sendInitialElectionTimeoutToFollowerMember2() {
144 testLog.info("sendInitialElectionTimeoutToFollowerMember2 starting");
146 // Send ElectionTimeout to member 2 to simulate missing heartbeat from the Leader. member 2
147 // should switch to Candidate and send out RequestVote messages. Set member 1 and 3 actors
148 // to capture RequestVote but not to forward to the behavior just yet as we want to
149 // control the order of RequestVote messages to member 1 and 3.
151 member1Actor.dropMessagesToBehavior(RequestVote.class);
153 member2Actor.expectBehaviorStateChange();
155 member3Actor.dropMessagesToBehavior(RequestVote.class);
157 member2ActorRef.tell(TimeoutNow.INSTANCE, ActorRef.noSender());
159 member1Actor.waitForExpectedMessages(RequestVote.class);
160 member3Actor.waitForExpectedMessages(RequestVote.class);
162 member2Actor.waitForBehaviorStateChange();
163 verifyBehaviorState("member 2", member2Actor, RaftState.Candidate);
165 assertEquals("member 1 election term", 1, member1Context.getTermInformation().getCurrentTerm());
166 assertEquals("member 2 election term", 2, member2Context.getTermInformation().getCurrentTerm());
167 assertEquals("member 3 election term", 1, member3Context.getTermInformation().getCurrentTerm());
169 testLog.info("sendInitialElectionTimeoutToFollowerMember2 ending");
172 private void setupInitialMemberBehaviors() throws Exception {
173 testLog.info("setupInitialMemberBehaviors starting");
175 // Create member 2's behavior initially as Follower
177 member2Context = newRaftActorContext("member2", member2ActorRef,
178 ImmutableMap.<String,String>builder().
179 put("member1", member1ActorRef.path().toString()).
180 put("member3", member3ActorRef.path().toString()).build());
182 DefaultConfigParamsImpl member2ConfigParams = newConfigParams();
183 member2Context.setConfigParams(member2ConfigParams);
185 member2Actor.behavior = new Follower(member2Context);
186 member2Context.setCurrentBehavior(member2Actor.behavior);
188 // Create member 3's behavior initially as Follower
190 member3Context = newRaftActorContext("member3", member3ActorRef,
191 ImmutableMap.<String,String>builder().
192 put("member1", member1ActorRef.path().toString()).
193 put("member2", member2ActorRef.path().toString()).build());
195 DefaultConfigParamsImpl member3ConfigParams = newConfigParams();
196 member3Context.setConfigParams(member3ConfigParams);
198 member3Actor.behavior = new Follower(member3Context);
199 member3Context.setCurrentBehavior(member3Actor.behavior);
201 // Create member 1's behavior initially as Leader
203 member1Context = newRaftActorContext("member1", member1ActorRef,
204 ImmutableMap.<String,String>builder().
205 put("member2", member2ActorRef.path().toString()).
206 put("member3", member3ActorRef.path().toString()).build());
208 DefaultConfigParamsImpl member1ConfigParams = newConfigParams();
209 member1Context.setConfigParams(member1ConfigParams);
211 initializeLeaderBehavior(member1Actor, member1Context, 2);
213 member2Actor.clear();
214 member3Actor.clear();
216 testLog.info("setupInitialMemberBehaviors ending");