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