diff --git a/CHANGES.txt b/CHANGES.txt index aca219e3f1..4a22af4201 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,6 +12,7 @@ * Duplicate the buffer before passing it to analyser in SASI operation (CASSANDRA-13512) * Properly evict pstmts from prepared statements cache (CASSANDRA-13641) Merged from 3.0: + * Deserialise sstable metadata in nodetool verify (CASSANDRA-13922) * Improve TRUNCATE performance (CASSANDRA-13909) * Implement short read protection on partition boundaries (CASSANDRA-13595) * Fix ISE thrown by UPI.Serializer.hasNext() for some SELECT queries (CASSANDRA-13911) diff --git a/src/java/org/apache/cassandra/db/compaction/Verifier.java b/src/java/org/apache/cassandra/db/compaction/Verifier.java index df659e439a..4c07bb62e5 100644 --- a/src/java/org/apache/cassandra/db/compaction/Verifier.java +++ b/src/java/org/apache/cassandra/db/compaction/Verifier.java @@ -26,6 +26,9 @@ 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; @@ -58,7 +61,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 +91,20 @@ public class Verifier implements Closeable long rowStart = 0; outputHandler.output(String.format("Verifying %s (%s)", sstable, FBUtilities.prettyPrintMemory(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 +203,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 = SSTableIdentityIterator.create(sstable, dataFile, key)) { } @@ -204,7 +220,6 @@ public class Verifier implements Closeable } catch (Throwable th) { - badRows++; markAndThrow(); } } @@ -235,8 +250,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 4e55a601d0..6808c47f39 100644 --- a/test/unit/org/apache/cassandra/db/VerifyTest.java +++ b/test/unit/org/apache/cassandra/db/VerifyTest.java @@ -31,6 +31,7 @@ import org.apache.cassandra.db.marshal.UUIDType; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.io.FSWriteError; +import org.apache.cassandra.io.sstable.Component; import org.apache.cassandra.io.sstable.CorruptSSTableException; import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.io.util.FileUtils; @@ -344,9 +345,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++)