Repository: hbase Updated Branches: refs/heads/hbase-12439 6c3fd3447 -> 8b7796b0b
HBASE-14597 Fix Groups cache in multi-threaded env Project: http://git-wip-us.apache.org/repos/asf/hbase/repo Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/0ed8b0e5 Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/0ed8b0e5 Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/0ed8b0e5 Branch: refs/heads/hbase-12439 Commit: 0ed8b0e5d559e0499a85f8b1fbb3d3925a611688 Parents: 6076ed2 Author: Elliott Clark <ecl...@apache.org> Authored: Tue Oct 13 08:30:12 2015 -0700 Committer: Elliott Clark <ecl...@apache.org> Committed: Fri Oct 16 15:15:26 2015 -0700 ---------------------------------------------------------------------- .../org/apache/hadoop/hbase/security/User.java | 44 +++++++++++- .../hadoop/hbase/security/UserProvider.java | 40 ++++++++--- .../apache/hadoop/hbase/security/TestUser.java | 74 ++++++++++++++++++-- 3 files changed, 142 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hbase/blob/0ed8b0e5/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java ---------------------------------------------------------------------- diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java index 93ce003..c480dad 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/User.java @@ -22,13 +22,18 @@ package org.apache.hadoop.hbase.security; import java.io.IOException; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; +import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import com.google.common.cache.LoadingCache; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.classification.InterfaceStability; import org.apache.hadoop.hbase.util.Methods; +import org.apache.hadoop.security.Groups; import org.apache.hadoop.security.SecurityUtil; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.token.Token; @@ -255,7 +260,7 @@ public abstract class User { @InterfaceAudience.Private public static final class SecureHadoopUser extends User { private String shortName; - private LoadingCache<UserGroupInformation, String[]> cache; + private LoadingCache<String, String[]> cache; public SecureHadoopUser() throws IOException { ugi = UserGroupInformation.getCurrentUser(); @@ -268,7 +273,7 @@ public abstract class User { } public SecureHadoopUser(UserGroupInformation ugi, - LoadingCache<UserGroupInformation, String[]> cache) { + LoadingCache<String, String[]> cache) { this.ugi = ugi; this.cache = cache; } @@ -289,7 +294,7 @@ public abstract class User { public String[] getGroupNames() { if (cache != null) { try { - return this.cache.get(ugi); + return this.cache.get(getShortName()); } catch (ExecutionException e) { return new String[0]; } @@ -311,6 +316,13 @@ public abstract class User { /** @see User#createUserForTesting(org.apache.hadoop.conf.Configuration, String, String[]) */ public static User createUserForTesting(Configuration conf, String name, String[] groups) { + synchronized (UserProvider.class) { + if (!(UserProvider.groups instanceof TestingGroups)) { + UserProvider.groups = new TestingGroups(UserProvider.groups); + } + } + + ((TestingGroups)UserProvider.groups).setUserGroups(name, groups); return new SecureHadoopUser(UserGroupInformation.createUserForTesting(name, groups)); } @@ -340,4 +352,30 @@ public abstract class User { return UserGroupInformation.isSecurityEnabled(); } } + + static class TestingGroups extends Groups { + private final Map<String, List<String>> userToGroupsMapping = + new HashMap<String,List<String>>(); + private Groups underlyingImplementation; + + TestingGroups(Groups underlyingImplementation) { + super(new Configuration()); + this.underlyingImplementation = underlyingImplementation; + } + + @Override + public List<String> getGroups(String user) throws IOException { + List<String> result = userToGroupsMapping.get(user); + + if (result == null) { + result = underlyingImplementation.getGroups(user); + } + + return result; + } + + private void setUserGroups(String user, String[] groups) { + userToGroupsMapping.put(user, Arrays.asList(groups)); + } + } } http://git-wip-us.apache.org/repos/asf/hbase/blob/0ed8b0e5/hbase-common/src/main/java/org/apache/hadoop/hbase/security/UserProvider.java ---------------------------------------------------------------------- diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/UserProvider.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/UserProvider.java index 8ffa753..43b1c89 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/security/UserProvider.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/security/UserProvider.java @@ -18,6 +18,8 @@ package org.apache.hadoop.hbase.security; import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -33,6 +35,7 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.hbase.BaseConfigurable; import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.security.Groups; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.util.ReflectionUtils; @@ -48,12 +51,20 @@ public class UserProvider extends BaseConfigurable { 1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("group-cache-%d").build())); - private LoadingCache<UserGroupInformation, String[]> groupCache = null; + private LoadingCache<String, String[]> groupCache = null; + static Groups groups = Groups.getUserToGroupsMappingService(); @Override - public void setConf(Configuration conf) { + public void setConf(final Configuration conf) { super.setConf(conf); + + synchronized (UserProvider.class) { + if (!(groups instanceof User.TestingGroups)) { + groups = Groups.getUserToGroupsMappingService(conf); + } + } + long cacheTimeout = getConf().getLong(CommonConfigurationKeys.HADOOP_SECURITY_GROUPS_CACHE_SECS, CommonConfigurationKeys.HADOOP_SECURITY_GROUPS_CACHE_SECS_DEFAULT) * 1000; @@ -67,21 +78,34 @@ public class UserProvider extends BaseConfigurable { .concurrencyLevel(20) // create the loader // This just delegates to UGI. - .build(new CacheLoader<UserGroupInformation, String[]>() { + .build(new CacheLoader<String, String[]>() { + + // Since UGI's don't hash based on the user id + // The cache needs to be keyed on the same thing that Hadoop's Groups class + // uses. So this cache uses shortname. @Override - public String[] load(UserGroupInformation ugi) throws Exception { - return ugi.getGroupNames(); + public String[] load(String ugi) throws Exception { + return getGroupStrings(ugi); + } + + private String[] getGroupStrings(String ugi) { + try { + Set<String> result = new LinkedHashSet<String>(groups.getGroups(ugi)); + return result.toArray(new String[result.size()]); + } catch (Exception e) { + return new String[0]; + } } // Provide the reload function that uses the executor thread. - public ListenableFuture<String[]> reload(final UserGroupInformation k, + public ListenableFuture<String[]> reload(final String k, String[] oldValue) throws Exception { return executor.submit(new Callable<String[]>() { - UserGroupInformation userGroupInformation = k; + @Override public String[] call() throws Exception { - return userGroupInformation.getGroupNames(); + return getGroupStrings(k); } }); } http://git-wip-us.apache.org/repos/asf/hbase/blob/0ed8b0e5/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java index 02b7f9a..709e796 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/TestUser.java @@ -18,15 +18,11 @@ */ package org.apache.hadoop.hbase.security; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - import java.io.IOException; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; +import org.apache.commons.lang.SystemUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; @@ -34,16 +30,84 @@ import org.apache.hadoop.fs.CommonConfigurationKeys; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.testclassification.SecurityTests; import org.apache.hadoop.hbase.testclassification.SmallTests; +import org.apache.hadoop.security.UserGroupInformation; import org.junit.Test; import org.junit.experimental.categories.Category; import com.google.common.collect.ImmutableSet; +import static org.junit.Assert.*; + @Category({SecurityTests.class, SmallTests.class}) public class TestUser { private static final Log LOG = LogFactory.getLog(TestUser.class); @Test + public void testCreateUserForTestingGroupCache() throws Exception { + Configuration conf = HBaseConfiguration.create(); + User uCreated = User.createUserForTesting(conf, "group_user", new String[] { "MYGROUP" }); + UserProvider up = UserProvider.instantiate(conf); + User uProvided = up.create(UserGroupInformation.createRemoteUser("group_user")); + assertArrayEquals(uCreated.getGroupNames(), uProvided.getGroupNames()); + + } + + @Test + public void testCacheGetGroups() throws Exception { + Configuration conf = HBaseConfiguration.create(); + UserProvider up = UserProvider.instantiate(conf); + + // VERY unlikely that this user will exist on the box. + // This should mean the user has no groups. + String nonUser = "kklvfnvhdhcenfnniilggljhdecjhidkle"; + + // Create two UGI's for this username + UserGroupInformation ugiOne = UserGroupInformation.createRemoteUser(nonUser); + UserGroupInformation ugiTwo = UserGroupInformation.createRemoteUser(nonUser); + + // Now try and get the user twice. + User uOne = up.create(ugiOne); + User uTwo = up.create(ugiTwo); + + // Make sure that we didn't break groups and everything worked well. + assertArrayEquals(uOne.getGroupNames(),uTwo.getGroupNames()); + + // Check that they are referentially equal. + // Since getting a group for a users that doesn't exist creates a new string array + // the only way that they should be referentially equal is if the cache worked and + // made sure we didn't go to hadoop's script twice. + assertTrue(uOne.getGroupNames() == uTwo.getGroupNames()); + assertEquals(0, ugiOne.getGroupNames().length); + } + + @Test + public void testCacheGetGroupsRoot() throws Exception { + // Windows users don't have a root user. + // However pretty much every other *NIX os will have root. + if (!SystemUtils.IS_OS_WINDOWS) { + Configuration conf = HBaseConfiguration.create(); + UserProvider up = UserProvider.instantiate(conf); + + + String rootUserName = "root"; + + // Create two UGI's for this username + UserGroupInformation ugiOne = UserGroupInformation.createRemoteUser(rootUserName); + UserGroupInformation ugiTwo = UserGroupInformation.createRemoteUser(rootUserName); + + // Now try and get the user twice. + User uOne = up.create(ugiOne); + User uTwo = up.create(ugiTwo); + + // Make sure that we didn't break groups and everything worked well. + assertArrayEquals(uOne.getGroupNames(),uTwo.getGroupNames()); + String[] groupNames = ugiOne.getGroupNames(); + assertTrue(groupNames.length > 0); + } + } + + + @Test public void testBasicAttributes() throws Exception { Configuration conf = HBaseConfiguration.create(); User user = User.createUserForTesting(conf, "simple", new String[]{"foo"});