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 static org.mockito.Mockito.when;
24  
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.concurrent.ConcurrentMap;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.hbase.*;
36  import org.apache.hadoop.hbase.client.Get;
37  import org.apache.hadoop.hbase.client.Scan;
38  import org.apache.hadoop.hbase.regionserver.InternalScanner;
39  import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
40  import org.apache.hadoop.hbase.regionserver.HRegion;
41  import org.apache.hadoop.hbase.regionserver.RegionScanner;
42  import org.apache.hadoop.hbase.regionserver.SplitTransaction;
43  import org.apache.hadoop.hbase.regionserver.Store;
44  import org.apache.hadoop.hbase.regionserver.StoreFile;
45  import org.apache.hadoop.hbase.util.Bytes;
46  import org.apache.hadoop.hbase.util.PairOfSameType;
47  import org.junit.experimental.categories.Category;
48  import org.mockito.Mockito;
49  
50  @Category(SmallTests.class)
51  public class TestCoprocessorInterface extends HBaseTestCase {
52    static final Log LOG = LogFactory.getLog(TestCoprocessorInterface.class);
53    private static final HBaseTestingUtility TEST_UTIL =
54      new HBaseTestingUtility();
55    static final Path DIR = TEST_UTIL.getDataTestDir();
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);
384     r.initialize();
385 
386     // this following piece is a hack. currently a coprocessorHost
387     // is secretly loaded at OpenRegionHandler. we don't really
388     // start a region server here, so just manually create cphost
389     // and set it to region.
390     RegionCoprocessorHost host = new RegionCoprocessorHost(r, null, conf);
391     r.setCoprocessorHost(host);
392 
393     for (Class<?> implClass : implClasses) {
394       host.load(implClass, Coprocessor.PRIORITY_USER, conf);
395     }
396     // we need to manually call pre- and postOpen here since the
397     // above load() is not the real case for CP loading. A CP is
398     // expected to be loaded by default from 1) configuration; or 2)
399     // HTableDescriptor. If it's loaded after HRegion initialized,
400     // the pre- and postOpen() won't be triggered automatically.
401     // Here we have to call pre and postOpen explicitly.
402     host.preOpen();
403     host.postOpen();
404     return r;
405   }
406 
407   HRegion initHRegion (byte [] tableName, String callingMethod,
408       Configuration conf, Class<?> [] implClasses, byte [][] families)
409       throws IOException {
410     HTableDescriptor htd = new HTableDescriptor(tableName);
411     for(byte [] family : families) {
412       htd.addFamily(new HColumnDescriptor(family));
413     }
414     HRegionInfo info = new HRegionInfo(tableName, null, null, false);
415     Path path = new Path(DIR + callingMethod);
416     HRegion r = HRegion.createHRegion(info, path, conf, htd);
417 
418     // this following piece is a hack.
419     RegionCoprocessorHost host = new RegionCoprocessorHost(r, null, conf);
420     r.setCoprocessorHost(host);
421 
422     for (Class<?> implClass : implClasses) {
423       host.load(implClass, Coprocessor.PRIORITY_USER, conf);
424       Coprocessor c = host.findCoprocessor(implClass.getName());
425       assertNotNull(c);
426     }
427 
428     // Here we have to call pre and postOpen explicitly.
429     host.preOpen();
430     host.postOpen();
431     return r;
432   }
433 
434   Configuration initSplit() {
435     // Always compact if there is more than one store file.
436     TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 2);
437     // Make lease timeout longer, lease checks less frequent
438     TEST_UTIL.getConfiguration().setInt(
439         "hbase.master.lease.thread.wakefrequency", 5 * 1000);
440     TEST_UTIL.getConfiguration().setInt(
441         "hbase.regionserver.lease.period", 10 * 1000);
442     // Increase the amount of time between client retries
443     TEST_UTIL.getConfiguration().setLong("hbase.client.pause", 15 * 1000);
444     // This size should make it so we always split using the addContent
445     // below.  After adding all data, the first region is 1.3M
446     TEST_UTIL.getConfiguration().setLong(HConstants.HREGION_MAX_FILESIZE,
447         1024 * 128);
448     TEST_UTIL.getConfiguration().setBoolean("hbase.testing.nocluster",
449         true);
450 
451     return TEST_UTIL.getConfiguration();
452   }
453 
454   private HRegion [] split(final HRegion r, final byte [] splitRow)
455       throws IOException {
456 
457     HRegion[] regions = new HRegion[2];
458 
459     SplitTransaction st = new SplitTransaction(r, splitRow);
460     int i = 0;
461 
462     if (!st.prepare()) {
463       // test fails.
464       assertTrue(false);
465     }
466     try {
467       Server mockServer = Mockito.mock(Server.class);
468       when(mockServer.getConfiguration()).thenReturn(
469           TEST_UTIL.getConfiguration());
470       PairOfSameType<HRegion> daughters = st.execute(mockServer, null);
471       for (HRegion each_daughter: daughters) {
472         regions[i] = each_daughter;
473         i++;
474       }
475     } catch (IOException ioe) {
476       LOG.info("Split transaction of " + r.getRegionNameAsString() +
477           " failed:" + ioe.getMessage());
478       assertTrue(false);
479     } catch (RuntimeException e) {
480       LOG.info("Failed rollback of failed split of " +
481           r.getRegionNameAsString() + e.getMessage());
482     }
483 
484     assertTrue(i == 2);
485     return regions;
486   }
487 
488   @org.junit.Rule
489   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
490     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
491 }
492 
493 
494