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.regionserver;
019
020import static org.junit.Assert.assertArrayEquals;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertNotNull;
024import static org.junit.Assert.assertNull;
025import static org.junit.Assert.assertTrue;
026import static org.junit.Assert.fail;
027import static org.mockito.ArgumentMatchers.any;
028import static org.mockito.Mockito.mock;
029import static org.mockito.Mockito.when;
030
031import java.io.IOException;
032import java.nio.ByteBuffer;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.Collections;
036import java.util.Comparator;
037import java.util.List;
038import java.util.Map;
039import java.util.OptionalLong;
040import java.util.TreeSet;
041import java.util.concurrent.atomic.AtomicInteger;
042import org.apache.hadoop.conf.Configuration;
043import org.apache.hadoop.fs.FileSystem;
044import org.apache.hadoop.fs.Path;
045import org.apache.hadoop.hbase.Cell;
046import org.apache.hadoop.hbase.CellUtil;
047import org.apache.hadoop.hbase.HBaseClassTestRule;
048import org.apache.hadoop.hbase.HBaseTestingUtility;
049import org.apache.hadoop.hbase.HConstants;
050import org.apache.hadoop.hbase.HRegionInfo;
051import org.apache.hadoop.hbase.KeyValue;
052import org.apache.hadoop.hbase.KeyValueUtil;
053import org.apache.hadoop.hbase.PrivateCellUtil;
054import org.apache.hadoop.hbase.TableDescriptors;
055import org.apache.hadoop.hbase.TableName;
056import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
057import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
058import org.apache.hadoop.hbase.client.RegionInfo;
059import org.apache.hadoop.hbase.client.RegionInfoBuilder;
060import org.apache.hadoop.hbase.client.Scan;
061import org.apache.hadoop.hbase.client.TableDescriptor;
062import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
063import org.apache.hadoop.hbase.io.FSDataInputStreamWrapper;
064import org.apache.hadoop.hbase.io.HFileLink;
065import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
066import org.apache.hadoop.hbase.io.hfile.BlockCache;
067import org.apache.hadoop.hbase.io.hfile.BlockCacheFactory;
068import org.apache.hadoop.hbase.io.hfile.CacheConfig;
069import org.apache.hadoop.hbase.io.hfile.CacheStats;
070import org.apache.hadoop.hbase.io.hfile.HFileContext;
071import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
072import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder;
073import org.apache.hadoop.hbase.io.hfile.HFileInfo;
074import org.apache.hadoop.hbase.io.hfile.HFileScanner;
075import org.apache.hadoop.hbase.io.hfile.ReaderContext;
076import org.apache.hadoop.hbase.io.hfile.ReaderContextBuilder;
077import org.apache.hadoop.hbase.master.MasterServices;
078import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
079import org.apache.hadoop.hbase.testclassification.MediumTests;
080import org.apache.hadoop.hbase.testclassification.RegionServerTests;
081import org.apache.hadoop.hbase.util.BloomFilterFactory;
082import org.apache.hadoop.hbase.util.Bytes;
083import org.apache.hadoop.hbase.util.ChecksumType;
084import org.apache.hadoop.hbase.util.CommonFSUtils;
085import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
086import org.junit.AfterClass;
087import org.junit.Before;
088import org.junit.ClassRule;
089import org.junit.Rule;
090import org.junit.Test;
091import org.junit.experimental.categories.Category;
092import org.junit.rules.TestName;
093import org.mockito.Mockito;
094import org.slf4j.Logger;
095import org.slf4j.LoggerFactory;
096
097import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
098import org.apache.hbase.thirdparty.com.google.common.collect.Iterables;
099import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
100
101/**
102 * Test HStoreFile
103 */
104@Category({ RegionServerTests.class, MediumTests.class })
105public class TestHStoreFile {
106
107  @ClassRule
108  public static final HBaseClassTestRule CLASS_RULE =
109    HBaseClassTestRule.forClass(TestHStoreFile.class);
110
111  private static final Logger LOG = LoggerFactory.getLogger(TestHStoreFile.class);
112  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
113  private CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration());
114  private static String ROOT_DIR = TEST_UTIL.getDataTestDir("TestStoreFile").toString();
115  private static final ChecksumType CKTYPE = ChecksumType.CRC32C;
116  private static final int CKBYTES = 512;
117  private static String TEST_FAMILY = "cf";
118  private static final char FIRST_CHAR = 'a';
119  private static final char LAST_CHAR = 'z';
120
121  @Rule
122  public TestName name = new TestName();
123
124  private Configuration conf;
125  private Path testDir;
126  private FileSystem fs;
127
128  @Before
129  public void setUp() throws IOException {
130    conf = TEST_UTIL.getConfiguration();
131    testDir = TEST_UTIL.getDataTestDir(name.getMethodName());
132    fs = testDir.getFileSystem(conf);
133  }
134
135  @AfterClass
136  public static void tearDownAfterClass() {
137    TEST_UTIL.cleanupTestDir();
138  }
139
140  /**
141   * Write a file and then assert that we can read from top and bottom halves using two
142   * HalfMapFiles.
143   */
144  @Test
145  public void testBasicHalfAndHFileLinkMapFile() throws Exception {
146    final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testBasicHalfAndHFileLinkMapFile"));
147    // The locations of HFileLink refers hfiles only should be consistent with the table dir
148    // create by CommonFSUtils directory, so we should make the region directory under
149    // the mode of CommonFSUtils.getTableDir here.
150    HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs,
151      CommonFSUtils.getTableDir(CommonFSUtils.getRootDir(conf), hri.getTable()), hri);
152
153    HFileContext meta = new HFileContextBuilder().withBlockSize(2 * 1024).build();
154    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs)
155      .withFilePath(regionFs.createTempName()).withFileContext(meta).build();
156    writeStoreFile(writer);
157
158    Path sfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
159    HStoreFile sf = new HStoreFile(this.fs, sfPath, conf, cacheConf, BloomType.NONE, true);
160    checkHalfHFile(regionFs, sf);
161  }
162
163  private void writeStoreFile(final StoreFileWriter writer) throws IOException {
164    writeStoreFile(writer, Bytes.toBytes(name.getMethodName()),
165      Bytes.toBytes(name.getMethodName()));
166  }
167
168  // pick an split point (roughly halfway)
169  byte[] SPLITKEY = new byte[] { (LAST_CHAR + FIRST_CHAR) / 2, FIRST_CHAR };
170
171  /*
172   * Writes HStoreKey and ImmutableBytes data to passed writer and then closes it. nn
173   */
174  public static void writeStoreFile(final StoreFileWriter writer, byte[] fam, byte[] qualifier)
175    throws IOException {
176    long now = EnvironmentEdgeManager.currentTime();
177    try {
178      for (char d = FIRST_CHAR; d <= LAST_CHAR; d++) {
179        for (char e = FIRST_CHAR; e <= LAST_CHAR; e++) {
180          byte[] b = new byte[] { (byte) d, (byte) e };
181          writer.append(new KeyValue(b, fam, qualifier, now, b));
182        }
183      }
184    } finally {
185      writer.close();
186    }
187  }
188
189  /**
190   * Test that our mechanism of writing store files in one region to reference store files in other
191   * regions works.
192   */
193  @Test
194  public void testReference() throws IOException {
195    final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testReferenceTb"));
196    HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs,
197      new Path(testDir, hri.getTable().getNameAsString()), hri);
198
199    HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
200    // Make a store file and write data to it.
201    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs)
202      .withFilePath(regionFs.createTempName()).withFileContext(meta).build();
203    writeStoreFile(writer);
204
205    Path hsfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
206    HStoreFile hsf = new HStoreFile(this.fs, hsfPath, conf, cacheConf, BloomType.NONE, true);
207    hsf.initReader();
208    StoreFileReader reader = hsf.getReader();
209    // Split on a row, not in middle of row. Midkey returned by reader
210    // may be in middle of row. Create new one with empty column and
211    // timestamp.
212    byte[] midRow = CellUtil.cloneRow(reader.midKey().get());
213    byte[] finalRow = CellUtil.cloneRow(reader.getLastKey().get());
214    hsf.closeStoreFile(true);
215
216    // Make a reference
217    HRegionInfo splitHri = new HRegionInfo(hri.getTable(), null, midRow);
218    Path refPath = splitStoreFile(regionFs, splitHri, TEST_FAMILY, hsf, midRow, true);
219    HStoreFile refHsf = new HStoreFile(this.fs, refPath, conf, cacheConf, BloomType.NONE, true);
220    refHsf.initReader();
221    // Now confirm that I can read from the reference and that it only gets
222    // keys from top half of the file.
223    HFileScanner s = refHsf.getReader().getScanner(false, false);
224    Cell kv = null;
225    for (boolean first = true; (!s.isSeeked() && s.seekTo()) || s.next();) {
226      ByteBuffer bb = ByteBuffer.wrap(((KeyValue) s.getKey()).getKey());
227      kv = KeyValueUtil.createKeyValueFromKey(bb);
228      if (first) {
229        assertTrue(Bytes.equals(kv.getRowArray(), kv.getRowOffset(), kv.getRowLength(), midRow, 0,
230          midRow.length));
231        first = false;
232      }
233    }
234    assertTrue(Bytes.equals(kv.getRowArray(), kv.getRowOffset(), kv.getRowLength(), finalRow, 0,
235      finalRow.length));
236  }
237
238  @Test
239  public void testStoreFileReference() throws Exception {
240    final RegionInfo hri =
241      RegionInfoBuilder.newBuilder(TableName.valueOf("testStoreFileReference")).build();
242    HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(conf, fs,
243      new Path(testDir, hri.getTable().getNameAsString()), hri);
244    HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
245
246    // Make a store file and write data to it.
247    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs)
248      .withFilePath(regionFs.createTempName()).withFileContext(meta).build();
249    writeStoreFile(writer);
250    Path hsfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
251    writer.close();
252
253    HStoreFile file = new HStoreFile(this.fs, hsfPath, conf, cacheConf, BloomType.NONE, true);
254    file.initReader();
255    StoreFileReader r = file.getReader();
256    assertNotNull(r);
257    StoreFileScanner scanner =
258      new StoreFileScanner(r, mock(HFileScanner.class), false, false, 0, 0, false);
259
260    // Verify after instantiating scanner refCount is increased
261    assertTrue("Verify file is being referenced", file.isReferencedInReads());
262    scanner.close();
263    // Verify after closing scanner refCount is decreased
264    assertFalse("Verify file is not being referenced", file.isReferencedInReads());
265  }
266
267  @Test
268  public void testEmptyStoreFileRestrictKeyRanges() throws Exception {
269    StoreFileReader reader = mock(StoreFileReader.class);
270    HStore store = mock(HStore.class);
271    byte[] cf = Bytes.toBytes("ty");
272    ColumnFamilyDescriptor cfd = ColumnFamilyDescriptorBuilder.of(cf);
273    when(store.getColumnFamilyDescriptor()).thenReturn(cfd);
274    try (StoreFileScanner scanner =
275      new StoreFileScanner(reader, mock(HFileScanner.class), false, false, 0, 0, true)) {
276      Scan scan = new Scan();
277      scan.setColumnFamilyTimeRange(cf, 0, 1);
278      assertFalse(scanner.shouldUseScanner(scan, store, 0));
279    }
280  }
281
282  @Test
283  public void testHFileLink() throws IOException {
284    final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testHFileLinkTb"));
285    // force temp data in hbase/target/test-data instead of /tmp/hbase-xxxx/
286    Configuration testConf = new Configuration(this.conf);
287    CommonFSUtils.setRootDir(testConf, testDir);
288    HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(testConf, fs,
289      CommonFSUtils.getTableDir(testDir, hri.getTable()), hri);
290    HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
291
292    // Make a store file and write data to it.
293    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs)
294      .withFilePath(regionFs.createTempName()).withFileContext(meta).build();
295    writeStoreFile(writer);
296
297    Path storeFilePath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
298    Path dstPath = new Path(regionFs.getTableDir(), new Path("test-region", TEST_FAMILY));
299    HFileLink.create(testConf, this.fs, dstPath, hri, storeFilePath.getName());
300    Path linkFilePath =
301      new Path(dstPath, HFileLink.createHFileLinkName(hri, storeFilePath.getName()));
302
303    // Try to open store file from link
304    StoreFileInfo storeFileInfo = new StoreFileInfo(testConf, this.fs, linkFilePath, true);
305    HStoreFile hsf = new HStoreFile(storeFileInfo, BloomType.NONE, cacheConf);
306    assertTrue(storeFileInfo.isLink());
307    hsf.initReader();
308
309    // Now confirm that I can read from the link
310    int count = 1;
311    HFileScanner s = hsf.getReader().getScanner(false, false);
312    s.seekTo();
313    while (s.next()) {
314      count++;
315    }
316    assertEquals((LAST_CHAR - FIRST_CHAR + 1) * (LAST_CHAR - FIRST_CHAR + 1), count);
317  }
318
319  /**
320   * This test creates an hfile and then the dir structures and files to verify that references to
321   * hfilelinks (created by snapshot clones) can be properly interpreted.
322   */
323  @Test
324  public void testReferenceToHFileLink() throws IOException {
325    // force temp data in hbase/target/test-data instead of /tmp/hbase-xxxx/
326    Configuration testConf = new Configuration(this.conf);
327    CommonFSUtils.setRootDir(testConf, testDir);
328
329    // adding legal table name chars to verify regex handles it.
330    HRegionInfo hri = new HRegionInfo(TableName.valueOf("_original-evil-name"));
331    HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(testConf, fs,
332      CommonFSUtils.getTableDir(testDir, hri.getTable()), hri);
333
334    HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
335    // Make a store file and write data to it. <root>/<tablename>/<rgn>/<cf>/<file>
336    StoreFileWriter writer = new StoreFileWriter.Builder(testConf, cacheConf, this.fs)
337      .withFilePath(regionFs.createTempName()).withFileContext(meta).build();
338    writeStoreFile(writer);
339    Path storeFilePath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
340
341    // create link to store file. <root>/clone/region/<cf>/<hfile>-<region>-<table>
342    RegionInfo hriClone = RegionInfoBuilder.newBuilder(TableName.valueOf("clone")).build();
343    HRegionFileSystem cloneRegionFs = HRegionFileSystem.createRegionOnFileSystem(testConf, fs,
344      CommonFSUtils.getTableDir(testDir, hri.getTable()), hriClone);
345    Path dstPath = cloneRegionFs.getStoreDir(TEST_FAMILY);
346    HFileLink.create(testConf, this.fs, dstPath, hri, storeFilePath.getName());
347    Path linkFilePath =
348      new Path(dstPath, HFileLink.createHFileLinkName(hri, storeFilePath.getName()));
349
350    // create splits of the link.
351    // <root>/clone/splitA/<cf>/<reftohfilelink>,
352    // <root>/clone/splitB/<cf>/<reftohfilelink>
353    HRegionInfo splitHriA = new HRegionInfo(hri.getTable(), null, SPLITKEY);
354    HRegionInfo splitHriB = new HRegionInfo(hri.getTable(), SPLITKEY, null);
355    HStoreFile f = new HStoreFile(fs, linkFilePath, testConf, cacheConf, BloomType.NONE, true);
356    f.initReader();
357    Path pathA = splitStoreFile(cloneRegionFs, splitHriA, TEST_FAMILY, f, SPLITKEY, true); // top
358    Path pathB = splitStoreFile(cloneRegionFs, splitHriB, TEST_FAMILY, f, SPLITKEY, false);// bottom
359    f.closeStoreFile(true);
360    // OK test the thing
361    CommonFSUtils.logFileSystemState(fs, testDir, LOG);
362
363    // There is a case where a file with the hfilelink pattern is actually a daughter
364    // reference to a hfile link. This code in StoreFile that handles this case.
365
366    // Try to open store file from link
367    HStoreFile hsfA = new HStoreFile(this.fs, pathA, testConf, cacheConf, BloomType.NONE, true);
368    hsfA.initReader();
369
370    // Now confirm that I can read from the ref to link
371    int count = 1;
372    HFileScanner s = hsfA.getReader().getScanner(false, false);
373    s.seekTo();
374    while (s.next()) {
375      count++;
376    }
377    assertTrue(count > 0); // read some rows here
378
379    // Try to open store file from link
380    HStoreFile hsfB = new HStoreFile(this.fs, pathB, testConf, cacheConf, BloomType.NONE, true);
381    hsfB.initReader();
382
383    // Now confirm that I can read from the ref to link
384    HFileScanner sB = hsfB.getReader().getScanner(false, false);
385    sB.seekTo();
386
387    // count++ as seekTo() will advance the scanner
388    count++;
389    while (sB.next()) {
390      count++;
391    }
392
393    // read the rest of the rows
394    assertEquals((LAST_CHAR - FIRST_CHAR + 1) * (LAST_CHAR - FIRST_CHAR + 1), count);
395  }
396
397  private void checkHalfHFile(final HRegionFileSystem regionFs, final HStoreFile f)
398    throws IOException {
399    f.initReader();
400    Cell midkey = f.getReader().midKey().get();
401    KeyValue midKV = (KeyValue) midkey;
402    // 1. test using the midRow as the splitKey, this test will generate two Reference files
403    // in the children
404    byte[] midRow = CellUtil.cloneRow(midKV);
405    // Create top split.
406    HRegionInfo topHri = new HRegionInfo(regionFs.getRegionInfo().getTable(), null, midRow);
407    Path topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, midRow, true);
408    // Create bottom split.
409    HRegionInfo bottomHri = new HRegionInfo(regionFs.getRegionInfo().getTable(), midRow, null);
410    Path bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, midRow, false);
411    // Make readers on top and bottom.
412    HStoreFile topF = new HStoreFile(this.fs, topPath, conf, cacheConf, BloomType.NONE, true);
413    topF.initReader();
414    StoreFileReader top = topF.getReader();
415    HStoreFile bottomF = new HStoreFile(this.fs, bottomPath, conf, cacheConf, BloomType.NONE, true);
416    bottomF.initReader();
417    StoreFileReader bottom = bottomF.getReader();
418    ByteBuffer previous = null;
419    LOG.info("Midkey: " + midKV.toString());
420    ByteBuffer bbMidkeyBytes = ByteBuffer.wrap(midKV.getKey());
421    try {
422      // Now make two HalfMapFiles and assert they can read the full backing
423      // file, one from the top and the other from the bottom.
424      // Test bottom half first.
425      // Now test reading from the top.
426      boolean first = true;
427      ByteBuffer key = null;
428      HFileScanner topScanner = top.getScanner(false, false);
429      while (
430        (!topScanner.isSeeked() && topScanner.seekTo())
431          || (topScanner.isSeeked() && topScanner.next())
432      ) {
433        key = ByteBuffer.wrap(((KeyValue) topScanner.getKey()).getKey());
434
435        if (
436          (PrivateCellUtil.compare(topScanner.getReader().getComparator(), midKV, key.array(),
437            key.arrayOffset(), key.limit())) > 0
438        ) {
439          fail("key=" + Bytes.toStringBinary(key) + " < midkey=" + midkey);
440        }
441        if (first) {
442          first = false;
443          LOG.info("First in top: " + Bytes.toString(Bytes.toBytes(key)));
444        }
445      }
446      LOG.info("Last in top: " + Bytes.toString(Bytes.toBytes(key)));
447
448      first = true;
449      HFileScanner bottomScanner = bottom.getScanner(false, false);
450      while ((!bottomScanner.isSeeked() && bottomScanner.seekTo()) || bottomScanner.next()) {
451        previous = ByteBuffer.wrap(((KeyValue) bottomScanner.getKey()).getKey());
452        key = ByteBuffer.wrap(((KeyValue) bottomScanner.getKey()).getKey());
453        if (first) {
454          first = false;
455          LOG.info("First in bottom: " + Bytes.toString(Bytes.toBytes(previous)));
456        }
457        assertTrue(key.compareTo(bbMidkeyBytes) < 0);
458      }
459      if (previous != null) {
460        LOG.info("Last in bottom: " + Bytes.toString(Bytes.toBytes(previous)));
461      }
462      // Remove references.
463      regionFs.cleanupDaughterRegion(topHri);
464      regionFs.cleanupDaughterRegion(bottomHri);
465
466      // 2. test using a midkey which will generate one Reference file and one HFileLink file.
467      // First, do a key that is < than first key. Ensure splits behave
468      // properly.
469      byte[] badmidkey = Bytes.toBytes("  .");
470      assertTrue(fs.exists(f.getPath()));
471      topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, badmidkey, true);
472      bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, badmidkey, false);
473
474      assertNull(bottomPath);
475
476      topF = new HStoreFile(this.fs, topPath, conf, cacheConf, BloomType.NONE, true);
477      topF.initReader();
478      top = topF.getReader();
479      // Now read from the top.
480      first = true;
481      topScanner = top.getScanner(false, false);
482      KeyValue.KeyOnlyKeyValue keyOnlyKV = new KeyValue.KeyOnlyKeyValue();
483      while ((!topScanner.isSeeked() && topScanner.seekTo()) || topScanner.next()) {
484        key = ByteBuffer.wrap(((KeyValue) topScanner.getKey()).getKey());
485        keyOnlyKV.setKey(key.array(), 0 + key.arrayOffset(), key.limit());
486        assertTrue(PrivateCellUtil.compare(topScanner.getReader().getComparator(), keyOnlyKV,
487          badmidkey, 0, badmidkey.length) >= 0);
488        if (first) {
489          first = false;
490          KeyValue keyKV = KeyValueUtil.createKeyValueFromKey(key);
491          LOG.info("First top when key < bottom: " + keyKV);
492          String tmp =
493            Bytes.toString(keyKV.getRowArray(), keyKV.getRowOffset(), keyKV.getRowLength());
494          for (int i = 0; i < tmp.length(); i++) {
495            assertTrue(tmp.charAt(i) == 'a');
496          }
497        }
498      }
499      KeyValue keyKV = KeyValueUtil.createKeyValueFromKey(key);
500      LOG.info("Last top when key < bottom: " + keyKV);
501      String tmp = Bytes.toString(keyKV.getRowArray(), keyKV.getRowOffset(), keyKV.getRowLength());
502      for (int i = 0; i < tmp.length(); i++) {
503        assertTrue(tmp.charAt(i) == 'z');
504      }
505      // Remove references.
506      regionFs.cleanupDaughterRegion(topHri);
507      regionFs.cleanupDaughterRegion(bottomHri);
508
509      // Test when badkey is > than last key in file ('||' > 'zz').
510      badmidkey = Bytes.toBytes("|||");
511      topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, badmidkey, true);
512      bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, badmidkey, false);
513      assertNull(topPath);
514
515      bottomF = new HStoreFile(this.fs, bottomPath, conf, cacheConf, BloomType.NONE, true);
516      bottomF.initReader();
517      bottom = bottomF.getReader();
518      first = true;
519      bottomScanner = bottom.getScanner(false, false);
520      while ((!bottomScanner.isSeeked() && bottomScanner.seekTo()) || bottomScanner.next()) {
521        key = ByteBuffer.wrap(((KeyValue) bottomScanner.getKey()).getKey());
522        if (first) {
523          first = false;
524          keyKV = KeyValueUtil.createKeyValueFromKey(key);
525          LOG.info("First bottom when key > top: " + keyKV);
526          tmp = Bytes.toString(keyKV.getRowArray(), keyKV.getRowOffset(), keyKV.getRowLength());
527          for (int i = 0; i < tmp.length(); i++) {
528            assertTrue(tmp.charAt(i) == 'a');
529          }
530        }
531      }
532      keyKV = KeyValueUtil.createKeyValueFromKey(key);
533      LOG.info("Last bottom when key > top: " + keyKV);
534      for (int i = 0; i < tmp.length(); i++) {
535        assertTrue(
536          Bytes.toString(keyKV.getRowArray(), keyKV.getRowOffset(), keyKV.getRowLength()).charAt(i)
537              == 'z');
538      }
539    } finally {
540      if (top != null) {
541        top.close(true); // evict since we are about to delete the file
542      }
543      if (bottom != null) {
544        bottom.close(true); // evict since we are about to delete the file
545      }
546      fs.delete(f.getPath(), true);
547    }
548  }
549
550  private static StoreFileScanner getStoreFileScanner(StoreFileReader reader, boolean cacheBlocks,
551    boolean pread) {
552    return reader.getStoreFileScanner(cacheBlocks, pread, false, 0, 0, false);
553  }
554
555  private static final String localFormatter = "%010d";
556
557  private void bloomWriteRead(StoreFileWriter writer, FileSystem fs) throws Exception {
558    float err = conf.getFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, 0);
559    Path f = writer.getPath();
560    long now = EnvironmentEdgeManager.currentTime();
561    for (int i = 0; i < 2000; i += 2) {
562      String row = String.format(localFormatter, i);
563      KeyValue kv = new KeyValue(Bytes.toBytes(row), Bytes.toBytes("family"), Bytes.toBytes("col"),
564        now, Bytes.toBytes("value"));
565      writer.append(kv);
566    }
567    writer.close();
568
569    ReaderContext context = new ReaderContextBuilder().withFileSystemAndPath(fs, f).build();
570    HFileInfo fileInfo = new HFileInfo(context, conf);
571    StoreFileReader reader =
572      new StoreFileReader(context, fileInfo, cacheConf, new AtomicInteger(0), conf);
573    fileInfo.initMetaAndIndex(reader.getHFileReader());
574    reader.loadFileInfo();
575    reader.loadBloomfilter();
576    StoreFileScanner scanner = getStoreFileScanner(reader, false, false);
577
578    // check false positives rate
579    int falsePos = 0;
580    int falseNeg = 0;
581    for (int i = 0; i < 2000; i++) {
582      String row = String.format(localFormatter, i);
583      TreeSet<byte[]> columns = new TreeSet<>(Bytes.BYTES_COMPARATOR);
584      columns.add(Bytes.toBytes("family:col"));
585
586      Scan scan = new Scan().withStartRow(Bytes.toBytes(row)).withStopRow(Bytes.toBytes(row), true);
587      scan.addColumn(Bytes.toBytes("family"), Bytes.toBytes("family:col"));
588      HStore store = mock(HStore.class);
589      when(store.getColumnFamilyDescriptor())
590        .thenReturn(ColumnFamilyDescriptorBuilder.of("family"));
591      boolean exists = scanner.shouldUseScanner(scan, store, Long.MIN_VALUE);
592      if (i % 2 == 0) {
593        if (!exists) {
594          falseNeg++;
595        }
596      } else {
597        if (exists) {
598          falsePos++;
599        }
600      }
601    }
602    reader.close(true); // evict because we are about to delete the file
603    fs.delete(f, true);
604    assertEquals("False negatives: " + falseNeg, 0, falseNeg);
605    int maxFalsePos = (int) (2 * 2000 * err);
606    assertTrue("Too many false positives: " + falsePos + " (err=" + err + ", expected no more than "
607      + maxFalsePos + ")", falsePos <= maxFalsePos);
608  }
609
610  private static final int BLOCKSIZE_SMALL = 8192;
611
612  @Test
613  public void testBloomFilter() throws Exception {
614    conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, (float) 0.01);
615    conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
616
617    // write the file
618    Path f = new Path(ROOT_DIR, name.getMethodName());
619    HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
620      .withChecksumType(CKTYPE).withBytesPerCheckSum(CKBYTES).build();
621    // Make a store file and write data to it.
622    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs).withFilePath(f)
623      .withBloomType(BloomType.ROW).withMaxKeyCount(2000).withFileContext(meta).build();
624    bloomWriteRead(writer, fs);
625  }
626
627  @Test
628  public void testDeleteFamilyBloomFilter() throws Exception {
629    conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, (float) 0.01);
630    conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
631    float err = conf.getFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, 0);
632
633    // write the file
634    Path f = new Path(ROOT_DIR, name.getMethodName());
635
636    HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
637      .withChecksumType(CKTYPE).withBytesPerCheckSum(CKBYTES).build();
638    // Make a store file and write data to it.
639    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs).withFilePath(f)
640      .withMaxKeyCount(2000).withFileContext(meta).build();
641
642    // add delete family
643    long now = EnvironmentEdgeManager.currentTime();
644    for (int i = 0; i < 2000; i += 2) {
645      String row = String.format(localFormatter, i);
646      KeyValue kv = new KeyValue(Bytes.toBytes(row), Bytes.toBytes("family"), Bytes.toBytes("col"),
647        now, KeyValue.Type.DeleteFamily, Bytes.toBytes("value"));
648      writer.append(kv);
649    }
650    writer.close();
651
652    ReaderContext context = new ReaderContextBuilder().withFileSystemAndPath(fs, f).build();
653    HFileInfo fileInfo = new HFileInfo(context, conf);
654    StoreFileReader reader =
655      new StoreFileReader(context, fileInfo, cacheConf, new AtomicInteger(0), conf);
656    fileInfo.initMetaAndIndex(reader.getHFileReader());
657    reader.loadFileInfo();
658    reader.loadBloomfilter();
659
660    // check false positives rate
661    int falsePos = 0;
662    int falseNeg = 0;
663    for (int i = 0; i < 2000; i++) {
664      String row = String.format(localFormatter, i);
665      byte[] rowKey = Bytes.toBytes(row);
666      boolean exists = reader.passesDeleteFamilyBloomFilter(rowKey, 0, rowKey.length);
667      if (i % 2 == 0) {
668        if (!exists) {
669          falseNeg++;
670        }
671      } else {
672        if (exists) {
673          falsePos++;
674        }
675      }
676    }
677    assertEquals(1000, reader.getDeleteFamilyCnt());
678    reader.close(true); // evict because we are about to delete the file
679    fs.delete(f, true);
680    assertEquals("False negatives: " + falseNeg, 0, falseNeg);
681    int maxFalsePos = (int) (2 * 2000 * err);
682    assertTrue("Too many false positives: " + falsePos + " (err=" + err + ", expected no more than "
683      + maxFalsePos, falsePos <= maxFalsePos);
684  }
685
686  /**
687   * Test for HBASE-8012
688   */
689  @Test
690  public void testReseek() throws Exception {
691    // write the file
692    Path f = new Path(ROOT_DIR, name.getMethodName());
693    HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
694    // Make a store file and write data to it.
695    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs).withFilePath(f)
696      .withFileContext(meta).build();
697
698    writeStoreFile(writer);
699    writer.close();
700
701    ReaderContext context = new ReaderContextBuilder().withFileSystemAndPath(fs, f).build();
702    HFileInfo fileInfo = new HFileInfo(context, conf);
703    StoreFileReader reader =
704      new StoreFileReader(context, fileInfo, cacheConf, new AtomicInteger(0), conf);
705    fileInfo.initMetaAndIndex(reader.getHFileReader());
706
707    // Now do reseek with empty KV to position to the beginning of the file
708
709    KeyValue k = KeyValueUtil.createFirstOnRow(HConstants.EMPTY_BYTE_ARRAY);
710    StoreFileScanner s = getStoreFileScanner(reader, false, false);
711    s.reseek(k);
712
713    assertNotNull("Intial reseek should position at the beginning of the file", s.peek());
714  }
715
716  @Test
717  public void testBloomTypes() throws Exception {
718    float err = (float) 0.01;
719    FileSystem fs = FileSystem.getLocal(conf);
720    conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, err);
721    conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
722
723    int rowCount = 50;
724    int colCount = 10;
725    int versions = 2;
726
727    // run once using columns and once using rows
728    BloomType[] bt = { BloomType.ROWCOL, BloomType.ROW };
729    int[] expKeys = { rowCount * colCount, rowCount };
730    // below line deserves commentary. it is expected bloom false positives
731    // column = rowCount*2*colCount inserts
732    // row-level = only rowCount*2 inserts, but failures will be magnified by
733    // 2nd for loop for every column (2*colCount)
734    float[] expErr = { 2 * rowCount * colCount * err, 2 * rowCount * 2 * colCount * err };
735
736    for (int x : new int[] { 0, 1 }) {
737      // write the file
738      Path f = new Path(ROOT_DIR, name.getMethodName() + x);
739      HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
740        .withChecksumType(CKTYPE).withBytesPerCheckSum(CKBYTES).build();
741      // Make a store file and write data to it.
742      StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs).withFilePath(f)
743        .withBloomType(bt[x]).withMaxKeyCount(expKeys[x]).withFileContext(meta).build();
744
745      long now = EnvironmentEdgeManager.currentTime();
746      for (int i = 0; i < rowCount * 2; i += 2) { // rows
747        for (int j = 0; j < colCount * 2; j += 2) { // column qualifiers
748          String row = String.format(localFormatter, i);
749          String col = String.format(localFormatter, j);
750          for (int k = 0; k < versions; ++k) { // versions
751            KeyValue kv = new KeyValue(Bytes.toBytes(row), Bytes.toBytes("family"),
752              Bytes.toBytes("col" + col), now - k, Bytes.toBytes(-1L));
753            writer.append(kv);
754          }
755        }
756      }
757      writer.close();
758
759      ReaderContext context =
760        new ReaderContextBuilder().withFilePath(f).withFileSize(fs.getFileStatus(f).getLen())
761          .withFileSystem(fs).withInputStreamWrapper(new FSDataInputStreamWrapper(fs, f)).build();
762      HFileInfo fileInfo = new HFileInfo(context, conf);
763      StoreFileReader reader =
764        new StoreFileReader(context, fileInfo, cacheConf, new AtomicInteger(0), conf);
765      fileInfo.initMetaAndIndex(reader.getHFileReader());
766      reader.loadFileInfo();
767      reader.loadBloomfilter();
768      StoreFileScanner scanner = getStoreFileScanner(reader, false, false);
769      assertEquals(expKeys[x], reader.getGeneralBloomFilter().getKeyCount());
770
771      HStore store = mock(HStore.class);
772      when(store.getColumnFamilyDescriptor())
773        .thenReturn(ColumnFamilyDescriptorBuilder.of("family"));
774      // check false positives rate
775      int falsePos = 0;
776      int falseNeg = 0;
777      for (int i = 0; i < rowCount * 2; ++i) { // rows
778        for (int j = 0; j < colCount * 2; ++j) { // column qualifiers
779          String row = String.format(localFormatter, i);
780          String col = String.format(localFormatter, j);
781          TreeSet<byte[]> columns = new TreeSet<>(Bytes.BYTES_COMPARATOR);
782          columns.add(Bytes.toBytes("col" + col));
783
784          Scan scan =
785            new Scan().withStartRow(Bytes.toBytes(row)).withStopRow(Bytes.toBytes(row), true);
786          scan.addColumn(Bytes.toBytes("family"), Bytes.toBytes(("col" + col)));
787
788          boolean exists = scanner.shouldUseScanner(scan, store, Long.MIN_VALUE);
789          boolean shouldRowExist = i % 2 == 0;
790          boolean shouldColExist = j % 2 == 0;
791          shouldColExist = shouldColExist || bt[x] == BloomType.ROW;
792          if (shouldRowExist && shouldColExist) {
793            if (!exists) {
794              falseNeg++;
795            }
796          } else {
797            if (exists) {
798              falsePos++;
799            }
800          }
801        }
802      }
803      reader.close(true); // evict because we are about to delete the file
804      fs.delete(f, true);
805      System.out.println(bt[x].toString());
806      System.out.println("  False negatives: " + falseNeg);
807      System.out.println("  False positives: " + falsePos);
808      assertEquals(0, falseNeg);
809      assertTrue(falsePos < 2 * expErr[x]);
810    }
811  }
812
813  @Test
814  public void testSeqIdComparator() {
815    assertOrdering(StoreFileComparators.SEQ_ID, mockStoreFile(true, 100, 1000, -1, "/foo/123"),
816      mockStoreFile(true, 100, 1000, -1, "/foo/124"), mockStoreFile(true, 99, 1000, -1, "/foo/126"),
817      mockStoreFile(true, 98, 2000, -1, "/foo/126"), mockStoreFile(false, 3453, -1, 1, "/foo/1"),
818      mockStoreFile(false, 2, -1, 3, "/foo/2"), mockStoreFile(false, 1000, -1, 5, "/foo/2"),
819      mockStoreFile(false, 76, -1, 5, "/foo/3"));
820  }
821
822  /**
823   * Assert that the given comparator orders the given storefiles in the same way that they're
824   * passed.
825   */
826  private void assertOrdering(Comparator<? super HStoreFile> comparator, HStoreFile... sfs) {
827    ArrayList<HStoreFile> sorted = Lists.newArrayList(sfs);
828    Collections.shuffle(sorted);
829    Collections.sort(sorted, comparator);
830    LOG.debug("sfs: " + Joiner.on(",").join(sfs));
831    LOG.debug("sorted: " + Joiner.on(",").join(sorted));
832    assertTrue(Iterables.elementsEqual(Arrays.asList(sfs), sorted));
833  }
834
835  /**
836   * Create a mock StoreFile with the given attributes.
837   */
838  private HStoreFile mockStoreFile(boolean bulkLoad, long size, long bulkTimestamp, long seqId,
839    String path) {
840    HStoreFile mock = Mockito.mock(HStoreFile.class);
841    StoreFileReader reader = Mockito.mock(StoreFileReader.class);
842
843    Mockito.doReturn(size).when(reader).length();
844
845    Mockito.doReturn(reader).when(mock).getReader();
846    Mockito.doReturn(bulkLoad).when(mock).isBulkLoadResult();
847    Mockito.doReturn(OptionalLong.of(bulkTimestamp)).when(mock).getBulkLoadTimestamp();
848    Mockito.doReturn(seqId).when(mock).getMaxSequenceId();
849    Mockito.doReturn(new Path(path)).when(mock).getPath();
850    String name = "mock storefile, bulkLoad=" + bulkLoad + " bulkTimestamp=" + bulkTimestamp
851      + " seqId=" + seqId + " path=" + path;
852    Mockito.doReturn(name).when(mock).toString();
853    return mock;
854  }
855
856  /**
857   * Generate a list of KeyValues for testing based on given parameters
858   * @return the rows key-value list
859   */
860  List<KeyValue> getKeyValueSet(long[] timestamps, int numRows, byte[] qualifier, byte[] family) {
861    List<KeyValue> kvList = new ArrayList<>();
862    for (int i = 1; i <= numRows; i++) {
863      byte[] b = Bytes.toBytes(i);
864      LOG.info(Bytes.toString(b));
865      LOG.info(Bytes.toString(b));
866      for (long timestamp : timestamps) {
867        kvList.add(new KeyValue(b, family, qualifier, timestamp, b));
868      }
869    }
870    return kvList;
871  }
872
873  /**
874   * Test to ensure correctness when using StoreFile with multiple timestamps
875   */
876  @Test
877  public void testMultipleTimestamps() throws IOException {
878    byte[] family = Bytes.toBytes("familyname");
879    byte[] qualifier = Bytes.toBytes("qualifier");
880    int numRows = 10;
881    long[] timestamps = new long[] { 20, 10, 5, 1 };
882    Scan scan = new Scan();
883
884    // Make up a directory hierarchy that has a regiondir ("7e0102") and familyname.
885    Path storedir = new Path(new Path(testDir, "7e0102"), Bytes.toString(family));
886    Path dir = new Path(storedir, "1234567890");
887    HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
888    // Make a store file and write data to it.
889    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs)
890      .withOutputDir(dir).withFileContext(meta).build();
891
892    List<KeyValue> kvList = getKeyValueSet(timestamps, numRows, qualifier, family);
893
894    for (KeyValue kv : kvList) {
895      writer.append(kv);
896    }
897    writer.appendMetadata(0, false);
898    writer.close();
899
900    HStoreFile hsf =
901      new HStoreFile(this.fs, writer.getPath(), conf, cacheConf, BloomType.NONE, true);
902    HStore store = mock(HStore.class);
903    when(store.getColumnFamilyDescriptor()).thenReturn(ColumnFamilyDescriptorBuilder.of(family));
904    hsf.initReader();
905    StoreFileReader reader = hsf.getReader();
906    StoreFileScanner scanner = getStoreFileScanner(reader, false, false);
907    TreeSet<byte[]> columns = new TreeSet<>(Bytes.BYTES_COMPARATOR);
908    columns.add(qualifier);
909
910    scan.setTimeRange(20, 100);
911    assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
912
913    scan.setTimeRange(1, 2);
914    assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
915
916    scan.setTimeRange(8, 10);
917    assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
918
919    // lets make sure it still works with column family time ranges
920    scan.setColumnFamilyTimeRange(family, 7, 50);
921    assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
922
923    // This test relies on the timestamp range optimization
924    scan = new Scan();
925    scan.setTimeRange(27, 50);
926    assertTrue(!scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
927
928    // should still use the scanner because we override the family time range
929    scan = new Scan();
930    scan.setTimeRange(27, 50);
931    scan.setColumnFamilyTimeRange(family, 7, 50);
932    assertTrue(scanner.shouldUseScanner(scan, store, Long.MIN_VALUE));
933  }
934
935  @Test
936  public void testCacheOnWriteEvictOnClose() throws Exception {
937    Configuration conf = this.conf;
938
939    // Find a home for our files (regiondir ("7e0102") and familyname).
940    Path baseDir = new Path(new Path(testDir, "7e0102"), "twoCOWEOC");
941
942    // Grab the block cache and get the initial hit/miss counts
943    BlockCache bc = BlockCacheFactory.createBlockCache(conf);
944    assertNotNull(bc);
945    CacheStats cs = bc.getStats();
946    long startHit = cs.getHitCount();
947    long startMiss = cs.getMissCount();
948    long startEvicted = cs.getEvictedCount();
949
950    // Let's write a StoreFile with three blocks, with cache on write off
951    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
952    CacheConfig cacheConf = new CacheConfig(conf, bc);
953    Path pathCowOff = new Path(baseDir, "123456789");
954    StoreFileWriter writer = writeStoreFile(conf, cacheConf, pathCowOff, 3);
955    HStoreFile hsf =
956      new HStoreFile(this.fs, writer.getPath(), conf, cacheConf, BloomType.NONE, true);
957    LOG.debug(hsf.getPath().toString());
958
959    // Read this file, we should see 3 misses
960    hsf.initReader();
961    StoreFileReader reader = hsf.getReader();
962    reader.loadFileInfo();
963    StoreFileScanner scanner = getStoreFileScanner(reader, true, true);
964    scanner.seek(KeyValue.LOWESTKEY);
965    while (scanner.next() != null) {
966      continue;
967    }
968    assertEquals(startHit, cs.getHitCount());
969    assertEquals(startMiss + 3, cs.getMissCount());
970    assertEquals(startEvicted, cs.getEvictedCount());
971    startMiss += 3;
972    scanner.close();
973    reader.close(cacheConf.shouldEvictOnClose());
974
975    // Now write a StoreFile with three blocks, with cache on write on
976    conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true);
977    cacheConf = new CacheConfig(conf, bc);
978    Path pathCowOn = new Path(baseDir, "123456788");
979    writer = writeStoreFile(conf, cacheConf, pathCowOn, 3);
980    hsf = new HStoreFile(this.fs, writer.getPath(), conf, cacheConf, BloomType.NONE, true);
981
982    // Read this file, we should see 3 hits
983    hsf.initReader();
984    reader = hsf.getReader();
985    scanner = getStoreFileScanner(reader, true, true);
986    scanner.seek(KeyValue.LOWESTKEY);
987    while (scanner.next() != null) {
988      continue;
989    }
990    assertEquals(startHit + 3, cs.getHitCount());
991    assertEquals(startMiss, cs.getMissCount());
992    assertEquals(startEvicted, cs.getEvictedCount());
993    startHit += 3;
994    scanner.close();
995    reader.close(cacheConf.shouldEvictOnClose());
996
997    // Let's read back the two files to ensure the blocks exactly match
998    hsf = new HStoreFile(this.fs, pathCowOff, conf, cacheConf, BloomType.NONE, true);
999    hsf.initReader();
1000    StoreFileReader readerOne = hsf.getReader();
1001    readerOne.loadFileInfo();
1002    StoreFileScanner scannerOne = getStoreFileScanner(readerOne, true, true);
1003    scannerOne.seek(KeyValue.LOWESTKEY);
1004    hsf = new HStoreFile(this.fs, pathCowOn, conf, cacheConf, BloomType.NONE, true);
1005    hsf.initReader();
1006    StoreFileReader readerTwo = hsf.getReader();
1007    readerTwo.loadFileInfo();
1008    StoreFileScanner scannerTwo = getStoreFileScanner(readerTwo, true, true);
1009    scannerTwo.seek(KeyValue.LOWESTKEY);
1010    Cell kv1 = null;
1011    Cell kv2 = null;
1012    while ((kv1 = scannerOne.next()) != null) {
1013      kv2 = scannerTwo.next();
1014      assertTrue(kv1.equals(kv2));
1015      KeyValue keyv1 = KeyValueUtil.ensureKeyValue(kv1);
1016      KeyValue keyv2 = KeyValueUtil.ensureKeyValue(kv2);
1017      assertTrue(Bytes.compareTo(keyv1.getBuffer(), keyv1.getKeyOffset(), keyv1.getKeyLength(),
1018        keyv2.getBuffer(), keyv2.getKeyOffset(), keyv2.getKeyLength()) == 0);
1019      assertTrue(Bytes.compareTo(kv1.getValueArray(), kv1.getValueOffset(), kv1.getValueLength(),
1020        kv2.getValueArray(), kv2.getValueOffset(), kv2.getValueLength()) == 0);
1021    }
1022    assertNull(scannerTwo.next());
1023    assertEquals(startHit + 6, cs.getHitCount());
1024    assertEquals(startMiss, cs.getMissCount());
1025    assertEquals(startEvicted, cs.getEvictedCount());
1026    startHit += 6;
1027    scannerOne.close();
1028    readerOne.close(cacheConf.shouldEvictOnClose());
1029    scannerTwo.close();
1030    readerTwo.close(cacheConf.shouldEvictOnClose());
1031
1032    // Let's close the first file with evict on close turned on
1033    conf.setBoolean("hbase.rs.evictblocksonclose", true);
1034    cacheConf = new CacheConfig(conf, bc);
1035    hsf = new HStoreFile(this.fs, pathCowOff, conf, cacheConf, BloomType.NONE, true);
1036    hsf.initReader();
1037    reader = hsf.getReader();
1038    reader.close(cacheConf.shouldEvictOnClose());
1039
1040    // We should have 3 new evictions but the evict count stat should not change. Eviction because
1041    // of HFile invalidation is not counted along with normal evictions
1042    assertEquals(startHit, cs.getHitCount());
1043    assertEquals(startMiss, cs.getMissCount());
1044    assertEquals(startEvicted, cs.getEvictedCount());
1045
1046    // Let's close the second file with evict on close turned off
1047    conf.setBoolean("hbase.rs.evictblocksonclose", false);
1048    cacheConf = new CacheConfig(conf, bc);
1049    hsf = new HStoreFile(this.fs, pathCowOn, conf, cacheConf, BloomType.NONE, true);
1050    hsf.initReader();
1051    reader = hsf.getReader();
1052    reader.close(cacheConf.shouldEvictOnClose());
1053
1054    // We expect no changes
1055    assertEquals(startHit, cs.getHitCount());
1056    assertEquals(startMiss, cs.getMissCount());
1057    assertEquals(startEvicted, cs.getEvictedCount());
1058  }
1059
1060  private Path splitStoreFile(final HRegionFileSystem regionFs, final RegionInfo hri,
1061    final String family, final HStoreFile sf, final byte[] splitKey, boolean isTopRef)
1062    throws IOException {
1063    Path path = regionFs.splitStoreFile(hri, family, sf, splitKey, isTopRef, null);
1064    if (null == path) {
1065      return null;
1066    }
1067    List<Path> splitFiles = new ArrayList<>();
1068    splitFiles.add(path);
1069    MasterProcedureEnv mockEnv = mock(MasterProcedureEnv.class);
1070    MasterServices mockServices = mock(MasterServices.class);
1071    when(mockEnv.getMasterServices()).thenReturn(mockServices);
1072    when(mockEnv.getMasterConfiguration()).thenReturn(new Configuration());
1073    TableDescriptors mockTblDescs = mock(TableDescriptors.class);
1074    when(mockServices.getTableDescriptors()).thenReturn(mockTblDescs);
1075    TableDescriptor mockTblDesc = TableDescriptorBuilder.newBuilder(hri.getTable())
1076      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(family)).build();
1077    when(mockTblDescs.get(any())).thenReturn(mockTblDesc);
1078    Path regionDir = regionFs.commitDaughterRegion(hri, splitFiles, mockEnv);
1079    return new Path(new Path(regionDir, family), path.getName());
1080  }
1081
1082  private StoreFileWriter writeStoreFile(Configuration conf, CacheConfig cacheConf, Path path,
1083    int numBlocks) throws IOException {
1084    // Let's put ~5 small KVs in each block, so let's make 5*numBlocks KVs
1085    int numKVs = 5 * numBlocks;
1086    List<KeyValue> kvs = new ArrayList<>(numKVs);
1087    byte[] b = Bytes.toBytes("x");
1088    int totalSize = 0;
1089    for (int i = numKVs; i > 0; i--) {
1090      KeyValue kv = new KeyValue(b, b, b, i, b);
1091      kvs.add(kv);
1092      // kv has memstoreTS 0, which takes 1 byte to store.
1093      totalSize += kv.getLength() + 1;
1094    }
1095    int blockSize = totalSize / numBlocks;
1096    HFileContext meta = new HFileContextBuilder().withBlockSize(blockSize).withChecksumType(CKTYPE)
1097      .withBytesPerCheckSum(CKBYTES).build();
1098    // Make a store file and write data to it.
1099    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs)
1100      .withFilePath(path).withMaxKeyCount(2000).withFileContext(meta).build();
1101    // We'll write N-1 KVs to ensure we don't write an extra block
1102    kvs.remove(kvs.size() - 1);
1103    for (KeyValue kv : kvs) {
1104      writer.append(kv);
1105    }
1106    writer.appendMetadata(0, false);
1107    writer.close();
1108    return writer;
1109  }
1110
1111  /**
1112   * Check if data block encoding information is saved correctly in HFile's file info.
1113   */
1114  @Test
1115  public void testDataBlockEncodingMetaData() throws IOException {
1116    // Make up a directory hierarchy that has a regiondir ("7e0102") and familyname.
1117    Path dir = new Path(new Path(testDir, "7e0102"), "familyname");
1118    Path path = new Path(dir, "1234567890");
1119
1120    DataBlockEncoding dataBlockEncoderAlgo = DataBlockEncoding.FAST_DIFF;
1121    cacheConf = new CacheConfig(conf);
1122    HFileContext meta =
1123      new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL).withChecksumType(CKTYPE)
1124        .withBytesPerCheckSum(CKBYTES).withDataBlockEncoding(dataBlockEncoderAlgo).build();
1125    // Make a store file and write data to it.
1126    StoreFileWriter writer = new StoreFileWriter.Builder(conf, cacheConf, this.fs)
1127      .withFilePath(path).withMaxKeyCount(2000).withFileContext(meta).build();
1128    writer.close();
1129
1130    HStoreFile storeFile =
1131      new HStoreFile(fs, writer.getPath(), conf, cacheConf, BloomType.NONE, true);
1132    storeFile.initReader();
1133    StoreFileReader reader = storeFile.getReader();
1134
1135    Map<byte[], byte[]> fileInfo = reader.loadFileInfo();
1136    byte[] value = fileInfo.get(HFileDataBlockEncoder.DATA_BLOCK_ENCODING);
1137    assertArrayEquals(dataBlockEncoderAlgo.getNameInBytes(), value);
1138  }
1139}