View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import java.io.IOException;
22  import java.nio.ByteBuffer;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collections;
26  import java.util.Comparator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.TreeSet;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.fs.FileSystem;
35  import org.apache.hadoop.fs.Path;
36  import org.apache.hadoop.hbase.Cell;
37  import org.apache.hadoop.hbase.HBaseTestCase;
38  import org.apache.hadoop.hbase.HBaseTestingUtility;
39  import org.apache.hadoop.hbase.HConstants;
40  import org.apache.hadoop.hbase.HRegionInfo;
41  import org.apache.hadoop.hbase.KeyValue;
42  import org.apache.hadoop.hbase.KeyValueUtil;
43  import org.apache.hadoop.hbase.testclassification.SmallTests;
44  import org.apache.hadoop.hbase.TableName;
45  import org.apache.hadoop.hbase.client.Scan;
46  import org.apache.hadoop.hbase.io.HFileLink;
47  import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding;
48  import org.apache.hadoop.hbase.io.hfile.BlockCache;
49  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
50  import org.apache.hadoop.hbase.io.hfile.CacheStats;
51  import org.apache.hadoop.hbase.io.hfile.HFileContext;
52  import org.apache.hadoop.hbase.io.hfile.HFileContextBuilder;
53  import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoder;
54  import org.apache.hadoop.hbase.io.hfile.HFileDataBlockEncoderImpl;
55  import org.apache.hadoop.hbase.io.hfile.HFileScanner;
56  import org.apache.hadoop.hbase.util.BloomFilterFactory;
57  import org.apache.hadoop.hbase.util.Bytes;
58  import org.apache.hadoop.hbase.util.ChecksumType;
59  import org.apache.hadoop.hbase.util.FSUtils;
60  import org.junit.experimental.categories.Category;
61  import org.mockito.Mockito;
62  
63  import com.google.common.base.Joiner;
64  import com.google.common.collect.Iterables;
65  import com.google.common.collect.Lists;
66  
67  /**
68   * Test HStoreFile
69   */
70  @Category(SmallTests.class)
71  public class TestStoreFile extends HBaseTestCase {
72    static final Log LOG = LogFactory.getLog(TestStoreFile.class);
73    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
74    private CacheConfig cacheConf =  new CacheConfig(TEST_UTIL.getConfiguration());
75    private static String ROOT_DIR = TEST_UTIL.getDataTestDir("TestStoreFile").toString();
76    private static final ChecksumType CKTYPE = ChecksumType.CRC32;
77    private static final int CKBYTES = 512;
78    private static String TEST_FAMILY = "cf";
79  
80    @Override
81    public void setUp() throws Exception {
82      super.setUp();
83    }
84  
85    @Override
86    public void tearDown() throws Exception {
87      super.tearDown();
88    }
89  
90    /**
91     * Write a file and then assert that we can read from top and bottom halves
92     * using two HalfMapFiles.
93     * @throws Exception
94     */
95    public void testBasicHalfMapFile() throws Exception {
96      final HRegionInfo hri =
97          new HRegionInfo(TableName.valueOf("testBasicHalfMapFileTb"));
98      HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(
99        conf, fs, new Path(this.testDir, hri.getTable().getNameAsString()), hri);
100 
101     HFileContext meta = new HFileContextBuilder().withBlockSize(2*1024).build();
102     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
103             .withFilePath(regionFs.createTempName())
104             .withFileContext(meta)
105             .build();
106     writeStoreFile(writer);
107 
108     Path sfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
109     StoreFile sf = new StoreFile(this.fs, sfPath, conf, cacheConf,
110       BloomType.NONE);
111     checkHalfHFile(regionFs, sf);
112   }
113 
114   private void writeStoreFile(final StoreFile.Writer writer) throws IOException {
115     writeStoreFile(writer, Bytes.toBytes(getName()), Bytes.toBytes(getName()));
116   }
117 
118   // pick an split point (roughly halfway)
119   byte[] SPLITKEY = new byte[] { (LAST_CHAR + FIRST_CHAR)/2, FIRST_CHAR};
120 
121   /*
122    * Writes HStoreKey and ImmutableBytes data to passed writer and
123    * then closes it.
124    * @param writer
125    * @throws IOException
126    */
127   public static void writeStoreFile(final StoreFile.Writer writer, byte[] fam, byte[] qualifier)
128   throws IOException {
129     long now = System.currentTimeMillis();
130     try {
131       for (char d = FIRST_CHAR; d <= LAST_CHAR; d++) {
132         for (char e = FIRST_CHAR; e <= LAST_CHAR; e++) {
133           byte[] b = new byte[] { (byte) d, (byte) e };
134           writer.append(new KeyValue(b, fam, qualifier, now, b));
135         }
136       }
137     } finally {
138       writer.close();
139     }
140   }
141 
142   /**
143    * Test that our mechanism of writing store files in one region to reference
144    * store files in other regions works.
145    * @throws IOException
146    */
147   public void testReference() throws IOException {
148     final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testReferenceTb"));
149     HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(
150       conf, fs, new Path(this.testDir, hri.getTable().getNameAsString()), hri);
151 
152     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
153     // Make a store file and write data to it.
154     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
155             .withFilePath(regionFs.createTempName())
156             .withFileContext(meta)
157             .build();
158     writeStoreFile(writer);
159 
160     Path hsfPath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
161     StoreFile hsf = new StoreFile(this.fs, hsfPath, conf, cacheConf,
162       BloomType.NONE);
163     StoreFile.Reader reader = hsf.createReader();
164     // Split on a row, not in middle of row.  Midkey returned by reader
165     // may be in middle of row.  Create new one with empty column and
166     // timestamp.
167 
168     KeyValue kv = KeyValue.createKeyValueFromKey(reader.midkey());
169     byte [] midRow = kv.getRow();
170     kv = KeyValue.createKeyValueFromKey(reader.getLastKey());
171     byte [] finalRow = kv.getRow();
172     hsf.closeReader(true);
173 
174     // Make a reference
175     HRegionInfo splitHri = new HRegionInfo(hri.getTable(), null, midRow);
176     Path refPath = splitStoreFile(regionFs, splitHri, TEST_FAMILY, hsf, midRow, true);
177     StoreFile refHsf = new StoreFile(this.fs, refPath, conf, cacheConf,
178       BloomType.NONE);
179     // Now confirm that I can read from the reference and that it only gets
180     // keys from top half of the file.
181     HFileScanner s = refHsf.createReader().getScanner(false, false);
182     for(boolean first = true; (!s.isSeeked() && s.seekTo()) || s.next();) {
183       ByteBuffer bb = s.getKey();
184       kv = KeyValue.createKeyValueFromKey(bb);
185       if (first) {
186         assertTrue(Bytes.equals(kv.getRow(), midRow));
187         first = false;
188       }
189     }
190     assertTrue(Bytes.equals(kv.getRow(), finalRow));
191   }
192 
193   public void testHFileLink() throws IOException {
194     final HRegionInfo hri = new HRegionInfo(TableName.valueOf("testHFileLinkTb"));
195     // force temp data in hbase/target/test-data instead of /tmp/hbase-xxxx/
196     Configuration testConf = new Configuration(this.conf);
197     FSUtils.setRootDir(testConf, this.testDir);
198     HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(
199       testConf, fs, FSUtils.getTableDir(this.testDir, hri.getTable()), hri);
200     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
201 
202     // Make a store file and write data to it.
203     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
204             .withFilePath(regionFs.createTempName())
205             .withFileContext(meta)
206             .build();
207     writeStoreFile(writer);
208 
209     Path storeFilePath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
210     Path dstPath = new Path(regionFs.getTableDir(), new Path("test-region", TEST_FAMILY));
211     HFileLink.create(testConf, this.fs, dstPath, hri, storeFilePath.getName());
212     Path linkFilePath = new Path(dstPath,
213                   HFileLink.createHFileLinkName(hri, storeFilePath.getName()));
214 
215     // Try to open store file from link
216     StoreFileInfo storeFileInfo = new StoreFileInfo(testConf, this.fs, linkFilePath);
217     StoreFile hsf = new StoreFile(this.fs, storeFileInfo, testConf, cacheConf,
218       BloomType.NONE);
219     assertTrue(storeFileInfo.isLink());
220 
221     // Now confirm that I can read from the link
222     int count = 1;
223     HFileScanner s = hsf.createReader().getScanner(false, false);
224     s.seekTo();
225     while (s.next()) {
226       count++;
227     }
228     assertEquals((LAST_CHAR - FIRST_CHAR + 1) * (LAST_CHAR - FIRST_CHAR + 1), count);
229   }
230 
231   /**
232    * This test creates an hfile and then the dir structures and files to verify that references
233    * to hfilelinks (created by snapshot clones) can be properly interpreted.
234    */
235   public void testReferenceToHFileLink() throws IOException {
236     // force temp data in hbase/target/test-data instead of /tmp/hbase-xxxx/
237     Configuration testConf = new Configuration(this.conf);
238     FSUtils.setRootDir(testConf, this.testDir);
239 
240     // adding legal table name chars to verify regex handles it.
241     HRegionInfo hri = new HRegionInfo(TableName.valueOf("_original-evil-name"));
242     HRegionFileSystem regionFs = HRegionFileSystem.createRegionOnFileSystem(
243       testConf, fs, FSUtils.getTableDir(this.testDir, hri.getTable()), hri);
244 
245     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
246     // Make a store file and write data to it. <root>/<tablename>/<rgn>/<cf>/<file>
247     StoreFile.Writer writer = new StoreFile.WriterBuilder(testConf, cacheConf, this.fs)
248             .withFilePath(regionFs.createTempName())
249             .withFileContext(meta)
250             .build();
251     writeStoreFile(writer);
252     Path storeFilePath = regionFs.commitStoreFile(TEST_FAMILY, writer.getPath());
253 
254     // create link to store file. <root>/clone/region/<cf>/<hfile>-<region>-<table>
255     HRegionInfo hriClone = new HRegionInfo(TableName.valueOf("clone"));
256     HRegionFileSystem cloneRegionFs = HRegionFileSystem.createRegionOnFileSystem(
257       testConf, fs, FSUtils.getTableDir(this.testDir, hri.getTable()),
258         hriClone);
259     Path dstPath = cloneRegionFs.getStoreDir(TEST_FAMILY);
260     HFileLink.create(testConf, this.fs, dstPath, hri, storeFilePath.getName());
261     Path linkFilePath = new Path(dstPath,
262                   HFileLink.createHFileLinkName(hri, storeFilePath.getName()));
263 
264     // create splits of the link.
265     // <root>/clone/splitA/<cf>/<reftohfilelink>,
266     // <root>/clone/splitB/<cf>/<reftohfilelink>
267     HRegionInfo splitHriA = new HRegionInfo(hri.getTable(), null, SPLITKEY);
268     HRegionInfo splitHriB = new HRegionInfo(hri.getTable(), SPLITKEY, null);
269     StoreFile f = new StoreFile(fs, linkFilePath, testConf, cacheConf, BloomType.NONE);
270     f.createReader();
271     Path pathA = splitStoreFile(cloneRegionFs, splitHriA, TEST_FAMILY, f, SPLITKEY, true); // top
272     Path pathB = splitStoreFile(cloneRegionFs, splitHriB, TEST_FAMILY, f, SPLITKEY, false);// bottom
273     f.closeReader(true);
274     // OK test the thing
275     FSUtils.logFileSystemState(fs, this.testDir, LOG);
276 
277     // There is a case where a file with the hfilelink pattern is actually a daughter
278     // reference to a hfile link.  This code in StoreFile that handles this case.
279 
280     // Try to open store file from link
281     StoreFile hsfA = new StoreFile(this.fs, pathA, testConf, cacheConf,
282       BloomType.NONE);
283 
284     // Now confirm that I can read from the ref to link
285     int count = 1;
286     HFileScanner s = hsfA.createReader().getScanner(false, false);
287     s.seekTo();
288     while (s.next()) {
289       count++;
290     }
291     assertTrue(count > 0); // read some rows here
292 
293     // Try to open store file from link
294     StoreFile hsfB = new StoreFile(this.fs, pathB, testConf, cacheConf,
295       BloomType.NONE);
296 
297     // Now confirm that I can read from the ref to link
298     HFileScanner sB = hsfB.createReader().getScanner(false, false);
299     sB.seekTo();
300     
301     //count++ as seekTo() will advance the scanner
302     count++;
303     while (sB.next()) {
304       count++;
305     }
306 
307     // read the rest of the rows
308     assertEquals((LAST_CHAR - FIRST_CHAR + 1) * (LAST_CHAR - FIRST_CHAR + 1), count);
309   }
310 
311   private void checkHalfHFile(final HRegionFileSystem regionFs, final StoreFile f)
312       throws IOException {
313     byte [] midkey = f.createReader().midkey();
314     KeyValue midKV = KeyValue.createKeyValueFromKey(midkey);
315     byte [] midRow = midKV.getRow();
316     // Create top split.
317     HRegionInfo topHri = new HRegionInfo(regionFs.getRegionInfo().getTable(),
318         null, midRow);
319     Path topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, midRow, true);
320     // Create bottom split.
321     HRegionInfo bottomHri = new HRegionInfo(regionFs.getRegionInfo().getTable(),
322         midRow, null);
323     Path bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, midRow, false);
324     // Make readers on top and bottom.
325     StoreFile.Reader top = new StoreFile(
326       this.fs, topPath, conf, cacheConf, BloomType.NONE).createReader();
327     StoreFile.Reader bottom = new StoreFile(
328       this.fs, bottomPath, conf, cacheConf, BloomType.NONE).createReader();
329     ByteBuffer previous = null;
330     LOG.info("Midkey: " + midKV.toString());
331     ByteBuffer bbMidkeyBytes = ByteBuffer.wrap(midkey);
332     try {
333       // Now make two HalfMapFiles and assert they can read the full backing
334       // file, one from the top and the other from the bottom.
335       // Test bottom half first.
336       // Now test reading from the top.
337       boolean first = true;
338       ByteBuffer key = null;
339       HFileScanner topScanner = top.getScanner(false, false);
340       while ((!topScanner.isSeeked() && topScanner.seekTo()) ||
341              (topScanner.isSeeked() && topScanner.next())) {
342         key = topScanner.getKey();
343 
344         if (topScanner.getReader().getComparator().compareFlatKey(key.array(),
345           key.arrayOffset(), key.limit(), midkey, 0, midkey.length) < 0) {
346           fail("key=" + Bytes.toStringBinary(key) + " < midkey=" +
347               Bytes.toStringBinary(midkey));
348         }
349         if (first) {
350           first = false;
351           LOG.info("First in top: " + Bytes.toString(Bytes.toBytes(key)));
352         }
353       }
354       LOG.info("Last in top: " + Bytes.toString(Bytes.toBytes(key)));
355 
356       first = true;
357       HFileScanner bottomScanner = bottom.getScanner(false, false);
358       while ((!bottomScanner.isSeeked() && bottomScanner.seekTo()) ||
359           bottomScanner.next()) {
360         previous = bottomScanner.getKey();
361         key = bottomScanner.getKey();
362         if (first) {
363           first = false;
364           LOG.info("First in bottom: " +
365             Bytes.toString(Bytes.toBytes(previous)));
366         }
367         assertTrue(key.compareTo(bbMidkeyBytes) < 0);
368       }
369       if (previous != null) {
370         LOG.info("Last in bottom: " + Bytes.toString(Bytes.toBytes(previous)));
371       }
372       // Remove references.
373       regionFs.cleanupDaughterRegion(topHri);
374       regionFs.cleanupDaughterRegion(bottomHri);
375 
376       // Next test using a midkey that does not exist in the file.
377       // First, do a key that is < than first key. Ensure splits behave
378       // properly.
379       byte [] badmidkey = Bytes.toBytes("  .");
380       assertTrue(fs.exists(f.getPath()));
381       topPath = splitStoreFile(regionFs, topHri, TEST_FAMILY, f, badmidkey, true);
382       bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, badmidkey, false);
383       
384       assertNull(bottomPath);
385       
386       top = new StoreFile(this.fs, topPath, conf, cacheConf, BloomType.NONE).createReader();
387       // Now read from the top.
388       first = true;
389       topScanner = top.getScanner(false, false);
390       while ((!topScanner.isSeeked() && topScanner.seekTo()) ||
391           topScanner.next()) {
392         key = topScanner.getKey();
393         assertTrue(topScanner.getReader().getComparator().compareFlatKey(key.array(),
394           key.arrayOffset(), key.limit(), badmidkey, 0, badmidkey.length) >= 0);
395         if (first) {
396           first = false;
397           KeyValue keyKV = KeyValue.createKeyValueFromKey(key);
398           LOG.info("First top when key < bottom: " + keyKV);
399           String tmp = Bytes.toString(keyKV.getRow());
400           for (int i = 0; i < tmp.length(); i++) {
401             assertTrue(tmp.charAt(i) == 'a');
402           }
403         }
404       }
405       KeyValue keyKV = KeyValue.createKeyValueFromKey(key);
406       LOG.info("Last top when key < bottom: " + keyKV);
407       String tmp = Bytes.toString(keyKV.getRow());
408       for (int i = 0; i < tmp.length(); i++) {
409         assertTrue(tmp.charAt(i) == 'z');
410       }
411       // Remove references.
412       regionFs.cleanupDaughterRegion(topHri);
413       regionFs.cleanupDaughterRegion(bottomHri);
414 
415       // Test when badkey is > than last key in file ('||' > 'zz').
416       badmidkey = Bytes.toBytes("|||");
417       topPath = splitStoreFile(regionFs,topHri, TEST_FAMILY, f, badmidkey, true);
418       bottomPath = splitStoreFile(regionFs, bottomHri, TEST_FAMILY, f, badmidkey, false);
419       assertNull(topPath);
420       bottom = new StoreFile(this.fs, bottomPath, conf, cacheConf,
421         BloomType.NONE).createReader();
422       first = true;
423       bottomScanner = bottom.getScanner(false, false);
424       while ((!bottomScanner.isSeeked() && bottomScanner.seekTo()) ||
425           bottomScanner.next()) {
426         key = bottomScanner.getKey();
427         if (first) {
428           first = false;
429           keyKV = KeyValue.createKeyValueFromKey(key);
430           LOG.info("First bottom when key > top: " + keyKV);
431           tmp = Bytes.toString(keyKV.getRow());
432           for (int i = 0; i < tmp.length(); i++) {
433             assertTrue(tmp.charAt(i) == 'a');
434           }
435         }
436       }
437       keyKV = KeyValue.createKeyValueFromKey(key);
438       LOG.info("Last bottom when key > top: " + keyKV);
439       for (int i = 0; i < tmp.length(); i++) {
440         assertTrue(Bytes.toString(keyKV.getRow()).charAt(i) == 'z');
441       }
442     } finally {
443       if (top != null) {
444         top.close(true); // evict since we are about to delete the file
445       }
446       if (bottom != null) {
447         bottom.close(true); // evict since we are about to delete the file
448       }
449       fs.delete(f.getPath(), true);
450     }
451   }
452 
453   private static final String localFormatter = "%010d";
454 
455   private void bloomWriteRead(StoreFile.Writer writer, FileSystem fs) throws Exception {
456     float err = conf.getFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, 0);
457     Path f = writer.getPath();
458     long now = System.currentTimeMillis();
459     for (int i = 0; i < 2000; i += 2) {
460       String row = String.format(localFormatter, i);
461       KeyValue kv = new KeyValue(row.getBytes(), "family".getBytes(),
462         "col".getBytes(), now, "value".getBytes());
463       writer.append(kv);
464     }
465     writer.close();
466 
467     StoreFile.Reader reader = new StoreFile.Reader(fs, f, cacheConf, conf);
468     reader.loadFileInfo();
469     reader.loadBloomfilter();
470     StoreFileScanner scanner = reader.getStoreFileScanner(false, false);
471 
472     // check false positives rate
473     int falsePos = 0;
474     int falseNeg = 0;
475     for (int i = 0; i < 2000; i++) {
476       String row = String.format(localFormatter, i);
477       TreeSet<byte[]> columns = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
478       columns.add("family:col".getBytes());
479 
480       Scan scan = new Scan(row.getBytes(),row.getBytes());
481       scan.addColumn("family".getBytes(), "family:col".getBytes());
482       boolean exists = scanner.shouldUseScanner(scan, columns, Long.MIN_VALUE);
483       if (i % 2 == 0) {
484         if (!exists) falseNeg++;
485       } else {
486         if (exists) falsePos++;
487       }
488     }
489     reader.close(true); // evict because we are about to delete the file
490     fs.delete(f, true);
491     assertEquals("False negatives: " + falseNeg, 0, falseNeg);
492     int maxFalsePos = (int) (2 * 2000 * err);
493     assertTrue("Too many false positives: " + falsePos + " (err=" + err
494         + ", expected no more than " + maxFalsePos + ")",
495         falsePos <= maxFalsePos);
496   }
497   
498   private static final int BLOCKSIZE_SMALL = 8192;
499 
500   public void testBloomFilter() throws Exception {
501     FileSystem fs = FileSystem.getLocal(conf);
502     conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, (float) 0.01);
503     conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
504 
505     // write the file
506     Path f = new Path(ROOT_DIR, getName());
507     HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
508                         .withChecksumType(CKTYPE)
509                         .withBytesPerCheckSum(CKBYTES).build();
510     // Make a store file and write data to it.
511     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
512             .withFilePath(f)
513             .withBloomType(BloomType.ROW)
514             .withMaxKeyCount(2000)
515             .withFileContext(meta)
516             .build();
517     bloomWriteRead(writer, fs);
518   }
519 
520   public void testDeleteFamilyBloomFilter() throws Exception {
521     FileSystem fs = FileSystem.getLocal(conf);
522     conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, (float) 0.01);
523     conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
524     float err = conf.getFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, 0);
525 
526     // write the file
527     Path f = new Path(ROOT_DIR, getName());
528 
529     HFileContext meta = new HFileContextBuilder()
530                         .withBlockSize(BLOCKSIZE_SMALL)
531                         .withChecksumType(CKTYPE)
532                         .withBytesPerCheckSum(CKBYTES).build();
533     // Make a store file and write data to it.
534     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
535             .withFilePath(f)
536             .withMaxKeyCount(2000)
537             .withFileContext(meta)
538             .build();
539 
540     // add delete family
541     long now = System.currentTimeMillis();
542     for (int i = 0; i < 2000; i += 2) {
543       String row = String.format(localFormatter, i);
544       KeyValue kv = new KeyValue(row.getBytes(), "family".getBytes(),
545           "col".getBytes(), now, KeyValue.Type.DeleteFamily, "value".getBytes());
546       writer.append(kv);
547     }
548     writer.close();
549 
550     StoreFile.Reader reader = new StoreFile.Reader(fs, f, cacheConf, conf);
551     reader.loadFileInfo();
552     reader.loadBloomfilter();
553 
554     // check false positives rate
555     int falsePos = 0;
556     int falseNeg = 0;
557     for (int i = 0; i < 2000; i++) {
558       String row = String.format(localFormatter, i);
559       byte[] rowKey = Bytes.toBytes(row);
560       boolean exists = reader.passesDeleteFamilyBloomFilter(rowKey, 0,
561           rowKey.length);
562       if (i % 2 == 0) {
563         if (!exists)
564           falseNeg++;
565       } else {
566         if (exists)
567           falsePos++;
568       }
569     }
570     assertEquals(1000, reader.getDeleteFamilyCnt());
571     reader.close(true); // evict because we are about to delete the file
572     fs.delete(f, true);
573     assertEquals("False negatives: " + falseNeg, 0, falseNeg);
574     int maxFalsePos = (int) (2 * 2000 * err);
575     assertTrue("Too many false positives: " + falsePos + " (err=" + err
576         + ", expected no more than " + maxFalsePos, falsePos <= maxFalsePos);
577   }
578 
579   /**
580    * Test for HBASE-8012
581    */
582   public void testReseek() throws Exception {
583     // write the file
584     Path f = new Path(ROOT_DIR, getName());
585     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
586     // Make a store file and write data to it.
587     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
588             .withFilePath(f)
589             .withFileContext(meta)
590             .build();
591 
592     writeStoreFile(writer);
593     writer.close();
594 
595     StoreFile.Reader reader = new StoreFile.Reader(fs, f, cacheConf, conf);
596 
597     // Now do reseek with empty KV to position to the beginning of the file
598 
599     KeyValue k = KeyValueUtil.createFirstOnRow(HConstants.EMPTY_BYTE_ARRAY);
600     StoreFileScanner s = reader.getStoreFileScanner(false, false);
601     s.reseek(k);
602 
603     assertNotNull("Intial reseek should position at the beginning of the file", s.peek());
604   }
605 
606   public void testBloomTypes() throws Exception {
607     float err = (float) 0.01;
608     FileSystem fs = FileSystem.getLocal(conf);
609     conf.setFloat(BloomFilterFactory.IO_STOREFILE_BLOOM_ERROR_RATE, err);
610     conf.setBoolean(BloomFilterFactory.IO_STOREFILE_BLOOM_ENABLED, true);
611 
612     int rowCount = 50;
613     int colCount = 10;
614     int versions = 2;
615 
616     // run once using columns and once using rows
617     BloomType[] bt = {BloomType.ROWCOL, BloomType.ROW};
618     int[] expKeys  = {rowCount*colCount, rowCount};
619     // below line deserves commentary.  it is expected bloom false positives
620     //  column = rowCount*2*colCount inserts
621     //  row-level = only rowCount*2 inserts, but failures will be magnified by
622     //              2nd for loop for every column (2*colCount)
623     float[] expErr   = {2*rowCount*colCount*err, 2*rowCount*2*colCount*err};
624 
625     for (int x : new int[]{0,1}) {
626       // write the file
627       Path f = new Path(ROOT_DIR, getName() + x);
628       HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
629           .withChecksumType(CKTYPE)
630           .withBytesPerCheckSum(CKBYTES).build();
631       // Make a store file and write data to it.
632       StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
633               .withFilePath(f)
634               .withBloomType(bt[x])
635               .withMaxKeyCount(expKeys[x])
636               .withFileContext(meta)
637               .build();
638 
639       long now = System.currentTimeMillis();
640       for (int i = 0; i < rowCount*2; i += 2) { // rows
641         for (int j = 0; j < colCount*2; j += 2) {   // column qualifiers
642           String row = String.format(localFormatter, i);
643           String col = String.format(localFormatter, j);
644           for (int k= 0; k < versions; ++k) { // versions
645             KeyValue kv = new KeyValue(row.getBytes(),
646               "family".getBytes(), ("col" + col).getBytes(),
647                 now-k, Bytes.toBytes((long)-1));
648             writer.append(kv);
649           }
650         }
651       }
652       writer.close();
653 
654       StoreFile.Reader reader = new StoreFile.Reader(fs, f, cacheConf, conf);
655       reader.loadFileInfo();
656       reader.loadBloomfilter();
657       StoreFileScanner scanner = reader.getStoreFileScanner(false, false);
658       assertEquals(expKeys[x], reader.generalBloomFilter.getKeyCount());
659 
660       // check false positives rate
661       int falsePos = 0;
662       int falseNeg = 0;
663       for (int i = 0; i < rowCount*2; ++i) { // rows
664         for (int j = 0; j < colCount*2; ++j) {   // column qualifiers
665           String row = String.format(localFormatter, i);
666           String col = String.format(localFormatter, j);
667           TreeSet<byte[]> columns = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
668           columns.add(("col" + col).getBytes());
669 
670           Scan scan = new Scan(row.getBytes(),row.getBytes());
671           scan.addColumn("family".getBytes(), ("col"+col).getBytes());
672           boolean exists =
673               scanner.shouldUseScanner(scan, columns, Long.MIN_VALUE);
674           boolean shouldRowExist = i % 2 == 0;
675           boolean shouldColExist = j % 2 == 0;
676           shouldColExist = shouldColExist || bt[x] == BloomType.ROW;
677           if (shouldRowExist && shouldColExist) {
678             if (!exists) falseNeg++;
679           } else {
680             if (exists) falsePos++;
681           }
682         }
683       }
684       reader.close(true); // evict because we are about to delete the file
685       fs.delete(f, true);
686       System.out.println(bt[x].toString());
687       System.out.println("  False negatives: " + falseNeg);
688       System.out.println("  False positives: " + falsePos);
689       assertEquals(0, falseNeg);
690       assertTrue(falsePos < 2*expErr[x]);
691     }
692   }
693 
694   public void testSeqIdComparator() {
695     assertOrdering(StoreFile.Comparators.SEQ_ID,
696         mockStoreFile(true,  100,   1000, -1, "/foo/123"),
697         mockStoreFile(true,  100,   1000, -1, "/foo/124"),
698         mockStoreFile(true,  99,    1000, -1, "/foo/126"),
699         mockStoreFile(true,  98,    2000, -1, "/foo/126"),
700         mockStoreFile(false, 3453, -1,     1, "/foo/1"),
701         mockStoreFile(false, 2,    -1,     3, "/foo/2"),
702         mockStoreFile(false, 1000, -1,     5, "/foo/2"),
703         mockStoreFile(false, 76,   -1,     5, "/foo/3"));
704   }
705 
706   /**
707    * Assert that the given comparator orders the given storefiles in the
708    * same way that they're passed.
709    */
710   private void assertOrdering(Comparator<StoreFile> comparator, StoreFile ... sfs) {
711     ArrayList<StoreFile> sorted = Lists.newArrayList(sfs);
712     Collections.shuffle(sorted);
713     Collections.sort(sorted, comparator);
714     LOG.debug("sfs: " + Joiner.on(",").join(sfs));
715     LOG.debug("sorted: " + Joiner.on(",").join(sorted));
716     assertTrue(Iterables.elementsEqual(Arrays.asList(sfs), sorted));
717   }
718 
719   /**
720    * Create a mock StoreFile with the given attributes.
721    */
722   private StoreFile mockStoreFile(boolean bulkLoad,
723                                   long size,
724                                   long bulkTimestamp,
725                                   long seqId,
726                                   String path) {
727     StoreFile mock = Mockito.mock(StoreFile.class);
728     StoreFile.Reader reader = Mockito.mock(StoreFile.Reader.class);
729 
730     Mockito.doReturn(size).when(reader).length();
731 
732     Mockito.doReturn(reader).when(mock).getReader();
733     Mockito.doReturn(bulkLoad).when(mock).isBulkLoadResult();
734     Mockito.doReturn(bulkTimestamp).when(mock).getBulkLoadTimestamp();
735     Mockito.doReturn(seqId).when(mock).getMaxSequenceId();
736     Mockito.doReturn(new Path(path)).when(mock).getPath();
737     String name = "mock storefile, bulkLoad=" + bulkLoad +
738       " bulkTimestamp=" + bulkTimestamp +
739       " seqId=" + seqId +
740       " path=" + path;
741     Mockito.doReturn(name).when(mock).toString();
742     return mock;
743   }
744 
745   /**
746    * Generate a list of KeyValues for testing based on given parameters
747    * @param timestamps
748    * @param numRows
749    * @param qualifier
750    * @param family
751    * @return
752    */
753   List<KeyValue> getKeyValueSet(long[] timestamps, int numRows,
754       byte[] qualifier, byte[] family) {
755     List<KeyValue> kvList = new ArrayList<KeyValue>();
756     for (int i=1;i<=numRows;i++) {
757       byte[] b = Bytes.toBytes(i) ;
758       LOG.info(Bytes.toString(b));
759       LOG.info(Bytes.toString(b));
760       for (long timestamp: timestamps)
761       {
762         kvList.add(new KeyValue(b, family, qualifier, timestamp, b));
763       }
764     }
765     return kvList;
766   }
767 
768   /**
769    * Test to ensure correctness when using StoreFile with multiple timestamps
770    * @throws IOException
771    */
772   public void testMultipleTimestamps() throws IOException {
773     byte[] family = Bytes.toBytes("familyname");
774     byte[] qualifier = Bytes.toBytes("qualifier");
775     int numRows = 10;
776     long[] timestamps = new long[] {20,10,5,1};
777     Scan scan = new Scan();
778 
779     // Make up a directory hierarchy that has a regiondir ("7e0102") and familyname.
780     Path storedir = new Path(new Path(this.testDir, "7e0102"), "familyname");
781     Path dir = new Path(storedir, "1234567890");
782     HFileContext meta = new HFileContextBuilder().withBlockSize(8 * 1024).build();
783     // Make a store file and write data to it.
784     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
785             .withOutputDir(dir)
786             .withFileContext(meta)
787             .build();
788 
789     List<KeyValue> kvList = getKeyValueSet(timestamps,numRows,
790         family, qualifier);
791 
792     for (KeyValue kv : kvList) {
793       writer.append(kv);
794     }
795     writer.appendMetadata(0, false);
796     writer.close();
797 
798     StoreFile hsf = new StoreFile(this.fs, writer.getPath(), conf, cacheConf,
799       BloomType.NONE);
800     StoreFile.Reader reader = hsf.createReader();
801     StoreFileScanner scanner = reader.getStoreFileScanner(false, false);
802     TreeSet<byte[]> columns = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
803     columns.add(qualifier);
804 
805     scan.setTimeRange(20, 100);
806     assertTrue(scanner.shouldUseScanner(scan, columns, Long.MIN_VALUE));
807 
808     scan.setTimeRange(1, 2);
809     assertTrue(scanner.shouldUseScanner(scan, columns, Long.MIN_VALUE));
810 
811     scan.setTimeRange(8, 10);
812     assertTrue(scanner.shouldUseScanner(scan, columns, Long.MIN_VALUE));
813 
814     scan.setTimeRange(7, 50);
815     assertTrue(scanner.shouldUseScanner(scan, columns, Long.MIN_VALUE));
816 
817     // This test relies on the timestamp range optimization
818     scan.setTimeRange(27, 50);
819     assertTrue(!scanner.shouldUseScanner(scan, columns, Long.MIN_VALUE));
820   }
821 
822   public void testCacheOnWriteEvictOnClose() throws Exception {
823     Configuration conf = this.conf;
824 
825     // Find a home for our files (regiondir ("7e0102") and familyname).
826     Path baseDir = new Path(new Path(this.testDir, "7e0102"),"twoCOWEOC");
827 
828     // Grab the block cache and get the initial hit/miss counts
829     BlockCache bc = new CacheConfig(conf).getBlockCache();
830     assertNotNull(bc);
831     CacheStats cs = bc.getStats();
832     long startHit = cs.getHitCount();
833     long startMiss = cs.getMissCount();
834     long startEvicted = cs.getEvictedCount();
835 
836     // Let's write a StoreFile with three blocks, with cache on write off
837     conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, false);
838     CacheConfig cacheConf = new CacheConfig(conf);
839     Path pathCowOff = new Path(baseDir, "123456789");
840     StoreFile.Writer writer = writeStoreFile(conf, cacheConf, pathCowOff, 3);
841     StoreFile hsf = new StoreFile(this.fs, writer.getPath(), conf, cacheConf,
842       BloomType.NONE);
843     LOG.debug(hsf.getPath().toString());
844 
845     // Read this file, we should see 3 misses
846     StoreFile.Reader reader = hsf.createReader();
847     reader.loadFileInfo();
848     StoreFileScanner scanner = reader.getStoreFileScanner(true, true);
849     scanner.seek(KeyValue.LOWESTKEY);
850     while (scanner.next() != null);
851     assertEquals(startHit, cs.getHitCount());
852     assertEquals(startMiss + 3, cs.getMissCount());
853     assertEquals(startEvicted, cs.getEvictedCount());
854     startMiss += 3;
855     scanner.close();
856     reader.close(cacheConf.shouldEvictOnClose());
857 
858     // Now write a StoreFile with three blocks, with cache on write on
859     conf.setBoolean(CacheConfig.CACHE_BLOCKS_ON_WRITE_KEY, true);
860     cacheConf = new CacheConfig(conf);
861     Path pathCowOn = new Path(baseDir, "123456788");
862     writer = writeStoreFile(conf, cacheConf, pathCowOn, 3);
863     hsf = new StoreFile(this.fs, writer.getPath(), conf, cacheConf,
864       BloomType.NONE);
865 
866     // Read this file, we should see 3 hits
867     reader = hsf.createReader();
868     scanner = reader.getStoreFileScanner(true, true);
869     scanner.seek(KeyValue.LOWESTKEY);
870     while (scanner.next() != null);
871     assertEquals(startHit + 3, cs.getHitCount());
872     assertEquals(startMiss, cs.getMissCount());
873     assertEquals(startEvicted, cs.getEvictedCount());
874     startHit += 3;
875     scanner.close();
876     reader.close(cacheConf.shouldEvictOnClose());
877 
878     // Let's read back the two files to ensure the blocks exactly match
879     hsf = new StoreFile(this.fs, pathCowOff, conf, cacheConf,
880       BloomType.NONE);
881     StoreFile.Reader readerOne = hsf.createReader();
882     readerOne.loadFileInfo();
883     StoreFileScanner scannerOne = readerOne.getStoreFileScanner(true, true);
884     scannerOne.seek(KeyValue.LOWESTKEY);
885     hsf = new StoreFile(this.fs, pathCowOn, conf, cacheConf,
886       BloomType.NONE);
887     StoreFile.Reader readerTwo = hsf.createReader();
888     readerTwo.loadFileInfo();
889     StoreFileScanner scannerTwo = readerTwo.getStoreFileScanner(true, true);
890     scannerTwo.seek(KeyValue.LOWESTKEY);
891     Cell kv1 = null;
892     Cell kv2 = null;
893     while ((kv1 = scannerOne.next()) != null) {
894       kv2 = scannerTwo.next();
895       assertTrue(kv1.equals(kv2));
896       KeyValue keyv1 = KeyValueUtil.ensureKeyValue(kv1);
897       KeyValue keyv2 = KeyValueUtil.ensureKeyValue(kv2);
898       assertTrue(Bytes.compareTo(
899           keyv1.getBuffer(), keyv1.getKeyOffset(), keyv1.getKeyLength(), 
900           keyv2.getBuffer(), keyv2.getKeyOffset(), keyv2.getKeyLength()) == 0);
901       assertTrue(Bytes.compareTo(
902           kv1.getValueArray(), kv1.getValueOffset(), kv1.getValueLength(),
903           kv2.getValueArray(), kv2.getValueOffset(), kv2.getValueLength()) == 0);
904     }
905     assertNull(scannerTwo.next());
906     assertEquals(startHit + 6, cs.getHitCount());
907     assertEquals(startMiss, cs.getMissCount());
908     assertEquals(startEvicted, cs.getEvictedCount());
909     startHit += 6;
910     scannerOne.close();
911     readerOne.close(cacheConf.shouldEvictOnClose());
912     scannerTwo.close();
913     readerTwo.close(cacheConf.shouldEvictOnClose());
914 
915     // Let's close the first file with evict on close turned on
916     conf.setBoolean("hbase.rs.evictblocksonclose", true);
917     cacheConf = new CacheConfig(conf);
918     hsf = new StoreFile(this.fs, pathCowOff, conf, cacheConf,
919       BloomType.NONE);
920     reader = hsf.createReader();
921     reader.close(cacheConf.shouldEvictOnClose());
922 
923     // We should have 3 new evictions
924     assertEquals(startHit, cs.getHitCount());
925     assertEquals(startMiss, cs.getMissCount());
926     assertEquals(startEvicted + 3, cs.getEvictedCount());
927     startEvicted += 3;
928 
929     // Let's close the second file with evict on close turned off
930     conf.setBoolean("hbase.rs.evictblocksonclose", false);
931     cacheConf = new CacheConfig(conf);
932     hsf = new StoreFile(this.fs, pathCowOn, conf, cacheConf,
933       BloomType.NONE);
934     reader = hsf.createReader();
935     reader.close(cacheConf.shouldEvictOnClose());
936 
937     // We expect no changes
938     assertEquals(startHit, cs.getHitCount());
939     assertEquals(startMiss, cs.getMissCount());
940     assertEquals(startEvicted, cs.getEvictedCount());
941   }
942 
943   private Path splitStoreFile(final HRegionFileSystem regionFs, final HRegionInfo hri,
944       final String family, final StoreFile sf, final byte[] splitKey, boolean isTopRef)
945       throws IOException {
946     FileSystem fs = regionFs.getFileSystem();
947     Path path = regionFs.splitStoreFile(hri, family, sf, splitKey, isTopRef, null);
948     if (null == path) {
949       return null;
950     }
951     Path regionDir = regionFs.commitDaughterRegion(hri);
952     return new Path(new Path(regionDir, family), path.getName());
953   }
954 
955   private StoreFile.Writer writeStoreFile(Configuration conf,
956       CacheConfig cacheConf, Path path, int numBlocks)
957   throws IOException {
958     // Let's put ~5 small KVs in each block, so let's make 5*numBlocks KVs
959     int numKVs = 5 * numBlocks;
960     List<KeyValue> kvs = new ArrayList<KeyValue>(numKVs);
961     byte [] b = Bytes.toBytes("x");
962     int totalSize = 0;
963     for (int i=numKVs;i>0;i--) {
964       KeyValue kv = new KeyValue(b, b, b, i, b);
965       kvs.add(kv);
966       // kv has memstoreTS 0, which takes 1 byte to store.
967       totalSize += kv.getLength() + 1;
968     }
969     int blockSize = totalSize / numBlocks;
970     HFileContext meta = new HFileContextBuilder().withBlockSize(blockSize)
971                         .withChecksumType(CKTYPE)
972                         .withBytesPerCheckSum(CKBYTES)
973                         .build();
974     // Make a store file and write data to it.
975     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
976             .withFilePath(path)
977             .withMaxKeyCount(2000)
978             .withFileContext(meta)
979             .build();
980     // We'll write N-1 KVs to ensure we don't write an extra block
981     kvs.remove(kvs.size()-1);
982     for (KeyValue kv : kvs) {
983       writer.append(kv);
984     }
985     writer.appendMetadata(0, false);
986     writer.close();
987     return writer;
988   }
989 
990   /**
991    * Check if data block encoding information is saved correctly in HFile's
992    * file info.
993    */
994   public void testDataBlockEncodingMetaData() throws IOException {
995     // Make up a directory hierarchy that has a regiondir ("7e0102") and familyname.
996     Path dir = new Path(new Path(this.testDir, "7e0102"), "familyname");
997     Path path = new Path(dir, "1234567890");
998 
999     DataBlockEncoding dataBlockEncoderAlgo =
1000         DataBlockEncoding.FAST_DIFF;
1001     HFileDataBlockEncoder dataBlockEncoder =
1002         new HFileDataBlockEncoderImpl(
1003             dataBlockEncoderAlgo);
1004     cacheConf = new CacheConfig(conf);
1005     HFileContext meta = new HFileContextBuilder().withBlockSize(BLOCKSIZE_SMALL)
1006         .withChecksumType(CKTYPE)
1007         .withBytesPerCheckSum(CKBYTES)
1008         .withDataBlockEncoding(dataBlockEncoderAlgo)
1009         .build();
1010     // Make a store file and write data to it.
1011     StoreFile.Writer writer = new StoreFile.WriterBuilder(conf, cacheConf, this.fs)
1012             .withFilePath(path)
1013             .withMaxKeyCount(2000)
1014             .withFileContext(meta)
1015             .build();
1016     writer.close();
1017 
1018     StoreFile storeFile = new StoreFile(fs, writer.getPath(), conf,
1019       cacheConf, BloomType.NONE);
1020     StoreFile.Reader reader = storeFile.createReader();
1021 
1022     Map<byte[], byte[]> fileInfo = reader.loadFileInfo();
1023     byte[] value = fileInfo.get(HFileDataBlockEncoder.DATA_BLOCK_ENCODING);
1024     assertEquals(dataBlockEncoderAlgo.getNameInBytes(), value);
1025   }
1026 }
1027