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 java.io.IOException;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.SortedSet;
31  import java.util.TreeSet;
32  import java.util.UUID;
33  import java.util.concurrent.ExecutorService;
34  import java.util.concurrent.atomic.AtomicInteger;
35  
36  import com.google.protobuf.Descriptors;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.hadoop.classification.InterfaceAudience;
40  import org.apache.hadoop.classification.InterfaceStability;
41  import org.apache.hadoop.conf.Configuration;
42  import org.apache.hadoop.fs.Path;
43  import org.apache.hadoop.hbase.Abortable;
44  import org.apache.hadoop.hbase.Coprocessor;
45  import org.apache.hadoop.hbase.CoprocessorEnvironment;
46  import org.apache.hadoop.hbase.DoNotRetryIOException;
47  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
48  import org.apache.hadoop.hbase.HTableDescriptor;
49  import org.apache.hadoop.hbase.TableName;
50  import org.apache.hadoop.hbase.client.Append;
51  import org.apache.hadoop.hbase.client.CoprocessorHConnection;
52  import org.apache.hadoop.hbase.client.Delete;
53  import org.apache.hadoop.hbase.client.Durability;
54  import org.apache.hadoop.hbase.client.Get;
55  import org.apache.hadoop.hbase.client.HConnection;
56  import org.apache.hadoop.hbase.client.HTable;
57  import org.apache.hadoop.hbase.client.HTableInterface;
58  import org.apache.hadoop.hbase.client.Increment;
59  import org.apache.hadoop.hbase.client.Put;
60  import org.apache.hadoop.hbase.client.Result;
61  import org.apache.hadoop.hbase.client.ResultScanner;
62  import org.apache.hadoop.hbase.client.Row;
63  import org.apache.hadoop.hbase.client.RowMutations;
64  import org.apache.hadoop.hbase.client.Scan;
65  import org.apache.hadoop.hbase.client.coprocessor.Batch;
66  import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
67  import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
68  import org.apache.hadoop.hbase.util.Bytes;
69  import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
70  import org.apache.hadoop.hbase.util.SortedCopyOnWriteSet;
71  import org.apache.hadoop.hbase.util.VersionInfo;
72  import org.apache.hadoop.io.MultipleIOException;
73  
74  import com.google.protobuf.Descriptors.ServiceDescriptor;
75  import com.google.protobuf.Message;
76  import com.google.protobuf.Service;
77  import com.google.protobuf.ServiceException;
78  
79  /**
80   * Provides the common setup framework and runtime services for coprocessor
81   * invocation from HBase services.
82   * @param <E> the specific environment extension that a concrete implementation
83   * provides
84   */
85  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
86  @InterfaceStability.Evolving
87  public abstract class CoprocessorHost<E extends CoprocessorEnvironment> {
88    public static final String REGION_COPROCESSOR_CONF_KEY =
89        "hbase.coprocessor.region.classes";
90    public static final String REGIONSERVER_COPROCESSOR_CONF_KEY =
91        "hbase.coprocessor.regionserver.classes";
92    public static final String USER_REGION_COPROCESSOR_CONF_KEY =
93        "hbase.coprocessor.user.region.classes";
94    public static final String MASTER_COPROCESSOR_CONF_KEY =
95        "hbase.coprocessor.master.classes";
96    public static final String WAL_COPROCESSOR_CONF_KEY =
97      "hbase.coprocessor.wal.classes";
98    public static final String ABORT_ON_ERROR_KEY = "hbase.coprocessor.abortonerror";
99    public static final boolean DEFAULT_ABORT_ON_ERROR = true;
100 
101   private static final Log LOG = LogFactory.getLog(CoprocessorHost.class);
102   protected Abortable abortable;
103   /** Ordered set of loaded coprocessors with lock */
104   protected SortedSet<E> coprocessors =
105       new SortedCopyOnWriteSet<E>(new EnvironmentPriorityComparator());
106   protected Configuration conf;
107   // unique file prefix to use for local copies of jars when classloading
108   protected String pathPrefix;
109   protected AtomicInteger loadSequence = new AtomicInteger();
110 
111   public CoprocessorHost(Abortable abortable) {
112     this.abortable = abortable;
113     this.pathPrefix = UUID.randomUUID().toString();
114   }
115 
116   /**
117    * Not to be confused with the per-object _coprocessors_ (above),
118    * coprocessorNames is static and stores the set of all coprocessors ever
119    * loaded by any thread in this JVM. It is strictly additive: coprocessors are
120    * added to coprocessorNames, by loadInstance() but are never removed, since
121    * the intention is to preserve a history of all loaded coprocessors for
122    * diagnosis in case of server crash (HBASE-4014).
123    */
124   private static Set<String> coprocessorNames =
125       Collections.synchronizedSet(new HashSet<String>());
126   public static Set<String> getLoadedCoprocessors() {
127       return coprocessorNames;
128   }
129 
130   /**
131    * Used to create a parameter to the HServerLoad constructor so that
132    * HServerLoad can provide information about the coprocessors loaded by this
133    * regionserver.
134    * (HBASE-4070: Improve region server metrics to report loaded coprocessors
135    * to master).
136    */
137   public Set<String> getCoprocessors() {
138     Set<String> returnValue = new TreeSet<String>();
139     for(CoprocessorEnvironment e: coprocessors) {
140       returnValue.add(e.getInstance().getClass().getSimpleName());
141     }
142     return returnValue;
143   }
144 
145   /**
146    * Load system coprocessors. Read the class names from configuration.
147    * Called by constructor.
148    */
149   protected void loadSystemCoprocessors(Configuration conf, String confKey) {
150     Class<?> implClass = null;
151 
152     // load default coprocessors from configure file
153     String[] defaultCPClasses = conf.getStrings(confKey);
154     if (defaultCPClasses == null || defaultCPClasses.length == 0)
155       return;
156 
157     int priority = Coprocessor.PRIORITY_SYSTEM;
158     List<E> configured = new ArrayList<E>();
159     for (String className : defaultCPClasses) {
160       className = className.trim();
161       if (findCoprocessor(className) != null) {
162         continue;
163       }
164       ClassLoader cl = this.getClass().getClassLoader();
165       Thread.currentThread().setContextClassLoader(cl);
166       try {
167         implClass = cl.loadClass(className);
168         configured.add(loadInstance(implClass, Coprocessor.PRIORITY_SYSTEM, conf));
169         LOG.info("System coprocessor " + className + " was loaded " +
170             "successfully with priority (" + priority++ + ").");
171       } catch (Throwable t) {
172         // We always abort if system coprocessors cannot be loaded
173         abortServer(className, t);
174       }
175     }
176 
177     // add entire set to the collection for COW efficiency
178     coprocessors.addAll(configured);
179   }
180 
181   /**
182    * Load a coprocessor implementation into the host
183    * @param path path to implementation jar
184    * @param className the main class name
185    * @param priority chaining priority
186    * @param conf configuration for coprocessor
187    * @throws java.io.IOException Exception
188    */
189   public E load(Path path, String className, int priority,
190       Configuration conf) throws IOException {
191     Class<?> implClass = null;
192     LOG.debug("Loading coprocessor class " + className + " with path " +
193         path + " and priority " + priority);
194 
195     ClassLoader cl = null;
196     if (path == null) {
197       try {
198         implClass = getClass().getClassLoader().loadClass(className);
199       } catch (ClassNotFoundException e) {
200         throw new IOException("No jar path specified for " + className);
201       }
202     } else {
203       cl = CoprocessorClassLoader.getClassLoader(
204         path, getClass().getClassLoader(), pathPrefix, conf);
205       try {
206         implClass = cl.loadClass(className);
207       } catch (ClassNotFoundException e) {
208         throw new IOException("Cannot load external coprocessor class " + className, e);
209       }
210     }
211 
212     //load custom code for coprocessor
213     Thread currentThread = Thread.currentThread();
214     ClassLoader hostClassLoader = currentThread.getContextClassLoader();
215     try{
216       // switch temporarily to the thread classloader for custom CP
217       currentThread.setContextClassLoader(cl);
218       E cpInstance = loadInstance(implClass, priority, conf);
219       return cpInstance;
220     } finally {
221       // restore the fresh (host) classloader
222       currentThread.setContextClassLoader(hostClassLoader);
223     }
224   }
225 
226   /**
227    * @param implClass Implementation class
228    * @param priority priority
229    * @param conf configuration
230    * @throws java.io.IOException Exception
231    */
232   public void load(Class<?> implClass, int priority, Configuration conf)
233       throws IOException {
234     E env = loadInstance(implClass, priority, conf);
235     coprocessors.add(env);
236   }
237 
238   /**
239    * @param implClass Implementation class
240    * @param priority priority
241    * @param conf configuration
242    * @throws java.io.IOException Exception
243    */
244   public E loadInstance(Class<?> implClass, int priority, Configuration conf)
245       throws IOException {
246     if (!Coprocessor.class.isAssignableFrom(implClass)) {
247       throw new IOException("Configured class " + implClass.getName() + " must implement "
248           + Coprocessor.class.getName() + " interface ");
249     }
250 
251     // create the instance
252     Coprocessor impl;
253     Object o = null;
254     try {
255       o = implClass.newInstance();
256       impl = (Coprocessor)o;
257     } catch (InstantiationException e) {
258       throw new IOException(e);
259     } catch (IllegalAccessException e) {
260       throw new IOException(e);
261     }
262     // create the environment
263     E env = createEnvironment(implClass, impl, priority, loadSequence.incrementAndGet(), conf);
264     if (env instanceof Environment) {
265       ((Environment)env).startup();
266     }
267     // HBASE-4014: maintain list of loaded coprocessors for later crash analysis
268     // if server (master or regionserver) aborts.
269     coprocessorNames.add(implClass.getName());
270     return env;
271   }
272 
273   /**
274    * Called when a new Coprocessor class is loaded
275    */
276   public abstract E createEnvironment(Class<?> implClass, Coprocessor instance,
277       int priority, int sequence, Configuration conf);
278 
279   public void shutdown(CoprocessorEnvironment e) {
280     if (e instanceof Environment) {
281       ((Environment)e).shutdown();
282     } else {
283       LOG.warn("Shutdown called on unknown environment: "+
284           e.getClass().getName());
285     }
286   }
287 
288   /**
289    * Find a coprocessor implementation by class name
290    * @param className the class name
291    * @return the coprocessor, or null if not found
292    */
293   public Coprocessor findCoprocessor(String className) {
294     for (E env: coprocessors) {
295       if (env.getInstance().getClass().getName().equals(className) ||
296           env.getInstance().getClass().getSimpleName().equals(className)) {
297         return env.getInstance();
298       }
299     }
300     return null;
301   }
302 
303   /**
304    * Find a coprocessor environment by class name
305    * @param className the class name
306    * @return the coprocessor, or null if not found
307    */
308   public CoprocessorEnvironment findCoprocessorEnvironment(String className) {
309     for (E env: coprocessors) {
310       if (env.getInstance().getClass().getName().equals(className) ||
311           env.getInstance().getClass().getSimpleName().equals(className)) {
312         return env;
313       }
314     }
315     return null;
316   }
317 
318   /**
319    * Retrieves the set of classloaders used to instantiate Coprocessor classes defined in external
320    * jar files.
321    * @return A set of ClassLoader instances
322    */
323   Set<ClassLoader> getExternalClassLoaders() {
324     Set<ClassLoader> externalClassLoaders = new HashSet<ClassLoader>();
325     final ClassLoader systemClassLoader = this.getClass().getClassLoader();
326     for (E env : coprocessors) {
327       ClassLoader cl = env.getInstance().getClass().getClassLoader();
328       if (cl != systemClassLoader ){
329         //do not include system classloader
330         externalClassLoaders.add(cl);
331       }
332     }
333     return externalClassLoaders;
334   }
335 
336   /**
337    * Environment priority comparator.
338    * Coprocessors are chained in sorted order.
339    */
340   static class EnvironmentPriorityComparator
341       implements Comparator<CoprocessorEnvironment> {
342     public int compare(final CoprocessorEnvironment env1,
343         final CoprocessorEnvironment env2) {
344       if (env1.getPriority() < env2.getPriority()) {
345         return -1;
346       } else if (env1.getPriority() > env2.getPriority()) {
347         return 1;
348       }
349       if (env1.getLoadSequence() < env2.getLoadSequence()) {
350         return -1;
351       } else if (env1.getLoadSequence() > env2.getLoadSequence()) {
352         return 1;
353       }
354       return 0;
355     }
356   }
357 
358   /**
359    * Encapsulation of the environment of each coprocessor
360    */
361   public static class Environment implements CoprocessorEnvironment {
362 
363     /**
364      * A wrapper for HTable. Can be used to restrict privilege.
365      *
366      * Currently it just helps to track tables opened by a Coprocessor and
367      * facilitate close of them if it is aborted.
368      *
369      * We also disallow row locking.
370      *
371      * There is nothing now that will stop a coprocessor from using HTable
372      * objects directly instead of this API, but in the future we intend to
373      * analyze coprocessor implementations as they are loaded and reject those
374      * which attempt to use objects and methods outside the Environment
375      * sandbox.
376      */
377     class HTableWrapper implements HTableInterface {
378 
379       private TableName tableName;
380       private HTable table;
381       private HConnection connection;
382 
383       public HTableWrapper(TableName tableName, HConnection connection, ExecutorService pool)
384           throws IOException {
385         this.tableName = tableName;
386         this.table = new HTable(tableName, connection, pool);
387         this.connection = connection;
388         openTables.add(this);
389       }
390 
391       void internalClose() throws IOException {
392         List<IOException> exceptions = new ArrayList<IOException>(2);
393         try {
394         table.close();
395         } catch (IOException e) {
396           exceptions.add(e);
397         }
398         try {
399           // have to self-manage our connection, as per the HTable contract
400           if (this.connection != null) {
401             this.connection.close();
402           }
403         } catch (IOException e) {
404           exceptions.add(e);
405         }
406         if (!exceptions.isEmpty()) {
407           throw MultipleIOException.createIOException(exceptions);
408         }
409       }
410 
411       public Configuration getConfiguration() {
412         return table.getConfiguration();
413       }
414 
415       public void close() throws IOException {
416         try {
417           internalClose();
418         } finally {
419           openTables.remove(this);
420         }
421       }
422 
423       public Result getRowOrBefore(byte[] row, byte[] family)
424           throws IOException {
425         return table.getRowOrBefore(row, family);
426       }
427 
428       public Result get(Get get) throws IOException {
429         return table.get(get);
430       }
431 
432       public boolean exists(Get get) throws IOException {
433         return table.exists(get);
434       }
435 
436       public Boolean[] exists(List<Get> gets) throws IOException{
437         return table.exists(gets);
438       }
439 
440       public void put(Put put) throws IOException {
441         table.put(put);
442       }
443 
444       public void put(List<Put> puts) throws IOException {
445         table.put(puts);
446       }
447 
448       public void delete(Delete delete) throws IOException {
449         table.delete(delete);
450       }
451 
452       public void delete(List<Delete> deletes) throws IOException {
453         table.delete(deletes);
454       }
455 
456       public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
457           byte[] value, Put put) throws IOException {
458         return table.checkAndPut(row, family, qualifier, value, put);
459       }
460 
461       public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
462           byte[] value, Delete delete) throws IOException {
463         return table.checkAndDelete(row, family, qualifier, value, delete);
464       }
465 
466       public long incrementColumnValue(byte[] row, byte[] family,
467           byte[] qualifier, long amount) throws IOException {
468         return table.incrementColumnValue(row, family, qualifier, amount);
469       }
470 
471       public long incrementColumnValue(byte[] row, byte[] family,
472           byte[] qualifier, long amount, Durability durability)
473           throws IOException {
474         return table.incrementColumnValue(row, family, qualifier, amount,
475             durability);
476       }
477 
478       @Override
479       public Result append(Append append) throws IOException {
480         return table.append(append);
481       }
482 
483       @Override
484       public Result increment(Increment increment) throws IOException {
485         return table.increment(increment);
486       }
487 
488       public void flushCommits() throws IOException {
489         table.flushCommits();
490       }
491 
492       public boolean isAutoFlush() {
493         return table.isAutoFlush();
494       }
495 
496       public ResultScanner getScanner(Scan scan) throws IOException {
497         return table.getScanner(scan);
498       }
499 
500       public ResultScanner getScanner(byte[] family) throws IOException {
501         return table.getScanner(family);
502       }
503 
504       public ResultScanner getScanner(byte[] family, byte[] qualifier)
505           throws IOException {
506         return table.getScanner(family, qualifier);
507       }
508 
509       public HTableDescriptor getTableDescriptor() throws IOException {
510         return table.getTableDescriptor();
511       }
512 
513       @Override
514       public byte[] getTableName() {
515         return tableName.getName();
516       }
517 
518       @Override
519       public TableName getName() {
520         return table.getName();
521       }
522 
523       @Override
524       public void batch(List<? extends Row> actions, Object[] results)
525           throws IOException, InterruptedException {
526         table.batch(actions, results);
527       }
528 
529       /**
530        * {@inheritDoc}
531        * @deprecated If any exception is thrown by one of the actions, there is no way to
532        * retrieve the partially executed results. Use {@link #batch(List, Object[])} instead.
533        */
534       @Override
535       public Object[] batch(List<? extends Row> actions)
536           throws IOException, InterruptedException {
537         return table.batch(actions);
538       }
539 
540       @Override
541       public <R> void batchCallback(List<? extends Row> actions, Object[] results,
542           Batch.Callback<R> callback) throws IOException, InterruptedException {
543         table.batchCallback(actions, results, callback);
544       }
545 
546       /**
547        * {@inheritDoc}
548        * @deprecated If any exception is thrown by one of the actions, there is no way to
549        * retrieve the partially executed results. Use 
550        * {@link #batchCallback(List, Object[], org.apache.hadoop.hbase.client.coprocessor.Batch.Callback)}
551        * instead.
552        */
553       @Override
554       public <R> Object[] batchCallback(List<? extends Row> actions,
555           Batch.Callback<R> callback) throws IOException, InterruptedException {
556         return table.batchCallback(actions, callback);
557       }
558 
559       @Override
560       public Result[] get(List<Get> gets) throws IOException {
561         return table.get(gets);
562       }
563 
564       @Override
565       public CoprocessorRpcChannel coprocessorService(byte[] row) {
566         return table.coprocessorService(row);
567       }
568 
569       @Override
570       public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service,
571           byte[] startKey, byte[] endKey, Batch.Call<T, R> callable)
572           throws ServiceException, Throwable {
573         return table.coprocessorService(service, startKey, endKey, callable);
574       }
575 
576       @Override
577       public <T extends Service, R> void coprocessorService(Class<T> service,
578           byte[] startKey, byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
579           throws ServiceException, Throwable {
580         table.coprocessorService(service, startKey, endKey, callable, callback);
581       }
582 
583       @Override
584       public void mutateRow(RowMutations rm) throws IOException {
585         table.mutateRow(rm);
586       }
587 
588       @Override
589       public void setAutoFlush(boolean autoFlush) {
590         table.setAutoFlush(autoFlush, autoFlush);
591       }
592 
593       @Override
594       public void setAutoFlush(boolean autoFlush, boolean clearBufferOnFail) {
595         table.setAutoFlush(autoFlush, clearBufferOnFail);
596       }
597 
598       @Override
599       public void setAutoFlushTo(boolean autoFlush) {
600         table.setAutoFlushTo(autoFlush);
601       }
602 
603       @Override
604       public long getWriteBufferSize() {
605          return table.getWriteBufferSize();
606       }
607 
608       @Override
609       public void setWriteBufferSize(long writeBufferSize) throws IOException {
610         table.setWriteBufferSize(writeBufferSize);
611       }
612 
613       @Override
614       public long incrementColumnValue(byte[] row, byte[] family,
615           byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
616         return table.incrementColumnValue(row, family, qualifier, amount, writeToWAL);
617       }
618 
619       @Override
620       public <R extends Message> Map<byte[], R> batchCoprocessorService(
621           Descriptors.MethodDescriptor method, Message request, byte[] startKey,
622           byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
623         return table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype);
624       }
625 
626       @Override
627       public <R extends Message> void batchCoprocessorService(Descriptors.MethodDescriptor method,
628           Message request, byte[] startKey, byte[] endKey, R responsePrototype,
629           Callback<R> callback) throws ServiceException, Throwable {
630         table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype,
631             callback);
632       }
633     }
634 
635     /** The coprocessor */
636     public Coprocessor impl;
637     /** Chaining priority */
638     protected int priority = Coprocessor.PRIORITY_USER;
639     /** Current coprocessor state */
640     Coprocessor.State state = Coprocessor.State.UNINSTALLED;
641     /** Accounting for tables opened by the coprocessor */
642     protected List<HTableInterface> openTables =
643       Collections.synchronizedList(new ArrayList<HTableInterface>());
644     private int seq;
645     private Configuration conf;
646 
647     /**
648      * Constructor
649      * @param impl the coprocessor instance
650      * @param priority chaining priority
651      */
652     public Environment(final Coprocessor impl, final int priority,
653         final int seq, final Configuration conf) {
654       this.impl = impl;
655       this.priority = priority;
656       this.state = Coprocessor.State.INSTALLED;
657       this.seq = seq;
658       this.conf = conf;
659     }
660 
661     /** Initialize the environment */
662     public void startup() throws IOException {
663       if (state == Coprocessor.State.INSTALLED ||
664           state == Coprocessor.State.STOPPED) {
665         state = Coprocessor.State.STARTING;
666         Thread currentThread = Thread.currentThread();
667         ClassLoader hostClassLoader = currentThread.getContextClassLoader();
668         try {
669           currentThread.setContextClassLoader(this.getClassLoader());
670           impl.start(this);
671           state = Coprocessor.State.ACTIVE;
672         } finally {
673           currentThread.setContextClassLoader(hostClassLoader);
674         }
675       } else {
676         LOG.warn("Not starting coprocessor "+impl.getClass().getName()+
677             " because not inactive (state="+state.toString()+")");
678       }
679     }
680 
681     /** Clean up the environment */
682     protected void shutdown() {
683       if (state == Coprocessor.State.ACTIVE) {
684         state = Coprocessor.State.STOPPING;
685         Thread currentThread = Thread.currentThread();
686         ClassLoader hostClassLoader = currentThread.getContextClassLoader();
687         try {
688           currentThread.setContextClassLoader(this.getClassLoader());
689           impl.stop(this);
690           state = Coprocessor.State.STOPPED;
691         } catch (IOException ioe) {
692           LOG.error("Error stopping coprocessor "+impl.getClass().getName(), ioe);
693         } finally {
694           currentThread.setContextClassLoader(hostClassLoader);
695         }
696       } else {
697         LOG.warn("Not stopping coprocessor "+impl.getClass().getName()+
698             " because not active (state="+state.toString()+")");
699       }
700       // clean up any table references
701       for (HTableInterface table: openTables) {
702         try {
703           ((HTableWrapper)table).internalClose();
704         } catch (IOException e) {
705           // nothing can be done here
706           LOG.warn("Failed to close " +
707               Bytes.toStringBinary(table.getTableName()), e);
708         }
709       }
710     }
711 
712     @Override
713     public Coprocessor getInstance() {
714       return impl;
715     }
716 
717     @Override
718     public ClassLoader getClassLoader() {
719       return impl.getClass().getClassLoader();
720     }
721 
722     @Override
723     public int getPriority() {
724       return priority;
725     }
726 
727     @Override
728     public int getLoadSequence() {
729       return seq;
730     }
731 
732     /** @return the coprocessor environment version */
733     @Override
734     public int getVersion() {
735       return Coprocessor.VERSION;
736     }
737 
738     /** @return the HBase release */
739     @Override
740     public String getHBaseVersion() {
741       return VersionInfo.getVersion();
742     }
743 
744     @Override
745     public Configuration getConfiguration() {
746       return conf;
747     }
748 
749     /**
750      * Open a table from within the Coprocessor environment
751      * @param tableName the table name
752      * @return an interface for manipulating the table
753      * @exception java.io.IOException Exception
754      */
755     @Override
756     public HTableInterface getTable(TableName tableName) throws IOException {
757       return this.getTable(tableName, HTable.getDefaultExecutor(getConfiguration()));
758     }
759 
760     /**
761      * Open a table from within the Coprocessor environment
762      * @param tableName the table name
763      * @return an interface for manipulating the table
764      * @exception java.io.IOException Exception
765      */
766     @Override
767     public HTableInterface getTable(TableName tableName, ExecutorService pool) throws IOException {
768       return new HTableWrapper(tableName, CoprocessorHConnection.getConnectionForEnvironment(this),
769           pool);
770     }
771   }
772 
773   protected void abortServer(final CoprocessorEnvironment environment, final Throwable e) {
774     abortServer(environment.getInstance().getClass().getName(), e);
775   }
776 
777   protected void abortServer(final String coprocessorName, final Throwable e) {
778     String message = "The coprocessor " + coprocessorName + " threw an unexpected exception";
779     LOG.error(message, e);
780     if (abortable != null) {
781       abortable.abort(message, e);
782     } else {
783       LOG.warn("No available Abortable, process was not aborted");
784     }
785   }
786 
787   /**
788    * This is used by coprocessor hooks which are declared to throw IOException
789    * (or its subtypes). For such hooks, we should handle throwable objects
790    * depending on the Throwable's type. Those which are instances of
791    * IOException should be passed on to the client. This is in conformance with
792    * the HBase idiom regarding IOException: that it represents a circumstance
793    * that should be passed along to the client for its own handling. For
794    * example, a coprocessor that implements access controls would throw a
795    * subclass of IOException, such as AccessDeniedException, in its preGet()
796    * method to prevent an unauthorized client's performing a Get on a particular
797    * table.
798    * @param env Coprocessor Environment
799    * @param e Throwable object thrown by coprocessor.
800    * @exception IOException Exception
801    */
802   protected void handleCoprocessorThrowable(final CoprocessorEnvironment env, final Throwable e)
803       throws IOException {
804     if (e instanceof IOException) {
805       throw (IOException)e;
806     }
807     // If we got here, e is not an IOException. A loaded coprocessor has a
808     // fatal bug, and the server (master or regionserver) should remove the
809     // faulty coprocessor from its set of active coprocessors. Setting
810     // 'hbase.coprocessor.abortonerror' to true will cause abortServer(),
811     // which may be useful in development and testing environments where
812     // 'failing fast' for error analysis is desired.
813     if (env.getConfiguration().getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
814       // server is configured to abort.
815       abortServer(env, e);
816     } else {
817       LOG.error("Removing coprocessor '" + env.toString() + "' from " +
818           "environment because it threw:  " + e,e);
819       coprocessors.remove(env);
820       try {
821         shutdown(env);
822       } catch (Exception x) {
823         LOG.error("Uncaught exception when shutting down coprocessor '"
824             + env.toString() + "'", x);
825       }
826       throw new DoNotRetryIOException("Coprocessor: '" + env.toString() +
827           "' threw: '" + e + "' and has been removed from the active " +
828           "coprocessor set.", e);
829     }
830   }
831 }