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