use of org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry in project controller by opendaylight.
the class RaftActor method persistData.
/**
* Persists the given Payload in the journal and replicates to any followers. After successful completion,
* {@link #applyState(ActorRef, Identifier, Object)} is notified.
*
* @param clientActor optional ActorRef that is provided via the applyState callback
* @param identifier the payload identifier
* @param data the payload data to persist
* @param batchHint if true, an attempt is made to delay immediate replication and batch the payload with
* subsequent payloads for efficiency. Otherwise the payload is immediately replicated.
*/
protected final void persistData(final ActorRef clientActor, final Identifier identifier, final Payload data, final boolean batchHint) {
ReplicatedLogEntry replicatedLogEntry = new SimpleReplicatedLogEntry(context.getReplicatedLog().lastIndex() + 1, context.getTermInformation().getCurrentTerm(), data);
replicatedLogEntry.setPersistencePending(true);
LOG.debug("{}: Persist data {}", persistenceId(), replicatedLogEntry);
final RaftActorContext raftContext = getRaftActorContext();
boolean wasAppended = replicatedLog().appendAndPersist(replicatedLogEntry, persistedLogEntry -> {
// Clear the persistence pending flag in the log entry.
persistedLogEntry.setPersistencePending(false);
if (!hasFollowers()) {
// Increment the Commit Index and the Last Applied values
raftContext.setCommitIndex(persistedLogEntry.getIndex());
raftContext.setLastApplied(persistedLogEntry.getIndex());
// Apply the state immediately.
handleApplyState(new ApplyState(clientActor, identifier, persistedLogEntry));
// Send a ApplyJournalEntries message so that we write the fact that we applied
// the state to durable storage
self().tell(new ApplyJournalEntries(persistedLogEntry.getIndex()), self());
} else {
context.getReplicatedLog().captureSnapshotIfReady(replicatedLogEntry);
// Local persistence is complete so send the CheckConsensusReached message to the behavior (which
// normally should still be the leader) to check if consensus has now been reached in conjunction with
// follower replication.
getCurrentBehavior().handleMessage(getSelf(), CheckConsensusReached.INSTANCE);
}
}, true);
if (wasAppended && hasFollowers()) {
// Send log entry for replication.
getCurrentBehavior().handleMessage(getSelf(), new Replicate(clientActor, identifier, replicatedLogEntry, !batchHint));
}
}
use of org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry in project controller by opendaylight.
the class ShardTest method testDataTreeCandidateRecovery.
@Test
public void testDataTreeCandidateRecovery() throws Exception {
// Set up the InMemorySnapshotStore.
final DataTree source = setupInMemorySnapshotStore();
final DataTreeModification writeMod = source.takeSnapshot().newModification();
writeMod.write(TestModel.OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build());
writeMod.ready();
InMemoryJournal.addEntry(shardID.toString(), 0, DUMMY_DATA);
// Set up the InMemoryJournal.
InMemoryJournal.addEntry(shardID.toString(), 1, new SimpleReplicatedLogEntry(0, 1, payloadForModification(source, writeMod, nextTransactionId())));
final int nListEntries = 16;
final Set<Integer> listEntryKeys = new HashSet<>();
// Add some ModificationPayload entries
for (int i = 1; i <= nListEntries; i++) {
listEntryKeys.add(Integer.valueOf(i));
final YangInstanceIdentifier path = YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH).nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, i).build();
final DataTreeModification mod = source.takeSnapshot().newModification();
mod.merge(path, ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, i));
mod.ready();
InMemoryJournal.addEntry(shardID.toString(), i + 1, new SimpleReplicatedLogEntry(i, 1, payloadForModification(source, mod, nextTransactionId())));
}
InMemoryJournal.addEntry(shardID.toString(), nListEntries + 2, new ApplyJournalEntries(nListEntries));
testRecovery(listEntryKeys);
}
use of org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry in project controller by opendaylight.
the class LeaderTest method testInitiateForceInstallSnapshot.
@Test
public void testInitiateForceInstallSnapshot() throws Exception {
logStart("testInitiateForceInstallSnapshot");
MockRaftActorContext actorContext = createActorContextWithFollower();
final int followersLastIndex = 2;
final int snapshotIndex = -1;
final int newEntryIndex = 4;
final int snapshotTerm = -1;
final int currentTerm = 2;
// set the snapshot variables in replicatedlog
actorContext.getReplicatedLog().setSnapshotIndex(snapshotIndex);
actorContext.getReplicatedLog().setSnapshotTerm(snapshotTerm);
actorContext.setLastApplied(3);
actorContext.setCommitIndex(followersLastIndex);
actorContext.getReplicatedLog().removeFrom(0);
AtomicReference<java.util.Optional<OutputStream>> installSnapshotStream = new AtomicReference<>();
actorContext.setCreateSnapshotProcedure(installSnapshotStream::set);
leader = new Leader(actorContext);
actorContext.setCurrentBehavior(leader);
// Leader will send an immediate heartbeat - ignore it.
MessageCollectorActor.expectFirstMatching(followerActor, AppendEntries.class);
// set the snapshot as absent and check if capture-snapshot is invoked.
leader.setSnapshotHolder(null);
for (int i = 0; i < 4; i++) {
actorContext.getReplicatedLog().append(new SimpleReplicatedLogEntry(i, 1, new MockRaftActorContext.MockPayload("X" + i)));
}
// new entry
SimpleReplicatedLogEntry entry = new SimpleReplicatedLogEntry(newEntryIndex, currentTerm, new MockRaftActorContext.MockPayload("D"));
actorContext.getReplicatedLog().append(entry);
// update follower timestamp
leader.markFollowerActive(FOLLOWER_ID);
// Sending this AppendEntriesReply forces the Leader to capture a snapshot, which subsequently gets
// installed with a SendInstallSnapshot
leader.handleMessage(leaderActor, new AppendEntriesReply(FOLLOWER_ID, 1, false, 1, 1, (short) 1, true));
assertEquals("isCapturing", true, actorContext.getSnapshotManager().isCapturing());
CaptureSnapshot cs = actorContext.getSnapshotManager().getCaptureSnapshot();
assertEquals(3, cs.getLastAppliedIndex());
assertEquals(1, cs.getLastAppliedTerm());
assertEquals(4, cs.getLastIndex());
assertEquals(2, cs.getLastTerm());
assertNotNull("Create snapshot procedure not invoked", installSnapshotStream.get());
assertTrue("Install snapshot stream present", installSnapshotStream.get().isPresent());
MessageCollectorActor.clearMessages(followerActor);
// Sending Replicate message should not initiate another capture since the first is in progress.
leader.handleMessage(leaderActor, new Replicate(null, new MockIdentifier("state-id"), entry, true));
assertSame("CaptureSnapshot instance", cs, actorContext.getSnapshotManager().getCaptureSnapshot());
// Similarly sending another AppendEntriesReply to force a snapshot should not initiate another capture.
leader.handleMessage(leaderActor, new AppendEntriesReply(FOLLOWER_ID, 1, false, 1, 1, (short) 1, true));
assertSame("CaptureSnapshot instance", cs, actorContext.getSnapshotManager().getCaptureSnapshot());
// Now simulate the CaptureSnapshotReply to initiate snapshot install - the first chunk should be sent.
final byte[] bytes = new byte[] { 1, 2, 3 };
installSnapshotStream.get().get().write(bytes);
actorContext.getSnapshotManager().persist(ByteState.of(bytes), installSnapshotStream.get(), Runtime.getRuntime().totalMemory());
MessageCollectorActor.expectFirstMatching(followerActor, InstallSnapshot.class);
// Sending another AppendEntriesReply to force a snapshot should be a no-op and not try to re-send the chunk.
MessageCollectorActor.clearMessages(followerActor);
leader.handleMessage(leaderActor, new AppendEntriesReply(FOLLOWER_ID, 1, false, 1, 1, (short) 1, true));
MessageCollectorActor.assertNoneMatching(followerActor, InstallSnapshot.class, 200);
}
use of org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry in project controller by opendaylight.
the class LeaderTest method testHandleReplicateMessageWhenThereAreNoFollowers.
@Test
public void testHandleReplicateMessageWhenThereAreNoFollowers() throws Exception {
logStart("testHandleReplicateMessageWhenThereAreNoFollowers");
MockRaftActorContext actorContext = createActorContext();
leader = new Leader(actorContext);
actorContext.setLastApplied(0);
long newLogIndex = actorContext.getReplicatedLog().lastIndex() + 1;
long term = actorContext.getTermInformation().getCurrentTerm();
ReplicatedLogEntry newEntry = new SimpleReplicatedLogEntry(newLogIndex, term, new MockRaftActorContext.MockPayload("foo"));
actorContext.getReplicatedLog().append(newEntry);
final Identifier id = new MockIdentifier("state-id");
RaftActorBehavior raftBehavior = leader.handleMessage(leaderActor, new Replicate(leaderActor, id, newEntry, true));
// State should not change
assertTrue(raftBehavior instanceof Leader);
assertEquals("getCommitIndex", newLogIndex, actorContext.getCommitIndex());
// We should get 2 ApplyState messages - 1 for new log entry and 1 for the previous
// one since lastApplied state is 0.
List<ApplyState> applyStateList = MessageCollectorActor.getAllMatching(leaderActor, ApplyState.class);
assertEquals("ApplyState count", newLogIndex, applyStateList.size());
for (int i = 0; i <= newLogIndex - 1; i++) {
ApplyState applyState = applyStateList.get(i);
assertEquals("getIndex", i + 1, applyState.getReplicatedLogEntry().getIndex());
assertEquals("getTerm", term, applyState.getReplicatedLogEntry().getTerm());
}
ApplyState last = applyStateList.get((int) newLogIndex - 1);
assertEquals("getData", newEntry.getData(), last.getReplicatedLogEntry().getData());
assertEquals("getIdentifier", id, last.getIdentifier());
}
use of org.opendaylight.controller.cluster.raft.persisted.SimpleReplicatedLogEntry in project controller by opendaylight.
the class PartitionedCandidateOnStartupElectionScenarioTest method setupPartitionedCandidateMember3AndSendElectionTimeouts.
private void setupPartitionedCandidateMember3AndSendElectionTimeouts() {
testLog.info("setupPartitionedCandidateMember3AndSendElectionTimeouts starting");
// Create member 3's behavior initially as a Candidate.
member3Context = newRaftActorContext("member3", member3ActorRef, ImmutableMap.<String, String>builder().put("member1", member1ActorRef.path().toString()).put("member2", member2ActorRef.path().toString()).build());
DefaultConfigParamsImpl member3ConfigParams = newConfigParams();
member3Context.setConfigParams(member3ConfigParams);
// Initialize the ReplicatedLog and election term info for Candidate member 3. The current term
// will be 2 and the last term will be 1 so it is behind the leader's log.
SimpleReplicatedLog candidateReplicatedLog = new SimpleReplicatedLog();
candidateReplicatedLog.append(new SimpleReplicatedLogEntry(0, 2, new MockPayload("")));
member3Context.setReplicatedLog(candidateReplicatedLog);
member3Context.setCommitIndex(candidateReplicatedLog.lastIndex());
member3Context.setLastApplied(candidateReplicatedLog.lastIndex());
member3Context.getTermInformation().update(2, member1Context.getId());
// The member 3 Candidate will start a new term and send RequestVotes. However it will be
// partitioned from the cluster by having member 1 and 2 drop its RequestVote messages.
candidateElectionTerm = member3Context.getTermInformation().getCurrentTerm() + numCandidateElections;
member1Actor.dropMessagesToBehavior(RequestVote.class, numCandidateElections);
member2Actor.dropMessagesToBehavior(RequestVote.class, numCandidateElections);
member3Actor.self().tell(new SetBehavior(new Candidate(member3Context), member3Context), ActorRef.noSender());
for (int i = 0; i < numCandidateElections - 1; i++) {
member3ActorRef.tell(ElectionTimeout.INSTANCE, ActorRef.noSender());
}
member1Actor.waitForExpectedMessages(RequestVote.class);
member2Actor.waitForExpectedMessages(RequestVote.class);
verifyBehaviorState("member 1", member1Actor, RaftState.Leader);
verifyBehaviorState("member 2", member2Actor, RaftState.Follower);
verifyBehaviorState("member 3", member3Actor, RaftState.Candidate);
assertEquals("member 1 election term", 3, member1Context.getTermInformation().getCurrentTerm());
assertEquals("member 2 election term", 3, member2Context.getTermInformation().getCurrentTerm());
assertEquals("member 3 election term", candidateElectionTerm, member3Context.getTermInformation().getCurrentTerm());
testLog.info("setupPartitionedCandidateMember3AndSendElectionTimeouts ending");
}
Aggregations