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