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