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}