use of org.apache.ratis.statemachine.SnapshotInfo in project incubator-ratis by apache.
the class LogAppender method shouldInstallSnapshot.
/**
* Should this {@link LogAppender} send a snapshot to the follower?
*
* @return the snapshot if it should install a snapshot; otherwise, return null.
*/
default SnapshotInfo shouldInstallSnapshot() {
// we should install snapshot if the follower needs to catch up and:
// 1. there is no local log entry but there is snapshot
// 2. or the follower's next index is smaller than the log start index
// 3. or the follower is bootstrapping and has not installed any snapshot yet
final FollowerInfo follower = getFollower();
final boolean isFollowerBootstrapping = getLeaderState().isFollowerBootstrapping(follower);
final SnapshotInfo snapshot = getServer().getStateMachine().getLatestSnapshot();
if (isFollowerBootstrapping && !follower.hasAttemptedToInstallSnapshot()) {
if (snapshot == null) {
// Leader cannot send null snapshot to follower. Hence, acknowledge InstallSnapshot attempt (even though it
// was not attempted) so that follower can come out of staging state after appending log entries.
follower.setAttemptedToInstallSnapshot();
} else {
return snapshot;
}
}
final long followerNextIndex = getFollower().getNextIndex();
if (followerNextIndex < getRaftLog().getNextIndex()) {
final long logStartIndex = getRaftLog().getStartIndex();
if (followerNextIndex < logStartIndex || (logStartIndex == RaftLog.INVALID_LOG_INDEX && snapshot != null)) {
return snapshot;
}
}
return null;
}
use of org.apache.ratis.statemachine.SnapshotInfo in project incubator-ratis by apache.
the class SnapshotManager method installSnapshot.
@SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
public void installSnapshot(StateMachine stateMachine, InstallSnapshotRequestProto request) throws IOException {
final InstallSnapshotRequestProto.SnapshotChunkProto snapshotChunkRequest = request.getSnapshotChunk();
final long lastIncludedIndex = snapshotChunkRequest.getTermIndex().getIndex();
final RaftStorageDirectory dir = storage.getStorageDir();
// create a unique temporary directory
final File tmpDir = new File(dir.getTmpDir(), UUID.randomUUID().toString());
FileUtils.createDirectories(tmpDir);
tmpDir.deleteOnExit();
LOG.info("Installing snapshot:{}, to tmp dir:{}", request, tmpDir);
for (FileChunkProto chunk : snapshotChunkRequest.getFileChunksList()) {
SnapshotInfo pi = stateMachine.getLatestSnapshot();
if (pi != null && pi.getTermIndex().getIndex() >= lastIncludedIndex) {
throw new IOException("There exists snapshot file " + pi.getFiles() + " in " + selfId + " with endIndex >= lastIncludedIndex " + lastIncludedIndex);
}
// this is relative to the root dir
String fileName = chunk.getFilename();
// TODO: assumes flat layout inside SM dir
File tmpSnapshotFile = new File(tmpDir, new File(dir.getRoot(), fileName).getName());
FileOutputStream out = null;
try {
// same last index.
if (chunk.getOffset() == 0) {
if (tmpSnapshotFile.exists()) {
FileUtils.deleteFully(tmpSnapshotFile);
}
// create the temp snapshot file and put padding inside
out = new FileOutputStream(tmpSnapshotFile);
} else {
Preconditions.assertTrue(tmpSnapshotFile.exists());
out = new FileOutputStream(tmpSnapshotFile, true);
FileChannel fc = out.getChannel();
fc.position(chunk.getOffset());
}
// write data to the file
out.write(chunk.getData().toByteArray());
} finally {
IOUtils.cleanup(null, out);
}
// the md5 digest and create the md5 meta-file.
if (chunk.getDone()) {
final MD5Hash expectedDigest = new MD5Hash(chunk.getFileDigest().toByteArray());
// calculate the checksum of the snapshot file and compare it with the
// file digest in the request
MD5Hash digest = MD5FileUtil.computeMd5ForFile(tmpSnapshotFile);
if (!digest.equals(expectedDigest)) {
LOG.warn("The snapshot md5 digest {} does not match expected {}", digest, expectedDigest);
// rename the temp snapshot file to .corrupt
FileUtils.renameFileToCorrupt(tmpSnapshotFile);
throw new CorruptedFileException(tmpSnapshotFile, "MD5 mismatch for snapshot-" + lastIncludedIndex + " installation");
} else {
MD5FileUtil.saveMD5File(tmpSnapshotFile, digest);
}
}
}
if (snapshotChunkRequest.getDone()) {
LOG.info("Install snapshot is done, renaming tnp dir:{} to:{}", tmpDir, dir.getStateMachineDir());
dir.getStateMachineDir().delete();
tmpDir.renameTo(dir.getStateMachineDir());
}
}
use of org.apache.ratis.statemachine.SnapshotInfo in project incubator-ratis by apache.
the class InstallSnapshotNotificationTests method testAddNewFollowers.
private void testAddNewFollowers(CLUSTER cluster) throws Exception {
leaderSnapshotInfoRef.set(null);
final List<LogSegmentPath> logs;
int i = 0;
try {
RaftTestUtil.waitForLeader(cluster);
final RaftPeerId leaderId = cluster.getLeader().getId();
try (final RaftClient client = cluster.createClient(leaderId)) {
for (; i < SNAPSHOT_TRIGGER_THRESHOLD * 2 - 1; i++) {
RaftClientReply reply = client.io().send(new RaftTestUtil.SimpleMessage("m" + i));
Assert.assertTrue(reply.isSuccess());
}
}
// wait for the snapshot to be done
final RaftServer.Division leader = cluster.getLeader();
final long nextIndex = leader.getRaftLog().getNextIndex();
LOG.info("nextIndex = {}", nextIndex);
final List<File> snapshotFiles = RaftSnapshotBaseTest.getSnapshotFiles(cluster, nextIndex - SNAPSHOT_TRIGGER_THRESHOLD, nextIndex);
JavaUtils.attemptRepeatedly(() -> {
Assert.assertTrue(snapshotFiles.stream().anyMatch(RaftSnapshotBaseTest::exists));
return null;
}, 10, ONE_SECOND, "snapshotFile.exist", LOG);
logs = LogSegmentPath.getLogSegmentPaths(leader.getRaftStorage());
} finally {
cluster.shutdown();
}
// delete the log segments from the leader
LOG.info("Delete logs {}", logs);
for (LogSegmentPath path : logs) {
// the log may be already puged
FileUtils.deleteFully(path.getPath());
}
// restart the peer
LOG.info("Restarting the cluster");
cluster.restart(false);
try {
RaftSnapshotBaseTest.assertLeaderContent(cluster);
// generate some more traffic
try (final RaftClient client = cluster.createClient(cluster.getLeader().getId())) {
Assert.assertTrue(client.io().send(new RaftTestUtil.SimpleMessage("m" + i)).isSuccess());
}
final SnapshotInfo leaderSnapshotInfo = cluster.getLeader().getStateMachine().getLatestSnapshot();
final boolean set = leaderSnapshotInfoRef.compareAndSet(null, leaderSnapshotInfo);
Assert.assertTrue(set);
// add two more peers
final MiniRaftCluster.PeerChanges change = cluster.addNewPeers(2, true, true);
// trigger setConfiguration
cluster.setConfiguration(change.allPeersInNewConf);
RaftServerTestUtil.waitAndCheckNewConf(cluster, change.allPeersInNewConf, 0, null);
// leader snapshot.
for (RaftServer.Division follower : cluster.getFollowers()) {
Assert.assertEquals(leaderSnapshotInfo.getIndex(), RaftServerTestUtil.getLatestInstalledSnapshotIndex(follower));
}
// restart the peer and check if it can correctly handle conf change
cluster.restartServer(cluster.getLeader().getId(), false);
RaftSnapshotBaseTest.assertLeaderContent(cluster);
} finally {
cluster.shutdown();
}
}
use of org.apache.ratis.statemachine.SnapshotInfo in project incubator-ratis by apache.
the class InstallSnapshotNotificationTests method testInstallSnapshotNotificationCount.
private void testInstallSnapshotNotificationCount(CLUSTER cluster) throws Exception {
leaderSnapshotInfoRef.set(null);
numSnapshotRequests.set(0);
int i = 0;
try {
RaftTestUtil.waitForLeader(cluster);
final RaftPeerId leaderId = cluster.getLeader().getId();
// Let a few heartbeats pass.
ONE_SECOND.sleep();
Assert.assertEquals(0, numSnapshotRequests.get());
// Generate data.
try (final RaftClient client = cluster.createClient(leaderId)) {
for (; i < 10; i++) {
RaftClientReply reply = client.io().send(new RaftTestUtil.SimpleMessage("m" + i));
Assert.assertTrue(reply.isSuccess());
}
}
// Wait until index has been updated
RaftTestUtil.waitFor(() -> cluster.getLeader().getStateMachine().getLastAppliedTermIndex().getIndex() == 20, 300, 15000);
// Take snapshot and check result.
long snapshotIndex = cluster.getLeader().getStateMachine().takeSnapshot();
Assert.assertEquals(20, snapshotIndex);
final SnapshotInfo leaderSnapshotInfo = cluster.getLeader().getStateMachine().getLatestSnapshot();
Assert.assertEquals(20, leaderSnapshotInfo.getIndex());
final boolean set = leaderSnapshotInfoRef.compareAndSet(null, leaderSnapshotInfo);
Assert.assertTrue(set);
// Wait for the snapshot to be done.
final RaftServer.Division leader = cluster.getLeader();
final long nextIndex = leader.getRaftLog().getNextIndex();
Assert.assertEquals(21, nextIndex);
// End index is exclusive.
final List<File> snapshotFiles = RaftSnapshotBaseTest.getSnapshotFiles(cluster, 0, nextIndex);
JavaUtils.attemptRepeatedly(() -> {
Assert.assertTrue(snapshotFiles.stream().anyMatch(RaftSnapshotBaseTest::exists));
return null;
}, 10, ONE_SECOND, "snapshotFile.exist", LOG);
// Clear all log files and reset cached log start index.
long snapshotInstallIndex = leader.getRaftLog().onSnapshotInstalled(leader.getRaftLog().getLastCommittedIndex()).get();
Assert.assertEquals(20, snapshotInstallIndex);
// Check that logs are gone.
Assert.assertEquals(0, LogSegmentPath.getLogSegmentPaths(leader.getRaftStorage()).size());
Assert.assertEquals(RaftLog.INVALID_LOG_INDEX, leader.getRaftLog().getStartIndex());
// Allow some heartbeats to go through, then make sure none of them had
// snapshot requests.
ONE_SECOND.sleep();
Assert.assertEquals(0, numSnapshotRequests.get());
// Make sure leader and followers are still up to date.
for (RaftServer.Division follower : cluster.getFollowers()) {
Assert.assertEquals(leader.getRaftLog().getNextIndex(), follower.getRaftLog().getNextIndex());
}
// Add two more peers who will need snapshots from the leader.
final MiniRaftCluster.PeerChanges change = cluster.addNewPeers(2, true, true);
// trigger setConfiguration
cluster.setConfiguration(change.allPeersInNewConf);
RaftServerTestUtil.waitAndCheckNewConf(cluster, change.allPeersInNewConf, 0, null);
// Generate more data.
try (final RaftClient client = cluster.createClient(leader.getId())) {
Assert.assertTrue(client.io().send(new RaftTestUtil.SimpleMessage("m" + i)).isSuccess());
}
// Make sure leader and followers are still up to date.
for (RaftServer.Division follower : cluster.getFollowers()) {
// Give follower slightly time to catch up
RaftTestUtil.waitFor(() -> leader.getRaftLog().getNextIndex() == follower.getRaftLog().getNextIndex(), 300, 15000);
}
// Make sure each new peer got one snapshot notification.
Assert.assertEquals(2, numSnapshotRequests.get());
} finally {
cluster.shutdown();
}
}
use of org.apache.ratis.statemachine.SnapshotInfo in project alluxio by Alluxio.
the class SnapshotReplicationManager method handleRequest.
/**
* Handles snapshot requests.
*
* @param queryRequest the query request
* @return the response message, or null if the request is not handled
* @throws IOException if any error occurred while handling the request
*/
public Message handleRequest(JournalQueryRequest queryRequest) throws IOException {
if (queryRequest.hasSnapshotInfoRequest()) {
SnapshotInfo latestSnapshot = mStorage.getLatestSnapshot();
if (latestSnapshot == null) {
LOG.debug("No snapshot to send");
return toMessage(GetSnapshotInfoResponse.getDefaultInstance());
}
JournalQueryResponse response = JournalQueryResponse.newBuilder().setSnapshotInfoResponse(GetSnapshotInfoResponse.newBuilder().setLatest(toSnapshotMetadata(latestSnapshot.getTermIndex()))).build();
LOG.debug("Sent snapshot info response {}", response);
return toMessage(response);
}
if (queryRequest.hasSnapshotRequest()) {
LOG.debug("Start sending snapshot to leader");
sendSnapshotToLeader();
return Message.EMPTY;
}
return null;
}
Aggregations