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.io.hfile;
20  
21  import java.io.DataInput;
22  import java.io.DataOutput;
23  import java.io.IOException;
24  import java.nio.ByteBuffer;
25  import java.util.Arrays;
26  import java.util.Map;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.fs.FSDataInputStream;
31  import org.apache.hadoop.fs.FSDataOutputStream;
32  import org.apache.hadoop.fs.FileStatus;
33  import org.apache.hadoop.fs.FileSystem;
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.hbase.Cell;
36  import org.apache.hadoop.hbase.CellUtil;
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.KeyValue;
41  import org.apache.hadoop.hbase.KeyValue.Type;
42  import org.apache.hadoop.hbase.testclassification.SmallTests;
43  import org.apache.hadoop.hbase.Tag;
44  import org.apache.hadoop.hbase.io.compress.Compression;
45  import org.apache.hadoop.hbase.io.hfile.HFile.Reader;
46  import org.apache.hadoop.hbase.io.hfile.HFile.Writer;
47  import org.apache.hadoop.hbase.util.Bytes;
48  import org.apache.hadoop.io.Writable;
49  import org.junit.Assert;
50  import org.junit.Test;
51  import org.junit.experimental.categories.Category;
52  import org.mockito.Mockito;
53  
54  /**
55   * test hfile features.
56   * <p>
57   * Copied from
58   * <a href="https://issues.apache.org/jira/browse/HADOOP-3315">hadoop-3315 tfile</a>.
59   * Remove after tfile is committed and use the tfile version of this class
60   * instead.</p>
61   */
62  @Category(SmallTests.class)
63  public class TestHFile extends HBaseTestCase {
64    private static final Log LOG = LogFactory.getLog(TestHFile.class);
65  
66    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
67    private static String ROOT_DIR =
68      TEST_UTIL.getDataTestDir("TestHFile").toString();
69    private final int minBlockSize = 512;
70    private static String localFormatter = "%010d";
71    private static CacheConfig cacheConf = null;
72    private Map<String, Long> startingMetrics;
73  
74    @Override
75    public void setUp() throws Exception {
76      super.setUp();
77    }
78  
79    @Override
80    public void tearDown() throws Exception {
81      super.tearDown();
82    }
83  
84  
85    /**
86     * Test empty HFile.
87     * Test all features work reasonably when hfile is empty of entries.
88     * @throws IOException
89     */
90    public void testEmptyHFile() throws IOException {
91      if (cacheConf == null) cacheConf = new CacheConfig(conf);
92      Path f = new Path(ROOT_DIR, getName());
93      HFileContext context = new HFileContextBuilder().withIncludesTags(false).build();
94      Writer w =
95          HFile.getWriterFactory(conf, cacheConf).withPath(fs, f).withFileContext(context).create();
96      w.close();
97      Reader r = HFile.createReader(fs, f, cacheConf, conf);
98      r.loadFileInfo();
99      assertNull(r.getFirstKey());
100     assertNull(r.getLastKey());
101   }
102 
103   @Test
104   public void testCorruptOutOfOrderHFileWrite() throws IOException {
105     Path path = new Path(ROOT_DIR, "testCorruptOutOfOrderHFileWrite");
106     FSDataOutputStream mockedOutputStream = Mockito.mock(FSDataOutputStream.class);
107     String columnFamily = "MyColumnFamily";
108     String tableName = "MyTableName";
109     HFileContext fileContext = new HFileContextBuilder()
110         .withHFileName("testCorruptOutOfOrderHFileWriteHFile")
111         .withBlockSize(minBlockSize)
112         .withColumnFamily(Bytes.toBytes(columnFamily))
113         .withTableName(Bytes.toBytes(tableName))
114         .withHBaseCheckSum(false)
115         .withCompression(Compression.Algorithm.NONE)
116         .withCompressTags(false)
117         .build();
118     HFileWriterV3 writer = new HFileWriterV3(conf, cacheConf, fs, path, mockedOutputStream,
119         new KeyValue.KVComparator(), fileContext);
120     byte[] row = Bytes.toBytes("foo");
121     byte[] qualifier = Bytes.toBytes("qualifier");
122     byte[] cf = Bytes.toBytes(columnFamily);
123     byte[] val = Bytes.toBytes("fooVal");
124     long firstTS = 100L;
125     long secondTS = 101L;
126     Cell firstCell = CellUtil.createCell(row,cf, qualifier, firstTS, Type.Put.getCode(), val);
127     Cell secondCell= CellUtil.createCell(row,cf, qualifier, secondTS, Type.Put.getCode(), val);
128     //second Cell will sort "higher" than the first because later timestamps should come first
129     writer.append(firstCell);
130     try {
131       writer.append(secondCell);
132     } catch(IOException ie){
133       String message = ie.getMessage();
134       Assert.assertTrue(message.contains("not lexically larger"));
135       Assert.assertTrue(message.contains(tableName));
136       Assert.assertTrue(message.contains(columnFamily));
137       return;
138     }
139     Assert.fail("Exception wasn't thrown even though Cells were appended in the wrong order!");
140   }
141   /**
142    * Create 0-length hfile and show that it fails
143    */
144   public void testCorrupt0LengthHFile() throws IOException {
145     if (cacheConf == null) cacheConf = new CacheConfig(conf);
146     Path f = new Path(ROOT_DIR, getName());
147     FSDataOutputStream fsos = fs.create(f);
148     fsos.close();
149 
150     try {
151       Reader r = HFile.createReader(fs, f, cacheConf, conf);
152     } catch (CorruptHFileException che) {
153       // Expected failure
154       return;
155     }
156     fail("Should have thrown exception");
157   }
158 
159   public static void truncateFile(FileSystem fs, Path src, Path dst) throws IOException {
160     FileStatus fst = fs.getFileStatus(src);
161     long len = fst.getLen();
162     len = len / 2 ;
163 
164     // create a truncated hfile
165     FSDataOutputStream fdos = fs.create(dst);
166     byte[] buf = new byte[(int)len];
167     FSDataInputStream fdis = fs.open(src);
168     fdis.read(buf);
169     fdos.write(buf);
170     fdis.close();
171     fdos.close();
172   }
173 
174   /**
175    * Create a truncated hfile and verify that exception thrown.
176    */
177   public void testCorruptTruncatedHFile() throws IOException {
178     if (cacheConf == null) cacheConf = new CacheConfig(conf);
179     Path f = new Path(ROOT_DIR, getName());
180     HFileContext  context = new HFileContextBuilder().build();
181     Writer w = HFile.getWriterFactory(conf, cacheConf).withPath(this.fs, f)
182         .withFileContext(context).create();
183     writeSomeRecords(w, 0, 100, false);
184     w.close();
185 
186     Path trunc = new Path(f.getParent(), "trucated");
187     truncateFile(fs, w.getPath(), trunc);
188 
189     try {
190       Reader r = HFile.createReader(fs, trunc, cacheConf, conf);
191     } catch (CorruptHFileException che) {
192       // Expected failure
193       return;
194     }
195     fail("Should have thrown exception");
196   }
197 
198   // write some records into the tfile
199   // write them twice
200   private int writeSomeRecords(Writer writer, int start, int n, boolean useTags)
201       throws IOException {
202     String value = "value";
203     KeyValue kv;
204     for (int i = start; i < (start + n); i++) {
205       String key = String.format(localFormatter, Integer.valueOf(i));
206       if (useTags) {
207         Tag t = new Tag((byte) 1, "myTag1");
208         Tag[] tags = new Tag[1];
209         tags[0] = t;
210         kv = new KeyValue(Bytes.toBytes(key), Bytes.toBytes("family"), Bytes.toBytes("qual"),
211             HConstants.LATEST_TIMESTAMP, Bytes.toBytes(value + key), tags);
212         writer.append(kv);
213       } else {
214         kv = new KeyValue(Bytes.toBytes(key), Bytes.toBytes("family"), Bytes.toBytes("qual"),
215             Bytes.toBytes(value + key));
216         writer.append(kv);
217       }
218     }
219     return (start + n);
220   }
221 
222   private void readAllRecords(HFileScanner scanner) throws IOException {
223     readAndCheckbytes(scanner, 0, 100);
224   }
225 
226   // read the records and check
227   private int readAndCheckbytes(HFileScanner scanner, int start, int n)
228       throws IOException {
229     String value = "value";
230     int i = start;
231     for (; i < (start + n); i++) {
232       ByteBuffer key = scanner.getKey();
233       ByteBuffer val = scanner.getValue();
234       String keyStr = String.format(localFormatter, Integer.valueOf(i));
235       String valStr = value + keyStr;
236       KeyValue kv = new KeyValue(Bytes.toBytes(keyStr), Bytes.toBytes("family"),
237           Bytes.toBytes("qual"), Bytes.toBytes(valStr));
238       byte[] keyBytes = new KeyValue.KeyOnlyKeyValue(Bytes.toBytes(key), 0,
239           Bytes.toBytes(key).length).getKey();
240       assertTrue("bytes for keys do not match " + keyStr + " " +
241         Bytes.toString(Bytes.toBytes(key)),
242           Arrays.equals(kv.getKey(), keyBytes));
243       byte [] valBytes = Bytes.toBytes(val);
244       assertTrue("bytes for vals do not match " + valStr + " " +
245         Bytes.toString(valBytes),
246         Arrays.equals(Bytes.toBytes(valStr), valBytes));
247       if (!scanner.next()) {
248         break;
249       }
250     }
251     assertEquals(i, start + n - 1);
252     return (start + n);
253   }
254 
255   private byte[] getSomeKey(int rowId) {
256     KeyValue kv = new KeyValue(String.format(localFormatter, Integer.valueOf(rowId)).getBytes(),
257         Bytes.toBytes("family"), Bytes.toBytes("qual"), HConstants.LATEST_TIMESTAMP, Type.Put);
258     return kv.getKey();
259   }
260 
261   private void writeRecords(Writer writer, boolean useTags) throws IOException {
262     writeSomeRecords(writer, 0, 100, useTags);
263     writer.close();
264   }
265 
266   private FSDataOutputStream createFSOutput(Path name) throws IOException {
267     //if (fs.exists(name)) fs.delete(name, true);
268     FSDataOutputStream fout = fs.create(name);
269     return fout;
270   }
271 
272   /**
273    * test none codecs
274    * @param useTags 
275    */
276   void basicWithSomeCodec(String codec, boolean useTags) throws IOException {
277     if (useTags) {
278       conf.setInt("hfile.format.version", 3);
279     }
280     if (cacheConf == null) cacheConf = new CacheConfig(conf);
281     Path ncTFile = new Path(ROOT_DIR, "basic.hfile." + codec.toString() + useTags);
282     FSDataOutputStream fout = createFSOutput(ncTFile);
283     HFileContext meta = new HFileContextBuilder()
284                         .withBlockSize(minBlockSize)
285                         .withCompression(AbstractHFileWriter.compressionByName(codec))
286                         .build();
287     Writer writer = HFile.getWriterFactory(conf, cacheConf)
288         .withOutputStream(fout)
289         .withFileContext(meta)
290         .withComparator(new KeyValue.KVComparator())
291         .create();
292     LOG.info(writer);
293     writeRecords(writer, useTags);
294     fout.close();
295     FSDataInputStream fin = fs.open(ncTFile);
296     Reader reader = HFile.createReaderFromStream(ncTFile, fs.open(ncTFile),
297       fs.getFileStatus(ncTFile).getLen(), cacheConf, conf);
298     System.out.println(cacheConf.toString());
299     // Load up the index.
300     reader.loadFileInfo();
301     // Get a scanner that caches and that does not use pread.
302     HFileScanner scanner = reader.getScanner(true, false);
303     // Align scanner at start of the file.
304     scanner.seekTo();
305     readAllRecords(scanner);
306     int seekTo = scanner.seekTo(KeyValue.createKeyValueFromKey(getSomeKey(50)));
307     System.out.println(seekTo);
308     assertTrue("location lookup failed",
309         scanner.seekTo(KeyValue.createKeyValueFromKey(getSomeKey(50))) == 0);
310     // read the key and see if it matches
311     ByteBuffer readKey = scanner.getKey();
312     assertTrue("seeked key does not match", Arrays.equals(getSomeKey(50),
313       Bytes.toBytes(readKey)));
314 
315     scanner.seekTo(KeyValue.createKeyValueFromKey(getSomeKey(0)));
316     ByteBuffer val1 = scanner.getValue();
317     scanner.seekTo(KeyValue.createKeyValueFromKey(getSomeKey(0)));
318     ByteBuffer val2 = scanner.getValue();
319     assertTrue(Arrays.equals(Bytes.toBytes(val1), Bytes.toBytes(val2)));
320 
321     reader.close();
322     fin.close();
323     fs.delete(ncTFile, true);
324   }
325 
326   public void testTFileFeatures() throws IOException {
327     testTFilefeaturesInternals(false);
328     testTFilefeaturesInternals(true);
329   }
330 
331   protected void testTFilefeaturesInternals(boolean useTags) throws IOException {
332     basicWithSomeCodec("none", useTags);
333     basicWithSomeCodec("gz", useTags);
334   }
335 
336   private void writeNumMetablocks(Writer writer, int n) {
337     for (int i = 0; i < n; i++) {
338       writer.appendMetaBlock("HFileMeta" + i, new Writable() {
339         private int val;
340         public Writable setVal(int val) { this.val = val; return this; }
341         
342         @Override
343         public void write(DataOutput out) throws IOException {
344           out.write(("something to test" + val).getBytes());
345         }
346         
347         @Override
348         public void readFields(DataInput in) throws IOException { }
349       }.setVal(i));
350     }
351   }
352 
353   private void someTestingWithMetaBlock(Writer writer) {
354     writeNumMetablocks(writer, 10);
355   }
356 
357   private void readNumMetablocks(Reader reader, int n) throws IOException {
358     for (int i = 0; i < n; i++) {
359       ByteBuffer actual = reader.getMetaBlock("HFileMeta" + i, false);
360       ByteBuffer expected = 
361         ByteBuffer.wrap(("something to test" + i).getBytes());
362       assertEquals("failed to match metadata",
363         Bytes.toStringBinary(expected), Bytes.toStringBinary(actual));
364     }
365   }
366 
367   private void someReadingWithMetaBlock(Reader reader) throws IOException {
368     readNumMetablocks(reader, 10);
369   }
370 
371   private void metablocks(final String compress) throws Exception {
372     if (cacheConf == null) cacheConf = new CacheConfig(conf);
373     Path mFile = new Path(ROOT_DIR, "meta.hfile");
374     FSDataOutputStream fout = createFSOutput(mFile);
375     HFileContext meta = new HFileContextBuilder()
376                         .withCompression(AbstractHFileWriter.compressionByName(compress))
377                         .withBlockSize(minBlockSize).build();
378     Writer writer = HFile.getWriterFactory(conf, cacheConf)
379         .withOutputStream(fout)
380         .withFileContext(meta)
381         .create();
382     someTestingWithMetaBlock(writer);
383     writer.close();
384     fout.close();
385     FSDataInputStream fin = fs.open(mFile);
386     Reader reader = HFile.createReaderFromStream(mFile, fs.open(mFile),
387         this.fs.getFileStatus(mFile).getLen(), cacheConf, conf);
388     reader.loadFileInfo();
389     // No data -- this should return false.
390     assertFalse(reader.getScanner(false, false).seekTo());
391     someReadingWithMetaBlock(reader);
392     fs.delete(mFile, true);
393     reader.close();
394     fin.close();
395   }
396 
397   // test meta blocks for tfiles
398   public void testMetaBlocks() throws Exception {
399     metablocks("none");
400     metablocks("gz");
401   }
402 
403   public void testNullMetaBlocks() throws Exception {
404     if (cacheConf == null) cacheConf = new CacheConfig(conf);
405     for (Compression.Algorithm compressAlgo : 
406         HBaseTestingUtility.COMPRESSION_ALGORITHMS) {
407       Path mFile = new Path(ROOT_DIR, "nometa_" + compressAlgo + ".hfile");
408       FSDataOutputStream fout = createFSOutput(mFile);
409       HFileContext meta = new HFileContextBuilder().withCompression(compressAlgo)
410                           .withBlockSize(minBlockSize).build();
411       Writer writer = HFile.getWriterFactory(conf, cacheConf)
412           .withOutputStream(fout)
413           .withFileContext(meta)
414           .create();
415       KeyValue kv = new KeyValue("foo".getBytes(), "f1".getBytes(), null, "value".getBytes());
416       writer.append(kv);
417       writer.close();
418       fout.close();
419       Reader reader = HFile.createReader(fs, mFile, cacheConf, conf);
420       reader.loadFileInfo();
421       assertNull(reader.getMetaBlock("non-existant", false));
422     }
423   }
424 
425   /**
426    * Make sure the ordinals for our compression algorithms do not change on us.
427    */
428   public void testCompressionOrdinance() {
429     assertTrue(Compression.Algorithm.LZO.ordinal() == 0);
430     assertTrue(Compression.Algorithm.GZ.ordinal() == 1);
431     assertTrue(Compression.Algorithm.NONE.ordinal() == 2);
432     assertTrue(Compression.Algorithm.SNAPPY.ordinal() == 3);
433     assertTrue(Compression.Algorithm.LZ4.ordinal() == 4);
434   }
435 
436 }
437