X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-akka-raft%2Fsrc%2Ftest%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fcluster%2Fraft%2Fbehaviors%2FFollowerTest.java;h=6b0857351df132fd30b10406dae2acfe3cbd1237;hb=614324d63a339ef4acbc9e2c3bbaaef469f97868;hp=83b9ad3ec7b7a5c7033f876c45d4db2788f50716;hpb=5448d6812e386bd56aec7209c4852c586a6163b3;p=controller.git diff --git a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java index 83b9ad3ec7..6b0857351d 100644 --- a/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java +++ b/opendaylight/md-sal/sal-akka-raft/src/test/java/org/opendaylight/controller/cluster/raft/behaviors/FollowerTest.java @@ -1,9 +1,20 @@ package org.opendaylight.controller.cluster.raft.behaviors; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import akka.actor.ActorRef; import akka.actor.Props; import akka.testkit.JavaTestKit; import com.google.protobuf.ByteString; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.junit.Test; import org.opendaylight.controller.cluster.raft.DefaultConfigParamsImpl; import org.opendaylight.controller.cluster.raft.MockRaftActorContext; @@ -20,19 +31,6 @@ import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply; import org.opendaylight.controller.cluster.raft.utils.DoNothingActor; import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectOutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - public class FollowerTest extends AbstractRaftActorBehaviorTest { private final ActorRef followerActor = getSystem().actorOf(Props.create( @@ -43,11 +41,13 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { return new Follower(actorContext); } - @Override protected RaftActorContext createActorContext() { + @Override + protected MockRaftActorContext createActorContext() { return createActorContext(followerActor); } - protected RaftActorContext createActorContext(ActorRef actorRef){ + @Override + protected MockRaftActorContext createActorContext(ActorRef actorRef){ return new MockRaftActorContext("test", getSystem(), actorRef); } @@ -56,12 +56,14 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { new JavaTestKit(getSystem()) {{ new Within(DefaultConfigParamsImpl.HEART_BEAT_INTERVAL.$times(6)) { + @Override protected void run() { Follower follower = new Follower(createActorContext(getTestActor())); final Boolean out = new ExpectMsg(DefaultConfigParamsImpl.HEART_BEAT_INTERVAL.$times(6), "ElectionTimeout") { // do not put code outside this method, will run afterwards + @Override protected Boolean match(Object in) { if (in instanceof ElectionTimeout) { return true; @@ -94,6 +96,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { new JavaTestKit(getSystem()) {{ new Within(duration("1 seconds")) { + @Override protected void run() { RaftActorContext context = createActorContext(getTestActor()); @@ -106,6 +109,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { final Boolean out = new ExpectMsg(duration("1 seconds"), "RequestVoteReply") { // do not put code outside this method, will run afterwards + @Override protected Boolean match(Object in) { if (in instanceof RequestVoteReply) { RequestVoteReply reply = (RequestVoteReply) in; @@ -127,6 +131,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { new JavaTestKit(getSystem()) {{ new Within(duration("1 seconds")) { + @Override protected void run() { RaftActorContext context = createActorContext(getTestActor()); @@ -139,6 +144,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { final Boolean out = new ExpectMsg(duration("1 seconds"), "RequestVoteReply") { // do not put code outside this method, will run afterwards + @Override protected Boolean match(Object in) { if (in instanceof RequestVoteReply) { RequestVoteReply reply = (RequestVoteReply) in; @@ -183,7 +189,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { // The new commitIndex is 101 AppendEntries appendEntries = - new AppendEntries(2, "leader-1", 100, 1, entries, 101); + new AppendEntries(2, "leader-1", 100, 1, entries, 101, 100); RaftActorBehavior raftBehavior = createBehavior(context).handleMessage(getRef(), appendEntries); @@ -205,8 +211,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { throws Exception { new JavaTestKit(getSystem()) {{ - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); + MockRaftActorContext context = createActorContext(); // First set the receivers term to lower number context.getTermInformation().update(95, "test"); @@ -219,7 +224,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { // AppendEntries is now sent with a bigger term // this will set the receivers term to be the same as the sender's term AppendEntries appendEntries = - new AppendEntries(100, "leader-1", 0, 0, null, 101); + new AppendEntries(100, "leader-1", 0, 0, null, 101, -1); RaftActorBehavior behavior = createBehavior(context); @@ -235,6 +240,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { final Boolean out = new ExpectMsg(duration("1 seconds"), "AppendEntriesReply") { // do not put code outside this method, will run afterwards + @Override protected Boolean match(Object in) { if (in instanceof AppendEntriesReply) { AppendEntriesReply reply = (AppendEntriesReply) in; @@ -265,8 +271,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { public void testHandleAppendEntriesAddNewEntries() throws Exception { new JavaTestKit(getSystem()) {{ - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); + MockRaftActorContext context = createActorContext(); // First set the receivers term to lower number context.getTermInformation().update(1, "test"); @@ -295,7 +300,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { // This will not work for a Candidate because as soon as a Candidate // is created it increments the term AppendEntries appendEntries = - new AppendEntries(1, "leader-1", 2, 1, entries, 4); + new AppendEntries(1, "leader-1", 2, 1, entries, 4, -1); RaftActorBehavior behavior = createBehavior(context); @@ -314,6 +319,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { final Boolean out = new ExpectMsg(duration("1 seconds"), "AppendEntriesReply") { // do not put code outside this method, will run afterwards + @Override protected Boolean match(Object in) { if (in instanceof AppendEntriesReply) { AppendEntriesReply reply = (AppendEntriesReply) in; @@ -345,8 +351,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { throws Exception { new JavaTestKit(getSystem()) {{ - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(); + MockRaftActorContext context = createActorContext(); // First set the receivers term to lower number context.getTermInformation().update(2, "test"); @@ -375,7 +380,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { // This will not work for a Candidate because as soon as a Candidate // is created it increments the term AppendEntries appendEntries = - new AppendEntries(2, "leader-1", 1, 1, entries, 3); + new AppendEntries(2, "leader-1", 1, 1, entries, 3, -1); RaftActorBehavior behavior = createBehavior(context); @@ -407,6 +412,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { final Boolean out = new ExpectMsg(duration("1 seconds"), "AppendEntriesReply") { // do not put code outside this method, will run afterwards + @Override protected Boolean match(Object in) { if (in instanceof AppendEntriesReply) { AppendEntriesReply reply = (AppendEntriesReply) in; @@ -423,6 +429,119 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { }}; } + @Test + public void testHandleAppendEntriesPreviousLogEntryMissing(){ + new JavaTestKit(getSystem()) {{ + + MockRaftActorContext context = createActorContext(); + + // Prepare the receivers log + MockRaftActorContext.SimpleReplicatedLog log = + new MockRaftActorContext.SimpleReplicatedLog(); + log.append( + new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero"))); + log.append( + new MockRaftActorContext.MockReplicatedLogEntry(1, 1, new MockRaftActorContext.MockPayload("one"))); + log.append( + new MockRaftActorContext.MockReplicatedLogEntry(1, 2, new MockRaftActorContext.MockPayload("two"))); + + context.setReplicatedLog(log); + + // Prepare the entries to be sent with AppendEntries + List entries = new ArrayList<>(); + entries.add( + new MockRaftActorContext.MockReplicatedLogEntry(1, 4, new MockRaftActorContext.MockPayload("two-1"))); + + AppendEntries appendEntries = + new AppendEntries(1, "leader-1", 3, 1, entries, 4, -1); + + RaftActorBehavior behavior = createBehavior(context); + + // Send an unknown message so that the state of the RaftActor remains unchanged + RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); + + RaftActorBehavior raftBehavior = + behavior.handleMessage(getRef(), appendEntries); + + assertEquals(expected, raftBehavior); + + // Also expect an AppendEntriesReply to be sent where success is false + final Boolean out = new ExpectMsg(duration("1 seconds"), + "AppendEntriesReply") { + // do not put code outside this method, will run afterwards + @Override + protected Boolean match(Object in) { + if (in instanceof AppendEntriesReply) { + AppendEntriesReply reply = (AppendEntriesReply) in; + return reply.isSuccess(); + } else { + throw noMatch(); + } + } + }.get(); + + assertEquals(false, out); + + }}; + + } + + @Test + public void testHandleAppendAfterInstallingSnapshot(){ + new JavaTestKit(getSystem()) {{ + + MockRaftActorContext context = createActorContext(); + + + // Prepare the receivers log + MockRaftActorContext.SimpleReplicatedLog log = + new MockRaftActorContext.SimpleReplicatedLog(); + + // Set up a log as if it has been snapshotted + log.setSnapshotIndex(3); + log.setSnapshotTerm(1); + + context.setReplicatedLog(log); + + // Prepare the entries to be sent with AppendEntries + List entries = new ArrayList<>(); + entries.add( + new MockRaftActorContext.MockReplicatedLogEntry(1, 4, new MockRaftActorContext.MockPayload("two-1"))); + + AppendEntries appendEntries = + new AppendEntries(1, "leader-1", 3, 1, entries, 4, 3); + + RaftActorBehavior behavior = createBehavior(context); + + // Send an unknown message so that the state of the RaftActor remains unchanged + RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown"); + + RaftActorBehavior raftBehavior = + behavior.handleMessage(getRef(), appendEntries); + + assertEquals(expected, raftBehavior); + + // Also expect an AppendEntriesReply to be sent where success is false + final Boolean out = new ExpectMsg(duration("1 seconds"), + "AppendEntriesReply") { + // do not put code outside this method, will run afterwards + @Override + protected Boolean match(Object in) { + if (in instanceof AppendEntriesReply) { + AppendEntriesReply reply = (AppendEntriesReply) in; + return reply.isSuccess(); + } else { + throw noMatch(); + } + } + }.get(); + + assertEquals(true, out); + + }}; + + } + /** * This test verifies that when InstallSnapshot is received by @@ -437,8 +556,7 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { ActorRef leaderActor = getSystem().actorOf(Props.create( MessageCollectorActor.class)); - MockRaftActorContext context = (MockRaftActorContext) - createActorContext(getRef()); + MockRaftActorContext context = createActorContext(getRef()); Follower follower = (Follower)createBehavior(context); @@ -452,18 +570,20 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { int offset = 0; int snapshotLength = bsSnapshot.size(); int i = 1; + int chunkIndex = 1; do { chunkData = getNextChunk(bsSnapshot, offset); final InstallSnapshot installSnapshot = new InstallSnapshot(1, "leader-1", i, 1, - chunkData, i, 3); + chunkData, chunkIndex, 3); follower.handleMessage(leaderActor, installSnapshot); offset = offset + 50; i++; + chunkIndex++; } while ((offset+50) < snapshotLength); - final InstallSnapshot installSnapshot3 = new InstallSnapshot(1, "leader-1", 3, 1, chunkData, 3, 3); + final InstallSnapshot installSnapshot3 = new InstallSnapshot(1, "leader-1", 3, 1, chunkData, chunkIndex, 3); follower.handleMessage(leaderActor, installSnapshot3); String[] matches = new ReceiveWhile(String.class, duration("2 seconds")) { @@ -490,6 +610,9 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { } }.get(); + // Verify that after a snapshot is successfully applied the collected snapshot chunks is reset to empty + assertEquals(ByteString.EMPTY, follower.getSnapshotChunksCollected()); + String applySnapshotMatch = ""; for (String reply: matches) { if (reply.startsWith("applySnapshot")) { @@ -517,6 +640,51 @@ public class FollowerTest extends AbstractRaftActorBehaviorTest { }}; } + @Test + public void testHandleOutOfSequenceInstallSnapshot() throws Exception { + JavaTestKit javaTestKit = new JavaTestKit(getSystem()) { + { + + ActorRef leaderActor = getSystem().actorOf(Props.create( + MessageCollectorActor.class)); + + MockRaftActorContext context = createActorContext(getRef()); + + Follower follower = (Follower) createBehavior(context); + + HashMap followerSnapshot = new HashMap<>(); + followerSnapshot.put("1", "A"); + followerSnapshot.put("2", "B"); + followerSnapshot.put("3", "C"); + + ByteString bsSnapshot = toByteString(followerSnapshot); + + final InstallSnapshot installSnapshot = new InstallSnapshot(1, "leader-1", 3, 1, getNextChunk(bsSnapshot, 10), 3, 3); + follower.handleMessage(leaderActor, installSnapshot); + + Object messages = executeLocalOperation(leaderActor, "get-all-messages"); + + assertNotNull(messages); + assertTrue(messages instanceof List); + List listMessages = (List) messages; + + int installSnapshotReplyReceivedCount = 0; + for (Object message: listMessages) { + if (message instanceof InstallSnapshotReply) { + ++installSnapshotReplyReceivedCount; + } + } + + assertEquals(1, installSnapshotReplyReceivedCount); + InstallSnapshotReply reply = (InstallSnapshotReply) listMessages.get(0); + assertEquals(false, reply.isSuccess()); + assertEquals(-1, reply.getChunkIndex()); + assertEquals(ByteString.EMPTY, follower.getSnapshotChunksCollected()); + + + }}; + } + public Object executeLocalOperation(ActorRef actor, Object message) throws Exception { return MessageCollectorActor.getAllMessages(actor); }