/* * Copyright (c) 2014 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 akka.actor.ActorRef; import akka.actor.Props; import akka.testkit.TestActorRef; import java.util.HashMap; import java.util.Map; import org.junit.After; import org.junit.Test; import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; import org.opendaylight.controller.cluster.raft.RaftActorContext; import org.opendaylight.controller.cluster.raft.RaftState; import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply; import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; public class IsolatedLeaderTest extends AbstractLeaderTest { private final TestActorRef leaderActor = actorFactory.createTestActor( Props.create(MessageCollectorActor.class), actorFactory.generateActorId("leader")); private final TestActorRef senderActor = actorFactory.createTestActor( Props.create(MessageCollectorActor.class), actorFactory.generateActorId("sender")); private AbstractLeader isolatedLeader; @Override @After public void tearDown() { if (isolatedLeader != null) { isolatedLeader.close(); } super.tearDown(); } @Override protected IsolatedLeader createBehavior(final RaftActorContext actorContext) { return new IsolatedLeader(actorContext); } @Override protected MockRaftActorContext createActorContext() { return createActorContext(leaderActor); } @Override protected MockRaftActorContext createActorContext(final ActorRef actor) { DefaultConfigParamsImpl configParams = new DefaultConfigParamsImpl(); configParams.setElectionTimeoutFactor(100000); MockRaftActorContext context = new MockRaftActorContext("isolated-leader", getSystem(), actor); context.setConfigParams(configParams); return context; } @Test public void testHandleMessageWithThreeMembers() throws Exception { String followerAddress1 = "akka://test/user/$a"; String followerAddress2 = "akka://test/user/$b"; MockRaftActorContext leaderActorContext = createActorContext(); Map peerAddresses = new HashMap<>(); peerAddresses.put("follower-1", followerAddress1); peerAddresses.put("follower-2", followerAddress2); leaderActorContext.setPeerAddresses(peerAddresses); isolatedLeader = new IsolatedLeader(leaderActorContext); leaderActorContext.setCurrentBehavior(isolatedLeader); assertEquals("Raft state", RaftState.IsolatedLeader, isolatedLeader.state()); // in a 3 node cluster, even if 1 follower is returns a reply, the isolatedLeader is not isolated RaftActorBehavior newBehavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-1", isolatedLeader.lastTerm() - 1, true, isolatedLeader.lastIndex() - 1, isolatedLeader.lastTerm() - 1, (short)0)); assertEquals("Raft state", RaftState.Leader, newBehavior.state()); isolatedLeader.close(); isolatedLeader = (AbstractLeader) newBehavior; newBehavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-2", isolatedLeader.lastTerm() - 1, true, isolatedLeader.lastIndex() - 1, isolatedLeader.lastTerm() - 1, (short) 0)); assertEquals("Raft state", RaftState.Leader, newBehavior.state()); } @Test public void testHandleMessageWithFiveMembers() throws Exception { String followerAddress1 = "akka://test/user/$a"; String followerAddress2 = "akka://test/user/$b"; String followerAddress3 = "akka://test/user/$c"; String followerAddress4 = "akka://test/user/$d"; final MockRaftActorContext leaderActorContext = createActorContext(); Map peerAddresses = new HashMap<>(); peerAddresses.put("follower-1", followerAddress1); peerAddresses.put("follower-2", followerAddress2); peerAddresses.put("follower-3", followerAddress3); peerAddresses.put("follower-4", followerAddress4); leaderActorContext.setPeerAddresses(peerAddresses); isolatedLeader = new IsolatedLeader(leaderActorContext); leaderActorContext.setCurrentBehavior(isolatedLeader); assertEquals("Raft state", RaftState.IsolatedLeader, isolatedLeader.state()); // in a 5 member cluster, atleast 2 followers need to be active and return a reply RaftActorBehavior newBehavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-1", isolatedLeader.lastTerm() - 1, true, isolatedLeader.lastIndex() - 1, isolatedLeader.lastTerm() - 1, (short) 0)); assertEquals("Raft state", RaftState.IsolatedLeader, newBehavior.state()); newBehavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-2", isolatedLeader.lastTerm() - 1, true, isolatedLeader.lastIndex() - 1, isolatedLeader.lastTerm() - 1, (short) 0)); assertEquals("Raft state", RaftState.Leader, newBehavior.state()); isolatedLeader.close(); isolatedLeader = (AbstractLeader) newBehavior; newBehavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-3", isolatedLeader.lastTerm() - 1, true, isolatedLeader.lastIndex() - 1, isolatedLeader.lastTerm() - 1, (short) 0)); assertEquals("Raft state", RaftState.Leader, newBehavior.state()); } @Test public void testHandleMessageFromAnotherLeader() throws Exception { String followerAddress1 = "akka://test/user/$a"; String followerAddress2 = "akka://test/user/$b"; MockRaftActorContext leaderActorContext = createActorContext(); Map peerAddresses = new HashMap<>(); peerAddresses.put("follower-1", followerAddress1); peerAddresses.put("follower-2", followerAddress2); leaderActorContext.setPeerAddresses(peerAddresses); isolatedLeader = new IsolatedLeader(leaderActorContext); assertEquals("Raft state", RaftState.IsolatedLeader, isolatedLeader.state()); // if an append-entries reply is received by the isolated-leader, and that reply // has a term > than its own term, then IsolatedLeader switches to Follower // bowing itself to another leader in the cluster RaftActorBehavior newBehavior = isolatedLeader.handleMessage(senderActor, new AppendEntriesReply("follower-1", isolatedLeader.lastTerm() + 1, true, isolatedLeader.lastIndex() + 1, isolatedLeader.lastTerm() + 1, (short)0)); assertEquals("Raft state", RaftState.Follower, newBehavior.state()); newBehavior.close(); } }