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.Set;
29 import java.util.TreeSet;
30 import java.util.UUID;
31 import java.util.concurrent.ConcurrentSkipListSet;
32 import java.util.concurrent.ExecutorService;
33 import java.util.concurrent.atomic.AtomicInteger;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.apache.hadoop.hbase.classification.InterfaceAudience;
38 import org.apache.hadoop.hbase.classification.InterfaceStability;
39 import org.apache.hadoop.conf.Configuration;
40 import org.apache.hadoop.fs.Path;
41 import org.apache.hadoop.hbase.Abortable;
42 import org.apache.hadoop.hbase.Coprocessor;
43 import org.apache.hadoop.hbase.CoprocessorEnvironment;
44 import org.apache.hadoop.hbase.DoNotRetryIOException;
45 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
46 import org.apache.hadoop.hbase.HConstants;
47 import org.apache.hadoop.hbase.TableName;
48 import org.apache.hadoop.hbase.client.HTable;
49 import org.apache.hadoop.hbase.client.HTableInterface;
50 import org.apache.hadoop.hbase.client.HTableWrapper;
51 import org.apache.hadoop.hbase.util.Bytes;
52 import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
53 import org.apache.hadoop.hbase.util.SortedList;
54 import org.apache.hadoop.hbase.util.VersionInfo;
55
56
57
58
59
60
61
62 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC)
63 @InterfaceStability.Evolving
64 public abstract class CoprocessorHost<E extends CoprocessorEnvironment> {
65 public static final String REGION_COPROCESSOR_CONF_KEY =
66 "hbase.coprocessor.region.classes";
67 public static final String REGIONSERVER_COPROCESSOR_CONF_KEY =
68 "hbase.coprocessor.regionserver.classes";
69 public static final String USER_REGION_COPROCESSOR_CONF_KEY =
70 "hbase.coprocessor.user.region.classes";
71 public static final String MASTER_COPROCESSOR_CONF_KEY =
72 "hbase.coprocessor.master.classes";
73 public static final String WAL_COPROCESSOR_CONF_KEY =
74 "hbase.coprocessor.wal.classes";
75 public static final String ABORT_ON_ERROR_KEY = "hbase.coprocessor.abortonerror";
76 public static final boolean DEFAULT_ABORT_ON_ERROR = true;
77 public static final String COPROCESSORS_ENABLED_CONF_KEY = "hbase.coprocessor.enabled";
78 public static final boolean DEFAULT_COPROCESSORS_ENABLED = true;
79 public static final String USER_COPROCESSORS_ENABLED_CONF_KEY =
80 "hbase.coprocessor.user.enabled";
81 public static final boolean DEFAULT_USER_COPROCESSORS_ENABLED = true;
82 public static final String SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR =
83 "hbase.skip.load.duplicate.table.coprocessor";
84 public static final boolean DEFAULT_SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR = false;
85
86 private static final Log LOG = LogFactory.getLog(CoprocessorHost.class);
87 protected Abortable abortable;
88
89 protected SortedList<E> coprocessors =
90 new SortedList<E>(new EnvironmentPriorityComparator());
91 protected Configuration conf;
92
93 protected String pathPrefix;
94 protected AtomicInteger loadSequence = new AtomicInteger();
95
96 public CoprocessorHost(Abortable abortable) {
97 this.abortable = abortable;
98 this.pathPrefix = UUID.randomUUID().toString();
99 }
100
101
102
103
104
105
106
107
108
109 private static Set<String> coprocessorNames =
110 Collections.synchronizedSet(new HashSet<String>());
111
112 public static Set<String> getLoadedCoprocessors() {
113 synchronized (coprocessorNames) {
114 return new HashSet(coprocessorNames);
115 }
116 }
117
118
119
120
121
122
123
124
125 public Set<String> getCoprocessors() {
126 Set<String> returnValue = new TreeSet<String>();
127 for (CoprocessorEnvironment e: coprocessors) {
128 returnValue.add(e.getInstance().getClass().getSimpleName());
129 }
130 return returnValue;
131 }
132
133
134
135
136
137 protected void loadSystemCoprocessors(Configuration conf, String confKey) {
138 boolean coprocessorsEnabled = conf.getBoolean(COPROCESSORS_ENABLED_CONF_KEY,
139 DEFAULT_COPROCESSORS_ENABLED);
140 if (!coprocessorsEnabled) {
141 return;
142 }
143
144 Class<?> implClass = null;
145
146
147 String[] defaultCPClasses = conf.getStrings(confKey);
148 if (defaultCPClasses == null || defaultCPClasses.length == 0)
149 return;
150
151 int currentSystemPriority = Coprocessor.PRIORITY_SYSTEM;
152 for (String className : defaultCPClasses) {
153 String[] classNameAndPriority = className.split("\\|");
154 boolean hasPriorityOverride = false;
155 className = classNameAndPriority[0];
156 int overridePriority = Coprocessor.PRIORITY_SYSTEM;
157 if (classNameAndPriority.length > 1){
158 overridePriority = Integer.parseInt(classNameAndPriority[1]);
159 hasPriorityOverride = true;
160 }
161 className = className.trim();
162 if (findCoprocessor(className) != null) {
163
164 LOG.warn("Attempted duplicate loading of " + className + "; skipped");
165 continue;
166 }
167 ClassLoader cl = this.getClass().getClassLoader();
168 Thread.currentThread().setContextClassLoader(cl);
169 try {
170 implClass = cl.loadClass(className);
171
172
173 int coprocPriority = hasPriorityOverride ? overridePriority : currentSystemPriority;
174 this.coprocessors.add(loadInstance(implClass, coprocPriority, conf));
175 LOG.info("System coprocessor " + className + " was loaded " +
176 "successfully with priority (" + coprocPriority + ").");
177 if (!hasPriorityOverride) {
178 ++currentSystemPriority;
179 }
180 } catch (Throwable t) {
181
182 abortServer(className, t);
183 }
184 }
185 }
186
187
188
189
190
191
192
193
194
195 public E load(Path path, String className, int priority,
196 Configuration conf) throws IOException {
197 String[] includedClassPrefixes = null;
198 if (conf.get(HConstants.CP_HTD_ATTR_INCLUSION_KEY) != null){
199 String prefixes = conf.get(HConstants.CP_HTD_ATTR_INCLUSION_KEY);
200 includedClassPrefixes = prefixes.split(";");
201 }
202 return load(path, className, priority, conf, includedClassPrefixes);
203 }
204
205
206
207
208
209
210
211
212
213
214 public E load(Path path, String className, int priority,
215 Configuration conf, String[] includedClassPrefixes) throws IOException {
216 Class<?> implClass = null;
217 LOG.debug("Loading coprocessor class " + className + " with path " +
218 path + " and priority " + priority);
219
220 boolean skipLoadDuplicateCoprocessor = conf.getBoolean(SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR,
221 DEFAULT_SKIP_LOAD_DUPLICATE_TABLE_COPROCESSOR);
222 if (skipLoadDuplicateCoprocessor && findCoprocessor(className) != null) {
223
224 LOG.warn("Attempted duplicate loading of " + className + "; skipped");
225 return null;
226 }
227
228 ClassLoader cl = null;
229 if (path == null) {
230 try {
231 implClass = getClass().getClassLoader().loadClass(className);
232 } catch (ClassNotFoundException e) {
233 throw new IOException("No jar path specified for " + className);
234 }
235 } else {
236 cl = CoprocessorClassLoader.getClassLoader(
237 path, getClass().getClassLoader(), pathPrefix, conf);
238 try {
239 implClass = ((CoprocessorClassLoader)cl).loadClass(className, includedClassPrefixes);
240 } catch (ClassNotFoundException e) {
241 throw new IOException("Cannot load external coprocessor class " + className, e);
242 }
243 }
244
245
246 Thread currentThread = Thread.currentThread();
247 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
248 try{
249
250 currentThread.setContextClassLoader(cl);
251 E cpInstance = loadInstance(implClass, priority, conf);
252 return cpInstance;
253 } finally {
254
255 currentThread.setContextClassLoader(hostClassLoader);
256 }
257 }
258
259
260
261
262
263
264
265 public void load(Class<?> implClass, int priority, Configuration conf)
266 throws IOException {
267 E env = loadInstance(implClass, priority, conf);
268 coprocessors.add(env);
269 }
270
271
272
273
274
275
276
277 public E loadInstance(Class<?> implClass, int priority, Configuration conf)
278 throws IOException {
279 if (!Coprocessor.class.isAssignableFrom(implClass)) {
280 throw new IOException("Configured class " + implClass.getName() + " must implement "
281 + Coprocessor.class.getName() + " interface ");
282 }
283
284
285 Coprocessor impl;
286 Object o = null;
287 try {
288 o = implClass.getDeclaredConstructor().newInstance();
289 impl = (Coprocessor)o;
290 } catch (Exception e) {
291 throw new IOException(e);
292 }
293
294 E env = createEnvironment(implClass, impl, priority, loadSequence.incrementAndGet(), conf);
295 if (env instanceof Environment) {
296 ((Environment)env).startup();
297 }
298
299
300 coprocessorNames.add(implClass.getName());
301 return env;
302 }
303
304
305
306
307 public abstract E createEnvironment(Class<?> implClass, Coprocessor instance,
308 int priority, int sequence, Configuration conf);
309
310 public void shutdown(CoprocessorEnvironment e) {
311 if (e instanceof Environment) {
312 if (LOG.isDebugEnabled()) {
313 LOG.debug("Stop coprocessor " + e.getInstance().getClass().getName());
314 }
315 ((Environment)e).shutdown();
316 } else {
317 LOG.warn("Shutdown called on unknown environment: "+
318 e.getClass().getName());
319 }
320 }
321
322
323
324
325
326
327 public Coprocessor findCoprocessor(String className) {
328 for (E env: coprocessors) {
329 if (env.getInstance().getClass().getName().equals(className) ||
330 env.getInstance().getClass().getSimpleName().equals(className)) {
331 return env.getInstance();
332 }
333 }
334 return null;
335 }
336
337
338
339
340
341
342 public <T extends Coprocessor> List<T> findCoprocessors(Class<T> cls) {
343 ArrayList<T> ret = new ArrayList<T>();
344
345 for (E env: coprocessors) {
346 Coprocessor cp = env.getInstance();
347
348 if(cp != null) {
349 if (cls.isAssignableFrom(cp.getClass())) {
350 ret.add((T)cp);
351 }
352 }
353 }
354 return ret;
355 }
356
357
358
359
360
361
362 public CoprocessorEnvironment findCoprocessorEnvironment(String className) {
363 for (E env: coprocessors) {
364 if (env.getInstance().getClass().getName().equals(className) ||
365 env.getInstance().getClass().getSimpleName().equals(className)) {
366 return env;
367 }
368 }
369 return null;
370 }
371
372
373
374
375
376
377 Set<ClassLoader> getExternalClassLoaders() {
378 Set<ClassLoader> externalClassLoaders = new HashSet<ClassLoader>();
379 final ClassLoader systemClassLoader = this.getClass().getClassLoader();
380 for (E env : coprocessors) {
381 ClassLoader cl = env.getInstance().getClass().getClassLoader();
382 if (cl != systemClassLoader ){
383
384 externalClassLoaders.add(cl);
385 }
386 }
387 return externalClassLoaders;
388 }
389
390
391
392
393
394 static class EnvironmentPriorityComparator
395 implements Comparator<CoprocessorEnvironment> {
396 @Override
397 public int compare(final CoprocessorEnvironment env1,
398 final CoprocessorEnvironment env2) {
399 if (env1.getPriority() < env2.getPriority()) {
400 return -1;
401 } else if (env1.getPriority() > env2.getPriority()) {
402 return 1;
403 }
404 if (env1.getLoadSequence() < env2.getLoadSequence()) {
405 return -1;
406 } else if (env1.getLoadSequence() > env2.getLoadSequence()) {
407 return 1;
408 }
409 return 0;
410 }
411 }
412
413
414
415
416 public static class Environment implements CoprocessorEnvironment {
417
418
419 public Coprocessor impl;
420
421 protected int priority = Coprocessor.PRIORITY_USER;
422
423 Coprocessor.State state = Coprocessor.State.UNINSTALLED;
424
425 protected final List<HTableInterface> openTables =
426 Collections.synchronizedList(new ArrayList<HTableInterface>());
427 private int seq;
428 private Configuration conf;
429 private ClassLoader classLoader;
430
431
432
433
434
435
436 public Environment(final Coprocessor impl, final int priority,
437 final int seq, final Configuration conf) {
438 this.impl = impl;
439 this.classLoader = impl.getClass().getClassLoader();
440 this.priority = priority;
441 this.state = Coprocessor.State.INSTALLED;
442 this.seq = seq;
443 this.conf = conf;
444 }
445
446
447 public void startup() throws IOException {
448 if (state == Coprocessor.State.INSTALLED ||
449 state == Coprocessor.State.STOPPED) {
450 state = Coprocessor.State.STARTING;
451 Thread currentThread = Thread.currentThread();
452 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
453 try {
454 currentThread.setContextClassLoader(this.getClassLoader());
455 impl.start(this);
456 state = Coprocessor.State.ACTIVE;
457 } finally {
458 currentThread.setContextClassLoader(hostClassLoader);
459 }
460 } else {
461 LOG.warn("Not starting coprocessor "+impl.getClass().getName()+
462 " because not inactive (state="+state.toString()+")");
463 }
464 }
465
466
467 protected void shutdown() {
468 if (state == Coprocessor.State.ACTIVE) {
469 state = Coprocessor.State.STOPPING;
470 Thread currentThread = Thread.currentThread();
471 ClassLoader hostClassLoader = currentThread.getContextClassLoader();
472 try {
473 currentThread.setContextClassLoader(this.getClassLoader());
474 impl.stop(this);
475 state = Coprocessor.State.STOPPED;
476 } catch (IOException ioe) {
477 LOG.error("Error stopping coprocessor "+impl.getClass().getName(), ioe);
478 } finally {
479 currentThread.setContextClassLoader(hostClassLoader);
480 }
481 } else {
482 LOG.warn("Not stopping coprocessor "+impl.getClass().getName()+
483 " because not active (state="+state.toString()+")");
484 }
485 synchronized (openTables) {
486
487 for (HTableInterface table: openTables) {
488 try {
489 ((HTableWrapper)table).internalClose();
490 } catch (IOException e) {
491
492 LOG.warn("Failed to close " +
493 Bytes.toStringBinary(table.getTableName()), e);
494 }
495 }
496 }
497 }
498
499 @Override
500 public Coprocessor getInstance() {
501 return impl;
502 }
503
504 @Override
505 public ClassLoader getClassLoader() {
506 return classLoader;
507 }
508
509 @Override
510 public int getPriority() {
511 return priority;
512 }
513
514 @Override
515 public int getLoadSequence() {
516 return seq;
517 }
518
519
520 @Override
521 public int getVersion() {
522 return Coprocessor.VERSION;
523 }
524
525
526 @Override
527 public String getHBaseVersion() {
528 return VersionInfo.getVersion();
529 }
530
531 @Override
532 public Configuration getConfiguration() {
533 return conf;
534 }
535
536
537
538
539
540
541
542 @Override
543 public HTableInterface getTable(TableName tableName) throws IOException {
544 return this.getTable(tableName, HTable.getDefaultExecutor(getConfiguration()));
545 }
546
547
548
549
550
551
552
553 @Override
554 public HTableInterface getTable(TableName tableName, ExecutorService pool) throws IOException {
555 return HTableWrapper.createWrapper(openTables, tableName, this, pool);
556 }
557 }
558
559 protected void abortServer(final CoprocessorEnvironment environment, final Throwable e) {
560 abortServer(environment.getInstance().getClass().getName(), e);
561 }
562
563 protected void abortServer(final String coprocessorName, final Throwable e) {
564 String message = "The coprocessor " + coprocessorName + " threw " + e.toString();
565 LOG.error(message, e);
566 if (abortable != null) {
567 abortable.abort(message, e);
568 } else {
569 LOG.warn("No available Abortable, process was not aborted");
570 }
571 }
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588 protected void handleCoprocessorThrowable(final CoprocessorEnvironment env, final Throwable e)
589 throws IOException {
590 if (e instanceof IOException) {
591 throw (IOException)e;
592 }
593
594
595
596
597
598
599 if (env.getConfiguration().getBoolean(ABORT_ON_ERROR_KEY, DEFAULT_ABORT_ON_ERROR)) {
600
601 abortServer(env, e);
602 } else {
603
604 if(env instanceof RegionCoprocessorEnvironment) {
605 String tableName = ((RegionCoprocessorEnvironment)env).getRegionInfo().getTable().getNameAsString();
606 LOG.error("Removing coprocessor '" + env.toString() + "' from table '"+ tableName + "'", e);
607 } else {
608 LOG.error("Removing coprocessor '" + env.toString() + "' from " +
609 "environment",e);
610 }
611
612 coprocessors.remove(env);
613 try {
614 shutdown(env);
615 } catch (Exception x) {
616 LOG.error("Uncaught exception when shutting down coprocessor '"
617 + env.toString() + "'", x);
618 }
619 throw new DoNotRetryIOException("Coprocessor: '" + env.toString() +
620 "' threw: '" + e + "' and has been removed from the active " +
621 "coprocessor set.", e);
622 }
623 }
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645 @InterfaceAudience.Private
646 protected static boolean useLegacyMethod(final Class<? extends Coprocessor> clazz,
647 final String methodName, final Class<?>... parameterTypes) {
648 boolean useLegacy;
649
650 try {
651 clazz.getDeclaredMethod(methodName, parameterTypes);
652 LOG.debug("Found an implementation of '" + methodName + "' that uses updated method " +
653 "signature. Skipping legacy support for invocations in '" + clazz +"'.");
654 useLegacy = false;
655 } catch (NoSuchMethodException exception) {
656 useLegacy = true;
657 } catch (SecurityException exception) {
658 LOG.warn("The Security Manager denied our attempt to detect if the coprocessor '" + clazz +
659 "' requires legacy support; assuming it does. If you get later errors about legacy " +
660 "coprocessor use, consider updating your security policy to allow access to the package" +
661 " and declared members of your implementation.");
662 LOG.debug("Details of Security Manager rejection.", exception);
663 useLegacy = true;
664 }
665 return useLegacy;
666 }
667
668
669
670
671 private static final Set<Class<? extends Coprocessor>> legacyWarning =
672 new ConcurrentSkipListSet<Class<? extends Coprocessor>>(
673 new Comparator<Class<? extends Coprocessor>>() {
674 @Override
675 public int compare(Class<? extends Coprocessor> c1, Class<? extends Coprocessor> c2) {
676 if (c1.equals(c2)) {
677 return 0;
678 }
679 return c1.getName().compareTo(c2.getName());
680 }
681 });
682
683
684
685
686
687
688
689 @InterfaceAudience.Private
690 protected void legacyWarning(final Class<? extends Coprocessor> clazz, final String message) {
691 if(legacyWarning.add(clazz)) {
692 LOG.error("You have a legacy coprocessor loaded and there are events we can't map to the " +
693 " deprecated API. Your coprocessor will not see these events. Please update '" + clazz +
694 "'. Details of the problem: " + message);
695 }
696 }
697 }