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.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNull; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.fail; 024 025import java.io.ByteArrayInputStream; 026import java.io.ByteArrayOutputStream; 027import java.io.DataInputStream; 028import java.io.DataOutputStream; 029import java.io.IOException; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.List; 033import org.apache.hadoop.fs.FSDataInputStream; 034import org.apache.hadoop.fs.FSDataOutputStream; 035import org.apache.hadoop.fs.FileSystem; 036import org.apache.hadoop.fs.Path; 037import org.apache.hadoop.hbase.CellComparator; 038import org.apache.hadoop.hbase.CellComparatorImpl; 039import org.apache.hadoop.hbase.HBaseClassTestRule; 040import org.apache.hadoop.hbase.HBaseTestingUtility; 041import org.apache.hadoop.hbase.KeyValue; 042import org.apache.hadoop.hbase.MetaCellComparator; 043import org.apache.hadoop.hbase.testclassification.IOTests; 044import org.apache.hadoop.hbase.testclassification.SmallTests; 045import org.apache.hadoop.hbase.util.Bytes; 046import org.junit.Before; 047import org.junit.ClassRule; 048import org.junit.Rule; 049import org.junit.Test; 050import org.junit.experimental.categories.Category; 051import org.junit.rules.ExpectedException; 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 058import org.apache.hadoop.hbase.shaded.protobuf.generated.HFileProtos; 059 060@RunWith(Parameterized.class) 061@Category({ IOTests.class, SmallTests.class }) 062public class TestFixedFileTrailer { 063 064 @ClassRule 065 public static final HBaseClassTestRule CLASS_RULE = 066 HBaseClassTestRule.forClass(TestFixedFileTrailer.class); 067 068 private static final Logger LOG = LoggerFactory.getLogger(TestFixedFileTrailer.class); 069 private static final int MAX_COMPARATOR_NAME_LENGTH = 128; 070 071 /** 072 * The number of used fields by version. Indexed by version minus two. Min version that we support 073 * is V2 074 */ 075 private static final int[] NUM_FIELDS_BY_VERSION = new int[] { 14, 15 }; 076 077 private HBaseTestingUtility util = new HBaseTestingUtility(); 078 private FileSystem fs; 079 private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 080 private int version; 081 082 static { 083 assert NUM_FIELDS_BY_VERSION.length == HFile.MAX_FORMAT_VERSION - HFile.MIN_FORMAT_VERSION + 1; 084 } 085 086 public TestFixedFileTrailer(int version) { 087 this.version = version; 088 } 089 090 @Rule 091 public ExpectedException expectedEx = ExpectedException.none(); 092 093 @Parameters 094 public static Collection<Object[]> getParameters() { 095 List<Object[]> versionsToTest = new ArrayList<>(); 096 for (int v = HFile.MIN_FORMAT_VERSION; v <= HFile.MAX_FORMAT_VERSION; ++v) 097 versionsToTest.add(new Integer[] { v }); 098 return versionsToTest; 099 } 100 101 @Before 102 public void setUp() throws IOException { 103 fs = FileSystem.get(util.getConfiguration()); 104 } 105 106 @Test 107 public void testComparatorIsHBase1Compatible() { 108 FixedFileTrailer t = new FixedFileTrailer(version, HFileReaderImpl.PBUF_TRAILER_MINOR_VERSION); 109 t.setComparatorClass(CellComparatorImpl.COMPARATOR.getClass()); 110 assertEquals(CellComparatorImpl.COMPARATOR.getClass().getName(), t.getComparatorClassName()); 111 HFileProtos.FileTrailerProto pb = t.toProtobuf(); 112 assertEquals(KeyValue.COMPARATOR.getClass().getName(), pb.getComparatorClassName()); 113 t.setComparatorClass(MetaCellComparator.META_COMPARATOR.getClass()); 114 pb = t.toProtobuf(); 115 assertEquals(KeyValue.META_COMPARATOR.getClass().getName(), pb.getComparatorClassName()); 116 } 117 118 @Test 119 public void testCreateComparator() throws IOException { 120 FixedFileTrailer t = new FixedFileTrailer(version, HFileReaderImpl.PBUF_TRAILER_MINOR_VERSION); 121 try { 122 assertEquals(CellComparatorImpl.class, 123 t.createComparator(KeyValue.COMPARATOR.getLegacyKeyComparatorName()).getClass()); 124 assertEquals(CellComparatorImpl.class, 125 t.createComparator(KeyValue.COMPARATOR.getClass().getName()).getClass()); 126 assertEquals(CellComparatorImpl.class, 127 t.createComparator(CellComparator.class.getName()).getClass()); 128 assertEquals(MetaCellComparator.class, 129 t.createComparator(KeyValue.META_COMPARATOR.getLegacyKeyComparatorName()).getClass()); 130 assertEquals(MetaCellComparator.class, 131 t.createComparator(KeyValue.META_COMPARATOR.getClass().getName()).getClass()); 132 assertEquals(MetaCellComparator.class, 133 t.createComparator("org.apache.hadoop.hbase.CellComparator$MetaCellComparator").getClass()); 134 assertEquals(MetaCellComparator.class, 135 t.createComparator("org.apache.hadoop.hbase.CellComparatorImpl$MetaCellComparator") 136 .getClass()); 137 assertEquals(MetaCellComparator.class, 138 t.createComparator(MetaCellComparator.META_COMPARATOR.getClass().getName()).getClass()); 139 assertEquals(MetaCellComparator.META_COMPARATOR.getClass(), 140 t.createComparator(MetaCellComparator.META_COMPARATOR.getClass().getName()).getClass()); 141 assertEquals(CellComparatorImpl.COMPARATOR.getClass(), 142 t.createComparator(MetaCellComparator.COMPARATOR.getClass().getName()).getClass()); 143 assertNull(t.createComparator(Bytes.BYTES_RAWCOMPARATOR.getClass().getName())); 144 assertNull(t.createComparator("org.apache.hadoop.hbase.KeyValue$RawBytesComparator")); 145 } catch (IOException e) { 146 fail("Unexpected exception while testing FixedFileTrailer#createComparator(), " 147 + e.getMessage()); 148 } 149 150 // Test an invalid comparatorClassName 151 expectedEx.expect(IOException.class); 152 t.createComparator(""); 153 154 } 155 156 @Test 157 public void testTrailer() throws IOException { 158 FixedFileTrailer t = new FixedFileTrailer(version, HFileReaderImpl.PBUF_TRAILER_MINOR_VERSION); 159 t.setDataIndexCount(3); 160 t.setEntryCount(((long) Integer.MAX_VALUE) + 1); 161 162 t.setLastDataBlockOffset(291); 163 t.setNumDataIndexLevels(3); 164 t.setComparatorClass(CellComparatorImpl.COMPARATOR.getClass()); 165 t.setFirstDataBlockOffset(9081723123L); // Completely unrealistic. 166 t.setUncompressedDataIndexSize(827398717L); // Something random. 167 168 t.setLoadOnOpenOffset(128); 169 t.setMetaIndexCount(7); 170 171 t.setTotalUncompressedBytes(129731987); 172 173 { 174 DataOutputStream dos = new DataOutputStream(baos); // Limited scope. 175 t.serialize(dos); 176 dos.flush(); 177 assertEquals(dos.size(), FixedFileTrailer.getTrailerSize(version)); 178 } 179 180 byte[] bytes = baos.toByteArray(); 181 baos.reset(); 182 183 assertEquals(bytes.length, FixedFileTrailer.getTrailerSize(version)); 184 185 ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 186 187 // Finished writing, trying to read. 188 { 189 DataInputStream dis = new DataInputStream(bais); 190 FixedFileTrailer t2 = 191 new FixedFileTrailer(version, HFileReaderImpl.PBUF_TRAILER_MINOR_VERSION); 192 t2.deserialize(dis); 193 assertEquals(-1, bais.read()); // Ensure we have read everything. 194 checkLoadedTrailer(version, t, t2); 195 } 196 197 // Now check what happens if the trailer is corrupted. 198 Path trailerPath = new Path(util.getDataTestDir(), "trailer_" + version); 199 200 { 201 for (byte invalidVersion : new byte[] { HFile.MIN_FORMAT_VERSION - 1, 202 HFile.MAX_FORMAT_VERSION + 1 }) { 203 bytes[bytes.length - 1] = invalidVersion; 204 writeTrailer(trailerPath, null, bytes); 205 try { 206 readTrailer(trailerPath); 207 fail("Exception expected"); 208 } catch (IllegalArgumentException ex) { 209 // Make it easy to debug this. 210 String msg = ex.getMessage(); 211 String cleanMsg = msg.replaceAll("^(java(\\.[a-zA-Z]+)+:\\s+)?|\\s+\\(.*\\)\\s*$", ""); 212 // will be followed by " expected: ..." 213 assertEquals("Actual exception message is \"" + msg + "\".\nCleaned-up message", 214 "Invalid HFile version: " + invalidVersion, cleanMsg); 215 LOG.info("Got an expected exception: " + msg); 216 } 217 } 218 219 } 220 221 // Now write the trailer into a file and auto-detect the version. 222 writeTrailer(trailerPath, t, null); 223 224 FixedFileTrailer t4 = readTrailer(trailerPath); 225 226 checkLoadedTrailer(version, t, t4); 227 228 String trailerStr = t.toString(); 229 assertEquals( 230 "Invalid number of fields in the string representation " + "of the trailer: " + trailerStr, 231 NUM_FIELDS_BY_VERSION[version - 2], trailerStr.split(", ").length); 232 assertEquals(trailerStr, t4.toString()); 233 } 234 235 @Test 236 public void testTrailerForV2NonPBCompatibility() throws Exception { 237 if (version == 2) { 238 FixedFileTrailer t = new FixedFileTrailer(version, HFileReaderImpl.MINOR_VERSION_NO_CHECKSUM); 239 t.setDataIndexCount(3); 240 t.setEntryCount(((long) Integer.MAX_VALUE) + 1); 241 t.setLastDataBlockOffset(291); 242 t.setNumDataIndexLevels(3); 243 t.setComparatorClass(CellComparatorImpl.COMPARATOR.getClass()); 244 t.setFirstDataBlockOffset(9081723123L); // Completely unrealistic. 245 t.setUncompressedDataIndexSize(827398717L); // Something random. 246 t.setLoadOnOpenOffset(128); 247 t.setMetaIndexCount(7); 248 t.setTotalUncompressedBytes(129731987); 249 250 { 251 DataOutputStream dos = new DataOutputStream(baos); // Limited scope. 252 serializeAsWritable(dos, t); 253 dos.flush(); 254 assertEquals(FixedFileTrailer.getTrailerSize(version), dos.size()); 255 } 256 257 byte[] bytes = baos.toByteArray(); 258 baos.reset(); 259 assertEquals(bytes.length, FixedFileTrailer.getTrailerSize(version)); 260 261 ByteArrayInputStream bais = new ByteArrayInputStream(bytes); 262 { 263 DataInputStream dis = new DataInputStream(bais); 264 FixedFileTrailer t2 = 265 new FixedFileTrailer(version, HFileReaderImpl.MINOR_VERSION_NO_CHECKSUM); 266 t2.deserialize(dis); 267 assertEquals(-1, bais.read()); // Ensure we have read everything. 268 checkLoadedTrailer(version, t, t2); 269 } 270 } 271 } 272 273 // Copied from FixedFileTrailer for testing the reading part of 274 // FixedFileTrailer of non PB 275 // serialized FFTs. 276 private void serializeAsWritable(DataOutputStream output, FixedFileTrailer fft) 277 throws IOException { 278 BlockType.TRAILER.write(output); 279 output.writeLong(fft.getFileInfoOffset()); 280 output.writeLong(fft.getLoadOnOpenDataOffset()); 281 output.writeInt(fft.getDataIndexCount()); 282 output.writeLong(fft.getUncompressedDataIndexSize()); 283 output.writeInt(fft.getMetaIndexCount()); 284 output.writeLong(fft.getTotalUncompressedBytes()); 285 output.writeLong(fft.getEntryCount()); 286 output.writeInt(fft.getCompressionCodec().ordinal()); 287 output.writeInt(fft.getNumDataIndexLevels()); 288 output.writeLong(fft.getFirstDataBlockOffset()); 289 output.writeLong(fft.getLastDataBlockOffset()); 290 Bytes.writeStringFixedSize(output, fft.getComparatorClassName(), MAX_COMPARATOR_NAME_LENGTH); 291 output 292 .writeInt(FixedFileTrailer.materializeVersion(fft.getMajorVersion(), fft.getMinorVersion())); 293 } 294 295 private FixedFileTrailer readTrailer(Path trailerPath) throws IOException { 296 FSDataInputStream fsdis = fs.open(trailerPath); 297 FixedFileTrailer trailerRead = 298 FixedFileTrailer.readFromStream(fsdis, fs.getFileStatus(trailerPath).getLen()); 299 fsdis.close(); 300 return trailerRead; 301 } 302 303 private void writeTrailer(Path trailerPath, FixedFileTrailer t, byte[] useBytesInstead) 304 throws IOException { 305 assert (t == null) != (useBytesInstead == null); // Expect one non-null. 306 307 FSDataOutputStream fsdos = fs.create(trailerPath); 308 fsdos.write(135); // to make deserializer's job less trivial 309 if (useBytesInstead != null) { 310 fsdos.write(useBytesInstead); 311 } else { 312 t.serialize(fsdos); 313 } 314 fsdos.close(); 315 } 316 317 private void checkLoadedTrailer(int version, FixedFileTrailer expected, FixedFileTrailer loaded) 318 throws IOException { 319 assertEquals(version, loaded.getMajorVersion()); 320 assertEquals(expected.getDataIndexCount(), loaded.getDataIndexCount()); 321 322 assertEquals( 323 Math.min(expected.getEntryCount(), version == 1 ? Integer.MAX_VALUE : Long.MAX_VALUE), 324 loaded.getEntryCount()); 325 326 if (version == 1) { 327 assertEquals(expected.getFileInfoOffset(), loaded.getFileInfoOffset()); 328 } 329 330 if (version == 2) { 331 assertEquals(expected.getLastDataBlockOffset(), loaded.getLastDataBlockOffset()); 332 assertEquals(expected.getNumDataIndexLevels(), loaded.getNumDataIndexLevels()); 333 assertEquals(expected.createComparator().getClass().getName(), 334 loaded.createComparator().getClass().getName()); 335 assertEquals(expected.getFirstDataBlockOffset(), loaded.getFirstDataBlockOffset()); 336 assertTrue(expected.createComparator() instanceof CellComparatorImpl); 337 assertEquals(expected.getUncompressedDataIndexSize(), loaded.getUncompressedDataIndexSize()); 338 } 339 340 assertEquals(expected.getLoadOnOpenDataOffset(), loaded.getLoadOnOpenDataOffset()); 341 assertEquals(expected.getMetaIndexCount(), loaded.getMetaIndexCount()); 342 343 assertEquals(expected.getTotalUncompressedBytes(), loaded.getTotalUncompressedBytes()); 344 } 345 346}