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  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.hadoop.conf.Configuration;
30  import org.apache.hadoop.fs.Path;
31  import org.apache.hadoop.hbase.HBaseTestCase;
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.CoprocessorEnvironment;
36  import org.apache.hadoop.hbase.Coprocessor;
37  import org.apache.hadoop.hbase.KeyValue;
38  import org.apache.hadoop.hbase.client.Scan;
39  import org.apache.hadoop.hbase.regionserver.InternalScanner;
40  import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;
41  import org.apache.hadoop.hbase.regionserver.HRegion;
42  import org.apache.hadoop.hbase.regionserver.RegionScanner;
43  import org.apache.hadoop.hbase.regionserver.SplitTransaction;
44  import org.apache.hadoop.hbase.regionserver.Store;
45  import org.apache.hadoop.hbase.regionserver.StoreFile;
46  import org.apache.hadoop.hbase.util.Bytes;
47  import org.apache.hadoop.hbase.util.PairOfSameType;
48  import org.apache.hadoop.hbase.Server;
49  import org.mockito.Mockito;
50  import org.apache.hadoop.hbase.HBaseTestingUtility;
51  import static org.mockito.Mockito.when;
52  
53  public class TestCoprocessorInterface extends HBaseTestCase {
54    static final Log LOG = LogFactory.getLog(TestCoprocessorInterface.class);
55    static final String DIR = "test/build/data/TestCoprocessorInterface/";
56    private static final HBaseTestingUtility TEST_UTIL =
57      new HBaseTestingUtility();
58  
59    private static class CustomScanner implements RegionScanner {
60  
61      private RegionScanner delegate;
62      
63      public CustomScanner(RegionScanner delegate) {
64        this.delegate = delegate;
65      }
66      
67      @Override
68      public boolean next(List<KeyValue> results) throws IOException {
69        return delegate.next(results);
70      }
71  
72      @Override
73      public boolean next(List<KeyValue> result, int limit) throws IOException {
74        return delegate.next(result, limit);
75      }
76  
77      @Override
78      public void close() throws IOException {
79        delegate.close();
80      }
81  
82      @Override
83      public HRegionInfo getRegionInfo() {
84        return delegate.getRegionInfo();
85      }
86  
87      @Override
88      public boolean isFilterDone() {
89        return delegate.isFilterDone();
90      }
91      
92    }
93    
94    public static class CoprocessorImpl extends BaseRegionObserver {
95  
96      private boolean startCalled;
97      private boolean stopCalled;
98      private boolean preOpenCalled;
99      private boolean postOpenCalled;
100     private boolean preCloseCalled;
101     private boolean postCloseCalled;
102     private boolean preCompactCalled;
103     private boolean postCompactCalled;
104     private boolean preFlushCalled;
105     private boolean postFlushCalled;
106     private boolean preSplitCalled;
107     private boolean postSplitCalled;
108 
109     @Override
110     public void start(CoprocessorEnvironment e) {
111       startCalled = true;
112     }
113 
114     @Override
115     public void stop(CoprocessorEnvironment e) {
116       stopCalled = true;
117     }
118 
119     @Override
120     public void preOpen(ObserverContext<RegionCoprocessorEnvironment> e) {
121       preOpenCalled = true;
122     }
123     @Override
124     public void postOpen(ObserverContext<RegionCoprocessorEnvironment> e) {
125       postOpenCalled = true;
126     }
127     @Override
128     public void preClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested) {
129       preCloseCalled = true;
130     }
131     @Override
132     public void postClose(ObserverContext<RegionCoprocessorEnvironment> e, boolean abortRequested) {
133       postCloseCalled = true;
134     }
135     @Override
136     public InternalScanner preCompact(ObserverContext<RegionCoprocessorEnvironment> e,
137         Store store, InternalScanner scanner) {
138       preCompactCalled = true;
139       return scanner;
140     }
141     @Override
142     public void postCompact(ObserverContext<RegionCoprocessorEnvironment> e,
143         Store store, StoreFile resultFile) {
144       postCompactCalled = true;
145     }
146     @Override
147     public void preFlush(ObserverContext<RegionCoprocessorEnvironment> e) {
148       preFlushCalled = true;
149     }
150     @Override
151     public void postFlush(ObserverContext<RegionCoprocessorEnvironment> e) {
152       postFlushCalled = true;
153     }
154     @Override
155     public void preSplit(ObserverContext<RegionCoprocessorEnvironment> e) {
156       preSplitCalled = true;
157     }
158     @Override
159     public void postSplit(ObserverContext<RegionCoprocessorEnvironment> e, HRegion l, HRegion r) {
160       postSplitCalled = true;
161     }
162 
163     @Override
164     public RegionScanner postScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> e,
165         final Scan scan, final RegionScanner s) throws IOException {
166       return new CustomScanner(s);
167     }
168 
169     boolean wasStarted() {
170       return startCalled;
171     }
172     boolean wasStopped() {
173       return stopCalled;
174     }
175     boolean wasOpened() {
176       return (preOpenCalled && postOpenCalled);
177     }
178     boolean wasClosed() {
179       return (preCloseCalled && postCloseCalled);
180     }
181     boolean wasFlushed() {
182       return (preFlushCalled && postFlushCalled);
183     }
184     boolean wasCompacted() {
185       return (preCompactCalled && postCompactCalled);
186     }
187     boolean wasSplit() {
188       return (preSplitCalled && postSplitCalled);
189     }
190   }
191 
192   public void testCoprocessorInterface() throws IOException {
193     byte [] tableName = Bytes.toBytes("testtable");
194     byte [][] families = { fam1, fam2, fam3 };
195 
196     Configuration hc = initSplit();
197     HRegion region = initHRegion(tableName, getName(), hc,
198       CoprocessorImpl.class, families);
199     for (int i = 0; i < 3; i++) {
200       addContent(region, fam3);
201       region.flushcache();
202     }
203     
204     region.compactStores();
205 
206     byte [] splitRow = region.checkSplit();
207     
208     assertNotNull(splitRow);
209     HRegion [] regions = split(region, splitRow);
210     for (int i = 0; i < regions.length; i++) {
211       regions[i] = reopenRegion(regions[i], CoprocessorImpl.class);
212     }
213     region.close();
214     region.getLog().closeAndDelete();
215     Coprocessor c = region.getCoprocessorHost().
216       findCoprocessor(CoprocessorImpl.class.getName());
217 
218     // HBASE-4197
219     Scan s = new Scan();
220     RegionScanner scanner = regions[0].getCoprocessorHost().postScannerOpen(s, regions[0].getScanner(s));
221     assertTrue(scanner instanceof CustomScanner);
222     // this would throw an exception before HBASE-4197
223     scanner.next(new ArrayList<KeyValue>());
224     
225     assertTrue("Coprocessor not started", ((CoprocessorImpl)c).wasStarted());
226     assertTrue("Coprocessor not stopped", ((CoprocessorImpl)c).wasStopped());
227     assertTrue(((CoprocessorImpl)c).wasOpened());
228     assertTrue(((CoprocessorImpl)c).wasClosed());
229     assertTrue(((CoprocessorImpl)c).wasFlushed());
230     assertTrue(((CoprocessorImpl)c).wasCompacted());
231     assertTrue(((CoprocessorImpl)c).wasSplit());
232 
233     for (int i = 0; i < regions.length; i++) {
234       regions[i].close();
235       regions[i].getLog().closeAndDelete();
236       c = region.getCoprocessorHost()
237             .findCoprocessor(CoprocessorImpl.class.getName());
238       assertTrue("Coprocessor not started", ((CoprocessorImpl)c).wasStarted());
239       assertTrue("Coprocessor not stopped", ((CoprocessorImpl)c).wasStopped());
240       assertTrue(((CoprocessorImpl)c).wasOpened());
241       assertTrue(((CoprocessorImpl)c).wasClosed());
242       assertTrue(((CoprocessorImpl)c).wasCompacted());
243     }
244   }
245 
246   HRegion reopenRegion(final HRegion closedRegion, Class<?> implClass)
247       throws IOException {
248     //HRegionInfo info = new HRegionInfo(tableName, null, null, false);
249     HRegion r = new HRegion(closedRegion.getTableDir(), closedRegion.getLog(),
250         closedRegion.getFilesystem(), closedRegion.getConf(),
251         closedRegion.getRegionInfo(), closedRegion.getTableDesc(), null);
252     r.initialize();
253 
254     // this following piece is a hack. currently a coprocessorHost
255     // is secretly loaded at OpenRegionHandler. we don't really
256     // start a region server here, so just manually create cphost
257     // and set it to region.
258     RegionCoprocessorHost host = new RegionCoprocessorHost(r, null, conf);
259     r.setCoprocessorHost(host);
260 
261     host.load(implClass, Coprocessor.PRIORITY_USER, conf);
262     // we need to manually call pre- and postOpen here since the
263     // above load() is not the real case for CP loading. A CP is
264     // expected to be loaded by default from 1) configuration; or 2)
265     // HTableDescriptor. If it's loaded after HRegion initialized,
266     // the pre- and postOpen() won't be triggered automatically.
267     // Here we have to call pre and postOpen explicitly.
268     host.preOpen();
269     host.postOpen();
270     return r;
271   }
272 
273   HRegion initHRegion (byte [] tableName, String callingMethod,
274       Configuration conf, Class<?> implClass, byte [] ... families)
275       throws IOException {
276     HTableDescriptor htd = new HTableDescriptor(tableName);
277     for(byte [] family : families) {
278       htd.addFamily(new HColumnDescriptor(family));
279     }
280     HRegionInfo info = new HRegionInfo(tableName, null, null, false);
281     Path path = new Path(DIR + callingMethod);
282     HRegion r = HRegion.createHRegion(info, path, conf, htd);
283 
284     // this following piece is a hack.
285     RegionCoprocessorHost host = new RegionCoprocessorHost(r, null, conf);
286     r.setCoprocessorHost(host);
287 
288     host.load(implClass, Coprocessor.PRIORITY_USER, conf);
289 
290     Coprocessor c = host.findCoprocessor(implClass.getName());
291     assertNotNull(c);
292 
293     // Here we have to call pre and postOpen explicitly.
294     host.preOpen();
295     host.postOpen();
296     return r;
297   }
298 
299   Configuration initSplit() {
300     // Always compact if there is more than one store file.
301     TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 2);
302     // Make lease timeout longer, lease checks less frequent
303     TEST_UTIL.getConfiguration().setInt(
304         "hbase.master.lease.thread.wakefrequency", 5 * 1000);
305     TEST_UTIL.getConfiguration().setInt(
306         "hbase.regionserver.lease.period", 10 * 1000);
307     // Increase the amount of time between client retries
308     TEST_UTIL.getConfiguration().setLong("hbase.client.pause", 15 * 1000);
309     // This size should make it so we always split using the addContent
310     // below.  After adding all data, the first region is 1.3M
311     TEST_UTIL.getConfiguration().setLong("hbase.hregion.max.filesize",
312         1024 * 128);
313     TEST_UTIL.getConfiguration().setBoolean("hbase.testing.nocluster",
314         true);
315 
316     return TEST_UTIL.getConfiguration();
317   }
318 
319   private HRegion [] split(final HRegion r, final byte [] splitRow)
320       throws IOException {
321 
322     HRegion[] regions = new HRegion[2];
323 
324     SplitTransaction st = new SplitTransaction(r, splitRow);
325     int i = 0;
326 
327     if (!st.prepare()) {
328       // test fails.
329       assertTrue(false);
330     }
331     try {
332       Server mockServer = Mockito.mock(Server.class);
333       when(mockServer.getConfiguration()).thenReturn(
334           TEST_UTIL.getConfiguration());
335       PairOfSameType<HRegion> daughters = st.execute(mockServer, null);
336       for (HRegion each_daughter: daughters) {
337         regions[i] = each_daughter;
338         i++;
339       }
340     } catch (IOException ioe) {
341       LOG.info("Split transaction of " + r.getRegionNameAsString() +
342           " failed:" + ioe.getMessage());
343       assertTrue(false);
344     } catch (RuntimeException e) {
345       LOG.info("Failed rollback of failed split of " +
346           r.getRegionNameAsString() + e.getMessage());
347     }
348 
349     assertTrue(i == 2);
350     return regions;
351   }
352 }
353 
354