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.util.List;
23  import java.util.Random;
24  
25  import org.apache.hadoop.conf.Configuration;
26  import org.apache.hadoop.fs.FileStatus;
27  import org.apache.hadoop.fs.FileSystem;
28  import org.apache.hadoop.fs.Path;
29  import org.apache.hadoop.hbase.Cell;
30  import org.apache.hadoop.hbase.CellUtil;
31  import org.apache.hadoop.hbase.HBaseTestingUtility;
32  import org.apache.hadoop.hbase.HColumnDescriptor;
33  import org.apache.hadoop.hbase.HRegionInfo;
34  import org.apache.hadoop.hbase.HTableDescriptor;
35  import org.apache.hadoop.hbase.TableName;
36  import org.apache.hadoop.hbase.client.*;
37  import org.apache.hadoop.hbase.io.hfile.CorruptHFileException;
38  import org.apache.hadoop.hbase.io.hfile.TestHFile;
39  import org.apache.hadoop.hbase.mob.MobConstants;
40  import org.apache.hadoop.hbase.mob.MobUtils;
41  import org.apache.hadoop.hbase.testclassification.MediumTests;
42  import org.apache.hadoop.hbase.util.Bytes;
43  import org.apache.hadoop.hbase.util.FSUtils;
44  import org.apache.hadoop.hbase.util.HFileArchiveUtil;
45  import org.junit.AfterClass;
46  import org.junit.Assert;
47  import org.junit.BeforeClass;
48  import org.junit.Test;
49  import org.junit.experimental.categories.Category;
50  
51  @Category(MediumTests.class)
52  public class TestMobStoreScanner {
53  
54    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
55    private final static byte [] row1 = Bytes.toBytes("row1");
56    private final static byte [] row2 = Bytes.toBytes("row2");
57    private final static byte [] family = Bytes.toBytes("family");
58    private final static byte [] qf1 = Bytes.toBytes("qualifier1");
59    private final static byte [] qf2 = Bytes.toBytes("qualifier2");
60    protected final byte[] qf3 = Bytes.toBytes("qualifier3");
61    private static HTable table;
62    private static HBaseAdmin admin;
63    private static HColumnDescriptor hcd;
64    private static HTableDescriptor desc;
65    private static Random random = new Random();
66    private static long defaultThreshold = 10;
67    private FileSystem fs;
68    private Configuration conf;
69  
70    @BeforeClass
71    public static void setUpBeforeClass() throws Exception {
72      TEST_UTIL.getConfiguration().setInt("hfile.format.version", 3);
73      TEST_UTIL.getConfiguration().setInt("hbase.master.info.port", 0);
74      TEST_UTIL.getConfiguration().setBoolean("hbase.regionserver.info.port.auto", true);
75      TEST_UTIL.getConfiguration().setInt("hbase.client.keyvalue.maxsize", 100 * 1024 * 1024);
76  
77      TEST_UTIL.startMiniCluster(1);
78    }
79  
80    @AfterClass
81    public static void tearDownAfterClass() throws Exception {
82      TEST_UTIL.shutdownMiniCluster();
83    }
84  
85    public void setUp(long threshold, TableName tn) throws Exception {
86      conf = TEST_UTIL.getConfiguration();
87      fs = FileSystem.get(conf);
88      desc = new HTableDescriptor(tn);
89      hcd = new HColumnDescriptor(family);
90      hcd.setMobEnabled(true);
91      hcd.setMobThreshold(threshold);
92      hcd.setMaxVersions(4);
93      desc.addFamily(hcd);
94      admin = new HBaseAdmin(TEST_UTIL.getConfiguration());
95      admin.createTable(desc);
96      table = new HTable(TEST_UTIL.getConfiguration(), tn);
97    }
98  
99    /**
100    * Generate the mob value.
101    *
102    * @param size the size of the value
103    * @return the mob value generated
104    */
105   private static byte[] generateMobValue(int size) {
106     byte[] mobVal = new byte[size];
107     random.nextBytes(mobVal);
108     return mobVal;
109   }
110 
111   /**
112    * Set the scan attribute
113    *
114    * @param reversed if true, scan will be backward order
115    * @param mobScanRaw if true, scan will get the mob reference
116    * @return this
117    */
118   public void setScan(Scan scan, boolean reversed, boolean mobScanRaw) {
119     scan.setReversed(reversed);
120     scan.setMaxVersions(4);
121     if(mobScanRaw) {
122       scan.setAttribute(MobConstants.MOB_SCAN_RAW, Bytes.toBytes(Boolean.TRUE));
123     }
124   }
125 
126   @Test
127   public void testMobStoreScanner() throws Exception {
128 	  testGetFromFiles(false);
129 	  testGetFromMemStore(false);
130     testGetReferences(false);
131     testMobThreshold(false);
132     testGetFromArchive(false);
133   }
134 
135   @Test
136   public void testReversedMobStoreScanner() throws Exception {
137 	  testGetFromFiles(true);
138 	  testGetFromMemStore(true);
139     testGetReferences(true);
140     testMobThreshold(true);
141     testGetFromArchive(true);
142   }
143 
144   @Test(timeout=60000)
145   public void testGetMassive() throws Exception {
146     setUp(defaultThreshold, TableName.valueOf("testGetMassive"));
147 
148     // Put some data 5 10, 15, 20  mb ok  (this would be right below protobuf default max size of 64MB.
149     // 25, 30, 40 fail.  these is above protobuf max size of 64MB
150     byte[] bigValue = new byte[25*1024*1024];
151 
152     Put put = new Put(row1);
153     put.add(family, qf1, bigValue);
154     put.add(family, qf2, bigValue);
155     put.add(family, qf3, bigValue);
156     table.put(put);
157 
158     Get g = new Get(row1);
159     Result r = table.get(g);
160     // should not have blown up.
161   }
162 
163   @Test
164   public void testReadPt() throws Exception {
165     TableName tn = TableName.valueOf("testReadPt");
166     setUp(0L, tn);
167     long ts = System.currentTimeMillis();
168     byte[] value1 = Bytes.toBytes("value1");
169     Put put1 = new Put(row1);
170     put1.addColumn(family, qf1, ts, value1);
171     table.put(put1);
172     Put put2 = new Put(row2);
173     byte[] value2 = Bytes.toBytes("value2");
174     put2.addColumn(family, qf1, ts, value2);
175     table.put(put2);
176 
177     Scan scan = new Scan();
178     scan.setCaching(1);
179     ResultScanner rs = table.getScanner(scan);
180 
181     Put put3 = new Put(row1);
182     byte[] value3 = Bytes.toBytes("value3");
183     put3.addColumn(family, qf1, ts, value3);
184     table.put(put3);
185     Put put4 = new Put(row2);
186     byte[] value4 = Bytes.toBytes("value4");
187     put4.addColumn(family, qf1, ts, value4);
188     table.put(put4);
189     Result result = rs.next();
190     Cell cell = result.getColumnLatestCell(family, qf1);
191     Assert.assertEquals("value1", Bytes.toString(cell.getValue()));
192 
193     admin.flush(tn);
194     result = rs.next();
195     cell = result.getColumnLatestCell(family, qf1);
196     Assert.assertEquals("value2", Bytes.toString(cell.getValue()));
197   }
198 
199   @Test
200   public void testReadFromCorruptMobFilesWithReadEmptyValueOnMobCellMiss() throws Exception {
201     TableName tn = TableName.valueOf("testReadFromCorruptMobFilesWithReadEmptyValueOnMobCellMiss");
202     setUp(0, tn);
203     createRecordAndCorruptMobFile(tn, row1, family, qf1, Bytes.toBytes("value1"));
204     Get get = new Get(row1);
205     get.setAttribute(MobConstants.EMPTY_VALUE_ON_MOBCELL_MISS, Bytes.toBytes(true));
206     Result result = table.get(get);
207     Cell cell = result.getColumnLatestCell(family, qf1);
208     Assert.assertEquals(0, CellUtil.cloneValue(cell).length);
209   }
210 
211   @Test
212   public void testReadFromCorruptMobFiles() throws Exception {
213     TableName tn = TableName.valueOf("testReadFromCorruptMobFiles");
214     setUp(0, tn);
215     createRecordAndCorruptMobFile(tn, row1, family, qf1, Bytes.toBytes("value1"));
216     Get get = new Get(row1);
217     IOException ioe = null;
218     try {
219       table.get(get);
220     } catch (IOException e) {
221       ioe = e;
222     }
223     Assert.assertNotNull(ioe);
224     Assert.assertEquals(CorruptHFileException.class.getName(), ioe.getClass().getName());
225   }
226 
227   private void createRecordAndCorruptMobFile(TableName tn, byte[] row, byte[] family, byte[] qf,
228     byte[] value) throws IOException {
229     Put put1 = new Put(row);
230     put1.addColumn(family, qf, value);
231     table.put(put1);
232     admin.flush(tn);
233     Path mobFile = getFlushedMobFile(conf, fs, tn, Bytes.toString(family));
234     Assert.assertNotNull(mobFile);
235     // create new corrupt mob file.
236     Path corruptFile = new Path(mobFile.getParent(), "dummy");
237     TestHFile.truncateFile(fs, mobFile, corruptFile);
238     fs.delete(mobFile, true);
239     fs.rename(corruptFile, mobFile);
240   }
241 
242   private Path getFlushedMobFile(Configuration conf, FileSystem fs, TableName table, String family)
243     throws IOException {
244     Path regionDir = MobUtils.getMobRegionPath(conf, table);
245     Path famDir = new Path(regionDir, family);
246     FileStatus[] hfFss = fs.listStatus(famDir);
247     for (FileStatus hfs : hfFss) {
248       if (!hfs.isDirectory()) {
249         return hfs.getPath();
250       }
251     }
252     return null;
253   }
254 
255   private void testGetFromFiles(boolean reversed) throws Exception {
256     TableName tn = TableName.valueOf("testGetFromFiles" + reversed);
257     setUp(defaultThreshold, tn);
258     long ts1 = System.currentTimeMillis();
259     long ts2 = ts1 + 1;
260     long ts3 = ts1 + 2;
261     byte [] value = generateMobValue((int)defaultThreshold+1);
262 
263     Put put1 = new Put(row1);
264     put1.add(family, qf1, ts3, value);
265     put1.add(family, qf2, ts2, value);
266     put1.add(family, qf3, ts1, value);
267     table.put(put1);
268 
269     table.flushCommits();
270     admin.flush(tn);
271 
272     Scan scan = new Scan();
273     setScan(scan, reversed, false);
274 
275     ResultScanner results = table.getScanner(scan);
276     int count = 0;
277     for (Result res : results) {
278       List<Cell> cells = res.listCells();
279       for(Cell cell : cells) {
280         // Verify the value
281         Assert.assertEquals(Bytes.toString(value),
282             Bytes.toString(CellUtil.cloneValue(cell)));
283         count++;
284       }
285     }
286     results.close();
287     Assert.assertEquals(3, count);
288   }
289 
290   private void testGetFromMemStore(boolean reversed) throws Exception {
291     setUp(defaultThreshold, TableName.valueOf("testGetFromMemStore" + reversed));
292     long ts1 = System.currentTimeMillis();
293     long ts2 = ts1 + 1;
294     long ts3 = ts1 + 2;
295     byte [] value = generateMobValue((int)defaultThreshold+1);;
296 
297     Put put1 = new Put(row1);
298     put1.add(family, qf1, ts3, value);
299     put1.add(family, qf2, ts2, value);
300     put1.add(family, qf3, ts1, value);
301     table.put(put1);
302 
303     Scan scan = new Scan();
304     setScan(scan, reversed, false);
305 
306     ResultScanner results = table.getScanner(scan);
307     int count = 0;
308     for (Result res : results) {
309       List<Cell> cells = res.listCells();
310       for(Cell cell : cells) {
311         // Verify the value
312         Assert.assertEquals(Bytes.toString(value),
313             Bytes.toString(CellUtil.cloneValue(cell)));
314         count++;
315       }
316     }
317     results.close();
318     Assert.assertEquals(3, count);
319   }
320 
321   public void testGetReferences(boolean reversed) throws Exception {
322     TableName tn = TableName.valueOf("testGetReferences" + reversed);
323     setUp(defaultThreshold, tn);
324     long ts1 = System.currentTimeMillis();
325     long ts2 = ts1 + 1;
326     long ts3 = ts1 + 2;
327     byte [] value = generateMobValue((int)defaultThreshold+1);;
328 
329     Put put1 = new Put(row1);
330     put1.add(family, qf1, ts3, value);
331     put1.add(family, qf2, ts2, value);
332     put1.add(family, qf3, ts1, value);
333     table.put(put1);
334 
335     table.flushCommits();
336     admin.flush(tn);
337 
338     Scan scan = new Scan();
339     setScan(scan, reversed, true);
340 
341     ResultScanner results = table.getScanner(scan);
342     int count = 0;
343     for (Result res : results) {
344       List<Cell> cells = res.listCells();
345       for(Cell cell : cells) {
346         // Verify the value
347         assertIsMobReference(cell, row1, family, value, tn);
348         count++;
349       }
350     }
351     results.close();
352     Assert.assertEquals(3, count);
353   }
354 
355   private void testMobThreshold(boolean reversed) throws Exception {
356     TableName tn = TableName.valueOf("testMobThreshold" + reversed);
357     setUp(defaultThreshold, tn);
358     byte [] valueLess = generateMobValue((int)defaultThreshold-1);
359     byte [] valueEqual = generateMobValue((int)defaultThreshold);
360     byte [] valueGreater = generateMobValue((int)defaultThreshold+1);
361     long ts1 = System.currentTimeMillis();
362     long ts2 = ts1 + 1;
363     long ts3 = ts1 + 2;
364 
365     Put put1 = new Put(row1);
366     put1.add(family, qf1, ts3, valueLess);
367     put1.add(family, qf2, ts2, valueEqual);
368     put1.add(family, qf3, ts1, valueGreater);
369     table.put(put1);
370 
371     table.flushCommits();
372     admin.flush(tn);
373 
374     Scan scan = new Scan();
375     setScan(scan, reversed, true);
376 
377     Cell cellLess= null;
378     Cell cellEqual = null;
379     Cell cellGreater = null;
380     ResultScanner results = table.getScanner(scan);
381     int count = 0;
382     for (Result res : results) {
383       List<Cell> cells = res.listCells();
384       for(Cell cell : cells) {
385         // Verify the value
386         String qf = Bytes.toString(CellUtil.cloneQualifier(cell));
387         if(qf.equals(Bytes.toString(qf1))) {
388           cellLess = cell;
389         }
390         if(qf.equals(Bytes.toString(qf2))) {
391           cellEqual = cell;
392         }
393         if(qf.equals(Bytes.toString(qf3))) {
394           cellGreater = cell;
395         }
396         count++;
397       }
398     }
399     Assert.assertEquals(3, count);
400     assertNotMobReference(cellLess, row1, family, valueLess);
401     assertNotMobReference(cellEqual, row1, family, valueEqual);
402     assertIsMobReference(cellGreater, row1, family, valueGreater, tn);
403     results.close();
404   }
405 
406   private void testGetFromArchive(boolean reversed) throws Exception {
407     TableName tn = TableName.valueOf("testGetFromArchive" + reversed);
408     setUp(defaultThreshold, tn);
409     long ts1 = System.currentTimeMillis();
410     long ts2 = ts1 + 1;
411     long ts3 = ts1 + 2;
412     byte [] value = generateMobValue((int)defaultThreshold+1);;
413     // Put some data
414     Put put1 = new Put(row1);
415     put1.add(family, qf1, ts3, value);
416     put1.add(family, qf2, ts2, value);
417     put1.add(family, qf3, ts1, value);
418     table.put(put1);
419 
420     table.flushCommits();
421     admin.flush(tn);
422 
423     // Get the files in the mob path
424     Path mobFamilyPath;
425     mobFamilyPath = new Path(MobUtils.getMobRegionPath(TEST_UTIL.getConfiguration(), tn),
426       hcd.getNameAsString());
427     FileSystem fs = FileSystem.get(TEST_UTIL.getConfiguration());
428     FileStatus[] files = fs.listStatus(mobFamilyPath);
429 
430     // Get the archive path
431     Path rootDir = FSUtils.getRootDir(TEST_UTIL.getConfiguration());
432     Path tableDir = FSUtils.getTableDir(rootDir, tn);
433     HRegionInfo regionInfo = MobUtils.getMobRegionInfo(tn);
434     Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(TEST_UTIL.getConfiguration(),
435         regionInfo, tableDir, family);
436 
437     // Move the files from mob path to archive path
438     fs.mkdirs(storeArchiveDir);
439     int fileCount = 0;
440     for(FileStatus file : files) {
441       fileCount++;
442       Path filePath = file.getPath();
443       Path src = new Path(mobFamilyPath, filePath.getName());
444       Path dst = new Path(storeArchiveDir, filePath.getName());
445       fs.rename(src, dst);
446     }
447 
448     // Verify the moving success
449     FileStatus[] files1 = fs.listStatus(mobFamilyPath);
450     Assert.assertEquals(0, files1.length);
451     FileStatus[] files2 = fs.listStatus(storeArchiveDir);
452     Assert.assertEquals(fileCount, files2.length);
453 
454     // Scan from archive
455     Scan scan = new Scan();
456     setScan(scan, reversed, false);
457     ResultScanner results = table.getScanner(scan);
458     int count = 0;
459     for (Result res : results) {
460       List<Cell> cells = res.listCells();
461       for(Cell cell : cells) {
462         // Verify the value
463         Assert.assertEquals(Bytes.toString(value),
464             Bytes.toString(CellUtil.cloneValue(cell)));
465         count++;
466       }
467     }
468     results.close();
469     Assert.assertEquals(3, count);
470   }
471 
472   /**
473    * Assert the value is not store in mob.
474    */
475   private static void assertNotMobReference(Cell cell, byte[] row, byte[] family,
476       byte[] value) throws IOException {
477     Assert.assertEquals(Bytes.toString(row),
478         Bytes.toString(CellUtil.cloneRow(cell)));
479     Assert.assertEquals(Bytes.toString(family),
480         Bytes.toString(CellUtil.cloneFamily(cell)));
481     Assert.assertTrue(Bytes.toString(value).equals(
482         Bytes.toString(CellUtil.cloneValue(cell))));
483   }
484 
485   /**
486    * Assert the value is store in mob.
487    */
488   private static void assertIsMobReference(Cell cell, byte[] row, byte[] family,
489       byte[] value, TableName tn) throws IOException {
490     Assert.assertEquals(Bytes.toString(row),
491         Bytes.toString(CellUtil.cloneRow(cell)));
492     Assert.assertEquals(Bytes.toString(family),
493         Bytes.toString(CellUtil.cloneFamily(cell)));
494     Assert.assertFalse(Bytes.toString(value).equals(
495         Bytes.toString(CellUtil.cloneValue(cell))));
496     byte[] referenceValue = CellUtil.cloneValue(cell);
497     String fileName = Bytes.toString(referenceValue, Bytes.SIZEOF_INT,
498         referenceValue.length - Bytes.SIZEOF_INT);
499     int valLen = Bytes.toInt(referenceValue, 0, Bytes.SIZEOF_INT);
500     Assert.assertEquals(value.length, valLen);
501     Path mobFamilyPath;
502     mobFamilyPath = new Path(MobUtils.getMobRegionPath(TEST_UTIL.getConfiguration(),
503         tn), hcd.getNameAsString());
504     Path targetPath = new Path(mobFamilyPath, fileName);
505     FileSystem fs = FileSystem.get(TEST_UTIL.getConfiguration());
506     Assert.assertTrue(fs.exists(targetPath));
507   }
508 }