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