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.ArrayList;
23  import java.util.List;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.hbase.HBaseTestCase;
28  import org.apache.hadoop.hbase.HBaseTestingUtility;
29  import org.apache.hadoop.hbase.HColumnDescriptor;
30  import org.apache.hadoop.hbase.HConstants;
31  import org.apache.hadoop.hbase.HRegionInfo;
32  import org.apache.hadoop.hbase.HTableDescriptor;
33  import org.apache.hadoop.hbase.KeyValue;
34  import org.apache.hadoop.hbase.SmallTests;
35  import org.apache.hadoop.hbase.TableName;
36  import org.apache.hadoop.hbase.UnknownScannerException;
37  import org.apache.hadoop.hbase.client.Delete;
38  import org.apache.hadoop.hbase.client.Get;
39  import org.apache.hadoop.hbase.client.Put;
40  import org.apache.hadoop.hbase.client.Result;
41  import org.apache.hadoop.hbase.client.Scan;
42  import org.apache.hadoop.hbase.filter.Filter;
43  import org.apache.hadoop.hbase.filter.InclusiveStopFilter;
44  import org.apache.hadoop.hbase.filter.PrefixFilter;
45  import org.apache.hadoop.hbase.filter.WhileMatchFilter;
46  import org.apache.hadoop.hbase.util.Bytes;
47  import org.junit.experimental.categories.Category;
48  
49  /**
50   * Test of a long-lived scanner validating as we go.
51   */
52  @Category(SmallTests.class)
53  public class TestScanner extends HBaseTestCase {
54    private final Log LOG = LogFactory.getLog(this.getClass());
55  
56    private static final byte [] FIRST_ROW = HConstants.EMPTY_START_ROW;
57    private static final byte [][] COLS = { HConstants.CATALOG_FAMILY };
58    private static final byte [][] EXPLICIT_COLS = {
59      HConstants.REGIONINFO_QUALIFIER, HConstants.SERVER_QUALIFIER,
60        // TODO ryan
61        //HConstants.STARTCODE_QUALIFIER
62    };
63  
64    static final HTableDescriptor TESTTABLEDESC =
65      new HTableDescriptor(TableName.valueOf("testscanner"));
66    static {
67      TESTTABLEDESC.addFamily(
68          new HColumnDescriptor(HConstants.CATALOG_FAMILY)
69              // Ten is an arbitrary number.  Keep versions to help debugging.
70              .setMaxVersions(10)
71              .setBlockCacheEnabled(false)
72              .setBlocksize(8 * 1024)
73      );
74    }
75    /** HRegionInfo for root region */
76    public static final HRegionInfo REGION_INFO =
77      new HRegionInfo(TESTTABLEDESC.getTableName(), HConstants.EMPTY_BYTE_ARRAY,
78      HConstants.EMPTY_BYTE_ARRAY);
79  
80    private static final byte [] ROW_KEY = REGION_INFO.getRegionName();
81  
82    private static final long START_CODE = Long.MAX_VALUE;
83  
84    private HRegion r;
85    private HRegionIncommon region;
86  
87    private byte[] firstRowBytes, secondRowBytes, thirdRowBytes;
88    final private byte[] col1, col2;
89  
90    public TestScanner() {
91      super();
92  
93      firstRowBytes = START_KEY_BYTES;
94      secondRowBytes = START_KEY_BYTES.clone();
95      // Increment the least significant character so we get to next row.
96      secondRowBytes[START_KEY_BYTES.length - 1]++;
97      thirdRowBytes = START_KEY_BYTES.clone();
98      thirdRowBytes[START_KEY_BYTES.length - 1] += 2;
99      col1 = Bytes.toBytes("column1");
100     col2 = Bytes.toBytes("column2");
101   }
102 
103   /**
104    * Test basic stop row filter works.
105    * @throws Exception
106    */
107   public void testStopRow() throws Exception {
108     byte [] startrow = Bytes.toBytes("bbb");
109     byte [] stoprow = Bytes.toBytes("ccc");
110     try {
111       this.r = createNewHRegion(TESTTABLEDESC, null, null);
112       addContent(this.r, HConstants.CATALOG_FAMILY);
113       List<KeyValue> results = new ArrayList<KeyValue>();
114       // Do simple test of getting one row only first.
115       Scan scan = new Scan(Bytes.toBytes("abc"), Bytes.toBytes("abd"));
116       scan.addFamily(HConstants.CATALOG_FAMILY);
117 
118       InternalScanner s = r.getScanner(scan);
119       int count = 0;
120       while (s.next(results)) {
121         count++;
122       }
123       s.close();
124       assertEquals(0, count);
125       // Now do something a bit more imvolved.
126       scan = new Scan(startrow, stoprow);
127       scan.addFamily(HConstants.CATALOG_FAMILY);
128 
129       s = r.getScanner(scan);
130       count = 0;
131       KeyValue kv = null;
132       results = new ArrayList<KeyValue>();
133       for (boolean first = true; s.next(results);) {
134         kv = results.get(0);
135         if (first) {
136           assertTrue(Bytes.BYTES_COMPARATOR.compare(startrow, kv.getRow()) == 0);
137           first = false;
138         }
139         count++;
140       }
141       assertTrue(Bytes.BYTES_COMPARATOR.compare(stoprow, kv.getRow()) > 0);
142       // We got something back.
143       assertTrue(count > 10);
144       s.close();
145     } finally {
146       HRegion.closeHRegion(this.r);
147     }
148   }
149 
150   void rowPrefixFilter(Scan scan) throws IOException {
151     List<KeyValue> results = new ArrayList<KeyValue>();
152     scan.addFamily(HConstants.CATALOG_FAMILY);
153     InternalScanner s = r.getScanner(scan);
154     boolean hasMore = true;
155     while (hasMore) {
156       hasMore = s.next(results);
157       for (KeyValue kv : results) {
158         assertEquals((byte)'a', kv.getRow()[0]);
159         assertEquals((byte)'b', kv.getRow()[1]);
160       }
161       results.clear();
162     }
163     s.close();
164   }
165 
166   void rowInclusiveStopFilter(Scan scan, byte[] stopRow) throws IOException {
167     List<KeyValue> results = new ArrayList<KeyValue>();
168     scan.addFamily(HConstants.CATALOG_FAMILY);
169     InternalScanner s = r.getScanner(scan);
170     boolean hasMore = true;
171     while (hasMore) {
172       hasMore = s.next(results);
173       for (KeyValue kv : results) {
174         assertTrue(Bytes.compareTo(kv.getRow(), stopRow) <= 0);
175       }
176       results.clear();
177     }
178     s.close();
179   }
180 
181   public void testFilters() throws IOException {
182     try {
183       this.r = createNewHRegion(TESTTABLEDESC, null, null);
184       addContent(this.r, HConstants.CATALOG_FAMILY);
185       byte [] prefix = Bytes.toBytes("ab");
186       Filter newFilter = new PrefixFilter(prefix);
187       Scan scan = new Scan();
188       scan.setFilter(newFilter);
189       rowPrefixFilter(scan);
190 
191       byte[] stopRow = Bytes.toBytes("bbc");
192       newFilter = new WhileMatchFilter(new InclusiveStopFilter(stopRow));
193       scan = new Scan();
194       scan.setFilter(newFilter);
195       rowInclusiveStopFilter(scan, stopRow);
196 
197     } finally {
198       HRegion.closeHRegion(this.r);
199     }
200   }
201 
202   /**
203    * Test that closing a scanner while a client is using it doesn't throw
204    * NPEs but instead a UnknownScannerException. HBASE-2503
205    * @throws Exception
206    */
207   public void testRaceBetweenClientAndTimeout() throws Exception {
208     try {
209       this.r = createNewHRegion(TESTTABLEDESC, null, null);
210       addContent(this.r, HConstants.CATALOG_FAMILY);
211       Scan scan = new Scan();
212       InternalScanner s = r.getScanner(scan);
213       List<KeyValue> results = new ArrayList<KeyValue>();
214       try {
215         s.next(results);
216         s.close();
217         s.next(results);
218         fail("We don't want anything more, we should be failing");
219       } catch (UnknownScannerException ex) {
220         // ok!
221         return;
222       }
223     } finally {
224       HRegion.closeHRegion(this.r);
225     }
226   }
227 
228   /** The test!
229    * @throws IOException
230    */
231   public void testScanner() throws IOException {
232     try {
233       r = createNewHRegion(TESTTABLEDESC, null, null);
234       region = new HRegionIncommon(r);
235 
236       // Write information to the meta table
237 
238       Put put = new Put(ROW_KEY, System.currentTimeMillis());
239 
240       put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
241           REGION_INFO.toByteArray());
242       region.put(put);
243 
244       // What we just committed is in the memstore. Verify that we can get
245       // it back both with scanning and get
246 
247       scan(false, null);
248       getRegionInfo();
249 
250       // Close and re-open
251 
252       r.close();
253       r = openClosedRegion(r);
254       region = new HRegionIncommon(r);
255 
256       // Verify we can get the data back now that it is on disk.
257 
258       scan(false, null);
259       getRegionInfo();
260 
261       // Store some new information
262 
263       String address = HConstants.LOCALHOST_IP + ":" + HBaseTestingUtility.randomFreePort();
264 
265       put = new Put(ROW_KEY, System.currentTimeMillis());
266       put.add(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER,
267           Bytes.toBytes(address));
268 
269 //      put.add(HConstants.COL_STARTCODE, Bytes.toBytes(START_CODE));
270 
271       region.put(put);
272 
273       // Validate that we can still get the HRegionInfo, even though it is in
274       // an older row on disk and there is a newer row in the memstore
275 
276       scan(true, address.toString());
277       getRegionInfo();
278 
279       // flush cache
280 
281       region.flushcache();
282 
283       // Validate again
284 
285       scan(true, address.toString());
286       getRegionInfo();
287 
288       // Close and reopen
289 
290       r.close();
291       r = openClosedRegion(r);
292       region = new HRegionIncommon(r);
293 
294       // Validate again
295 
296       scan(true, address.toString());
297       getRegionInfo();
298 
299       // Now update the information again
300 
301       address = "bar.foo.com:4321";
302 
303       put = new Put(ROW_KEY, System.currentTimeMillis());
304 
305       put.add(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER,
306           Bytes.toBytes(address));
307       region.put(put);
308 
309       // Validate again
310 
311       scan(true, address.toString());
312       getRegionInfo();
313 
314       // flush cache
315 
316       region.flushcache();
317 
318       // Validate again
319 
320       scan(true, address.toString());
321       getRegionInfo();
322 
323       // Close and reopen
324 
325       r.close();
326       r = openClosedRegion(r);
327       region = new HRegionIncommon(r);
328 
329       // Validate again
330 
331       scan(true, address.toString());
332       getRegionInfo();
333 
334     } finally {
335       // clean up
336       HRegion.closeHRegion(r);
337     }
338   }
339 
340   /** Compare the HRegionInfo we read from HBase to what we stored */
341   private void validateRegionInfo(byte [] regionBytes) throws IOException {
342     HRegionInfo info = HRegionInfo.parseFromOrNull(regionBytes);
343 
344     assertEquals(REGION_INFO.getRegionId(), info.getRegionId());
345     assertEquals(0, info.getStartKey().length);
346     assertEquals(0, info.getEndKey().length);
347     assertEquals(0, Bytes.compareTo(info.getRegionName(), REGION_INFO.getRegionName()));
348     //assertEquals(0, info.getTableDesc().compareTo(REGION_INFO.getTableDesc()));
349   }
350 
351   /** Use a scanner to get the region info and then validate the results */
352   private void scan(boolean validateStartcode, String serverName)
353   throws IOException {
354     InternalScanner scanner = null;
355     Scan scan = null;
356     List<KeyValue> results = new ArrayList<KeyValue>();
357     byte [][][] scanColumns = {
358         COLS,
359         EXPLICIT_COLS
360     };
361 
362     for(int i = 0; i < scanColumns.length; i++) {
363       try {
364         scan = new Scan(FIRST_ROW);
365         for (int ii = 0; ii < EXPLICIT_COLS.length; ii++) {
366           scan.addColumn(COLS[0],  EXPLICIT_COLS[ii]);
367         }
368         scanner = r.getScanner(scan);
369         while (scanner.next(results)) {
370           assertTrue(hasColumn(results, HConstants.CATALOG_FAMILY,
371               HConstants.REGIONINFO_QUALIFIER));
372           byte [] val = getColumn(results, HConstants.CATALOG_FAMILY,
373               HConstants.REGIONINFO_QUALIFIER).getValue();
374           validateRegionInfo(val);
375           if(validateStartcode) {
376 //            assertTrue(hasColumn(results, HConstants.CATALOG_FAMILY,
377 //                HConstants.STARTCODE_QUALIFIER));
378 //            val = getColumn(results, HConstants.CATALOG_FAMILY,
379 //                HConstants.STARTCODE_QUALIFIER).getValue();
380             assertNotNull(val);
381             assertFalse(val.length == 0);
382             long startCode = Bytes.toLong(val);
383             assertEquals(START_CODE, startCode);
384           }
385 
386           if(serverName != null) {
387             assertTrue(hasColumn(results, HConstants.CATALOG_FAMILY,
388                 HConstants.SERVER_QUALIFIER));
389             val = getColumn(results, HConstants.CATALOG_FAMILY,
390                 HConstants.SERVER_QUALIFIER).getValue();
391             assertNotNull(val);
392             assertFalse(val.length == 0);
393             String server = Bytes.toString(val);
394             assertEquals(0, server.compareTo(serverName));
395           }
396         }
397       } finally {
398         InternalScanner s = scanner;
399         scanner = null;
400         if(s != null) {
401           s.close();
402         }
403       }
404     }
405   }
406 
407   private boolean hasColumn(final List<KeyValue> kvs, final byte [] family,
408       final byte [] qualifier) {
409     for (KeyValue kv: kvs) {
410       if (kv.matchingFamily(family) && kv.matchingQualifier(qualifier)) {
411         return true;
412       }
413     }
414     return false;
415   }
416 
417   private KeyValue getColumn(final List<KeyValue> kvs, final byte [] family,
418       final byte [] qualifier) {
419     for (KeyValue kv: kvs) {
420       if (kv.matchingFamily(family) && kv.matchingQualifier(qualifier)) {
421         return kv;
422       }
423     }
424     return null;
425   }
426 
427 
428   /** Use get to retrieve the HRegionInfo and validate it */
429   private void getRegionInfo() throws IOException {
430     Get get = new Get(ROW_KEY);
431     get.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
432     Result result = region.get(get);
433     byte [] bytes = result.value();
434     validateRegionInfo(bytes);
435   }
436 
437   /**
438    * Tests to do a sync flush during the middle of a scan. This is testing the StoreScanner
439    * update readers code essentially.  This is not highly concurrent, since its all 1 thread.
440    * HBase-910.
441    * @throws Exception
442    */
443   public void testScanAndSyncFlush() throws Exception {
444     this.r = createNewHRegion(TESTTABLEDESC, null, null);
445     HRegionIncommon hri = new HRegionIncommon(r);
446     try {
447         LOG.info("Added: " + addContent(hri, Bytes.toString(HConstants.CATALOG_FAMILY),
448             Bytes.toString(HConstants.REGIONINFO_QUALIFIER)));
449       int count = count(hri, -1, false);
450       assertEquals(count, count(hri, 100, false)); // do a sync flush.
451     } catch (Exception e) {
452       LOG.error("Failed", e);
453       throw e;
454     } finally {
455       HRegion.closeHRegion(this.r);
456     }
457   }
458 
459   /**
460    * Tests to do a concurrent flush (using a 2nd thread) while scanning.  This tests both
461    * the StoreScanner update readers and the transition from memstore -> snapshot -> store file.
462    *
463    * @throws Exception
464    */
465   public void testScanAndRealConcurrentFlush() throws Exception {
466     this.r = createNewHRegion(TESTTABLEDESC, null, null);
467     HRegionIncommon hri = new HRegionIncommon(r);
468     try {
469         LOG.info("Added: " + addContent(hri, Bytes.toString(HConstants.CATALOG_FAMILY),
470             Bytes.toString(HConstants.REGIONINFO_QUALIFIER)));
471       int count = count(hri, -1, false);
472       assertEquals(count, count(hri, 100, true)); // do a true concurrent background thread flush
473     } catch (Exception e) {
474       LOG.error("Failed", e);
475       throw e;
476     } finally {
477       HRegion.closeHRegion(this.r);
478     }
479   }
480 
481   /**
482    * Make sure scanner returns correct result when we run a major compaction
483    * with deletes.
484    *
485    * @throws Exception
486    */
487   @SuppressWarnings("deprecation")
488   public void testScanAndConcurrentMajorCompact() throws Exception {
489     HTableDescriptor htd = createTableDescriptor(getName());
490     this.r = createNewHRegion(htd, null, null);
491     HRegionIncommon hri = new HRegionIncommon(r);
492 
493     try {
494       addContent(hri, Bytes.toString(fam1), Bytes.toString(col1),
495           firstRowBytes, secondRowBytes);
496       addContent(hri, Bytes.toString(fam2), Bytes.toString(col1),
497           firstRowBytes, secondRowBytes);
498 
499       Delete dc = new Delete(firstRowBytes);
500       /* delete column1 of firstRow */
501       dc.deleteColumns(fam1, col1);
502       r.delete(dc);
503       r.flushcache();
504 
505       addContent(hri, Bytes.toString(fam1), Bytes.toString(col1),
506           secondRowBytes, thirdRowBytes);
507       addContent(hri, Bytes.toString(fam2), Bytes.toString(col1),
508           secondRowBytes, thirdRowBytes);
509       r.flushcache();
510 
511       InternalScanner s = r.getScanner(new Scan());
512       // run a major compact, column1 of firstRow will be cleaned.
513       r.compactStores(true);
514 
515       List<KeyValue> results = new ArrayList<KeyValue>();
516       s.next(results);
517 
518       // make sure returns column2 of firstRow
519       assertTrue("result is not correct, keyValues : " + results,
520           results.size() == 1);
521       assertTrue(Bytes.BYTES_COMPARATOR.compare(firstRowBytes, results.get(0)
522           .getRow()) == 0);
523       assertTrue(Bytes.BYTES_COMPARATOR.compare(fam2, results.get(0)
524           .getFamily()) == 0);
525 
526       results = new ArrayList<KeyValue>();
527       s.next(results);
528 
529       // get secondRow
530       assertTrue(results.size() == 2);
531       assertTrue(Bytes.BYTES_COMPARATOR.compare(secondRowBytes, results.get(0)
532           .getRow()) == 0);
533       assertTrue(Bytes.BYTES_COMPARATOR.compare(fam1, results.get(0)
534           .getFamily()) == 0);
535       assertTrue(Bytes.BYTES_COMPARATOR.compare(fam2, results.get(1)
536           .getFamily()) == 0);
537     } finally {
538       HRegion.closeHRegion(this.r);
539     }
540   }
541 
542 
543   /*
544    * @param hri Region
545    * @param flushIndex At what row we start the flush.
546    * @param concurrent if the flush should be concurrent or sync.
547    * @return Count of rows found.
548    * @throws IOException
549    */
550   private int count(final HRegionIncommon hri, final int flushIndex,
551                     boolean concurrent)
552   throws IOException {
553     LOG.info("Taking out counting scan");
554     ScannerIncommon s = hri.getScanner(HConstants.CATALOG_FAMILY, EXPLICIT_COLS,
555         HConstants.EMPTY_START_ROW, HConstants.LATEST_TIMESTAMP);
556     List<KeyValue> values = new ArrayList<KeyValue>();
557     int count = 0;
558     boolean justFlushed = false;
559     while (s.next(values)) {
560       if (justFlushed) {
561         LOG.info("after next() just after next flush");
562         justFlushed=false;
563       }
564       count++;
565       if (flushIndex == count) {
566         LOG.info("Starting flush at flush index " + flushIndex);
567         Thread t = new Thread() {
568           public void run() {
569             try {
570               hri.flushcache();
571               LOG.info("Finishing flush");
572             } catch (IOException e) {
573               LOG.info("Failed flush cache");
574             }
575           }
576         };
577         if (concurrent) {
578           t.start(); // concurrently flush.
579         } else {
580           t.run(); // sync flush
581         }
582         LOG.info("Continuing on after kicking off background flush");
583         justFlushed = true;
584       }
585     }
586     s.close();
587     LOG.info("Found " + count + " items");
588     return count;
589   }
590 
591 }