+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
package org.opendaylight.controller.cluster.raft.behaviors;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.testkit.TestActorRef;
+import com.google.common.base.Stopwatch;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mockito;
+import org.opendaylight.controller.cluster.NonPersistentDataProvider;
+import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl;
+import org.opendaylight.controller.cluster.raft.ElectionTerm;
import org.opendaylight.controller.cluster.raft.MockRaftActorContext;
import org.opendaylight.controller.cluster.raft.RaftActorContext;
+import org.opendaylight.controller.cluster.raft.RaftActorContextImpl;
import org.opendaylight.controller.cluster.raft.RaftState;
import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
+import org.opendaylight.controller.cluster.raft.VotingState;
import org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout;
import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
import org.opendaylight.controller.cluster.raft.messages.RequestVote;
import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class CandidateTest extends AbstractRaftActorBehaviorTest {
+ static final Logger LOG = LoggerFactory.getLogger(CandidateTest.class);
private final TestActorRef<MessageCollectorActor> candidateActor = actorFactory.createTestActor(
Props.create(MessageCollectorActor.class), actorFactory.generateActorId("candidate"));
@Test
public void testBecomeLeaderOnReceivingMajorityVotesInFiveNodeCluster(){
MockRaftActorContext raftActorContext = createActorContext();
+ raftActorContext.getTermInformation().update(2L, "other");
+ raftActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().
+ createEntries(0, 5, 1).build());
raftActorContext.setPeerAddresses(setupPeers(4));
candidate = new Candidate(raftActorContext);
+ RequestVote requestVote = MessageCollectorActor.expectFirstMatching(peerActors[0], RequestVote.class);
+ assertEquals("getTerm", 3L, requestVote.getTerm());
+ assertEquals("getCandidateId", raftActorContext.getId(), requestVote.getCandidateId());
+ assertEquals("getLastLogTerm", 1L, requestVote.getLastLogTerm());
+ assertEquals("getLastLogIndex", 4L, requestVote.getLastLogIndex());
+
+ MessageCollectorActor.expectFirstMatching(peerActors[1], RequestVote.class);
+ MessageCollectorActor.expectFirstMatching(peerActors[2], RequestVote.class);
+ MessageCollectorActor.expectFirstMatching(peerActors[3], RequestVote.class);
+
// First peers denies the vote.
candidate = candidate.handleMessage(peerActors[0], new RequestVoteReply(1, false));
assertEquals("Behavior", RaftState.Leader, candidate.state());
}
+ @Test
+ public void testBecomeLeaderOnReceivingMajorityVotesWithNonVotingPeers(){
+ ElectionTerm mockElectionTerm = Mockito.mock(ElectionTerm.class);
+ Mockito.doReturn(1L).when(mockElectionTerm).getCurrentTerm();
+ RaftActorContext raftActorContext = new RaftActorContextImpl(candidateActor, candidateActor.actorContext(),
+ "candidate", mockElectionTerm, -1, -1, setupPeers(4), new DefaultConfigParamsImpl(),
+ new NonPersistentDataProvider(), LOG);
+ raftActorContext.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().build());
+ raftActorContext.getPeerInfo("peer1").setVotingState(VotingState.NON_VOTING);
+ raftActorContext.getPeerInfo("peer4").setVotingState(VotingState.NON_VOTING);
+ candidate = new Candidate(raftActorContext);
+
+ MessageCollectorActor.expectFirstMatching(peerActors[1], RequestVote.class);
+ MessageCollectorActor.expectFirstMatching(peerActors[2], RequestVote.class);
+ MessageCollectorActor.assertNoneMatching(peerActors[0], RequestVote.class, 300);
+ MessageCollectorActor.assertNoneMatching(peerActors[3], RequestVote.class, 100);
+
+ candidate = candidate.handleMessage(peerActors[1], new RequestVoteReply(1, false));
+
+ assertEquals("Behavior", RaftState.Candidate, candidate.state());
+
+ candidate = candidate.handleMessage(peerActors[2], new RequestVoteReply(1, true));
+
+ assertEquals("Behavior", RaftState.Leader, candidate.state());
+ }
+
@Test
public void testResponseToHandleAppendEntriesWithLowerTerm() {
candidate = new Candidate(createActorContext());
setupPeers(1);
- candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
- Collections.<ReplicatedLogEntry>emptyList(), 0, -1));
+ RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(1, "test", 0, 0,
+ Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
peerActors[0], AppendEntriesReply.class);
assertEquals("isSuccess", false, reply.isSuccess());
assertEquals("getTerm", 2, reply.getTerm());
+ assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Candidate);
}
+ @Test
+ public void testResponseToHandleAppendEntriesWithHigherTerm() {
+ candidate = new Candidate(createActorContext());
+
+ setupPeers(1);
+ RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(5, "test", 0, 0,
+ Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
+
+ assertTrue("New Behavior : " + newBehavior, newBehavior instanceof Follower);
+ }
+
+ @Test
+ public void testResponseToHandleAppendEntriesWithEqualTerm() {
+ MockRaftActorContext actorContext = createActorContext();
+
+ candidate = new Candidate(actorContext);
+
+ setupPeers(1);
+ RaftActorBehavior newBehavior = candidate.handleMessage(peerActors[0], new AppendEntries(2, "test", 0, 0,
+ Collections.<ReplicatedLogEntry>emptyList(), 0, -1, (short) 0));
+
+ assertTrue("New Behavior : " + newBehavior + " term = " + actorContext.getTermInformation().getCurrentTerm(),
+ newBehavior instanceof Follower);
+ }
+
+
@Test
public void testResponseToRequestVoteWithLowerTerm() {
candidate = new Candidate(createActorContext());
assertEquals("getTerm", 1001, reply.getTerm());
}
+ @Test
+ public void testCandidateSchedulesElectionTimeoutImmediatelyWhenItHasNoPeers(){
+ MockRaftActorContext context = createActorContext();
+
+ Stopwatch stopwatch = Stopwatch.createStarted();
+
+ candidate = createBehavior(context);
+
+ MessageCollectorActor.expectFirstMatching(candidateActor, ElectionTimeout.class);
+
+ long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
+
+ assertTrue(elapsed < context.getConfigParams().getElectionTimeOutInterval().toMillis());
+ }
+
+ @Test
+ @Override
+ public void testHandleAppendEntriesAddSameEntryToLog() throws Exception {
+ MockRaftActorContext context = createActorContext();
+
+ context.getTermInformation().update(2, "test");
+
+ // Prepare the receivers log
+ MockRaftActorContext.MockPayload payload = new MockRaftActorContext.MockPayload("zero");
+ setLastLogEntry(context, 2, 0, payload);
+
+ List<ReplicatedLogEntry> entries = new ArrayList<>();
+ entries.add(new MockRaftActorContext.MockReplicatedLogEntry(2, 0, payload));
+
+ AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
+
+ behavior = createBehavior(context);
+ // Resetting the Candidates term to make sure it will match
+ // the term sent by AppendEntries. If this was not done then
+ // the test will fail because the Candidate will assume that
+ // the message was sent to it from a lower term peer and will
+ // thus respond with a failure
+ context.getTermInformation().update(2, "test");
+
+ // Send an unknown message so that the state of the RaftActor remains unchanged
+ RaftActorBehavior expected = behavior.handleMessage(candidateActor, "unknown");
+
+ RaftActorBehavior raftBehavior = behavior.handleMessage(candidateActor, appendEntries);
+
+ assertEquals("Raft state", RaftState.Follower, raftBehavior.state());
+
+ assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
+
+ handleAppendEntriesAddSameEntryToLogReply(candidateActor);
+ }
@Override
protected RaftActorBehavior createBehavior(RaftActorContext actorContext) {