This is an automated email from the ASF dual-hosted git repository.
ndimiduk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/master by this push:
new 3e951120a47 HBASE-29158 Unknown checksum type code exception occurred
while reading HFileBlock (#6740)
3e951120a47 is described below
commit 3e951120a47f1b9bf44fd31dafb32a158b6209f4
Author: xiaguanglei <[email protected]>
AuthorDate: Fri Mar 21 17:01:43 2025 +0800
HBASE-29158 Unknown checksum type code exception occurred while reading
HFileBlock (#6740)
Co-authored-by: xiaguanglei <[email protected]>
Signed-off-by: Nick Dimiduk <[email protected]>
---
.../apache/hadoop/hbase/io/hfile/HFileBlock.java | 44 +++++++
.../io/hfile/TestHFileBlockHeaderCorruption.java | 140 ++++++++++++++++++++-
2 files changed, 183 insertions(+), 1 deletion(-)
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileBlock.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileBlock.java
index 91678d58b6e..3025022688a 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileBlock.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/io/hfile/HFileBlock.java
@@ -1587,6 +1587,24 @@ public class HFileBlock implements Cacheable {
}
}
+ /**
+ * Check that checksumType on {@code headerBuf} read from a block header
seems reasonable,
+ * within the known value range.
+ * @return {@code true} if the headerBuf is safe to proceed, {@code false}
otherwise.
+ */
+ private boolean checkCheckSumTypeOnHeaderBuf(ByteBuff headerBuf) {
+ if (headerBuf == null) {
+ return true;
+ }
+ byte b = headerBuf.get(HFileBlock.Header.CHECKSUM_TYPE_INDEX);
+ for (ChecksumType t : ChecksumType.values()) {
+ if (t.getCode() == b) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Check that {@code value} read from a block header seems reasonable,
within a large margin of
* error.
@@ -1742,6 +1760,21 @@ public class HFileBlock implements Cacheable {
onDiskSizeWithHeader = getOnDiskSizeWithHeader(headerBuf,
checksumSupport);
}
+ // Inspect the header's checksumType for known valid values. If we don't
find such a value,
+ // assume that the bytes read are corrupted.We will clear the cached
value and roll back to
+ // HDFS checksum
+ if (!checkCheckSumTypeOnHeaderBuf(headerBuf)) {
+ if (verifyChecksum) {
+ invalidateNextBlockHeader();
+ span.addEvent("Falling back to HDFS checksumming.",
attributesBuilder.build());
+ return null;
+ } else {
+ throw new IOException(
+ "Unknown checksum type code " +
headerBuf.get(HFileBlock.Header.CHECKSUM_TYPE_INDEX)
+ + "for file " + pathName + ", the headerBuf of HFileBlock may
corrupted.");
+ }
+ }
+
// The common case is that onDiskSizeWithHeader was produced by a read
without checksum
// validation, so give it a sanity check before trying to use it.
if (!checkOnDiskSizeWithHeader(onDiskSizeWithHeader)) {
@@ -1885,6 +1918,17 @@ public class HFileBlock implements Cacheable {
if (!fileContext.isUseHBaseChecksum()) {
return false;
}
+
+ // If the checksumType of the read block header is incorrect, it
indicates that the block is
+ // corrupted and can be directly rolled back to HDFS checksum
verification
+ if (!checkCheckSumTypeOnHeaderBuf(data)) {
+ HFile.LOG.warn(
+ "HBase checksumType verification failed for file {} at offset {}
filesize {}"
+ + " checksumType {}. Retrying read with HDFS checksums turned
on...",
+ pathName, offset, fileSize,
data.get(HFileBlock.Header.CHECKSUM_TYPE_INDEX));
+ return false;
+ }
+
return ChecksumUtil.validateChecksum(data, pathName, offset, hdrSize);
}
diff --git
a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestHFileBlockHeaderCorruption.java
b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestHFileBlockHeaderCorruption.java
index e9cd8260e9b..2f9147e8619 100644
---
a/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestHFileBlockHeaderCorruption.java
+++
b/hbase-server/src/test/java/org/apache/hadoop/hbase/io/hfile/TestHFileBlockHeaderCorruption.java
@@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -55,6 +56,7 @@ import org.apache.hadoop.hbase.nio.ByteBuff;
import org.apache.hadoop.hbase.testclassification.IOTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.ChecksumType;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
@@ -93,6 +95,141 @@ public class TestHFileBlockHeaderCorruption {
ruleChain = RuleChain.outerRule(testName).around(hFileTestRule);
}
+ @Test
+ public void testChecksumTypeCorruptionFirstBlock() throws Exception {
+ HFileBlockChannelPosition firstBlock = null;
+ try {
+ try (HFileBlockChannelPositionIterator it =
+ new HFileBlockChannelPositionIterator(hFileTestRule)) {
+ assertTrue(it.hasNext());
+ firstBlock = it.next();
+ }
+
+ Corrupter c = new Corrupter(firstBlock);
+
+ logHeader(firstBlock);
+
+ // test corrupted HFileBlock with unknown checksumType code -1
+ c.write(HFileBlock.Header.CHECKSUM_TYPE_INDEX, ByteBuffer.wrap(new
byte[] { -1 }));
+ logHeader(firstBlock);
+ try (HFileBlockChannelPositionIterator it =
+ new HFileBlockChannelPositionIterator(hFileTestRule)) {
+ CountingConsumer consumer = new CountingConsumer(it);
+ try {
+ consumer.readFully();
+ fail();
+ } catch (Exception e) {
+ assertThat(e, new
IsThrowableMatching().withInstanceOf(IOException.class)
+ .withMessage(startsWith("Unknown checksum type code")));
+ }
+ assertEquals(0, consumer.getItemsRead());
+ }
+
+ // valid checksumType code test
+ for (ChecksumType t : ChecksumType.values()) {
+ testValidChecksumTypeReadBlock(t.getCode(), c, firstBlock);
+ }
+
+ c.restore();
+ // test corrupted HFileBlock with unknown checksumType code 3
+ c.write(HFileBlock.Header.CHECKSUM_TYPE_INDEX, ByteBuffer.wrap(new
byte[] { 3 }));
+ logHeader(firstBlock);
+ try (HFileBlockChannelPositionIterator it =
+ new HFileBlockChannelPositionIterator(hFileTestRule)) {
+ CountingConsumer consumer = new CountingConsumer(it);
+ try {
+ consumer.readFully();
+ fail();
+ } catch (Exception e) {
+ assertThat(e, new
IsThrowableMatching().withInstanceOf(IOException.class)
+ .withMessage(startsWith("Unknown checksum type code")));
+ }
+ assertEquals(0, consumer.getItemsRead());
+ }
+ } finally {
+ if (firstBlock != null) {
+ firstBlock.close();
+ }
+ }
+ }
+
+ @Test
+ public void testChecksumTypeCorruptionSecondBlock() throws Exception {
+ HFileBlockChannelPosition secondBlock = null;
+ try {
+ try (HFileBlockChannelPositionIterator it =
+ new HFileBlockChannelPositionIterator(hFileTestRule)) {
+ assertTrue(it.hasNext());
+ it.next();
+ assertTrue(it.hasNext());
+ secondBlock = it.next();
+ }
+
+ Corrupter c = new Corrupter(secondBlock);
+
+ logHeader(secondBlock);
+ // test corrupted HFileBlock with unknown checksumType code -1
+ c.write(HFileBlock.Header.CHECKSUM_TYPE_INDEX, ByteBuffer.wrap(new
byte[] { -1 }));
+ logHeader(secondBlock);
+ try (HFileBlockChannelPositionIterator it =
+ new HFileBlockChannelPositionIterator(hFileTestRule)) {
+ CountingConsumer consumer = new CountingConsumer(it);
+ try {
+ consumer.readFully();
+ fail();
+ } catch (Exception e) {
+ assertThat(e, new
IsThrowableMatching().withInstanceOf(RuntimeException.class)
+ .withMessage(startsWith("Unknown checksum type code")));
+ }
+ assertEquals(1, consumer.getItemsRead());
+ }
+
+ // valid checksumType code test
+ for (ChecksumType t : ChecksumType.values()) {
+ testValidChecksumTypeReadBlock(t.getCode(), c, secondBlock);
+ }
+
+ c.restore();
+ // test corrupted HFileBlock with unknown checksumType code 3
+ c.write(HFileBlock.Header.CHECKSUM_TYPE_INDEX, ByteBuffer.wrap(new
byte[] { 3 }));
+ logHeader(secondBlock);
+ try (HFileBlockChannelPositionIterator it =
+ new HFileBlockChannelPositionIterator(hFileTestRule)) {
+ CountingConsumer consumer = new CountingConsumer(it);
+ try {
+ consumer.readFully();
+ fail();
+ } catch (Exception e) {
+ assertThat(e, new
IsThrowableMatching().withInstanceOf(RuntimeException.class)
+ .withMessage(startsWith("Unknown checksum type code")));
+ }
+ assertEquals(1, consumer.getItemsRead());
+ }
+ } finally {
+ if (secondBlock != null) {
+ secondBlock.close();
+ }
+ }
+ }
+
+ public void testValidChecksumTypeReadBlock(byte checksumTypeCode, Corrupter
c,
+ HFileBlockChannelPosition testBlock) throws IOException {
+ c.restore();
+ c.write(HFileBlock.Header.CHECKSUM_TYPE_INDEX,
+ ByteBuffer.wrap(new byte[] { checksumTypeCode }));
+ logHeader(testBlock);
+ try (
+ HFileBlockChannelPositionIterator it = new
HFileBlockChannelPositionIterator(hFileTestRule)) {
+ CountingConsumer consumer = new CountingConsumer(it);
+ try {
+ consumer.readFully();
+ } catch (Exception e) {
+ fail("test fail: valid checksumType are not executing properly");
+ }
+ assertNotEquals(0, consumer.getItemsRead());
+ }
+ }
+
@Test
public void testOnDiskSizeWithoutHeaderCorruptionFirstBlock() throws
Exception {
HFileBlockChannelPosition firstBlock = null;
@@ -331,7 +468,8 @@ public class TestHFileBlockHeaderCorruption {
try {
reader = HFile.createReader(hfs, hfsPath, CacheConfig.DISABLED, true,
conf);
HFileBlock.FSReader fsreader = reader.getUncachedBlockReader();
- iter = fsreader.blockRange(0, hfs.getFileStatus(hfsPath).getLen());
+ // The read block offset cannot out of the range:0,loadOnOpenDataOffset
+ iter = fsreader.blockRange(0,
reader.getTrailer().getLoadOnOpenDataOffset());
} catch (IOException e) {
if (reader != null) {
closeQuietly(reader::close);