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.HConstants.BUCKET_CACHE_IOENGINE_KEY;
021import static org.apache.hadoop.hbase.HConstants.BUCKET_CACHE_SIZE_KEY;
022import static org.apache.hadoop.hbase.io.ByteBuffAllocator.BUFFER_SIZE_KEY;
023import static org.apache.hadoop.hbase.io.ByteBuffAllocator.MAX_BUFFER_COUNT_KEY;
024import static org.apache.hadoop.hbase.io.ByteBuffAllocator.MIN_ALLOCATE_SIZE_KEY;
025import static org.apache.hadoop.hbase.io.hfile.CacheConfig.EVICT_BLOCKS_ON_CLOSE_KEY;
026import static org.junit.Assert.assertEquals;
027import static org.junit.Assert.assertFalse;
028import static org.junit.Assert.assertNull;
029import static org.junit.Assert.assertTrue;
030import static org.junit.Assert.fail;
031
032import java.io.DataInput;
033import java.io.DataOutput;
034import java.io.IOException;
035import java.nio.ByteBuffer;
036import java.util.ArrayList;
037import java.util.Arrays;
038import java.util.List;
039import java.util.Objects;
040import java.util.Random;
041import org.apache.hadoop.conf.Configuration;
042import org.apache.hadoop.fs.FSDataInputStream;
043import org.apache.hadoop.fs.FSDataOutputStream;
044import org.apache.hadoop.fs.FileStatus;
045import org.apache.hadoop.fs.FileSystem;
046import org.apache.hadoop.fs.Path;
047import org.apache.hadoop.hbase.ArrayBackedTag;
048import org.apache.hadoop.hbase.ByteBufferKeyValue;
049import org.apache.hadoop.hbase.Cell;
050import org.apache.hadoop.hbase.CellBuilder;
051import org.apache.hadoop.hbase.CellBuilderFactory;
052import org.apache.hadoop.hbase.CellBuilderType;
053import org.apache.hadoop.hbase.CellComparatorImpl;
054import org.apache.hadoop.hbase.CellUtil;
055import org.apache.hadoop.hbase.HBaseClassTestRule;
056import org.apache.hadoop.hbase.HBaseCommonTestingUtility;
057import org.apache.hadoop.hbase.HBaseConfiguration;
058import org.apache.hadoop.hbase.HBaseTestingUtility;
059import org.apache.hadoop.hbase.HConstants;
060import org.apache.hadoop.hbase.KeyValue;
061import org.apache.hadoop.hbase.KeyValue.Type;
062import org.apache.hadoop.hbase.KeyValueUtil;
063import org.apache.hadoop.hbase.PrivateCellUtil;
064import org.apache.hadoop.hbase.Tag;
065import org.apache.hadoop.hbase.io.ByteBuffAllocator;
066import org.apache.hadoop.hbase.io.compress.Compression;
067import org.apache.hadoop.hbase.io.encoding.DataBlockEncoder;
068import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
069import org.apache.hadoop.hbase.io.hfile.HFile.Reader;
070import org.apache.hadoop.hbase.io.hfile.HFile.Writer;
071import org.apache.hadoop.hbase.io.hfile.ReaderContext.ReaderType;
072import org.apache.hadoop.hbase.nio.ByteBuff;
073import org.apache.hadoop.hbase.regionserver.StoreFileWriter;
074import org.apache.hadoop.hbase.testclassification.IOTests;
075import org.apache.hadoop.hbase.testclassification.SmallTests;
076import org.apache.hadoop.hbase.util.ByteBufferUtils;
077import org.apache.hadoop.hbase.util.Bytes;
078import org.apache.hadoop.io.Writable;
079import org.junit.Assert;
080import org.junit.BeforeClass;
081import org.junit.ClassRule;
082import org.junit.Rule;
083import org.junit.Test;
084import org.junit.experimental.categories.Category;
085import org.junit.rules.TestName;
086import org.mockito.Mockito;
087import org.slf4j.Logger;
088import org.slf4j.LoggerFactory;
089
090/**
091 * test hfile features.
092 */
093@Category({IOTests.class, SmallTests.class})
094public class TestHFile  {
095
096  @ClassRule
097  public static final HBaseClassTestRule CLASS_RULE =
098      HBaseClassTestRule.forClass(TestHFile.class);
099
100  @Rule public TestName testName = new TestName();
101
102  private static final Logger LOG = LoggerFactory.getLogger(TestHFile.class);
103  private static final int NUM_VALID_KEY_TYPES = KeyValue.Type.values().length - 2;
104  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
105  private static String ROOT_DIR =
106    TEST_UTIL.getDataTestDir("TestHFile").toString();
107  private final int minBlockSize = 512;
108  private static String localFormatter = "%010d";
109  private static CacheConfig cacheConf;
110  private static Configuration conf ;
111  private static FileSystem fs;
112
113  @BeforeClass
114  public static void setUp() throws Exception {
115    conf = TEST_UTIL.getConfiguration();
116    cacheConf = new CacheConfig(conf);
117    fs = TEST_UTIL.getTestFileSystem();
118  }
119
120  public static Reader createReaderFromStream(ReaderContext context, CacheConfig cacheConf,
121      Configuration conf) throws IOException {
122    HFileInfo fileInfo = new HFileInfo(context, conf);
123    Reader preadReader = HFile.createReader(context, fileInfo, cacheConf, conf);
124    fileInfo.initMetaAndIndex(preadReader);
125    preadReader.close();
126    context = new ReaderContextBuilder()
127        .withFileSystemAndPath(context.getFileSystem(), context.getFilePath())
128        .withReaderType(ReaderType.STREAM)
129        .build();
130    Reader streamReader = HFile.createReader(context, fileInfo, cacheConf,  conf);
131    return streamReader;
132  }
133
134  private ByteBuffAllocator initAllocator(boolean reservoirEnabled, int bufSize, int bufCount,
135      int minAllocSize) {
136    Configuration that = HBaseConfiguration.create(conf);
137    that.setInt(BUFFER_SIZE_KEY, bufSize);
138    that.setInt(MAX_BUFFER_COUNT_KEY, bufCount);
139    // All ByteBuffers will be allocated from the buffers.
140    that.setInt(MIN_ALLOCATE_SIZE_KEY, minAllocSize);
141    return ByteBuffAllocator.create(that, reservoirEnabled);
142  }
143
144  private void fillByteBuffAllocator(ByteBuffAllocator alloc, int bufCount) {
145    // Fill the allocator with bufCount ByteBuffer
146    List<ByteBuff> buffs = new ArrayList<>();
147    for (int i = 0; i < bufCount; i++) {
148      buffs.add(alloc.allocateOneBuffer());
149      Assert.assertEquals(alloc.getFreeBufferCount(), 0);
150    }
151    buffs.forEach(ByteBuff::release);
152    Assert.assertEquals(alloc.getFreeBufferCount(), bufCount);
153  }
154
155  @Test
156  public void testReaderWithoutBlockCache() throws Exception {
157    int bufCount = 32;
158    // AllByteBuffers will be allocated from the buffers.
159    ByteBuffAllocator alloc = initAllocator(true, 64 * 1024, bufCount, 0);
160    fillByteBuffAllocator(alloc, bufCount);
161    // start write to store file.
162    Path path = writeStoreFile();
163    try {
164      readStoreFile(path, conf, alloc);
165    } catch (Exception e) {
166      // fail test
167      assertTrue(false);
168    }
169    Assert.assertEquals(bufCount, alloc.getFreeBufferCount());
170    alloc.clean();
171  }
172
173  /**
174   * Test case for HBASE-22127 in LruBlockCache.
175   */
176  @Test
177  public void testReaderWithLRUBlockCache() throws Exception {
178    int bufCount = 1024, blockSize = 64 * 1024;
179    ByteBuffAllocator alloc = initAllocator(true, bufCount, blockSize, 0);
180    fillByteBuffAllocator(alloc, bufCount);
181    Path storeFilePath = writeStoreFile();
182    // Open the file reader with LRUBlockCache
183    BlockCache lru = new LruBlockCache(1024 * 1024 * 32, blockSize, true, conf);
184    CacheConfig cacheConfig = new CacheConfig(conf, null, lru, alloc);
185    HFile.Reader reader = HFile.createReader(fs, storeFilePath, cacheConfig, true, conf);
186    long offset = 0;
187    while (offset < reader.getTrailer().getLoadOnOpenDataOffset()) {
188      BlockCacheKey key = new BlockCacheKey(storeFilePath.getName(), offset);
189      HFileBlock block = reader.readBlock(offset, -1, true, true, false, true, null, null);
190      offset += block.getOnDiskSizeWithHeader();
191      // Ensure the block is an heap one.
192      Cacheable cachedBlock = lru.getBlock(key, false, false, true);
193      Assert.assertNotNull(cachedBlock);
194      Assert.assertTrue(cachedBlock instanceof HFileBlock);
195      Assert.assertFalse(((HFileBlock) cachedBlock).isSharedMem());
196      // Should never allocate off-heap block from allocator because ensure that it's LRU.
197      Assert.assertEquals(bufCount, alloc.getFreeBufferCount());
198      block.release(); // return back the ByteBuffer back to allocator.
199    }
200    reader.close();
201    Assert.assertEquals(bufCount, alloc.getFreeBufferCount());
202    alloc.clean();
203    lru.shutdown();
204  }
205
206  private BlockCache initCombinedBlockCache() {
207    Configuration that = HBaseConfiguration.create(conf);
208    that.setFloat(BUCKET_CACHE_SIZE_KEY, 32); // 32MB for bucket cache.
209    that.set(BUCKET_CACHE_IOENGINE_KEY, "offheap");
210    BlockCache bc = BlockCacheFactory.createBlockCache(that);
211    Assert.assertNotNull(bc);
212    Assert.assertTrue(bc instanceof CombinedBlockCache);
213    return bc;
214  }
215
216  /**
217   * Test case for HBASE-22127 in CombinedBlockCache
218   */
219  @Test
220  public void testReaderWithCombinedBlockCache() throws Exception {
221    int bufCount = 1024, blockSize = 64 * 1024;
222    ByteBuffAllocator alloc = initAllocator(true, bufCount, blockSize, 0);
223    fillByteBuffAllocator(alloc, bufCount);
224    Path storeFilePath = writeStoreFile();
225    // Open the file reader with CombinedBlockCache
226    BlockCache combined = initCombinedBlockCache();
227    conf.setBoolean(EVICT_BLOCKS_ON_CLOSE_KEY, true);
228    CacheConfig cacheConfig = new CacheConfig(conf, null, combined, alloc);
229    HFile.Reader reader = HFile.createReader(fs, storeFilePath, cacheConfig, true, conf);
230    long offset = 0;
231    while (offset < reader.getTrailer().getLoadOnOpenDataOffset()) {
232      BlockCacheKey key = new BlockCacheKey(storeFilePath.getName(), offset);
233      HFileBlock block = reader.readBlock(offset, -1, true, true, false, true, null, null);
234      offset += block.getOnDiskSizeWithHeader();
235      // Read the cached block.
236      Cacheable cachedBlock = combined.getBlock(key, false, false, true);
237      try {
238        Assert.assertNotNull(cachedBlock);
239        Assert.assertTrue(cachedBlock instanceof HFileBlock);
240        HFileBlock hfb = (HFileBlock) cachedBlock;
241        // Data block will be cached in BucketCache, so it should be an off-heap block.
242        if (hfb.getBlockType().isData()) {
243          Assert.assertTrue(hfb.isSharedMem());
244        } else {
245          // Non-data block will be cached in LRUBlockCache, so it must be an on-heap block.
246          Assert.assertFalse(hfb.isSharedMem());
247        }
248      } finally {
249        cachedBlock.release();
250      }
251      block.release(); // return back the ByteBuffer back to allocator.
252    }
253    reader.close();
254    combined.shutdown();
255    Assert.assertEquals(bufCount, alloc.getFreeBufferCount());
256    alloc.clean();
257  }
258
259  private void readStoreFile(Path storeFilePath, Configuration conf, ByteBuffAllocator alloc)
260      throws Exception {
261    // Open the file reader with block cache disabled.
262    CacheConfig cache = new CacheConfig(conf, null, null, alloc);
263    HFile.Reader reader = HFile.createReader(fs, storeFilePath, cache, true, conf);
264    long offset = 0;
265    while (offset < reader.getTrailer().getLoadOnOpenDataOffset()) {
266      HFileBlock block = reader.readBlock(offset, -1, false, true, false, true, null, null);
267      offset += block.getOnDiskSizeWithHeader();
268      block.release(); // return back the ByteBuffer back to allocator.
269    }
270    reader.close();
271  }
272
273  private Path writeStoreFile() throws IOException {
274    Path storeFileParentDir = new Path(TEST_UTIL.getDataTestDir(), "TestHFile");
275    HFileContext meta = new HFileContextBuilder().withBlockSize(64 * 1024).build();
276    StoreFileWriter sfw =
277        new StoreFileWriter.Builder(conf, fs).withOutputDir(storeFileParentDir)
278          .withFileContext(meta).build();
279
280    final int rowLen = 32;
281    Random RNG = new Random();
282    for (int i = 0; i < 1000; ++i) {
283      byte[] k = RandomKeyValueUtil.randomOrderedKey(RNG, i);
284      byte[] v = RandomKeyValueUtil.randomValue(RNG);
285      int cfLen = RNG.nextInt(k.length - rowLen + 1);
286      KeyValue kv =
287          new KeyValue(k, 0, rowLen, k, rowLen, cfLen, k, rowLen + cfLen,
288              k.length - rowLen - cfLen, RNG.nextLong(), generateKeyType(RNG), v, 0, v.length);
289      sfw.append(kv);
290    }
291
292    sfw.close();
293    return sfw.getPath();
294  }
295
296  public static KeyValue.Type generateKeyType(Random rand) {
297    if (rand.nextBoolean()) {
298      // Let's make half of KVs puts.
299      return KeyValue.Type.Put;
300    } else {
301      KeyValue.Type keyType = KeyValue.Type.values()[1 + rand.nextInt(NUM_VALID_KEY_TYPES)];
302      if (keyType == KeyValue.Type.Minimum || keyType == KeyValue.Type.Maximum) {
303        throw new RuntimeException("Generated an invalid key type: " + keyType + ". "
304            + "Probably the layout of KeyValue.Type has changed.");
305      }
306      return keyType;
307    }
308  }
309
310  /**
311   * Test empty HFile.
312   * Test all features work reasonably when hfile is empty of entries.
313   * @throws IOException
314   */
315  @Test
316  public void testEmptyHFile() throws IOException {
317    Path f = new Path(ROOT_DIR, testName.getMethodName());
318    HFileContext context = new HFileContextBuilder().withIncludesTags(false).build();
319    Writer w =
320        HFile.getWriterFactory(conf, cacheConf).withPath(fs, f).withFileContext(context).create();
321    w.close();
322    Reader r = HFile.createReader(fs, f, cacheConf, true, conf);
323    assertFalse(r.getFirstKey().isPresent());
324    assertFalse(r.getLastKey().isPresent());
325  }
326
327  /**
328   * Create 0-length hfile and show that it fails
329   */
330  @Test
331  public void testCorrupt0LengthHFile() throws IOException {
332    Path f = new Path(ROOT_DIR, testName.getMethodName());
333    FSDataOutputStream fsos = fs.create(f);
334    fsos.close();
335
336    try {
337      Reader r = HFile.createReader(fs, f, cacheConf, true, conf);
338    } catch (CorruptHFileException | IllegalArgumentException che) {
339      // Expected failure
340      return;
341    }
342    fail("Should have thrown exception");
343  }
344
345  @Test
346  public void testCorruptOutOfOrderHFileWrite() throws IOException {
347    Path path = new Path(ROOT_DIR, testName.getMethodName());
348    FSDataOutputStream mockedOutputStream = Mockito.mock(FSDataOutputStream.class);
349    String columnFamily = "MyColumnFamily";
350    String tableName = "MyTableName";
351    HFileContext fileContext = new HFileContextBuilder()
352        .withHFileName(testName.getMethodName() + "HFile")
353        .withBlockSize(minBlockSize)
354        .withColumnFamily(Bytes.toBytes(columnFamily))
355        .withTableName(Bytes.toBytes(tableName))
356        .withHBaseCheckSum(false)
357        .withCompression(Compression.Algorithm.NONE)
358        .withCompressTags(false)
359        .build();
360    HFileWriterImpl writer = new HFileWriterImpl(conf, cacheConf, path, mockedOutputStream,
361      fileContext);
362    CellBuilder cellBuilder = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY);
363    byte[] row = Bytes.toBytes("foo");
364    byte[] qualifier = Bytes.toBytes("qualifier");
365    byte[] cf = Bytes.toBytes(columnFamily);
366    byte[] val = Bytes.toBytes("fooVal");
367    long firstTS = 100L;
368    long secondTS = 101L;
369    Cell firstCell = cellBuilder.setRow(row).setValue(val).setTimestamp(firstTS)
370        .setQualifier(qualifier).setFamily(cf).setType(Cell.Type.Put).build();
371    Cell secondCell= cellBuilder.setRow(row).setValue(val).setTimestamp(secondTS)
372        .setQualifier(qualifier).setFamily(cf).setType(Cell.Type.Put).build();
373    //second Cell will sort "higher" than the first because later timestamps should come first
374    writer.append(firstCell);
375    try {
376      writer.append(secondCell);
377    } catch(IOException ie){
378      String message = ie.getMessage();
379      Assert.assertTrue(message.contains("not lexically larger"));
380      Assert.assertTrue(message.contains(tableName));
381      Assert.assertTrue(message.contains(columnFamily));
382      return;
383    }
384    Assert.fail("Exception wasn't thrown even though Cells were appended in the wrong order!");
385  }
386
387  public static void truncateFile(FileSystem fs, Path src, Path dst) throws IOException {
388    FileStatus fst = fs.getFileStatus(src);
389    long len = fst.getLen();
390    len = len / 2 ;
391
392    // create a truncated hfile
393    FSDataOutputStream fdos = fs.create(dst);
394    byte[] buf = new byte[(int)len];
395    FSDataInputStream fdis = fs.open(src);
396    fdis.read(buf);
397    fdos.write(buf);
398    fdis.close();
399    fdos.close();
400  }
401
402  /**
403   * Create a truncated hfile and verify that exception thrown.
404   */
405  @Test
406  public void testCorruptTruncatedHFile() throws IOException {
407    Path f = new Path(ROOT_DIR, testName.getMethodName());
408    HFileContext  context = new HFileContextBuilder().build();
409    Writer w = HFile.getWriterFactory(conf, cacheConf).withPath(this.fs, f)
410        .withFileContext(context).create();
411    writeSomeRecords(w, 0, 100, false);
412    w.close();
413
414    Path trunc = new Path(f.getParent(), "trucated");
415    truncateFile(fs, w.getPath(), trunc);
416
417    try {
418      HFile.createReader(fs, trunc, cacheConf, true, conf);
419    } catch (CorruptHFileException | IllegalArgumentException che) {
420      // Expected failure
421      return;
422    }
423    fail("Should have thrown exception");
424  }
425
426  // write some records into the hfile
427  // write them twice
428  private int writeSomeRecords(Writer writer, int start, int n, boolean useTags)
429      throws IOException {
430    String value = "value";
431    KeyValue kv;
432    for (int i = start; i < (start + n); i++) {
433      String key = String.format(localFormatter, Integer.valueOf(i));
434      if (useTags) {
435        Tag t = new ArrayBackedTag((byte) 1, "myTag1");
436        Tag[] tags = new Tag[1];
437        tags[0] = t;
438        kv = new KeyValue(Bytes.toBytes(key), Bytes.toBytes("family"), Bytes.toBytes("qual"),
439            HConstants.LATEST_TIMESTAMP, Bytes.toBytes(value + key), tags);
440        writer.append(kv);
441      } else {
442        kv = new KeyValue(Bytes.toBytes(key), Bytes.toBytes("family"), Bytes.toBytes("qual"),
443            Bytes.toBytes(value + key));
444        writer.append(kv);
445      }
446    }
447    return (start + n);
448  }
449
450  private void readAllRecords(HFileScanner scanner) throws IOException {
451    readAndCheckbytes(scanner, 0, 100);
452  }
453
454  // read the records and check
455  private int readAndCheckbytes(HFileScanner scanner, int start, int n)
456      throws IOException {
457    String value = "value";
458    int i = start;
459    for (; i < (start + n); i++) {
460      ByteBuffer key = ByteBuffer.wrap(((KeyValue)scanner.getKey()).getKey());
461      ByteBuffer val = scanner.getValue();
462      String keyStr = String.format(localFormatter, Integer.valueOf(i));
463      String valStr = value + keyStr;
464      KeyValue kv = new KeyValue(Bytes.toBytes(keyStr), Bytes.toBytes("family"),
465          Bytes.toBytes("qual"), Bytes.toBytes(valStr));
466      byte[] keyBytes = new KeyValue.KeyOnlyKeyValue(Bytes.toBytes(key), 0,
467          Bytes.toBytes(key).length).getKey();
468      assertTrue("bytes for keys do not match " + keyStr + " " +
469        Bytes.toString(Bytes.toBytes(key)),
470          Arrays.equals(kv.getKey(), keyBytes));
471      byte [] valBytes = Bytes.toBytes(val);
472      assertTrue("bytes for vals do not match " + valStr + " " +
473        Bytes.toString(valBytes),
474        Arrays.equals(Bytes.toBytes(valStr), valBytes));
475      if (!scanner.next()) {
476        break;
477      }
478    }
479    assertEquals(i, start + n - 1);
480    return (start + n);
481  }
482
483  private byte[] getSomeKey(int rowId) {
484    KeyValue kv = new KeyValue(String.format(localFormatter, Integer.valueOf(rowId)).getBytes(),
485        Bytes.toBytes("family"), Bytes.toBytes("qual"), HConstants.LATEST_TIMESTAMP, Type.Put);
486    return kv.getKey();
487  }
488
489  private void writeRecords(Writer writer, boolean useTags) throws IOException {
490    writeSomeRecords(writer, 0, 100, useTags);
491    writer.close();
492  }
493
494  private FSDataOutputStream createFSOutput(Path name) throws IOException {
495    //if (fs.exists(name)) fs.delete(name, true);
496    FSDataOutputStream fout = fs.create(name);
497    return fout;
498  }
499
500  /**
501   * test none codecs
502   * @param useTags
503   */
504  void basicWithSomeCodec(String codec, boolean useTags) throws IOException {
505    if (useTags) {
506      conf.setInt("hfile.format.version", 3);
507    }
508    Path  ncHFile = new Path(ROOT_DIR, "basic.hfile." + codec.toString() + useTags);
509    FSDataOutputStream fout = createFSOutput(ncHFile);
510    HFileContext meta = new HFileContextBuilder()
511                        .withBlockSize(minBlockSize)
512                        .withCompression(HFileWriterImpl.compressionByName(codec))
513                        .build();
514    Writer writer = HFile.getWriterFactory(conf, cacheConf)
515        .withOutputStream(fout)
516        .withFileContext(meta)
517        .create();
518    LOG.info(Objects.toString(writer));
519    writeRecords(writer, useTags);
520    fout.close();
521    FSDataInputStream fin = fs.open(ncHFile);
522    ReaderContext context = new ReaderContextBuilder().withFileSystemAndPath(fs, ncHFile).build();
523    Reader reader = createReaderFromStream(context, cacheConf, conf);
524    System.out.println(cacheConf.toString());
525    // Load up the index.
526    // Get a scanner that caches and that does not use pread.
527    HFileScanner scanner = reader.getScanner(true, false);
528    // Align scanner at start of the file.
529    scanner.seekTo();
530    readAllRecords(scanner);
531    int seekTo = scanner.seekTo(KeyValueUtil.createKeyValueFromKey(getSomeKey(50)));
532    System.out.println(seekTo);
533    assertTrue("location lookup failed",
534        scanner.seekTo(KeyValueUtil.createKeyValueFromKey(getSomeKey(50))) == 0);
535    // read the key and see if it matches
536    ByteBuffer readKey = ByteBuffer.wrap(((KeyValue)scanner.getKey()).getKey());
537    assertTrue("seeked key does not match", Arrays.equals(getSomeKey(50),
538      Bytes.toBytes(readKey)));
539
540    scanner.seekTo(KeyValueUtil.createKeyValueFromKey(getSomeKey(0)));
541    ByteBuffer val1 = scanner.getValue();
542    scanner.seekTo(KeyValueUtil.createKeyValueFromKey(getSomeKey(0)));
543    ByteBuffer val2 = scanner.getValue();
544    assertTrue(Arrays.equals(Bytes.toBytes(val1), Bytes.toBytes(val2)));
545
546    reader.close();
547    fin.close();
548    fs.delete(ncHFile, true);
549  }
550
551  @Test
552  public void testTFileFeatures() throws IOException {
553    testHFilefeaturesInternals(false);
554    testHFilefeaturesInternals(true);
555  }
556
557  protected void testHFilefeaturesInternals(boolean useTags) throws IOException {
558    basicWithSomeCodec("none", useTags);
559    basicWithSomeCodec("gz", useTags);
560  }
561
562  private void writeNumMetablocks(Writer writer, int n) {
563    for (int i = 0; i < n; i++) {
564      writer.appendMetaBlock("HFileMeta" + i, new Writable() {
565        private int val;
566        public Writable setVal(int val) { this.val = val; return this; }
567
568        @Override
569        public void write(DataOutput out) throws IOException {
570          out.write(("something to test" + val).getBytes());
571        }
572
573        @Override
574        public void readFields(DataInput in) throws IOException { }
575      }.setVal(i));
576    }
577  }
578
579  private void someTestingWithMetaBlock(Writer writer) {
580    writeNumMetablocks(writer, 10);
581  }
582
583  private void readNumMetablocks(Reader reader, int n) throws IOException {
584    for (int i = 0; i < n; i++) {
585      ByteBuff actual = reader.getMetaBlock("HFileMeta" + i, false).getBufferWithoutHeader();
586      ByteBuffer expected =
587        ByteBuffer.wrap(("something to test" + i).getBytes());
588      assertEquals(
589          "failed to match metadata",
590          Bytes.toStringBinary(expected),
591          Bytes.toStringBinary(actual.array(), actual.arrayOffset() + actual.position(),
592              actual.capacity()));
593    }
594  }
595
596  private void someReadingWithMetaBlock(Reader reader) throws IOException {
597    readNumMetablocks(reader, 10);
598  }
599
600  private void metablocks(final String compress) throws Exception {
601    Path mFile = new Path(ROOT_DIR, "meta.hfile");
602    FSDataOutputStream fout = createFSOutput(mFile);
603    HFileContext meta = new HFileContextBuilder()
604                        .withCompression(HFileWriterImpl.compressionByName(compress))
605                        .withBlockSize(minBlockSize).build();
606    Writer writer = HFile.getWriterFactory(conf, cacheConf)
607        .withOutputStream(fout)
608        .withFileContext(meta)
609        .create();
610    someTestingWithMetaBlock(writer);
611    writer.close();
612    fout.close();
613    ReaderContext context = new ReaderContextBuilder().withFileSystemAndPath(fs, mFile).build();
614    Reader reader = createReaderFromStream(context, cacheConf, conf);
615    // No data -- this should return false.
616    assertFalse(reader.getScanner(false, false).seekTo());
617    someReadingWithMetaBlock(reader);
618    fs.delete(mFile, true);
619    reader.close();
620  }
621
622  // test meta blocks for hfiles
623  @Test
624  public void testMetaBlocks() throws Exception {
625    metablocks("none");
626    metablocks("gz");
627  }
628
629  @Test
630  public void testNullMetaBlocks() throws Exception {
631    for (Compression.Algorithm compressAlgo :
632        HBaseCommonTestingUtility.COMPRESSION_ALGORITHMS) {
633      Path mFile = new Path(ROOT_DIR, "nometa_" + compressAlgo + ".hfile");
634      FSDataOutputStream fout = createFSOutput(mFile);
635      HFileContext meta = new HFileContextBuilder().withCompression(compressAlgo)
636                          .withBlockSize(minBlockSize).build();
637      Writer writer = HFile.getWriterFactory(conf, cacheConf)
638          .withOutputStream(fout)
639          .withFileContext(meta)
640          .create();
641      KeyValue kv = new KeyValue("foo".getBytes(), "f1".getBytes(), null, "value".getBytes());
642      writer.append(kv);
643      writer.close();
644      fout.close();
645      Reader reader = HFile.createReader(fs, mFile, cacheConf, true, conf);
646      assertNull(reader.getMetaBlock("non-existant", false));
647    }
648  }
649
650  /**
651   * Make sure the ordinals for our compression algorithms do not change on us.
652   */
653  @Test
654  public void testCompressionOrdinance() {
655    assertTrue(Compression.Algorithm.LZO.ordinal() == 0);
656    assertTrue(Compression.Algorithm.GZ.ordinal() == 1);
657    assertTrue(Compression.Algorithm.NONE.ordinal() == 2);
658    assertTrue(Compression.Algorithm.SNAPPY.ordinal() == 3);
659    assertTrue(Compression.Algorithm.LZ4.ordinal() == 4);
660  }
661
662  @Test
663  public void testShortMidpointSameQual() {
664    Cell left = CellUtil.createCell(Bytes.toBytes("a"),
665        Bytes.toBytes("a"),
666        Bytes.toBytes("a"),
667        11,
668        KeyValue.Type.Maximum.getCode(),
669        HConstants.EMPTY_BYTE_ARRAY);
670    Cell right = CellUtil.createCell(Bytes.toBytes("a"),
671        Bytes.toBytes("a"),
672        Bytes.toBytes("a"),
673        9,
674        KeyValue.Type.Maximum.getCode(),
675        HConstants.EMPTY_BYTE_ARRAY);
676    Cell mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
677    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) <= 0);
678    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) == 0);
679  }
680
681  @Test
682  public void testGetShortMidpoint() {
683    Cell left = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("a"));
684    Cell right = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("a"));
685    Cell mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
686    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) <= 0);
687    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) <= 0);
688
689    left = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("a"));
690    right = CellUtil.createCell(Bytes.toBytes("b"), Bytes.toBytes("a"), Bytes.toBytes("a"));
691    mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
692    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) < 0);
693    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) <= 0);
694
695    left = CellUtil.createCell(Bytes.toBytes("g"), Bytes.toBytes("a"), Bytes.toBytes("a"));
696    right = CellUtil.createCell(Bytes.toBytes("i"), Bytes.toBytes("a"), Bytes.toBytes("a"));
697    mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
698    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) < 0);
699    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) <= 0);
700
701    left = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("a"));
702    right = CellUtil.createCell(Bytes.toBytes("bbbbbbb"), Bytes.toBytes("a"), Bytes.toBytes("a"));
703    mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
704    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) < 0);
705    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) < 0);
706    assertEquals(1, mid.getRowLength());
707
708    left = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("a"));
709    right = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("a"));
710    mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
711    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) < 0);
712    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) <= 0);
713
714    left = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("a"));
715    right = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("aaaaaaaa"), Bytes.toBytes("b"));
716    mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
717    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) < 0);
718    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) < 0);
719    assertEquals(2, mid.getFamilyLength());
720
721    left = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("a"));
722    right = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("aaaaaaaaa"));
723    mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
724    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) < 0);
725    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) < 0);
726    assertEquals(2, mid.getQualifierLength());
727
728    left = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("a"));
729    right = CellUtil.createCell(Bytes.toBytes("a"), Bytes.toBytes("a"), Bytes.toBytes("b"));
730    mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.COMPARATOR, left, right);
731    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) < 0);
732    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) <= 0);
733    assertEquals(1, mid.getQualifierLength());
734
735    // Assert that if meta comparator, it returns the right cell -- i.e. no
736    // optimization done.
737    left = CellUtil.createCell(Bytes.toBytes("g"), Bytes.toBytes("a"), Bytes.toBytes("a"));
738    right = CellUtil.createCell(Bytes.toBytes("i"), Bytes.toBytes("a"), Bytes.toBytes("a"));
739    mid = HFileWriterImpl.getMidpoint(CellComparatorImpl.META_COMPARATOR, left, right);
740    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, left, mid) < 0);
741    assertTrue(PrivateCellUtil.compareKeyIgnoresMvcc(CellComparatorImpl.COMPARATOR, mid, right) == 0);
742
743    /**
744     * See HBASE-7845
745     */
746    byte[] rowA = Bytes.toBytes("rowA");
747    byte[] rowB = Bytes.toBytes("rowB");
748
749    byte[] family = Bytes.toBytes("family");
750    byte[] qualA = Bytes.toBytes("qfA");
751    byte[] qualB = Bytes.toBytes("qfB");
752    final CellComparatorImpl keyComparator = CellComparatorImpl.COMPARATOR;
753    // verify that faked shorter rowkey could be generated
754    long ts = 5;
755    KeyValue kv1 = new KeyValue(Bytes.toBytes("the quick brown fox"), family, qualA, ts, Type.Put);
756    KeyValue kv2 = new KeyValue(Bytes.toBytes("the who test text"), family, qualA, ts, Type.Put);
757    Cell newKey = HFileWriterImpl.getMidpoint(keyComparator, kv1, kv2);
758    assertTrue(keyComparator.compare(kv1, newKey) < 0);
759    assertTrue((keyComparator.compare(kv2, newKey)) > 0);
760    byte[] expectedArray = Bytes.toBytes("the r");
761    Bytes.equals(newKey.getRowArray(), newKey.getRowOffset(), newKey.getRowLength(), expectedArray,
762        0, expectedArray.length);
763
764    // verify: same with "row + family + qualifier", return rightKey directly
765    kv1 = new KeyValue(Bytes.toBytes("ilovehbase"), family, qualA, 5, Type.Put);
766    kv2 = new KeyValue(Bytes.toBytes("ilovehbase"), family, qualA, 0, Type.Put);
767    assertTrue(keyComparator.compare(kv1, kv2) < 0);
768    newKey = HFileWriterImpl.getMidpoint(keyComparator, kv1, kv2);
769    assertTrue(keyComparator.compare(kv1, newKey) < 0);
770    assertTrue((keyComparator.compare(kv2, newKey)) == 0);
771    kv1 = new KeyValue(Bytes.toBytes("ilovehbase"), family, qualA, -5, Type.Put);
772    kv2 = new KeyValue(Bytes.toBytes("ilovehbase"), family, qualA, -10, Type.Put);
773    assertTrue(keyComparator.compare(kv1, kv2) < 0);
774    newKey = HFileWriterImpl.getMidpoint(keyComparator, kv1, kv2);
775    assertTrue(keyComparator.compare(kv1, newKey) < 0);
776    assertTrue((keyComparator.compare(kv2, newKey)) == 0);
777
778    // verify: same with row, different with qualifier
779    kv1 = new KeyValue(Bytes.toBytes("ilovehbase"), family, qualA, 5, Type.Put);
780    kv2 = new KeyValue(Bytes.toBytes("ilovehbase"), family, qualB, 5, Type.Put);
781    assertTrue(keyComparator.compare(kv1, kv2) < 0);
782    newKey = HFileWriterImpl.getMidpoint(keyComparator, kv1, kv2);
783    assertTrue(keyComparator.compare(kv1, newKey) < 0);
784    assertTrue((keyComparator.compare(kv2, newKey)) > 0);
785    assertTrue(Arrays.equals(CellUtil.cloneFamily(newKey), family));
786    assertTrue(Arrays.equals(CellUtil.cloneQualifier(newKey), qualB));
787    assertTrue(newKey.getTimestamp() == HConstants.LATEST_TIMESTAMP);
788    assertTrue(newKey.getTypeByte() == Type.Maximum.getCode());
789
790    // verify metaKeyComparator's getShortMidpointKey output
791    final CellComparatorImpl metaKeyComparator = CellComparatorImpl.META_COMPARATOR;
792    kv1 = new KeyValue(Bytes.toBytes("ilovehbase123"), family, qualA, 5, Type.Put);
793    kv2 = new KeyValue(Bytes.toBytes("ilovehbase234"), family, qualA, 0, Type.Put);
794    newKey = HFileWriterImpl.getMidpoint(metaKeyComparator, kv1, kv2);
795    assertTrue(metaKeyComparator.compare(kv1, newKey) < 0);
796    assertTrue((metaKeyComparator.compare(kv2, newKey) == 0));
797
798    // verify common fix scenario
799    kv1 = new KeyValue(Bytes.toBytes("ilovehbase"), family, qualA, ts, Type.Put);
800    kv2 = new KeyValue(Bytes.toBytes("ilovehbaseandhdfs"), family, qualA, ts, Type.Put);
801    assertTrue(keyComparator.compare(kv1, kv2) < 0);
802    newKey = HFileWriterImpl.getMidpoint(keyComparator, kv1, kv2);
803    assertTrue(keyComparator.compare(kv1, newKey) < 0);
804    assertTrue((keyComparator.compare(kv2, newKey)) > 0);
805    expectedArray = Bytes.toBytes("ilovehbasea");
806    Bytes.equals(newKey.getRowArray(), newKey.getRowOffset(), newKey.getRowLength(), expectedArray,
807        0, expectedArray.length);
808    // verify only 1 offset scenario
809    kv1 = new KeyValue(Bytes.toBytes("100abcdefg"), family, qualA, ts, Type.Put);
810    kv2 = new KeyValue(Bytes.toBytes("101abcdefg"), family, qualA, ts, Type.Put);
811    assertTrue(keyComparator.compare(kv1, kv2) < 0);
812    newKey = HFileWriterImpl.getMidpoint(keyComparator, kv1, kv2);
813    assertTrue(keyComparator.compare(kv1, newKey) < 0);
814    assertTrue((keyComparator.compare(kv2, newKey)) > 0);
815    expectedArray = Bytes.toBytes("101");
816    Bytes.equals(newKey.getRowArray(), newKey.getRowOffset(), newKey.getRowLength(), expectedArray,
817        0, expectedArray.length);
818  }
819
820  @Test
821  public void testDBEShipped() throws IOException {
822    for (DataBlockEncoding encoding : DataBlockEncoding.values()) {
823      DataBlockEncoder encoder = encoding.getEncoder();
824      if (encoder == null) {
825        continue;
826      }
827      Path f = new Path(ROOT_DIR, testName.getMethodName() + "_" + encoding);
828      HFileContext context = new HFileContextBuilder()
829          .withIncludesTags(false)
830          .withDataBlockEncoding(encoding).build();
831      HFileWriterImpl writer = (HFileWriterImpl) HFile.getWriterFactory(conf, cacheConf)
832          .withPath(fs, f).withFileContext(context).create();
833
834      KeyValue kv = new KeyValue(Bytes.toBytes("testkey1"), Bytes.toBytes("family"),
835          Bytes.toBytes("qual"), Bytes.toBytes("testvalue"));
836      KeyValue kv2 = new KeyValue(Bytes.toBytes("testkey2"), Bytes.toBytes("family"),
837        Bytes.toBytes("qual"), Bytes.toBytes("testvalue"));
838      KeyValue kv3 = new KeyValue(Bytes.toBytes("testkey3"), Bytes.toBytes("family"),
839        Bytes.toBytes("qual"), Bytes.toBytes("testvalue"));
840
841      ByteBuffer buffer = ByteBuffer.wrap(kv.getBuffer());
842      ByteBuffer buffer2 = ByteBuffer.wrap(kv2.getBuffer());
843      ByteBuffer buffer3 = ByteBuffer.wrap(kv3.getBuffer());
844
845      writer.append(new ByteBufferKeyValue(buffer, 0, buffer.remaining()));
846      writer.beforeShipped();
847
848      // pollute first cell's backing ByteBuffer
849      ByteBufferUtils.copyFromBufferToBuffer(buffer3, buffer);
850
851      // write another cell, if DBE not Shipped, test will fail
852      writer.append(new ByteBufferKeyValue(buffer2, 0, buffer2.remaining()));
853      writer.close();
854    }
855  }
856}