From e400b976751110d41405bac614189152bf88f7ef Mon Sep 17 00:00:00 2001 From: Marcus Eriksson Date: Mon, 2 Oct 2017 10:11:17 +0200 Subject: [PATCH] Deserialize sstable metadata in nodetool verify Patch by marcuse; reviewed by Jason Brown for CASSANDRA-13922 --- CHANGES.txt | 4 +++ .../cassandra/db/compaction/Verifier.java | 32 +++++++++++++++---- .../org/apache/cassandra/db/VerifyTest.java | 27 +++++++++++++++- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index d6423b4c34..df05f7f2de 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +3.0.16 + * Deserialise sstable metadata in nodetool verify (CASSANDRA-13922) + + 3.0.15 * Improve TRUNCATE performance (CASSANDRA-13909) * Implement short read protection on partition boundaries (CASSANDRA-13595) diff --git a/src/java/org/apache/cassandra/db/compaction/Verifier.java b/src/java/org/apache/cassandra/db/compaction/Verifier.java index 88bc3a7a10..68088b318a 100644 --- a/src/java/org/apache/cassandra/db/compaction/Verifier.java +++ b/src/java/org/apache/cassandra/db/compaction/Verifier.java @@ -26,13 +26,15 @@ import org.apache.cassandra.io.sstable.Component; import org.apache.cassandra.io.sstable.CorruptSSTableException; import org.apache.cassandra.io.sstable.SSTableIdentityIterator; import org.apache.cassandra.io.sstable.format.SSTableReader; +import org.apache.cassandra.io.sstable.metadata.MetadataComponent; +import org.apache.cassandra.io.sstable.metadata.MetadataType; +import org.apache.cassandra.io.sstable.metadata.ValidationMetadata; import org.apache.cassandra.io.util.DataIntegrityMetadata; import org.apache.cassandra.io.util.DataIntegrityMetadata.FileDigestValidator; import org.apache.cassandra.io.util.FileUtils; import org.apache.cassandra.io.util.RandomAccessReader; import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.utils.ByteBufferUtil; -import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.OutputHandler; import org.apache.cassandra.utils.UUIDGen; @@ -58,7 +60,6 @@ public class Verifier implements Closeable private final RowIndexEntry.IndexSerializer rowIndexEntrySerializer; private int goodRows; - private int badRows; private final OutputHandler outputHandler; private FileDigestValidator validator; @@ -89,6 +90,20 @@ public class Verifier implements Closeable long rowStart = 0; outputHandler.output(String.format("Verifying %s (%s bytes)", sstable, dataFile.length())); + outputHandler.output(String.format("Deserializing sstable metadata for %s ", sstable)); + try + { + EnumSet types = EnumSet.of(MetadataType.VALIDATION, MetadataType.STATS, MetadataType.HEADER); + Map sstableMetadata = sstable.descriptor.getMetadataSerializer().deserialize(sstable.descriptor, types); + if (sstableMetadata.containsKey(MetadataType.VALIDATION) && + !((ValidationMetadata)sstableMetadata.get(MetadataType.VALIDATION)).partitioner.equals(sstable.getPartitioner().getClass().getCanonicalName())) + throw new IOException("Partitioner does not match validation metadata"); + } + catch (Throwable t) + { + outputHandler.debug(t.getMessage()); + markAndThrow(false); + } outputHandler.output(String.format("Checking computed hash of %s ", sstable)); @@ -187,7 +202,7 @@ public class Verifier implements Closeable if (key == null || dataSize > dataFile.length()) markAndThrow(); - //mimic the scrub read path + //mimic the scrub read path, intentionally unused try (UnfilteredRowIterator iterator = new SSTableIdentityIterator(sstable, dataFile, key)) { } @@ -204,7 +219,6 @@ public class Verifier implements Closeable } catch (Throwable th) { - badRows++; markAndThrow(); } } @@ -235,8 +249,14 @@ public class Verifier implements Closeable private void markAndThrow() throws IOException { - sstable.descriptor.getMetadataSerializer().mutateRepairedAt(sstable.descriptor, ActiveRepairService.UNREPAIRED_SSTABLE); - throw new CorruptSSTableException(new Exception(String.format("Invalid SSTable %s, please force repair", sstable.getFilename())), sstable.getFilename()); + markAndThrow(true); + } + + private void markAndThrow(boolean mutateRepaired) throws IOException + { + if (mutateRepaired) // if we are able to mutate repaired flag, an incremental repair should be enough + sstable.descriptor.getMetadataSerializer().mutateRepairedAt(sstable.descriptor, ActiveRepairService.UNREPAIRED_SSTABLE); + throw new CorruptSSTableException(new Exception(String.format("Invalid SSTable %s, please force %srepair", sstable.getFilename(), mutateRepaired ? "" : "a full ")), sstable.getFilename()); } public CompactionInfo.Holder getVerifyInfo() diff --git a/test/unit/org/apache/cassandra/db/VerifyTest.java b/test/unit/org/apache/cassandra/db/VerifyTest.java index 9de01c1391..fc875204b3 100644 --- a/test/unit/org/apache/cassandra/db/VerifyTest.java +++ b/test/unit/org/apache/cassandra/db/VerifyTest.java @@ -341,9 +341,34 @@ public class VerifyTest } fail("Expected a CorruptSSTableException to be thrown"); } - } + @Test(expected = CorruptSSTableException.class) + public void testVerifyBrokenSSTableMetadata() throws IOException, WriteTimeoutException + { + CompactionManager.instance.disableAutoCompaction(); + Keyspace keyspace = Keyspace.open(KEYSPACE); + ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(CORRUPT_CF2); + + fillCF(cfs, 2); + + Util.getAll(Util.cmd(cfs).build()); + + SSTableReader sstable = cfs.getLiveSSTables().iterator().next(); + + String filenameToCorrupt = sstable.descriptor.filenameFor(Component.STATS); + RandomAccessFile file = new RandomAccessFile(filenameToCorrupt, "rw"); + file.seek(0); + file.writeBytes(StringUtils.repeat('z', 2)); + file.close(); + + try (Verifier verifier = new Verifier(cfs, sstable, false)) + { + verifier.verify(false); + } + } + + protected void fillCF(ColumnFamilyStore cfs, int partitionsPerSSTable) { for (int i = 0; i < partitionsPerSSTable; i++)