001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.io.hfile; 019 020import static org.apache.hadoop.hbase.io.hfile.FixedFileTrailer.createComparator; 021import static org.junit.Assert.assertEquals; 022import static org.junit.Assert.assertNull; 023import static org.junit.Assert.assertThrows; 024import static org.junit.Assert.assertTrue; 025import static org.junit.Assert.fail; 026 027import java.io.ByteArrayInputStream; 028import java.io.ByteArrayOutputStream; 029import java.io.DataInputStream; 030import java.io.DataOutputStream; 031import java.io.IOException; 032import java.util.ArrayList; 033import java.util.Collection; 034import java.util.List; 035import org.apache.hadoop.fs.FSDataInputStream; 036import org.apache.hadoop.fs.FSDataOutputStream; 037import org.apache.hadoop.fs.FileSystem; 038import org.apache.hadoop.fs.Path; 039import org.apache.hadoop.hbase.CellComparator; 040import org.apache.hadoop.hbase.CellComparatorImpl; 041import org.apache.hadoop.hbase.HBaseClassTestRule; 042import org.apache.hadoop.hbase.HBaseTestingUtil; 043import org.apache.hadoop.hbase.InnerStoreCellComparator; 044import org.apache.hadoop.hbase.MetaCellComparator; 045import org.apache.hadoop.hbase.testclassification.IOTests; 046import org.apache.hadoop.hbase.testclassification.SmallTests; 047import org.apache.hadoop.hbase.util.Bytes; 048import org.junit.Before; 049import org.junit.ClassRule; 050import org.junit.Test; 051import org.junit.experimental.categories.Category; 052import org.junit.runner.RunWith; 053import org.junit.runners.Parameterized; 054import org.junit.runners.Parameterized.Parameters; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058@RunWith(Parameterized.class) 059@Category({ IOTests.class, SmallTests.class }) 060public class TestFixedFileTrailer { 061 062 @ClassRule 063 public static final HBaseClassTestRule CLASS_RULE = 064 HBaseClassTestRule.forClass(TestFixedFileTrailer.class); 065 066 private static final Logger LOG = LoggerFactory.getLogger(TestFixedFileTrailer.class); 067 private static final int MAX_COMPARATOR_NAME_LENGTH = 128; 068 069 /** 070 * The number of used fields by version. Indexed by version minus two. Min version that we support 071 * is V2 072 */ 073 private static final int[] NUM_FIELDS_BY_VERSION = new int[] { 14, 15 }; 074 075 private HBaseTestingUtil util = new HBaseTestingUtil(); 076 private FileSystem fs; 077 private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 078 private int version; 079 080 static { 081 assert NUM_FIELDS_BY_VERSION.length == HFile.MAX_FORMAT_VERSION - HFile.MIN_FORMAT_VERSION + 1; 082 } 083 084 public TestFixedFileTrailer(int version) { 085 this.version = version; 086 } 087 088 @Parameters 089 public static Collection<Object[]> getParameters() { 090 List<Object[]> versionsToTest = new ArrayList<>(); 091 for (int v = HFile.MIN_FORMAT_VERSION; v <= HFile.MAX_FORMAT_VERSION; ++v) 092 versionsToTest.add(new Integer[] { v }); 093 return versionsToTest; 094 } 095 096 @Before 097 public void setUp() throws IOException { 098 fs = FileSystem.get(util.getConfiguration()); 099 } 100 101 @Test 102 public void testCreateComparator() throws IOException { 103 assertEquals(InnerStoreCellComparator.class, 104 createComparator("org.apache.hadoop.hbase.KeyValue$KVComparator").getClass()); 105 assertEquals(InnerStoreCellComparator.class, 106 createComparator(CellComparator.class.getName()).getClass()); 107 108 assertEquals(MetaCellComparator.class, 109 createComparator("org.apache.hadoop.hbase.KeyValue$MetaComparator").getClass()); 110 assertEquals(MetaCellComparator.class, 111 createComparator("org.apache.hadoop.hbase.CellComparator$MetaCellComparator").getClass()); 112 assertEquals(MetaCellComparator.class, 113 createComparator("org.apache.hadoop.hbase.CellComparatorImpl$MetaCellComparator").getClass()); 114 assertEquals(MetaCellComparator.class, 115 createComparator(MetaCellComparator.META_COMPARATOR.getClass().getName()).getClass()); 116 assertEquals(MetaCellComparator.META_COMPARATOR.getClass(), 117 createComparator(MetaCellComparator.META_COMPARATOR.getClass().getName()).getClass()); 118 119 assertEquals(CellComparatorImpl.COMPARATOR.getClass(), 120 createComparator(MetaCellComparator.COMPARATOR.getClass().getName()).getClass()); 121 122 assertNull(createComparator(Bytes.BYTES_RAWCOMPARATOR.getClass().getName())); 123 assertNull(createComparator("org.apache.hadoop.hbase.KeyValue$RawBytesComparator")); 124 125 // Test an invalid comparatorClassName 126 assertThrows(IOException.class, () -> createComparator("")); 127 } 128 129 @Test 130 public void testTrailer() throws IOException { 131 FixedFileTrailer t = new FixedFileTrailer(version, HFileReaderImpl.PBUF_TRAILER_MINOR_VERSION); 132 t.setDataIndexCount(3); 133 t.setEntryCount(((long) Integer.MAX_VALUE) + 1); 134 135 t.setLastDataBlockOffset(291); 136 t.setNumDataIndexLevels(3); 137 t.setComparatorClass(InnerStoreCellComparator.INNER_STORE_COMPARATOR.getClass()); 138 t.setFirstDataBlockOffset(9081723123L); // Completely unrealistic. 139 t.setUncompressedDataIndexSize(827398717L); // Something random. 140 141 t.setLoadOnOpenOffset(128); 142 t.setMetaIndexCount(7); 143 144 t.setTotalUncompressedBytes(129731987); 145 146 { 147 DataOutputStream dos = new DataOutputStream(baos); // Limited scope. 148 t.serialize(dos); 149 dos.flush(); 150 assertEquals(dos.size(), FixedFileTrailer.getTrailerSize(version)); 151 } 152 153 byte[] bytes = baos.toByteArray(); 154 baos.reset(); 155 156 assertEquals(bytes.length, FixedFileTrailer.getTrailerSize(version)); 157 158 ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 159 160 // Finished writing, trying to read. 161 { 162 DataInputStream dis = new DataInputStream(bais); 163 FixedFileTrailer t2 = 164 new FixedFileTrailer(version, HFileReaderImpl.PBUF_TRAILER_MINOR_VERSION); 165 t2.deserialize(dis); 166 assertEquals(-1, bais.read()); // Ensure we have read everything. 167 checkLoadedTrailer(version, t, t2); 168 } 169 170 // Now check what happens if the trailer is corrupted. 171 Path trailerPath = new Path(util.getDataTestDir(), "trailer_" + version); 172 173 { 174 for (byte invalidVersion : new byte[] { HFile.MIN_FORMAT_VERSION - 1, 175 HFile.MAX_FORMAT_VERSION + 1 }) { 176 bytes[bytes.length - 1] = invalidVersion; 177 writeTrailer(trailerPath, null, bytes); 178 try { 179 readTrailer(trailerPath); 180 fail("Exception expected"); 181 } catch (IllegalArgumentException ex) { 182 // Make it easy to debug this. 183 String msg = ex.getMessage(); 184 String cleanMsg = msg.replaceAll("^(java(\\.[a-zA-Z]+)+:\\s+)?|\\s+\\(.*\\)\\s*$", ""); 185 // will be followed by " expected: ..." 186 assertEquals("Actual exception message is \"" + msg + "\".\nCleaned-up message", 187 "Invalid HFile version: " + invalidVersion, cleanMsg); 188 LOG.info("Got an expected exception: " + msg); 189 } 190 } 191 192 } 193 194 // Now write the trailer into a file and auto-detect the version. 195 writeTrailer(trailerPath, t, null); 196 197 FixedFileTrailer t4 = readTrailer(trailerPath); 198 199 checkLoadedTrailer(version, t, t4); 200 201 String trailerStr = t.toString(); 202 assertEquals( 203 "Invalid number of fields in the string representation " + "of the trailer: " + trailerStr, 204 NUM_FIELDS_BY_VERSION[version - 2], trailerStr.split(", ").length); 205 assertEquals(trailerStr, t4.toString()); 206 } 207 208 @Test 209 public void testTrailerForV2NonPBCompatibility() throws Exception { 210 if (version == 2) { 211 FixedFileTrailer t = new FixedFileTrailer(version, HFileReaderImpl.MINOR_VERSION_NO_CHECKSUM); 212 t.setDataIndexCount(3); 213 t.setEntryCount(((long) Integer.MAX_VALUE) + 1); 214 t.setLastDataBlockOffset(291); 215 t.setNumDataIndexLevels(3); 216 t.setComparatorClass(CellComparatorImpl.COMPARATOR.getClass()); 217 t.setFirstDataBlockOffset(9081723123L); // Completely unrealistic. 218 t.setUncompressedDataIndexSize(827398717L); // Something random. 219 t.setLoadOnOpenOffset(128); 220 t.setMetaIndexCount(7); 221 t.setTotalUncompressedBytes(129731987); 222 223 { 224 DataOutputStream dos = new DataOutputStream(baos); // Limited scope. 225 serializeAsWritable(dos, t); 226 dos.flush(); 227 assertEquals(FixedFileTrailer.getTrailerSize(version), dos.size()); 228 } 229 230 byte[] bytes = baos.toByteArray(); 231 baos.reset(); 232 assertEquals(bytes.length, FixedFileTrailer.getTrailerSize(version)); 233 234 ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 235 { 236 DataInputStream dis = new DataInputStream(bais); 237 FixedFileTrailer t2 = 238 new FixedFileTrailer(version, HFileReaderImpl.MINOR_VERSION_NO_CHECKSUM); 239 t2.deserialize(dis); 240 assertEquals(-1, bais.read()); // Ensure we have read everything. 241 checkLoadedTrailer(version, t, t2); 242 } 243 } 244 } 245 246 // Copied from FixedFileTrailer for testing the reading part of 247 // FixedFileTrailer of non PB 248 // serialized FFTs. 249 private void serializeAsWritable(DataOutputStream output, FixedFileTrailer fft) 250 throws IOException { 251 BlockType.TRAILER.write(output); 252 output.writeLong(fft.getFileInfoOffset()); 253 output.writeLong(fft.getLoadOnOpenDataOffset()); 254 output.writeInt(fft.getDataIndexCount()); 255 output.writeLong(fft.getUncompressedDataIndexSize()); 256 output.writeInt(fft.getMetaIndexCount()); 257 output.writeLong(fft.getTotalUncompressedBytes()); 258 output.writeLong(fft.getEntryCount()); 259 output.writeInt(fft.getCompressionCodec().ordinal()); 260 output.writeInt(fft.getNumDataIndexLevels()); 261 output.writeLong(fft.getFirstDataBlockOffset()); 262 output.writeLong(fft.getLastDataBlockOffset()); 263 Bytes.writeStringFixedSize(output, fft.getComparatorClassName(), MAX_COMPARATOR_NAME_LENGTH); 264 output 265 .writeInt(FixedFileTrailer.materializeVersion(fft.getMajorVersion(), fft.getMinorVersion())); 266 } 267 268 private FixedFileTrailer readTrailer(Path trailerPath) throws IOException { 269 FSDataInputStream fsdis = fs.open(trailerPath); 270 FixedFileTrailer trailerRead = 271 FixedFileTrailer.readFromStream(fsdis, fs.getFileStatus(trailerPath).getLen()); 272 fsdis.close(); 273 return trailerRead; 274 } 275 276 private void writeTrailer(Path trailerPath, FixedFileTrailer t, byte[] useBytesInstead) 277 throws IOException { 278 assert (t == null) != (useBytesInstead == null); // Expect one non-null. 279 280 FSDataOutputStream fsdos = fs.create(trailerPath); 281 fsdos.write(135); // to make deserializer's job less trivial 282 if (useBytesInstead != null) { 283 fsdos.write(useBytesInstead); 284 } else { 285 t.serialize(fsdos); 286 } 287 fsdos.close(); 288 } 289 290 private void checkLoadedTrailer(int version, FixedFileTrailer expected, FixedFileTrailer loaded) 291 throws IOException { 292 assertEquals(version, loaded.getMajorVersion()); 293 assertEquals(expected.getDataIndexCount(), loaded.getDataIndexCount()); 294 295 assertEquals( 296 Math.min(expected.getEntryCount(), version == 1 ? Integer.MAX_VALUE : Long.MAX_VALUE), 297 loaded.getEntryCount()); 298 299 if (version == 1) { 300 assertEquals(expected.getFileInfoOffset(), loaded.getFileInfoOffset()); 301 } 302 303 if (version == 2) { 304 assertEquals(expected.getLastDataBlockOffset(), loaded.getLastDataBlockOffset()); 305 assertEquals(expected.getNumDataIndexLevels(), loaded.getNumDataIndexLevels()); 306 assertEquals(expected.createComparator().getClass().getName(), 307 loaded.createComparator().getClass().getName()); 308 assertEquals(expected.getFirstDataBlockOffset(), loaded.getFirstDataBlockOffset()); 309 assertTrue(expected.createComparator() instanceof CellComparatorImpl); 310 assertEquals(expected.getUncompressedDataIndexSize(), loaded.getUncompressedDataIndexSize()); 311 } 312 313 assertEquals(expected.getLoadOnOpenDataOffset(), loaded.getLoadOnOpenDataOffset()); 314 assertEquals(expected.getMetaIndexCount(), loaded.getMetaIndexCount()); 315 316 assertEquals(expected.getTotalUncompressedBytes(), loaded.getTotalUncompressedBytes()); 317 } 318 319}