Skip to content

Commit 6c02261

Browse files
sachinpkaleSachin Kale
andauthored
Validate checksum of each segment file post download from remote store (opensearch-project#10119)
--------- Signed-off-by: Sachin Kale <kalsac@amazon.com> Co-authored-by: Sachin Kale <kalsac@amazon.com>
1 parent 9c06228 commit 6c02261

3 files changed

Lines changed: 81 additions & 3 deletions

File tree

server/src/internalClusterTest/java/org/opensearch/remotestore/RemoteStoreRestoreIT.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,33 +10,39 @@
1010

1111
import org.opensearch.action.admin.cluster.remotestore.restore.RestoreRemoteStoreRequest;
1212
import org.opensearch.action.admin.cluster.remotestore.restore.RestoreRemoteStoreResponse;
13+
import org.opensearch.action.admin.indices.get.GetIndexRequest;
14+
import org.opensearch.action.admin.indices.get.GetIndexResponse;
1315
import org.opensearch.action.support.PlainActionFuture;
1416
import org.opensearch.cluster.health.ClusterHealthStatus;
17+
import org.opensearch.cluster.metadata.IndexMetadata;
1518
import org.opensearch.cluster.node.DiscoveryNode;
1619
import org.opensearch.cluster.service.ClusterService;
1720
import org.opensearch.common.settings.Settings;
1821
import org.opensearch.common.unit.TimeValue;
1922
import org.opensearch.core.common.unit.ByteSizeUnit;
2023
import org.opensearch.repositories.RepositoriesService;
2124
import org.opensearch.repositories.Repository;
25+
import org.opensearch.test.CorruptionUtils;
2226
import org.opensearch.test.InternalTestCluster;
2327
import org.opensearch.test.OpenSearchIntegTestCase;
2428

2529
import java.io.IOException;
30+
import java.nio.file.Files;
2631
import java.nio.file.Path;
2732
import java.util.HashMap;
2833
import java.util.Locale;
2934
import java.util.Map;
3035
import java.util.concurrent.ExecutionException;
3136
import java.util.concurrent.TimeUnit;
3237
import java.util.stream.Collectors;
38+
import java.util.stream.Stream;
3339

3440
import static org.opensearch.node.remotestore.RemoteStoreNodeAttribute.REMOTE_STORE_REPOSITORY_SETTINGS_ATTRIBUTE_KEY_PREFIX;
3541
import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked;
3642
import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertHitCount;
3743
import static org.hamcrest.Matchers.greaterThan;
3844

39-
@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.SUITE, numDataNodes = 0)
45+
@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.TEST, numDataNodes = 0)
4046
public class RemoteStoreRestoreIT extends BaseRemoteStoreRestoreIT {
4147

4248
/**
@@ -461,5 +467,30 @@ public void testRateLimitedRemoteDownloads() throws Exception {
461467
}
462468
}
463469

470+
public void testRestoreCorruptSegmentShouldFail() throws IOException, ExecutionException, InterruptedException {
471+
prepareCluster(1, 3, INDEX_NAME, 0, 1);
472+
indexData(randomIntBetween(3, 4), true, INDEX_NAME);
473+
474+
GetIndexResponse getIndexResponse = client().admin().indices().getIndex(new GetIndexRequest()).get();
475+
String indexUUID = getIndexResponse.getSettings().get(INDEX_NAME).get(IndexMetadata.SETTING_INDEX_UUID);
476+
477+
logger.info("--> Corrupting segment files in remote segment store");
478+
Path path = segmentRepoPath.resolve(indexUUID).resolve("0").resolve("segments").resolve("data");
479+
try (Stream<Path> dataPath = Files.list(path)) {
480+
CorruptionUtils.corruptFile(random(), dataPath.toArray(Path[]::new));
481+
}
482+
483+
logger.info("--> Stop primary");
484+
internalCluster().stopRandomNode(InternalTestCluster.nameFilter(primaryNodeName(INDEX_NAME)));
485+
486+
logger.info("--> Close and restore the index");
487+
client().admin()
488+
.cluster()
489+
.restoreRemoteStore(new RestoreRemoteStoreRequest().indices(INDEX_NAME).waitForCompletion(true), PlainActionFuture.newFuture());
490+
491+
logger.info("--> Check for index status, should be red due to corruption");
492+
ensureRed(INDEX_NAME);
493+
}
494+
464495
// TODO: Restore flow - index aliases
465496
}

server/src/main/java/org/opensearch/index/store/RemoteSegmentStoreDirectory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,10 @@ public void setWrittenByMajor(int writtenByMajor) {
290290
);
291291
}
292292
}
293+
294+
public int getWrittenByMajor() {
295+
return writtenByMajor;
296+
}
293297
}
294298

295299
/**

server/src/main/java/org/opensearch/index/store/Store.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
import java.io.UncheckedIOException;
106106
import java.nio.file.NoSuchFileException;
107107
import java.nio.file.Path;
108+
import java.text.ParseException;
108109
import java.util.ArrayList;
109110
import java.util.Collections;
110111
import java.util.HashMap;
@@ -120,6 +121,7 @@
120121
import java.util.zip.CRC32;
121122
import java.util.zip.Checksum;
122123

124+
import static java.lang.Character.MAX_RADIX;
123125
import static java.util.Collections.emptyMap;
124126
import static java.util.Collections.unmodifiableMap;
125127
import static org.opensearch.index.seqno.SequenceNumbers.LOCAL_CHECKPOINT_KEY;
@@ -975,7 +977,11 @@ public void copyFrom(Directory from, String src, String dest, IOContext context)
975977
boolean success = false;
976978
long startTime = System.currentTimeMillis();
977979
try {
978-
super.copyFrom(from, src, dest, context);
980+
if (from instanceof RemoteSegmentStoreDirectory) {
981+
copyFileAndValidateChecksum(from, src, dest, context, fileSize);
982+
} else {
983+
super.copyFrom(from, src, dest, context);
984+
}
979985
success = true;
980986
afterDownload(fileSize, startTime);
981987
} finally {
@@ -985,6 +991,43 @@ public void copyFrom(Directory from, String src, String dest, IOContext context)
985991
}
986992
}
987993

994+
private void copyFileAndValidateChecksum(Directory from, String src, String dest, IOContext context, long fileSize)
995+
throws IOException {
996+
RemoteSegmentStoreDirectory.UploadedSegmentMetadata metadata = ((RemoteSegmentStoreDirectory) from)
997+
.getSegmentsUploadedToRemoteStore()
998+
.get(dest);
999+
boolean success = false;
1000+
try (IndexInput is = from.openInput(src, context); IndexOutput os = createOutput(dest, context)) {
1001+
// Here, we don't need the exact version as LuceneVerifyingIndexOutput does not verify version
1002+
// It is just used to emit logs when the entire metadata object is provided as parameter. Also,
1003+
// we can't provide null version as StoreFileMetadata has non-null check on writtenBy field.
1004+
Version luceneMajorVersion = Version.parse(metadata.getWrittenByMajor() + ".0.0");
1005+
long checksum = Long.parseLong(metadata.getChecksum());
1006+
StoreFileMetadata storeFileMetadata = new StoreFileMetadata(
1007+
dest,
1008+
fileSize,
1009+
Long.toString(checksum, MAX_RADIX),
1010+
luceneMajorVersion
1011+
);
1012+
VerifyingIndexOutput verifyingIndexOutput = new LuceneVerifyingIndexOutput(storeFileMetadata, os);
1013+
verifyingIndexOutput.copyBytes(is, is.length());
1014+
verifyingIndexOutput.verify();
1015+
success = true;
1016+
} catch (ParseException e) {
1017+
throw new IOException("Exception while reading version info for segment file from remote store: " + dest, e);
1018+
} finally {
1019+
if (success == false) {
1020+
// If the exception is thrown after file is created, we clean up the file.
1021+
// We ignore the exception as the deletion is best-effort basis and can fail if file does not exist.
1022+
try {
1023+
deleteFile("Quietly deleting", dest);
1024+
} catch (Exception e) {
1025+
// Ignore
1026+
}
1027+
}
1028+
}
1029+
}
1030+
9881031
/**
9891032
* Updates the amount of bytes attempted for download
9901033
*/
@@ -1476,7 +1519,7 @@ public static boolean isAutogenerated(String name) {
14761519
* Produces a string representation of the given digest value.
14771520
*/
14781521
public static String digestToString(long digest) {
1479-
return Long.toString(digest, Character.MAX_RADIX);
1522+
return Long.toString(digest, MAX_RADIX);
14801523
}
14811524

14821525
/**

0 commit comments

Comments
 (0)