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  
20  package org.apache.hadoop.hbase.coprocessor;
21  
22  import static org.mockito.Mockito.when;
23  
24  import java.io.IOException;
25  import java.util.ArrayList;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.concurrent.ConcurrentMap;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.hbase.Coprocessor;
35  import org.apache.hadoop.hbase.CoprocessorEnvironment;
36  import org.apache.hadoop.hbase.TableName;
37  import org.apache.hadoop.hbase.HBaseTestCase;
38  import org.apache.hadoop.hbase.HBaseTestingUtility;
39  import org.apache.hadoop.hbase.HColumnDescriptor;
40  import org.apache.hadoop.hbase.HConstants;
41  import org.apache.hadoop.hbase.HRegionInfo;
42  import org.apache.hadoop.hbase.HTableDescriptor;
43  import org.apache.hadoop.hbase.KeyValue;
44  import org.apache.hadoop.hbase.Server;
45  import org.apache.hadoop.hbase.SmallTests;
46  import org.apache.hadoop.hbase.client.Get;
47  import org.apache.hadoop.hbase.client.Scan;
48  import org.apache.hadoop.hbase.regionserver.HRegion;
49  import org.apache.hadoop.hbase.regionserver.InternalScanner;
50  import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
51  import org.apache.hadoop.hbase.regionserver.RegionScanner;
52  import org.apache.hadoop.hbase.regionserver.ScanType;
53  import org.apache.hadoop.hbase.regionserver.SplitTransaction;
54  import org.apache.hadoop.hbase.regionserver.Store;
55  import org.apache.hadoop.hbase.regionserver.StoreFile;
56  import org.apache.hadoop.hbase.util.PairOfSameType;
57  import org.junit.experimental.categories.Category;
58  import org.mockito.Mockito;
59  
60  @Category(SmallTests.class)
61  public class TestCoprocessorInterface extends HBaseTestCase {
62    static final Log LOG = LogFactory.getLog(TestCoprocessorInterface.class);
63    private static final HBaseTestingUtility TEST_UTIL =
64      new HBaseTestingUtility();
65    static final Path DIR = TEST_UTIL.getDataTestDir();
66  
67    private static class CustomScanner implements RegionScanner {
68  
69      private RegionScanner delegate;
70  
71      public CustomScanner(RegionScanner delegate) {
72        this.delegate = delegate;
73      }
74  
75      @Override
76      public boolean next(List<KeyValue> results) throws IOException {
77        return delegate.next(results);
78      }
79  
80      @Override
81      public boolean next(List<KeyValue> result, int limit) throws IOException {
82        return delegate.next(result, limit);
83      }
84  
85      @Override
86      public boolean nextRaw(List<KeyValue> result) 
87          throws IOException {
88        return delegate.nextRaw(result);
89      }
90  
91      @Override
92      public boolean nextRaw(List<KeyValue> result, int limit)
93          throws IOException {
94        return delegate.nextRaw(result, limit);
95      }
96  
97      @Override
98      public void close() throws IOException {
99        delegate.close();
100     }
101 
102     @Override
103     public HRegionInfo getRegionInfo() {
104       return delegate.getRegionInfo();
105     }
106 
107     @Override
108     public boolean isFilterDone() throws IOException {
109       return delegate.isFilterDone();
110     }
111 
112     @Override
113     public boolean reseek(byte[] row) throws IOException {
114       return false;
115     }
116 
117     @Override
118     public long getMaxResultSize() {
119       return delegate.getMaxResultSize();
120     }
121 
122     @Override
123     public long getMvccReadPoint() {
124       return delegate.getMvccReadPoint();
125     }
126   }
127 
128   public static class CoprocessorImpl extends BaseRegionObserver {
129 
130     private boolean startCalled;
131     private boolean stopCalled;
132     private boolean preOpenCalled;
133     private boolean postOpenCalled;
134     private boolean preCloseCalled;
135     private boolean postCloseCalled;
136     private boolean preCompactCalled;
137     private boolean postCompactCalled;
138     private boolean preFlushCalled;
139     private boolean postFlushCalled;
140     private boolean preSplitCalled;
141     private boolean postSplitCalled;
142     private boolean preSplitWithSplitRowCalled;
143     private ConcurrentMap<String, Object> sharedData;
144 
145     @Override
146     public void start(CoprocessorEnvironment e) {
147       sharedData = ((RegionCoprocessorEnvironment)e).getSharedData();
148       // using new String here, so that there will be new object on each invocation
149       sharedData.putIfAbsent("test1", new Object());
150       startCalled = true;
151     }
152 
153     @Override
154     public void stop(CoprocessorEnvironment e) {
155       sharedData = null;
156       stopCalled = true;
157     }
158 
159     @Override
160     public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e) {
161       preOpenCalled = true;
162     }
163     @Override
164     public void postOpen(ObserverContext<RegionCoprocessorEnvironment> e) {
165       postOpenCalled = true;
166     }
167     @Override
168     public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested) {
169       preCloseCalled = true;
170     }
171     @Override
172     public void postClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested) {
173       postCloseCalled = true;
174     }
175     @Override
176     public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
177         Store store, InternalScanner scanner, ScanType scanType) {
178       preCompactCalled = true;
179       return scanner;
180     }
181     @Override
182     public void postCompact(ObserverContext<RegionCoprocessorEnvironment> e,
183         Store store, StoreFile resultFile) {
184       postCompactCalled = true;
185     }
186     @Override
187     public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) {
188       preFlushCalled = true;
189     }
190     @Override
191     public void postFlush(ObserverContext<RegionCoprocessorEnvironment> e) {
192       postFlushCalled = true;
193     }
194     @Override
195     public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) {
196       preSplitCalled = true;
197     }
198     
199     @Override
200     public void preSplit(ObserverContext<RegionCoprocessorEnvironment> c,
201         byte[] splitRow) throws IOException {
202       preSplitWithSplitRowCalled = true;
203     }
204     @Override
205     public void postSplit(ObserverContext<RegionCoprocessorEnvironment> e, HRegion l, HRegion r) {
206       postSplitCalled = true;
207     }
208 
209     @Override
210     public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> e,
211         final Scan scan, final RegionScanner s) throws IOException {
212       return new CustomScanner(s);
213     }
214 
215     boolean wasStarted() {
216       return startCalled;
217     }
218     boolean wasStopped() {
219       return stopCalled;
220     }
221     boolean wasOpened() {
222       return (preOpenCalled && postOpenCalled);
223     }
224     boolean wasClosed() {
225       return (preCloseCalled && postCloseCalled);
226     }
227     boolean wasFlushed() {
228       return (preFlushCalled && postFlushCalled);
229     }
230     boolean wasCompacted() {
231       return (preCompactCalled && postCompactCalled);
232     }
233     boolean wasSplit() {
234       return (preSplitCalled && postSplitCalled && preSplitWithSplitRowCalled);
235     }
236     Map<String, Object> getSharedData() {
237       return sharedData;
238     }
239   }
240 
241   public static class CoprocessorII extends BaseRegionObserver {
242     private ConcurrentMap<String, Object> sharedData;
243     @Override
244     public void start(CoprocessorEnvironment e) {
245       sharedData = ((RegionCoprocessorEnvironment)e).getSharedData();
246       sharedData.putIfAbsent("test2", new Object());
247     }
248     @Override
249     public void stop(CoprocessorEnvironment e) {
250       sharedData = null;
251     }
252     @Override
253     public void preGet(final ObserverContext<RegionCoprocessorEnvironment> e,
254         final Get get, final List<KeyValue> results) throws IOException {
255       if (1/0 == 1) {
256         e.complete();
257       }
258     }
259 
260     Map<String, Object> getSharedData() {
261       return sharedData;
262     }
263   }
264 
265   public void testSharedData() throws IOException {
266     TableName tableName =
267         TableName.valueOf("testtable");
268     byte [][] families = { fam1, fam2, fam3 };
269 
270     Configuration hc = initSplit();
271     HRegion region = initHRegion(tableName, getName(), hc,
272       new Class<?>[]{}, families);
273 
274     for (int i = 0; i < 3; i++) {
275       addContent(region, fam3);
276       region.flushcache();
277     }
278 
279     region.compactStores();
280 
281     byte [] splitRow = region.checkSplit();
282 
283     assertNotNull(splitRow);
284     HRegion [] regions = split(region, splitRow);
285     for (int i = 0; i < regions.length; i++) {
286       regions[i] = reopenRegion(regions[i], CoprocessorImpl.class, CoprocessorII.class);
287     }
288     Coprocessor c = regions[0].getCoprocessorHost().
289         findCoprocessor(CoprocessorImpl.class.getName());
290     Coprocessor c2 = regions[0].getCoprocessorHost().
291         findCoprocessor(CoprocessorII.class.getName());
292     Object o = ((CoprocessorImpl)c).getSharedData().get("test1");
293     Object o2 = ((CoprocessorII)c2).getSharedData().get("test2");
294     assertNotNull(o);
295     assertNotNull(o2);
296     // to coprocessors get different sharedDatas
297     assertFalse(((CoprocessorImpl)c).getSharedData() == ((CoprocessorII)c2).getSharedData());
298     for (int i = 1; i < regions.length; i++) {
299       c = regions[i].getCoprocessorHost().
300           findCoprocessor(CoprocessorImpl.class.getName());
301       c2 = regions[i].getCoprocessorHost().
302           findCoprocessor(CoprocessorII.class.getName());
303       // make sure that all coprocessor of a class have identical sharedDatas
304       assertTrue(((CoprocessorImpl)c).getSharedData().get("test1") == o);
305       assertTrue(((CoprocessorII)c2).getSharedData().get("test2") == o2);
306     }
307     // now have all Environments fail
308     for (int i = 0; i < regions.length; i++) {
309       try {
310         byte [] r = regions[i].getStartKey();
311         if (r == null || r.length <= 0) {
312           // Its the start row.  Can't ask for null.  Ask for minimal key instead.
313           r = new byte [] {0};
314         }
315         Get g = new Get(r);
316         regions[i].get(g);
317         fail();
318       } catch (org.apache.hadoop.hbase.DoNotRetryIOException xc) {
319       }
320       assertNull(regions[i].getCoprocessorHost().
321           findCoprocessor(CoprocessorII.class.getName()));
322     }
323     c = regions[0].getCoprocessorHost().
324         findCoprocessor(CoprocessorImpl.class.getName());
325     assertTrue(((CoprocessorImpl)c).getSharedData().get("test1") == o);
326     c = c2 = null;
327     // perform a GC
328     System.gc();
329     // reopen the region
330     region = reopenRegion(regions[0], CoprocessorImpl.class, CoprocessorII.class);
331     c = region.getCoprocessorHost().
332         findCoprocessor(CoprocessorImpl.class.getName());
333     // CPimpl is unaffected, still the same reference
334     assertTrue(((CoprocessorImpl)c).getSharedData().get("test1") == o);
335     c2 = region.getCoprocessorHost().
336         findCoprocessor(CoprocessorII.class.getName());
337     // new map and object created, hence the reference is different
338     // hence the old entry was indeed removed by the GC and new one has been created
339     Object o3 = ((CoprocessorII)c2).getSharedData().get("test2");
340     assertFalse(o3 == o2);
341   }
342 
343   public void testCoprocessorInterface() throws IOException {
344     TableName tableName =
345         TableName.valueOf("testtable");
346     byte [][] families = { fam1, fam2, fam3 };
347 
348     Configuration hc = initSplit();
349     HRegion region = initHRegion(tableName, getName(), hc,
350       new Class<?>[]{CoprocessorImpl.class}, families);
351     for (int i = 0; i < 3; i++) {
352       addContent(region, fam3);
353       region.flushcache();
354     }
355 
356     region.compactStores();
357 
358     byte [] splitRow = region.checkSplit();
359 
360     assertNotNull(splitRow);
361     HRegion [] regions = split(region, splitRow);
362     for (int i = 0; i < regions.length; i++) {
363       regions[i] = reopenRegion(regions[i], CoprocessorImpl.class);
364     }
365     HRegion.closeHRegion(region);
366     Coprocessor c = region.getCoprocessorHost().
367       findCoprocessor(CoprocessorImpl.class.getName());
368 
369     // HBASE-4197
370     Scan s = new Scan();
371     RegionScanner scanner = regions[0].getCoprocessorHost().postScannerOpen(s, regions[0].getScanner(s));
372     assertTrue(scanner instanceof CustomScanner);
373     // this would throw an exception before HBASE-4197
374     scanner.next(new ArrayList<KeyValue>());
375 
376     assertTrue("Coprocessor not started", ((CoprocessorImpl)c).wasStarted());
377     assertTrue("Coprocessor not stopped", ((CoprocessorImpl)c).wasStopped());
378     assertTrue(((CoprocessorImpl)c).wasOpened());
379     assertTrue(((CoprocessorImpl)c).wasClosed());
380     assertTrue(((CoprocessorImpl)c).wasFlushed());
381     assertTrue(((CoprocessorImpl)c).wasCompacted());
382     assertTrue(((CoprocessorImpl)c).wasSplit());
383 
384     for (int i = 0; i < regions.length; i++) {
385       HRegion.closeHRegion(regions[i]);
386       c = region.getCoprocessorHost()
387             .findCoprocessor(CoprocessorImpl.class.getName());
388       assertTrue("Coprocessor not started", ((CoprocessorImpl)c).wasStarted());
389       assertTrue("Coprocessor not stopped", ((CoprocessorImpl)c).wasStopped());
390       assertTrue(((CoprocessorImpl)c).wasOpened());
391       assertTrue(((CoprocessorImpl)c).wasClosed());
392       assertTrue(((CoprocessorImpl)c).wasCompacted());
393     }
394   }
395 
396   HRegion reopenRegion(final HRegion closedRegion, Class<?> ... implClasses)
397       throws IOException {
398     //HRegionInfo info = new HRegionInfo(tableName, null, null, false);
399     HRegion r = HRegion.openHRegion(closedRegion, null);
400 
401     // this following piece is a hack. currently a coprocessorHost
402     // is secretly loaded at OpenRegionHandler. we don't really
403     // start a region server here, so just manually create cphost
404     // and set it to region.
405     RegionCoprocessorHost host = new RegionCoprocessorHost(r, null, conf);
406     r.setCoprocessorHost(host);
407 
408     for (Class<?> implClass : implClasses) {
409       host.load(implClass, Coprocessor.PRIORITY_USER, conf);
410     }
411     // we need to manually call pre- and postOpen here since the
412     // above load() is not the real case for CP loading. A CP is
413     // expected to be loaded by default from 1) configuration; or 2)
414     // HTableDescriptor. If it's loaded after HRegion initialized,
415     // the pre- and postOpen() won't be triggered automatically.
416     // Here we have to call pre and postOpen explicitly.
417     host.preOpen();
418     host.postOpen();
419     return r;
420   }
421 
422   HRegion initHRegion (TableName tableName, String callingMethod,
423       Configuration conf, Class<?> [] implClasses, byte [][] families)
424       throws IOException {
425     HTableDescriptor htd = new HTableDescriptor(tableName);
426     for(byte [] family : families) {
427       htd.addFamily(new HColumnDescriptor(family));
428     }
429     HRegionInfo info = new HRegionInfo(tableName, null, null, false);
430     Path path = new Path(DIR + callingMethod);
431     HRegion r = HRegion.createHRegion(info, path, conf, htd);
432 
433     // this following piece is a hack.
434     RegionCoprocessorHost host = new RegionCoprocessorHost(r, null, conf);
435     r.setCoprocessorHost(host);
436 
437     for (Class<?> implClass : implClasses) {
438       host.load(implClass, Coprocessor.PRIORITY_USER, conf);
439       Coprocessor c = host.findCoprocessor(implClass.getName());
440       assertNotNull(c);
441     }
442 
443     // Here we have to call pre and postOpen explicitly.
444     host.preOpen();
445     host.postOpen();
446     return r;
447   }
448 
449   Configuration initSplit() {
450     // Always compact if there is more than one store file.
451     TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 2);
452     // Make lease timeout longer, lease checks less frequent
453     TEST_UTIL.getConfiguration().setInt(
454         "hbase.master.lease.thread.wakefrequency", 5 * 1000);
455     TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_SCANNER_TIMEOUT_PERIOD, 10 * 1000);
456     // Increase the amount of time between client retries
457     TEST_UTIL.getConfiguration().setLong("hbase.client.pause", 15 * 1000);
458     // This size should make it so we always split using the addContent
459     // below.  After adding all data, the first region is 1.3M
460     TEST_UTIL.getConfiguration().setLong(HConstants.HREGION_MAX_FILESIZE,
461         1024 * 128);
462     TEST_UTIL.getConfiguration().setBoolean("hbase.testing.nocluster",
463         true);
464 
465     return TEST_UTIL.getConfiguration();
466   }
467 
468   private HRegion [] split(final HRegion r, final byte [] splitRow)
469       throws IOException {
470 
471     HRegion[] regions = new HRegion[2];
472 
473     SplitTransaction st = new SplitTransaction(r, splitRow);
474     int i = 0;
475 
476     if (!st.prepare()) {
477       // test fails.
478       assertTrue(false);
479     }
480     try {
481       Server mockServer = Mockito.mock(Server.class);
482       when(mockServer.getConfiguration()).thenReturn(
483           TEST_UTIL.getConfiguration());
484       PairOfSameType<HRegion> daughters = st.execute(mockServer, null);
485       for (HRegion each_daughter: daughters) {
486         regions[i] = each_daughter;
487         i++;
488       }
489     } catch (IOException ioe) {
490       LOG.info("Split transaction of " + r.getRegionNameAsString() +
491           " failed:" + ioe.getMessage());
492       assertTrue(false);
493     } catch (RuntimeException e) {
494       LOG.info("Failed rollback of failed split of " +
495           r.getRegionNameAsString() + e.getMessage());
496     }
497 
498     assertTrue(i == 2);
499     return regions;
500   }
501 
502 }
503 
504 
505