1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38 import org.apache.hadoop.conf.Configuration;
39 import org.apache.hadoop.fs.Path;
40 import org.apache.hadoop.hbase.Abortable;
41 import org.apache.hadoop.hbase.Coprocessor;
42 import org.apache.hadoop.hbase.CoprocessorEnvironment;
43 import org.apache.hadoop.hbase.DoNotRetryIOException;
44 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
45 import org.apache.hadoop.hbase.HTableDescriptor;
46 import org.apache.hadoop.hbase.TableName;
47 import org.apache.hadoop.hbase.classification.InterfaceAudience;
48 import org.apache.hadoop.hbase.classification.InterfaceStability;
49 import org.apache.hadoop.hbase.client.Append;
50 import org.apache.hadoop.hbase.client.CoprocessorHConnection;
51 import org.apache.hadoop.hbase.client.Delete;
52 import org.apache.hadoop.hbase.client.Durability;
53 import org.apache.hadoop.hbase.client.Get;
54 import org.apache.hadoop.hbase.client.HConnection;
55 import org.apache.hadoop.hbase.client.HTable;
56 import org.apache.hadoop.hbase.client.HTableInterface;
57 import org.apache.hadoop.hbase.client.Increment;
58 import org.apache.hadoop.hbase.client.Put;
59 import org.apache.hadoop.hbase.client.Result;
60 import org.apache.hadoop.hbase.client.ResultScanner;
61 import org.apache.hadoop.hbase.client.Row;
62 import org.apache.hadoop.hbase.client.RowMutations;
63 import org.apache.hadoop.hbase.client.Scan;
64 import org.apache.hadoop.hbase.client.coprocessor.Batch;
65 import org.apache.hadoop.hbase.client.coprocessor.Batch.Callback;
66 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
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;
75 import com.google.protobuf.Message;
76 import com.google.protobuf.Service;
77 import com.google.protobuf.ServiceException;
78
79
80
81
82
83
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 protected static final Log LOG = LogFactory.getLog(CoprocessorHost.class);
102 protected Abortable abortable;
103
104 protected SortedSet<E> coprocessors =
105 new SortedCopyOnWriteSet<E>(new EnvironmentPriorityComparator());
106 protected Configuration conf;
107
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
118
119
120
121
122
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
132
133
134
135
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
147
148
149 protected void loadSystemCoprocessors(Configuration conf, String confKey) {
150 Class<?> implClass = null;
151
152
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
173 abortServer(className, t);
174 }
175 }
176
177
178 coprocessors.addAll(configured);
179 }
180
181
182
183
184
185
186
187
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
213 Thread currentThread = Thread.currentThread();
214 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
215 try{
216
217 currentThread.setContextClassLoader(cl);
218 E cpInstance = loadInstance(implClass, priority, conf);
219 return cpInstance;
220 } finally {
221
222 currentThread.setContextClassLoader(hostClassLoader);
223 }
224 }
225
226
227
228
229
230
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
240
241
242
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
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
263 E env = createEnvironment(implClass, impl, priority, loadSequence.incrementAndGet(), conf);
264 if (env instanceof Environment) {
265 ((Environment)env).startup();
266 }
267
268
269 coprocessorNames.add(implClass.getName());
270 return env;
271 }
272
273
274
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 if (LOG.isDebugEnabled()) {
282 LOG.debug("Stop coprocessor " + e.getInstance().getClass().getName());
283 }
284 ((Environment)e).shutdown();
285 } else {
286 LOG.warn("Shutdown called on unknown environment: "+
287 e.getClass().getName());
288 }
289 }
290
291
292
293
294
295
296 public Coprocessor findCoprocessor(String className) {
297 for (E env: coprocessors) {
298 if (env.getInstance().getClass().getName().equals(className) ||
299 env.getInstance().getClass().getSimpleName().equals(className)) {
300 return env.getInstance();
301 }
302 }
303 return null;
304 }
305
306
307
308
309
310
311 public <T extends Coprocessor> List<T> findCoprocessors(Class<T> cls) {
312 ArrayList<T> ret = new ArrayList<T>();
313
314 for (E env: coprocessors) {
315 Coprocessor cp = env.getInstance();
316
317 if(cp != null) {
318 if (cls.isAssignableFrom(cp.getClass())) {
319 ret.add((T)cp);
320 }
321 }
322 }
323 return ret;
324 }
325
326
327
328
329
330
331 public CoprocessorEnvironment findCoprocessorEnvironment(String className) {
332 for (E env: coprocessors) {
333 if (env.getInstance().getClass().getName().equals(className) ||
334 env.getInstance().getClass().getSimpleName().equals(className)) {
335 return env;
336 }
337 }
338 return null;
339 }
340
341
342
343
344
345
346 Set<ClassLoader> getExternalClassLoaders() {
347 Set<ClassLoader> externalClassLoaders = new HashSet<ClassLoader>();
348 final ClassLoader systemClassLoader = this.getClass().getClassLoader();
349 for (E env : coprocessors) {
350 ClassLoader cl = env.getInstance().getClass().getClassLoader();
351 if (cl != systemClassLoader ){
352
353 externalClassLoaders.add(cl);
354 }
355 }
356 return externalClassLoaders;
357 }
358
359
360
361
362
363 static class EnvironmentPriorityComparator
364 implements Comparator<CoprocessorEnvironment> {
365 public int compare(final CoprocessorEnvironment env1,
366 final CoprocessorEnvironment env2) {
367 if (env1.getPriority() < env2.getPriority()) {
368 return -1;
369 } else if (env1.getPriority() > env2.getPriority()) {
370 return 1;
371 }
372 if (env1.getLoadSequence() < env2.getLoadSequence()) {
373 return -1;
374 } else if (env1.getLoadSequence() > env2.getLoadSequence()) {
375 return 1;
376 }
377 return 0;
378 }
379 }
380
381
382
383
384 public static class Environment implements CoprocessorEnvironment {
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400 class HTableWrapper implements HTableInterface {
401
402 private TableName tableName;
403 private HTable table;
404 private HConnection connection;
405
406 public HTableWrapper(TableName tableName, HConnection connection, ExecutorService pool)
407 throws IOException {
408 this.tableName = tableName;
409 this.table = new HTable(tableName, connection, pool);
410 this.connection = connection;
411 openTables.add(this);
412 }
413
414 void internalClose() throws IOException {
415 List<IOException> exceptions = new ArrayList<IOException>(2);
416 try {
417 table.close();
418 } catch (IOException e) {
419 exceptions.add(e);
420 }
421 try {
422
423 if (this.connection != null) {
424 this.connection.close();
425 }
426 } catch (IOException e) {
427 exceptions.add(e);
428 }
429 if (!exceptions.isEmpty()) {
430 throw MultipleIOException.createIOException(exceptions);
431 }
432 }
433
434 public Configuration getConfiguration() {
435 return table.getConfiguration();
436 }
437
438 public void close() throws IOException {
439 try {
440 internalClose();
441 } finally {
442 openTables.remove(this);
443 }
444 }
445
446 public Result getRowOrBefore(byte[] row, byte[] family)
447 throws IOException {
448 return table.getRowOrBefore(row, family);
449 }
450
451 public Result get(Get get) throws IOException {
452 return table.get(get);
453 }
454
455 public boolean exists(Get get) throws IOException {
456 return table.exists(get);
457 }
458
459 public Boolean[] exists(List<Get> gets) throws IOException{
460 return table.exists(gets);
461 }
462
463 public void put(Put put) throws IOException {
464 table.put(put);
465 }
466
467 public void put(List<Put> puts) throws IOException {
468 table.put(puts);
469 }
470
471 public void delete(Delete delete) throws IOException {
472 table.delete(delete);
473 }
474
475 public void delete(List<Delete> deletes) throws IOException {
476 table.delete(deletes);
477 }
478
479 public boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
480 byte[] value, Put put) throws IOException {
481 return table.checkAndPut(row, family, qualifier, value, put);
482 }
483
484 public boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
485 byte[] value, Delete delete) throws IOException {
486 return table.checkAndDelete(row, family, qualifier, value, delete);
487 }
488
489 public long incrementColumnValue(byte[] row, byte[] family,
490 byte[] qualifier, long amount) throws IOException {
491 return table.incrementColumnValue(row, family, qualifier, amount);
492 }
493
494 public long incrementColumnValue(byte[] row, byte[] family,
495 byte[] qualifier, long amount, Durability durability)
496 throws IOException {
497 return table.incrementColumnValue(row, family, qualifier, amount,
498 durability);
499 }
500
501 @Override
502 public Result append(Append append) throws IOException {
503 return table.append(append);
504 }
505
506 @Override
507 public Result increment(Increment increment) throws IOException {
508 return table.increment(increment);
509 }
510
511 public void flushCommits() throws IOException {
512 table.flushCommits();
513 }
514
515 public boolean isAutoFlush() {
516 return table.isAutoFlush();
517 }
518
519 public ResultScanner getScanner(Scan scan) throws IOException {
520 return table.getScanner(scan);
521 }
522
523 public ResultScanner getScanner(byte[] family) throws IOException {
524 return table.getScanner(family);
525 }
526
527 public ResultScanner getScanner(byte[] family, byte[] qualifier)
528 throws IOException {
529 return table.getScanner(family, qualifier);
530 }
531
532 public HTableDescriptor getTableDescriptor() throws IOException {
533 return table.getTableDescriptor();
534 }
535
536 @Override
537 public byte[] getTableName() {
538 return tableName.getName();
539 }
540
541 @Override
542 public TableName getName() {
543 return table.getName();
544 }
545
546 @Override
547 public void batch(List<? extends Row> actions, Object[] results)
548 throws IOException, InterruptedException {
549 table.batch(actions, results);
550 }
551
552
553
554
555
556
557 @Override
558 public Object[] batch(List<? extends Row> actions)
559 throws IOException, InterruptedException {
560 return table.batch(actions);
561 }
562
563 @Override
564 public <R> void batchCallback(List<? extends Row> actions, Object[] results,
565 Batch.Callback<R> callback) throws IOException, InterruptedException {
566 table.batchCallback(actions, results, callback);
567 }
568
569
570
571
572
573
574
575
576 @Override
577 public <R> Object[] batchCallback(List<? extends Row> actions,
578 Batch.Callback<R> callback) throws IOException, InterruptedException {
579 return table.batchCallback(actions, callback);
580 }
581
582 @Override
583 public Result[] get(List<Get> gets) throws IOException {
584 return table.get(gets);
585 }
586
587 @Override
588 public CoprocessorRpcChannel coprocessorService(byte[] row) {
589 return table.coprocessorService(row);
590 }
591
592 @Override
593 public <T extends Service, R> Map<byte[], R> coprocessorService(Class<T> service,
594 byte[] startKey, byte[] endKey, Batch.Call<T, R> callable)
595 throws ServiceException, Throwable {
596 return table.coprocessorService(service, startKey, endKey, callable);
597 }
598
599 @Override
600 public <T extends Service, R> void coprocessorService(Class<T> service,
601 byte[] startKey, byte[] endKey, Batch.Call<T, R> callable, Batch.Callback<R> callback)
602 throws ServiceException, Throwable {
603 table.coprocessorService(service, startKey, endKey, callable, callback);
604 }
605
606 @Override
607 public void mutateRow(RowMutations rm) throws IOException {
608 table.mutateRow(rm);
609 }
610
611 @Override
612 public void setAutoFlush(boolean autoFlush) {
613 table.setAutoFlush(autoFlush, autoFlush);
614 }
615
616 @Override
617 public void setAutoFlush(boolean autoFlush, boolean clearBufferOnFail) {
618 table.setAutoFlush(autoFlush, clearBufferOnFail);
619 }
620
621 @Override
622 public void setAutoFlushTo(boolean autoFlush) {
623 table.setAutoFlushTo(autoFlush);
624 }
625
626 @Override
627 public long getWriteBufferSize() {
628 return table.getWriteBufferSize();
629 }
630
631 @Override
632 public void setWriteBufferSize(long writeBufferSize) throws IOException {
633 table.setWriteBufferSize(writeBufferSize);
634 }
635
636 @Override
637 public long incrementColumnValue(byte[] row, byte[] family,
638 byte[] qualifier, long amount, boolean writeToWAL) throws IOException {
639 return table.incrementColumnValue(row, family, qualifier, amount, writeToWAL);
640 }
641
642 @Override
643 public <R extends Message> Map<byte[], R> batchCoprocessorService(
644 Descriptors.MethodDescriptor method, Message request, byte[] startKey,
645 byte[] endKey, R responsePrototype) throws ServiceException, Throwable {
646 return table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype);
647 }
648
649 @Override
650 public <R extends Message> void batchCoprocessorService(Descriptors.MethodDescriptor method,
651 Message request, byte[] startKey, byte[] endKey, R responsePrototype,
652 Callback<R> callback) throws ServiceException, Throwable {
653 table.batchCoprocessorService(method, request, startKey, endKey, responsePrototype,
654 callback);
655 }
656
657 @Override
658 public boolean checkAndMutate(byte[] row, byte[] family, byte[] qualifier,
659 CompareOp compareOp, byte[] value, RowMutations mutation) throws IOException {
660 return table.checkAndMutate(row, family, qualifier, compareOp, value, mutation);
661 }
662 }
663
664
665 public Coprocessor impl;
666
667 protected int priority = Coprocessor.PRIORITY_USER;
668
669 Coprocessor.State state = Coprocessor.State.UNINSTALLED;
670
671 protected List<HTableInterface> openTables =
672 Collections.synchronizedList(new ArrayList<HTableInterface>());
673 private int seq;
674 private Configuration conf;
675 private ClassLoader classLoader;
676
677
678
679
680
681
682 public Environment(final Coprocessor impl, final int priority,
683 final int seq, final Configuration conf) {
684 this.impl = impl;
685 this.classLoader = impl.getClass().getClassLoader();
686 this.priority = priority;
687 this.state = Coprocessor.State.INSTALLED;
688 this.seq = seq;
689 this.conf = conf;
690 }
691
692
693 public void startup() throws IOException {
694 if (state == Coprocessor.State.INSTALLED ||
695 state == Coprocessor.State.STOPPED) {
696 state = Coprocessor.State.STARTING;
697 Thread currentThread = Thread.currentThread();
698 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
699 try {
700 currentThread.setContextClassLoader(this.getClassLoader());
701 impl.start(this);
702 state = Coprocessor.State.ACTIVE;
703 } finally {
704 currentThread.setContextClassLoader(hostClassLoader);
705 }
706 } else {
707 LOG.warn("Not starting coprocessor "+impl.getClass().getName()+
708 " because not inactive (state="+state.toString()+")");
709 }
710 }
711
712
713 protected void shutdown() {
714 if (state == Coprocessor.State.ACTIVE) {
715 state = Coprocessor.State.STOPPING;
716 Thread currentThread = Thread.currentThread();
717 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
718 try {
719 currentThread.setContextClassLoader(this.getClassLoader());
720 impl.stop(this);
721 state = Coprocessor.State.STOPPED;
722 } catch (IOException ioe) {
723 LOG.error("Error stopping coprocessor "+impl.getClass().getName(), ioe);
724 } finally {
725 currentThread.setContextClassLoader(hostClassLoader);
726 }
727 } else {
728 LOG.warn("Not stopping coprocessor "+impl.getClass().getName()+
729 " because not active (state="+state.toString()+")");
730 }
731
732 for (HTableInterface table: openTables) {
733 try {
734 ((HTableWrapper)table).internalClose();
735 } catch (IOException e) {
736
737 LOG.warn("Failed to close " +
738 Bytes.toStringBinary(table.getTableName()), e);
739 }
740 }
741 }
742
743 @Override
744 public Coprocessor getInstance() {
745 return impl;
746 }
747
748 @Override
749 public ClassLoader getClassLoader() {
750 return classLoader;
751 }
752
753 @Override
754 public int getPriority() {
755 return priority;
756 }
757
758 @Override
759 public int getLoadSequence() {
760 return seq;
761 }
762
763
764 @Override
765 public int getVersion() {
766 return Coprocessor.VERSION;
767 }
768
769
770 @Override
771 public String getHBaseVersion() {
772 return VersionInfo.getVersion();
773 }
774
775 @Override
776 public Configuration getConfiguration() {
777 return conf;
778 }
779
780
781
782
783
784
785
786 @Override
787 public HTableInterface getTable(TableName tableName) throws IOException {
788 return this.getTable(tableName, HTable.getDefaultExecutor(getConfiguration()));
789 }
790
791
792
793
794
795
796
797 @Override
798 public HTableInterface getTable(TableName tableName, ExecutorService pool) throws IOException {
799 return new HTableWrapper(tableName, CoprocessorHConnection.getConnectionForEnvironment(this),
800 pool);
801 }
802 }
803
804 protected void abortServer(final CoprocessorEnvironment environment, final Throwable e) {
805 abortServer(environment.getInstance().getClass().getName(), e);
806 }
807
808 protected void abortServer(final String coprocessorName, final Throwable e) {
809 String message = "The coprocessor " + coprocessorName + " threw " + e.toString();
810 LOG.error(message, e);
811 if (abortable != null) {
812 abortable.abort(message, e);
813 } else {
814 LOG.warn("No available Abortable, process was not aborted");
815 }
816 }
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833 protected void handleCoprocessorThrowable(final CoprocessorEnvironment env, final Throwable e)
834 throws IOException {
835 if (e instanceof IOException) {
836 throw (IOException)e;
837 }
838
839
840
841
842
843
844 if (env.getConfiguration().getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
845
846 abortServer(env, e);
847 } else {
848 LOG.error("Removing coprocessor '" + env.toString() + "' from " +
849 "environment because it threw: " + e,e);
850 coprocessors.remove(env);
851 try {
852 shutdown(env);
853 } catch (Exception x) {
854 LOG.error("Uncaught exception when shutting down coprocessor '"
855 + env.toString() + "'", x);
856 }
857 throw new DoNotRetryIOException("Coprocessor: '" + env.toString() +
858 "' threw: '" + e + "' and has been removed from the active " +
859 "coprocessor set.", e);
860 }
861 }
862 }