+/*
+ * 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.assertFalse;
+import static org.junit.Assert.assertNull;
+
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.testkit.TestActorRef;
import org.opendaylight.controller.cluster.raft.RaftActorContext;
import org.opendaylight.controller.cluster.raft.RaftState;
import org.opendaylight.controller.cluster.raft.ReplicatedLogEntry;
-import org.opendaylight.controller.cluster.raft.SerializationUtils;
import org.opendaylight.controller.cluster.raft.TestActorFactory;
import org.opendaylight.controller.cluster.raft.messages.AppendEntries;
import org.opendaylight.controller.cluster.raft.messages.AppendEntriesReply;
import org.opendaylight.controller.cluster.raft.messages.RaftRPC;
import org.opendaylight.controller.cluster.raft.messages.RequestVote;
import org.opendaylight.controller.cluster.raft.messages.RequestVoteReply;
+import org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry;
+import org.opendaylight.controller.cluster.raft.policy.RaftPolicy;
import org.opendaylight.controller.cluster.raft.protobuff.client.messages.Payload;
+import org.opendaylight.controller.cluster.raft.utils.InMemoryJournal;
+import org.opendaylight.controller.cluster.raft.utils.InMemorySnapshotStore;
import org.opendaylight.controller.cluster.raft.utils.MessageCollectorActor;
import org.slf4j.LoggerFactory;
-public abstract class AbstractRaftActorBehaviorTest extends AbstractActorTest {
+public abstract class AbstractRaftActorBehaviorTest<T extends RaftActorBehavior> extends AbstractActorTest {
protected final TestActorFactory actorFactory = new TestActorFactory(getSystem());
@After
public void tearDown() throws Exception {
- if(behavior != null) {
+ if (behavior != null) {
behavior.close();
}
actorFactory.close();
+
+ InMemoryJournal.clear();
+ InMemorySnapshotStore.clear();
}
/**
* This test checks that when a new Raft RPC message is received with a newer
* term the RaftActor gets into the Follower state.
- *
- * @throws Exception
*/
@Test
public void testHandleRaftRPCWithNewerTerm() throws Exception {
- RaftActorContext actorContext = createActorContext();
+ MockRaftActorContext actorContext = createActorContext();
assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(actorContext, behaviorActor,
createAppendEntriesWithNewerTerm());
/**
* This test verifies that when an AppendEntries is received with a term that
* is less that the currentTerm of the RaftActor then the RaftActor does not
- * change it's state and it responds back with a failure
- *
- * @throws Exception
+ * change it's state and it responds back with a failure.
*/
@Test
public void testHandleAppendEntriesSenderTermLessThanReceiverTerm() throws Exception {
- MockRaftActorContext context = createActorContext();
+ MockRaftActorContext context = createActorContext();
+ short payloadVersion = 5;
+ context.setPayloadVersion(payloadVersion);
- // First set the receivers term to a high number (1000)
- context.getTermInformation().update(1000, "test");
+ // First set the receivers term to a high number (1000)
+ context.getTermInformation().update(1000, "test");
- AppendEntries appendEntries = new AppendEntries(100, "leader-1", 0, 0, null, 101, -1);
+ AppendEntries appendEntries = new AppendEntries(100, "leader-1", 0, 0, null, 101, -1, (short)4);
- behavior = createBehavior(context);
+ behavior = createBehavior(context);
- // Send an unknown message so that the state of the RaftActor remains unchanged
- RaftActorBehavior expected = behavior.handleMessage(behaviorActor, "unknown");
+ RaftState expected = behavior.state();
- RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
+ RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
- assertEquals("Raft state", expected.state(), raftBehavior.state());
+ assertEquals("Raft state", expected, raftBehavior.state());
- // Also expect an AppendEntriesReply to be sent where success is false
+ // Also expect an AppendEntriesReply to be sent where success is false
- AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
- behaviorActor, AppendEntriesReply.class);
+ AppendEntriesReply reply = MessageCollectorActor.expectFirstMatching(
+ behaviorActor, AppendEntriesReply.class);
- assertEquals("isSuccess", false, reply.isSuccess());
+ assertEquals("isSuccess", false, reply.isSuccess());
+ assertEquals("getPayloadVersion", payloadVersion, reply.getPayloadVersion());
}
setLastLogEntry(context, 2, 0, payload);
List<ReplicatedLogEntry> entries = new ArrayList<>();
- entries.add(new MockRaftActorContext.MockReplicatedLogEntry(2, 0, payload));
+ entries.add(new SimpleReplicatedLogEntry(0, 2, payload));
- AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1);
+ final AppendEntries appendEntries = new AppendEntries(2, "leader-1", -1, -1, entries, 2, -1, (short)0);
behavior = createBehavior(context);
- if (behavior instanceof Candidate) {
- // 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");
- }
+ assertFalse("This test should be overridden when testing Candidate", behavior instanceof Candidate);
+
+ RaftState expected = behavior.state();
- // Send an unknown message so that the state of the RaftActor remains unchanged
- RaftActorBehavior expected = behavior.handleMessage(behaviorActor, "unknown");
+ // Check that the behavior does not handle unknwon message
+ assertNull(behavior.handleMessage(behaviorActor, "unknown"));
RaftActorBehavior raftBehavior = behavior.handleMessage(behaviorActor, appendEntries);
- assertEquals("Raft state", expected.state(), raftBehavior.state());
+ assertEquals("Raft state", expected, raftBehavior.state());
assertEquals("ReplicatedLog size", 1, context.getReplicatedLog().size());
/**
* This test verifies that when a RaftActor receives a RequestVote message
* with a term that is greater than it's currentTerm but a less up-to-date
- * log then the receiving RaftActor will not grant the vote to the sender
+ * log then the receiving RaftActor will not grant the vote to the sender.
*/
@Test
public void testHandleRequestVoteWhenSenderLogLessUptoDate() {
/**
* This test verifies that the receiving RaftActor will not grant a vote
* to a sender if the sender's term is lesser than the currentTerm of the
- * recipient RaftActor
+ * recipient RaftActor.
*/
@Test
public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() {
- RaftActorContext context = createActorContext();
+ MockRaftActorContext context = createActorContext();
context.getTermInformation().update(1000, null);
assertEquals(0, abstractBehavior.getReplicatedToAllIndex());
assertEquals(1, context.getReplicatedLog().size());
- //5 entries, lastApplied =2 and replicatedIndex = 3, but since we want to keep the lastapplied, indices 0 and 1 will only get purged
+ // 5 entries, lastApplied =2 and replicatedIndex = 3, but since we want to keep the lastapplied, indices 0 and
+ // 1 will only get purged
context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 5, 1).build());
context.setLastApplied(2);
abstractBehavior.performSnapshotWithoutCapture(3);
}
- protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(RaftActorContext actorContext,
+ protected void assertStateChangesToFollowerWhenRaftRPCHasNewerTerm(MockRaftActorContext actorContext,
ActorRef actorRef, RaftRPC rpc) throws Exception {
- Payload p = new MockRaftActorContext.MockPayload("");
- setLastLogEntry((MockRaftActorContext) actorContext, 1, 0, p);
+ Payload payload = new MockRaftActorContext.MockPayload("");
+ setLastLogEntry(actorContext, 1, 0, payload);
actorContext.getTermInformation().update(1, "test");
RaftActorBehavior origBehavior = createBehavior(actorContext);
protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(
MockRaftActorContext actorContext, long term, long index, Payload data) {
- return setLastLogEntry(actorContext,
- new MockRaftActorContext.MockReplicatedLogEntry(term, index, data));
+ return setLastLogEntry(actorContext, new SimpleReplicatedLogEntry(index, term, data));
}
protected MockRaftActorContext.SimpleReplicatedLog setLastLogEntry(MockRaftActorContext actorContext,
return log;
}
- protected abstract RaftActorBehavior createBehavior(
- RaftActorContext actorContext);
+ protected abstract T createBehavior(RaftActorContext actorContext);
+
+ protected final T createBehavior(MockRaftActorContext actorContext) {
+ T ret = createBehavior((RaftActorContext)actorContext);
+ actorContext.setCurrentBehavior(ret);
+ return ret;
+ }
protected RaftActorBehavior createBehavior() {
return createBehavior(createActorContext());
}
protected AppendEntries createAppendEntriesWithNewerTerm() {
- return new AppendEntries(100, "leader-1", 0, 0, null, 1, -1);
+ return new AppendEntries(100, "leader-1", 0, 0, null, 1, -1, (short)0);
}
protected AppendEntriesReply createAppendEntriesReplyWithNewerTerm() {
- return new AppendEntriesReply("follower-1", 100, false, 100, 100);
+ return new AppendEntriesReply("follower-1", 100, false, 100, 100, (short)0);
}
protected RequestVote createRequestVoteWithNewerTerm() {
return new RequestVoteReply(100, false);
}
- protected Object fromSerializableMessage(Object serializable){
- return SerializationUtils.fromSerializable(serializable);
- }
-
protected ByteString toByteString(Map<String, String> state) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- try(ObjectOutputStream oos = new ObjectOutputStream(bos)) {
+ try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(state);
return ByteString.copyFrom(bos.toByteArray());
} catch (IOException e) {
}
protected void logStart(String name) {
- LoggerFactory.getLogger(LeaderTest.class).info("Starting " + name);
+ LoggerFactory.getLogger(getClass()).info("Starting " + name);
+ }
+
+ protected RaftPolicy createRaftPolicy(final boolean automaticElectionsEnabled,
+ final boolean applyModificationToStateBeforeConsensus) {
+ return new RaftPolicy() {
+ @Override
+ public boolean automaticElectionsEnabled() {
+ return automaticElectionsEnabled;
+ }
+
+ @Override
+ public boolean applyModificationToStateBeforeConsensus() {
+ return applyModificationToStateBeforeConsensus;
+ }
+ };
}
}