HADOOP-13442. Optimize UGI group lookups. Contributed by Daryn Sharp.

(cherry picked from commit 9422515239)
This commit is contained in:
Kihwal Lee 2016-08-04 10:48:17 -05:00
parent 77b61d1f4e
commit c7fe1f5ec5
8 changed files with 52 additions and 33 deletions

View File

@ -26,7 +26,6 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
@ -2222,12 +2221,11 @@ public abstract class FileSystem extends Configured implements Closeable {
FsPermission perm = stat.getPermission(); FsPermission perm = stat.getPermission();
UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
String user = ugi.getShortUserName(); String user = ugi.getShortUserName();
List<String> groups = Arrays.asList(ugi.getGroupNames());
if (user.equals(stat.getOwner())) { if (user.equals(stat.getOwner())) {
if (perm.getUserAction().implies(mode)) { if (perm.getUserAction().implies(mode)) {
return; return;
} }
} else if (groups.contains(stat.getGroup())) { } else if (ugi.getGroups().contains(stat.getGroup())) {
if (perm.getGroupAction().implies(mode)) { if (perm.getGroupAction().implies(mode)) {
return; return;
} }

View File

@ -862,7 +862,7 @@ public class ViewFileSystem extends FileSystem {
public FileStatus getFileStatus(Path f) throws IOException { public FileStatus getFileStatus(Path f) throws IOException {
checkPathIsSlash(f); checkPathIsSlash(f);
return new FileStatus(0, true, 0, 0, creationTime, creationTime, return new FileStatus(0, true, 0, 0, creationTime, creationTime,
PERMISSION_555, ugi.getUserName(), ugi.getGroupNames()[0], PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
new Path(theInternalDir.fullPath).makeQualified( new Path(theInternalDir.fullPath).makeQualified(
myUri, ROOT_PATH)); myUri, ROOT_PATH));
@ -883,7 +883,7 @@ public class ViewFileSystem extends FileSystem {
result[i++] = new FileStatus(0, false, 0, 0, result[i++] = new FileStatus(0, false, 0, 0,
creationTime, creationTime, PERMISSION_555, creationTime, creationTime, PERMISSION_555,
ugi.getUserName(), ugi.getGroupNames()[0], ugi.getUserName(), ugi.getPrimaryGroupName(),
link.getTargetLink(), link.getTargetLink(),
new Path(inode.fullPath).makeQualified( new Path(inode.fullPath).makeQualified(
myUri, null)); myUri, null));
@ -1015,7 +1015,7 @@ public class ViewFileSystem extends FileSystem {
public AclStatus getAclStatus(Path path) throws IOException { public AclStatus getAclStatus(Path path) throws IOException {
checkPathIsSlash(path); checkPathIsSlash(path);
return new AclStatus.Builder().owner(ugi.getUserName()) return new AclStatus.Builder().owner(ugi.getUserName())
.group(ugi.getGroupNames()[0]) .group(ugi.getPrimaryGroupName())
.addEntries(AclUtil.getMinimalAcl(PERMISSION_555)) .addEntries(AclUtil.getMinimalAcl(PERMISSION_555))
.stickyBit(false).build(); .stickyBit(false).build();
} }

View File

@ -843,14 +843,14 @@ public class ViewFs extends AbstractFileSystem {
public FileStatus getFileStatus(final Path f) throws IOException { public FileStatus getFileStatus(final Path f) throws IOException {
checkPathIsSlash(f); checkPathIsSlash(f);
return new FileStatus(0, true, 0, 0, creationTime, creationTime, return new FileStatus(0, true, 0, 0, creationTime, creationTime,
PERMISSION_555, ugi.getUserName(), ugi.getGroupNames()[0], PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
new Path(theInternalDir.fullPath).makeQualified( new Path(theInternalDir.fullPath).makeQualified(
myUri, null)); myUri, null));
} }
@Override @Override
public FileStatus getFileLinkStatus(final Path f) public FileStatus getFileLinkStatus(final Path f)
throws FileNotFoundException { throws IOException {
// look up i internalDirs children - ignore first Slash // look up i internalDirs children - ignore first Slash
INode<AbstractFileSystem> inode = INode<AbstractFileSystem> inode =
theInternalDir.children.get(f.toUri().toString().substring(1)); theInternalDir.children.get(f.toUri().toString().substring(1));
@ -863,13 +863,13 @@ public class ViewFs extends AbstractFileSystem {
INodeLink<AbstractFileSystem> inodelink = INodeLink<AbstractFileSystem> inodelink =
(INodeLink<AbstractFileSystem>) inode; (INodeLink<AbstractFileSystem>) inode;
result = new FileStatus(0, false, 0, 0, creationTime, creationTime, result = new FileStatus(0, false, 0, 0, creationTime, creationTime,
PERMISSION_555, ugi.getUserName(), ugi.getGroupNames()[0], PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
inodelink.getTargetLink(), inodelink.getTargetLink(),
new Path(inode.fullPath).makeQualified( new Path(inode.fullPath).makeQualified(
myUri, null)); myUri, null));
} else { } else {
result = new FileStatus(0, true, 0, 0, creationTime, creationTime, result = new FileStatus(0, true, 0, 0, creationTime, creationTime,
PERMISSION_555, ugi.getUserName(), ugi.getGroupNames()[0], PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
new Path(inode.fullPath).makeQualified( new Path(inode.fullPath).makeQualified(
myUri, null)); myUri, null));
} }
@ -908,7 +908,7 @@ public class ViewFs extends AbstractFileSystem {
result[i++] = new FileStatus(0, false, 0, 0, result[i++] = new FileStatus(0, false, 0, 0,
creationTime, creationTime, creationTime, creationTime,
PERMISSION_555, ugi.getUserName(), ugi.getGroupNames()[0], PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
link.getTargetLink(), link.getTargetLink(),
new Path(inode.fullPath).makeQualified( new Path(inode.fullPath).makeQualified(
myUri, null)); myUri, null));
@ -1042,7 +1042,7 @@ public class ViewFs extends AbstractFileSystem {
public AclStatus getAclStatus(Path path) throws IOException { public AclStatus getAclStatus(Path path) throws IOException {
checkPathIsSlash(path); checkPathIsSlash(path);
return new AclStatus.Builder().owner(ugi.getUserName()) return new AclStatus.Builder().owner(ugi.getUserName())
.group(ugi.getGroupNames()[0]) .group(ugi.getPrimaryGroupName())
.addEntries(AclUtil.getMinimalAcl(PERMISSION_555)) .addEntries(AclUtil.getMinimalAcl(PERMISSION_555))
.stickyBit(false).build(); .stickyBit(false).build();
} }

View File

@ -23,7 +23,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.util.Arrays;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataInputStream;
@ -276,7 +275,7 @@ public class SecureIOUtils {
UserGroupInformation.createRemoteUser(expectedOwner); UserGroupInformation.createRemoteUser(expectedOwner);
final String adminsGroupString = "Administrators"; final String adminsGroupString = "Administrators";
success = owner.equals(adminsGroupString) success = owner.equals(adminsGroupString)
&& Arrays.asList(ugi.getGroupNames()).contains(adminsGroupString); && ugi.getGroups().contains(adminsGroupString);
} else { } else {
success = false; success = false;
} }

View File

@ -18,9 +18,11 @@
package org.apache.hadoop.security; package org.apache.hadoop.security;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -31,6 +33,7 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.htrace.core.TraceScope; import org.apache.htrace.core.TraceScope;
import org.apache.htrace.core.Tracer; import org.apache.htrace.core.Tracer;
@ -74,8 +77,8 @@ public class Groups {
private final GroupMappingServiceProvider impl; private final GroupMappingServiceProvider impl;
private final LoadingCache<String, List<String>> cache; private final LoadingCache<String, List<String>> cache;
private final Map<String, List<String>> staticUserToGroupsMap = private final AtomicReference<Map<String, List<String>>> staticMapRef =
new HashMap<String, List<String>>(); new AtomicReference<>();
private final long cacheTimeout; private final long cacheTimeout;
private final long negativeCacheTimeout; private final long negativeCacheTimeout;
private final long warningDeltaMs; private final long warningDeltaMs;
@ -164,6 +167,8 @@ public class Groups {
CommonConfigurationKeys.HADOOP_USER_GROUP_STATIC_OVERRIDES_DEFAULT); CommonConfigurationKeys.HADOOP_USER_GROUP_STATIC_OVERRIDES_DEFAULT);
Collection<String> mappings = StringUtils.getStringCollection( Collection<String> mappings = StringUtils.getStringCollection(
staticMapping, ";"); staticMapping, ";");
Map<String, List<String>> staticUserToGroupsMap =
new HashMap<String, List<String>>();
for (String users : mappings) { for (String users : mappings) {
Collection<String> userToGroups = StringUtils.getStringCollection(users, Collection<String> userToGroups = StringUtils.getStringCollection(users,
"="); "=");
@ -182,6 +187,8 @@ public class Groups {
} }
staticUserToGroupsMap.put(user, groups); staticUserToGroupsMap.put(user, groups);
} }
staticMapRef.set(
staticUserToGroupsMap.isEmpty() ? null : staticUserToGroupsMap);
} }
private boolean isNegativeCacheEnabled() { private boolean isNegativeCacheEnabled() {
@ -201,9 +208,12 @@ public class Groups {
*/ */
public List<String> getGroups(final String user) throws IOException { public List<String> getGroups(final String user) throws IOException {
// No need to lookup for groups of static users // No need to lookup for groups of static users
List<String> staticMapping = staticUserToGroupsMap.get(user); Map<String, List<String>> staticUserToGroupsMap = staticMapRef.get();
if (staticMapping != null) { if (staticUserToGroupsMap != null) {
return staticMapping; List<String> staticMapping = staticUserToGroupsMap.get(user);
if (staticMapping != null) {
return staticMapping;
}
} }
// Check the negative cache first // Check the negative cache first
@ -322,7 +332,9 @@ public class Groups {
throw noGroupsForUser(user); throw noGroupsForUser(user);
} }
return groups; // return immutable de-duped list
return Collections.unmodifiableList(
new ArrayList<>(new LinkedHashSet<>(groups)));
} }
/** /**

View File

@ -38,7 +38,6 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -1499,11 +1498,11 @@ public class UserGroupInformation {
} }
public String getPrimaryGroupName() throws IOException { public String getPrimaryGroupName() throws IOException {
String[] groups = getGroupNames(); List<String> groups = getGroups();
if (groups.length == 0) { if (groups.isEmpty()) {
throw new IOException("There is no primary group for UGI " + this); throw new IOException("There is no primary group for UGI " + this);
} }
return groups[0]; return groups.get(0);
} }
/** /**
@ -1615,27 +1614,36 @@ public class UserGroupInformation {
return credentials; return credentials;
} }
/**
* Get the group names for this user. {@ #getGroups(String)} is less
* expensive alternative when checking for a contained element.
* @return the list of users with the primary group first. If the command
* fails, it returns an empty list.
*/
public String[] getGroupNames() {
List<String> groups = getGroups();
return groups.toArray(new String[groups.size()]);
}
/** /**
* Get the group names for this user. * Get the group names for this user.
* @return the list of users with the primary group first. If the command * @return the list of users with the primary group first. If the command
* fails, it returns an empty list. * fails, it returns an empty list.
*/ */
public synchronized String[] getGroupNames() { public List<String> getGroups() {
ensureInitialized(); ensureInitialized();
try { try {
Set<String> result = new LinkedHashSet<String> return groups.getGroups(getShortUserName());
(groups.getGroups(getShortUserName()));
return result.toArray(new String[result.size()]);
} catch (IOException ie) { } catch (IOException ie) {
if (LOG.isDebugEnabled()) { if (LOG.isDebugEnabled()) {
LOG.debug("Failed to get groups for user " + getShortUserName() LOG.debug("Failed to get groups for user " + getShortUserName()
+ " by " + ie); + " by " + ie);
LOG.trace("TRACE", ie); LOG.trace("TRACE", ie);
} }
return StringUtils.emptyStringArray; return Collections.emptyList();
} }
} }
/** /**
* Return the username. * Return the username.
*/ */

View File

@ -231,7 +231,7 @@ public class AccessControlList implements Writable {
if (allAllowed || users.contains(ugi.getShortUserName())) { if (allAllowed || users.contains(ugi.getShortUserName())) {
return true; return true;
} else if (!groups.isEmpty()) { } else if (!groups.isEmpty()) {
for(String group: ugi.getGroupNames()) { for (String group : ugi.getGroups()) {
if (groups.contains(group)) { if (groups.contains(group)) {
return true; return true;
} }

View File

@ -443,8 +443,10 @@ public class TestUserGroupInformation {
UserGroupInformation uugi = UserGroupInformation uugi =
UserGroupInformation.createUserForTesting(USER_NAME, GROUP_NAMES); UserGroupInformation.createUserForTesting(USER_NAME, GROUP_NAMES);
assertEquals(USER_NAME, uugi.getUserName()); assertEquals(USER_NAME, uugi.getUserName());
assertArrayEquals(new String[]{GROUP1_NAME, GROUP2_NAME, GROUP3_NAME}, String[] expected = new String[]{GROUP1_NAME, GROUP2_NAME, GROUP3_NAME};
uugi.getGroupNames()); assertArrayEquals(expected, uugi.getGroupNames());
assertArrayEquals(expected, uugi.getGroups().toArray(new String[0]));
assertEquals(GROUP1_NAME, uugi.getPrimaryGroupName());
} }
@SuppressWarnings("unchecked") // from Mockito mocks @SuppressWarnings("unchecked") // from Mockito mocks