diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKUtil.java index 3d660cb2662..ff837cbc7b8 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKUtil.java @@ -915,7 +915,7 @@ public class ZKUtil { if (superUsers != null) { List groups = new ArrayList(); for (String user : superUsers) { - if (user.startsWith(AuthUtil.GROUP_PREFIX)) { + if (AuthUtil.isGroupPrincipal(user)) { // TODO: Set node ACL for groups when ZK supports this feature groups.add(user); } else { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java index 7670ce78762..e6c8eb914c0 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZooKeeperWatcher.java @@ -354,7 +354,7 @@ public class ZooKeeperWatcher implements Watcher, Abortable, Closeable { for (String user : superUsers) { boolean hasAccess = false; // TODO: Validate super group members also when ZK supports setting node ACL for groups. - if (!user.startsWith(AuthUtil.GROUP_PREFIX)) { + if (!AuthUtil.isGroupPrincipal(user)) { for (ACL acl : acls) { if (user.equals(acl.getId().getId())) { if (acl.getPerms() == Perms.ALL) { @@ -383,7 +383,7 @@ public class ZooKeeperWatcher implements Watcher, Abortable, Closeable { public static boolean isSuperUserId(String[] superUsers, Id id) { for (String user : superUsers) { // TODO: Validate super group members also when ZK supports setting node ACL for groups. - if (!user.startsWith(AuthUtil.GROUP_PREFIX) && new Id("sasl", user).equals(id)) { + if (!AuthUtil.isGroupPrincipal(user) && new Id("sasl", user).equals(id)) { return true; } } diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/AuthUtil.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/AuthUtil.java index 52f872cf26e..bbed218349d 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/AuthUtil.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/AuthUtil.java @@ -32,15 +32,48 @@ import org.apache.hadoop.hbase.util.Strings; import org.apache.hadoop.security.UserGroupInformation; /** - * Utility methods for helping with security tasks. + * Utility methods for helping with security tasks. Downstream users + * may rely on this class to handle authenticating via keytab where + * long running services need access to a secure HBase cluster. + * + * Callers must ensure: + * + * + * + *
+ * {@code
+ *   ChoreService choreService = null;
+ *   // Presumes HBase configuration files are on the classpath
+ *   final Configuration conf = HBaseConfiguration.create();
+ *   final ScheduledChore authChore = AuthUtil.getAuthChore(conf);
+ *   if (authChore != null) {
+ *     choreService = new ChoreService("MY_APPLICATION");
+ *     choreService.scheduleChore(authChore);
+ *   }
+ *   try {
+ *     // do application work
+ *   } finally {
+ *     if (choreService != null) {
+ *       choreService.shutdown();
+ *     }
+ *   }
+ * }
+ * 
+ * + * See the "Running Canary in a Kerberos-enabled Cluster" section of the HBase Reference Guide for + * an example of configuring a user of this Auth Chore to run on a secure cluster. */ -@InterfaceAudience.Private -@InterfaceStability.Evolving +@InterfaceAudience.Public +@InterfaceStability.Stable public class AuthUtil { private static final Log LOG = LogFactory.getLog(AuthUtil.class); /** Prefix character to denote group names */ - public static final String GROUP_PREFIX = "@"; + private static final String GROUP_PREFIX = "@"; private AuthUtil() { super(); @@ -48,6 +81,8 @@ public class AuthUtil { /** * Checks if security is enabled and if so, launches chore for refreshing kerberos ticket. + * @param conf the hbase service configuration + * @return a ScheduledChore for renewals, if needed, and null otherwise. */ public static ScheduledChore getAuthChore(Configuration conf) throws IOException { UserProvider userProvider = UserProvider.instantiate(conf); @@ -109,6 +144,7 @@ public class AuthUtil { * principal. Currently this simply checks if the name starts with the * special group prefix character ("@"). */ + @InterfaceAudience.Private public static boolean isGroupPrincipal(String name) { return name != null && name.startsWith(GROUP_PREFIX); } @@ -117,6 +153,7 @@ public class AuthUtil { * Returns the actual name for a group principal (stripped of the * group prefix). */ + @InterfaceAudience.Private public static String getGroupName(String aclKey) { if (!isGroupPrincipal(aclKey)) { return aclKey; @@ -128,6 +165,7 @@ public class AuthUtil { /** * Returns the group entry with the group prefix for a group principal. */ + @InterfaceAudience.Private public static String toGroupEntry(String name) { return GROUP_PREFIX + name; } diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/ChoreService.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/ChoreService.java index 5c3d2158538..1623c10c10f 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/ChoreService.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/ChoreService.java @@ -32,6 +32,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.ScheduledChore.ChoreServicer; import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.classification.InterfaceStability; /** * ChoreService is a service that can be used to schedule instances of {@link ScheduledChore} to run @@ -52,13 +53,15 @@ import org.apache.hadoop.hbase.classification.InterfaceAudience; * When finished with a ChoreService it is good practice to call {@link ChoreService#shutdown()}. * Calling this method ensures that all scheduled chores are cancelled and cleaned up properly. */ -@InterfaceAudience.Private +@InterfaceAudience.Public +@InterfaceStability.Stable public class ChoreService implements ChoreServicer { private static final Log LOG = LogFactory.getLog(ChoreService.class); /** * The minimum number of threads in the core pool of the underlying ScheduledThreadPoolExecutor */ + @InterfaceAudience.Private public final static int MIN_CORE_POOL_SIZE = 1; /** @@ -92,12 +95,15 @@ public class ChoreService implements ChoreServicer { * @param coreThreadPoolPrefix Prefix that will be applied to the Thread name of all threads * spawned by this service */ + @InterfaceAudience.Private @VisibleForTesting public ChoreService(final String coreThreadPoolPrefix) { this(coreThreadPoolPrefix, MIN_CORE_POOL_SIZE, false); } /** + * @param coreThreadPoolPrefix Prefix that will be applied to the Thread name of all threads + * spawned by this service * @param jitter Should chore service add some jitter for all of the scheduled chores. When set * to true this will add -10% to 10% jitter. */ @@ -111,6 +117,8 @@ public class ChoreService implements ChoreServicer { * @param corePoolSize The initial size to set the core pool of the ScheduledThreadPoolExecutor * to during initialization. The default size is 1, but specifying a larger size may be * beneficial if you know that 1 thread will not be enough. + * @param jitter Should chore service add some jitter for all of the scheduled chores. When set + * to true this will add -10% to 10% jitter. */ public ChoreService(final String coreThreadPoolPrefix, int corePoolSize, boolean jitter) { this.coreThreadPoolPrefix = coreThreadPoolPrefix; @@ -130,14 +138,6 @@ public class ChoreService implements ChoreServicer { choresMissingStartTime = new HashMap(); } - /** - * @param coreThreadPoolPrefix Prefix that will be applied to the Thread name of all threads - * spawned by this service - */ - public static ChoreService getInstance(final String coreThreadPoolPrefix) { - return new ChoreService(coreThreadPoolPrefix); - } - /** * @param chore Chore to be scheduled. If the chore is already scheduled with another ChoreService * instance, that schedule will be cancelled (i.e. a Chore can only ever be scheduled @@ -179,11 +179,13 @@ public class ChoreService implements ChoreServicer { scheduleChore(chore); } + @InterfaceAudience.Private @Override public synchronized void cancelChore(ScheduledChore chore) { cancelChore(chore, true); } + @InterfaceAudience.Private @Override public synchronized void cancelChore(ScheduledChore chore, boolean mayInterruptIfRunning) { if (chore != null && scheduledChores.containsKey(chore)) { @@ -200,12 +202,14 @@ public class ChoreService implements ChoreServicer { } } + @InterfaceAudience.Private @Override public synchronized boolean isChoreScheduled(ScheduledChore chore) { return chore != null && scheduledChores.containsKey(chore) && !scheduledChores.get(chore).isDone(); } + @InterfaceAudience.Private @Override public synchronized boolean triggerNow(ScheduledChore chore) { if (chore == null) { @@ -293,6 +297,7 @@ public class ChoreService implements ChoreServicer { } } + @InterfaceAudience.Private @Override public synchronized void onChoreMissedStartTime(ScheduledChore chore) { if (chore == null || !scheduledChores.containsKey(chore)) return; diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/ScheduledChore.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/ScheduledChore.java index 5c5bcd82056..422ca1a1d0a 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/ScheduledChore.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/ScheduledChore.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.classification.InterfaceStability; import com.google.common.annotations.VisibleForTesting; @@ -40,7 +41,8 @@ import com.google.common.annotations.VisibleForTesting; * Don't subclass ScheduledChore if the task relies on being woken up for something to do, such as * an entry being added to a queue, etc. */ -@InterfaceAudience.Private +@InterfaceAudience.Public +@InterfaceStability.Stable public abstract class ScheduledChore implements Runnable { private static final Log LOG = LogFactory.getLog(ScheduledChore.class); @@ -116,6 +118,8 @@ public abstract class ScheduledChore implements Runnable { /** * This constructor is for test only. It allows us to create an object and to call chore() on it. */ + @InterfaceAudience.Private + @VisibleForTesting protected ScheduledChore() { this.name = null; this.stopper = null; @@ -165,7 +169,7 @@ public abstract class ScheduledChore implements Runnable { } /** - * @see java.lang.Thread#run() + * @see java.lang.Runnable#run() */ @Override public void run() { @@ -327,6 +331,7 @@ public abstract class ScheduledChore implements Runnable { return choreServicer != null && choreServicer.isChoreScheduled(this); } + @InterfaceAudience.Private @VisibleForTesting public synchronized void choreForTesting() { chore(); @@ -352,6 +357,12 @@ public abstract class ScheduledChore implements Runnable { protected synchronized void cleanup() { } + /** + * A summation of this chore in human readable format. Downstream users should not presume + * parsing of this string can relaibly be done between versions. Instead, they should rely + * on the public accessor methods to get the information they desire. + */ + @InterfaceAudience.Private @Override public String toString() { return "[ScheduledChore: Name: " + getName() + " Period: " + getPeriod() + " Unit: " diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/Stoppable.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/Stoppable.java index fc83ba355d2..9adaa1ab13f 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/Stoppable.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/Stoppable.java @@ -24,11 +24,12 @@ import org.apache.hadoop.hbase.classification.InterfaceStability; /** * Implementers are Stoppable. */ -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.COPROC) -@InterfaceStability.Evolving +@InterfaceAudience.Public +@InterfaceStability.Stable public interface Stoppable { /** * Stop this service. + * Implementers should favor logging errors over throwing RuntimeExceptions. * @param why Why we're stopping. */ void stop(String why); diff --git a/hbase-common/src/test/java/org/apache/hadoop/hbase/TestChoreService.java b/hbase-common/src/test/java/org/apache/hadoop/hbase/TestChoreService.java index bb81cc20dbc..cc7b91fc4ae 100644 --- a/hbase-common/src/test/java/org/apache/hadoop/hbase/TestChoreService.java +++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/TestChoreService.java @@ -235,7 +235,7 @@ public class TestChoreService { @Test (timeout=20000) public void testInitialChorePrecedence() throws InterruptedException { - ChoreService service = ChoreService.getInstance("testInitialChorePrecedence"); + ChoreService service = new ChoreService("testInitialChorePrecedence"); final int period = 100; final int failureThreshold = 5; @@ -266,7 +266,7 @@ public class TestChoreService { public void testCancelChore() throws InterruptedException { final int period = 100; ScheduledChore chore1 = new DoNothingChore("chore1", period); - ChoreService service = ChoreService.getInstance("testCancelChore"); + ChoreService service = new ChoreService("testCancelChore"); try { service.scheduleChore(chore1); assertTrue(chore1.isScheduled()); @@ -344,7 +344,7 @@ public class TestChoreService { final int period = 100; // Small delta that acts as time buffer (allowing chores to complete if running slowly) final int delta = 5; - ChoreService service = ChoreService.getInstance("testFrequencyOfChores"); + ChoreService service = new ChoreService("testFrequencyOfChores"); CountingChore chore = new CountingChore("countingChore", period); try { service.scheduleChore(chore); @@ -370,7 +370,7 @@ public class TestChoreService { public void testForceTrigger() throws InterruptedException { final int period = 100; final int delta = 5; - ChoreService service = ChoreService.getInstance("testForceTrigger"); + ChoreService service = new ChoreService("testForceTrigger"); final CountingChore chore = new CountingChore("countingChore", period); try { service.scheduleChore(chore); @@ -717,7 +717,7 @@ public class TestChoreService { @Test (timeout=20000) public void testStopperForScheduledChores() throws InterruptedException { - ChoreService service = ChoreService.getInstance("testStopperForScheduledChores"); + ChoreService service = new ChoreService("testStopperForScheduledChores"); Stoppable stopperForGroup1 = new SampleStopper(); Stoppable stopperForGroup2 = new SampleStopper(); final int period = 100; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java index 296c1ea36e6..9a1cbfa4150 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/TableAuthManager.java @@ -82,8 +82,8 @@ public class TableAuthManager implements Closeable { } /** - * Returns a combined map of user and group permissions, with group names prefixed by - * {@link AuthUtil#GROUP_PREFIX}. + * Returns a combined map of user and group permissions, with group names + * distinguished according to {@link AuthUtil.isGroupPrincipal} */ public ListMultimap getAllPermissions() { ListMultimap tmp = ArrayListMultimap.create();