HADOOP-13442. Optimize UGI group lookups. Contributed by Daryn Sharp.
(cherry picked from commit 9422515239
)
This commit is contained in:
parent
77b61d1f4e
commit
c7fe1f5ec5
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue