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