HDFS-12895. RBF: Add ACL support for mount table. Contributed by Yiqun Lin.

This commit is contained in:
Yiqun Lin 2017-12-15 14:09:24 +08:00
parent 89b6c482c1
commit ee028bfdf1
13 changed files with 615 additions and 16 deletions

View File

@ -17,6 +17,9 @@
*/ */
package org.apache.hadoop.hdfs.server.federation.router; package org.apache.hadoop.hdfs.server.federation.router;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_PERMISSIONS_ENABLED_DEFAULT;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_PERMISSIONS_ENABLED_KEY;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -35,9 +38,12 @@ import org.apache.hadoop.hdfs.server.federation.store.protocol.RemoveMountTableE
import org.apache.hadoop.hdfs.server.federation.store.protocol.RemoveMountTableEntryResponse; import org.apache.hadoop.hdfs.server.federation.store.protocol.RemoveMountTableEntryResponse;
import org.apache.hadoop.hdfs.server.federation.store.protocol.UpdateMountTableEntryRequest; import org.apache.hadoop.hdfs.server.federation.store.protocol.UpdateMountTableEntryRequest;
import org.apache.hadoop.hdfs.server.federation.store.protocol.UpdateMountTableEntryResponse; import org.apache.hadoop.hdfs.server.federation.store.protocol.UpdateMountTableEntryResponse;
import org.apache.hadoop.hdfs.server.namenode.NameNode;
import org.apache.hadoop.ipc.ProtobufRpcEngine; import org.apache.hadoop.ipc.ProtobufRpcEngine;
import org.apache.hadoop.ipc.RPC; import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.ipc.RPC.Server; import org.apache.hadoop.ipc.RPC.Server;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.service.AbstractService; import org.apache.hadoop.service.AbstractService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -65,6 +71,14 @@ public class RouterAdminServer extends AbstractService
private final Server adminServer; private final Server adminServer;
private final InetSocketAddress adminAddress; private final InetSocketAddress adminAddress;
/**
* Permission related info used for constructing new router permission
* checker instance.
*/
private static String routerOwner;
private static String superGroup;
private static boolean isPermissionEnabled;
public RouterAdminServer(Configuration conf, Router router) public RouterAdminServer(Configuration conf, Router router)
throws IOException { throws IOException {
super(RouterAdminServer.class.getName()); super(RouterAdminServer.class.getName());
@ -96,6 +110,7 @@ public class RouterAdminServer extends AbstractService
LOG.info("Admin server binding to {}:{}", LOG.info("Admin server binding to {}:{}",
bindHost, confRpcAddress.getPort()); bindHost, confRpcAddress.getPort());
initializePermissionSettings(this.conf);
this.adminServer = new RPC.Builder(this.conf) this.adminServer = new RPC.Builder(this.conf)
.setProtocol(RouterAdminProtocolPB.class) .setProtocol(RouterAdminProtocolPB.class)
.setInstance(clientNNPbService) .setInstance(clientNNPbService)
@ -112,6 +127,22 @@ public class RouterAdminServer extends AbstractService
router.setAdminServerAddress(this.adminAddress); router.setAdminServerAddress(this.adminAddress);
} }
/**
* Initialize permission related settings.
*
* @param routerConf
* @throws IOException
*/
private static void initializePermissionSettings(Configuration routerConf)
throws IOException {
routerOwner = UserGroupInformation.getCurrentUser().getShortUserName();
superGroup = routerConf.get(
DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_KEY,
DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_DEFAULT);
isPermissionEnabled = routerConf.getBoolean(DFS_PERMISSIONS_ENABLED_KEY,
DFS_PERMISSIONS_ENABLED_DEFAULT);
}
/** Allow access to the client RPC server for testing. */ /** Allow access to the client RPC server for testing. */
@VisibleForTesting @VisibleForTesting
Server getAdminServer() { Server getAdminServer() {
@ -180,4 +211,44 @@ public class RouterAdminServer extends AbstractService
GetMountTableEntriesRequest request) throws IOException { GetMountTableEntriesRequest request) throws IOException {
return getMountTableStore().getMountTableEntries(request); return getMountTableStore().getMountTableEntries(request);
} }
/**
* Get a new permission checker used for making mount table access
* control. This method will be invoked during each RPC call in router
* admin server.
*
* @return Router permission checker
* @throws AccessControlException
*/
public static RouterPermissionChecker getPermissionChecker()
throws AccessControlException {
if (!isPermissionEnabled) {
return null;
}
try {
return new RouterPermissionChecker(routerOwner, superGroup,
NameNode.getRemoteUser());
} catch (IOException e) {
throw new AccessControlException(e);
}
}
/**
* Get super user name.
*
* @return String super user name.
*/
public static String getSuperUser() {
return routerOwner;
}
/**
* Get super group name.
*
* @return String super group name.
*/
public static String getSuperGroup(){
return superGroup;
}
} }

View File

@ -0,0 +1,82 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdfs.server.federation.router;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.server.federation.store.records.MountTable;
import org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
/**
* Class that helps in checking permissions in Router-based federation.
*/
public class RouterPermissionChecker extends FSPermissionChecker {
static final Log LOG = LogFactory.getLog(RouterPermissionChecker.class);
/** Mount table default permission. */
public static final short MOUNT_TABLE_PERMISSION_DEFAULT = 00755;
public RouterPermissionChecker(String routerOwner, String supergroup,
UserGroupInformation callerUgi) {
super(routerOwner, supergroup, callerUgi, null);
}
/**
* Whether a mount table entry can be accessed by the current context.
*
* @param mountTable
* MountTable being accessed
* @param access
* type of action being performed on the cache pool
* @throws AccessControlException
* if mount table cannot be accessed
*/
public void checkPermission(MountTable mountTable, FsAction access)
throws AccessControlException {
if (isSuperUser()) {
return;
}
FsPermission mode = mountTable.getMode();
if (getUser().equals(mountTable.getOwnerName())
&& mode.getUserAction().implies(access)) {
return;
}
if (isMemberOfGroup(mountTable.getGroupName())
&& mode.getGroupAction().implies(access)) {
return;
}
if (!getUser().equals(mountTable.getOwnerName())
&& !isMemberOfGroup(mountTable.getGroupName())
&& mode.getOtherAction().implies(access)) {
return;
}
throw new AccessControlException(
"Permission denied while accessing mount table "
+ mountTable.getSourcePath()
+ ": user " + getUser() + " does not have " + access.toString()
+ " permissions.");
}
}

View File

@ -24,6 +24,9 @@ import java.util.List;
import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability; import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.hdfs.server.federation.router.RouterAdminServer;
import org.apache.hadoop.hdfs.server.federation.router.RouterPermissionChecker;
import org.apache.hadoop.hdfs.server.federation.store.MountTableStore; import org.apache.hadoop.hdfs.server.federation.store.MountTableStore;
import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreDriver; import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreDriver;
import org.apache.hadoop.hdfs.server.federation.store.protocol.AddMountTableEntryRequest; import org.apache.hadoop.hdfs.server.federation.store.protocol.AddMountTableEntryRequest;
@ -36,6 +39,7 @@ import org.apache.hadoop.hdfs.server.federation.store.protocol.UpdateMountTableE
import org.apache.hadoop.hdfs.server.federation.store.protocol.UpdateMountTableEntryResponse; import org.apache.hadoop.hdfs.server.federation.store.protocol.UpdateMountTableEntryResponse;
import org.apache.hadoop.hdfs.server.federation.store.records.MountTable; import org.apache.hadoop.hdfs.server.federation.store.records.MountTable;
import org.apache.hadoop.hdfs.server.federation.store.records.Query; import org.apache.hadoop.hdfs.server.federation.store.records.Query;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.util.Time; import org.apache.hadoop.util.Time;
/** /**
@ -52,7 +56,15 @@ public class MountTableStoreImpl extends MountTableStore {
@Override @Override
public AddMountTableEntryResponse addMountTableEntry( public AddMountTableEntryResponse addMountTableEntry(
AddMountTableEntryRequest request) throws IOException { AddMountTableEntryRequest request) throws IOException {
boolean status = getDriver().put(request.getEntry(), false, true); MountTable mountTable = request.getEntry();
if (mountTable != null) {
RouterPermissionChecker pc = RouterAdminServer.getPermissionChecker();
if (pc != null) {
pc.checkPermission(mountTable, FsAction.WRITE);
}
}
boolean status = getDriver().put(mountTable, false, true);
AddMountTableEntryResponse response = AddMountTableEntryResponse response =
AddMountTableEntryResponse.newInstance(); AddMountTableEntryResponse.newInstance();
response.setStatus(status); response.setStatus(status);
@ -62,8 +74,15 @@ public class MountTableStoreImpl extends MountTableStore {
@Override @Override
public UpdateMountTableEntryResponse updateMountTableEntry( public UpdateMountTableEntryResponse updateMountTableEntry(
UpdateMountTableEntryRequest request) throws IOException { UpdateMountTableEntryRequest request) throws IOException {
MountTable entry = request.getEntry(); MountTable mountTable = request.getEntry();
boolean status = getDriver().put(entry, true, true); if (mountTable != null) {
RouterPermissionChecker pc = RouterAdminServer.getPermissionChecker();
if (pc != null) {
pc.checkPermission(mountTable, FsAction.WRITE);
}
}
boolean status = getDriver().put(mountTable, true, true);
UpdateMountTableEntryResponse response = UpdateMountTableEntryResponse response =
UpdateMountTableEntryResponse.newInstance(); UpdateMountTableEntryResponse.newInstance();
response.setStatus(status); response.setStatus(status);
@ -77,8 +96,17 @@ public class MountTableStoreImpl extends MountTableStore {
final MountTable partial = MountTable.newInstance(); final MountTable partial = MountTable.newInstance();
partial.setSourcePath(srcPath); partial.setSourcePath(srcPath);
final Query<MountTable> query = new Query<>(partial); final Query<MountTable> query = new Query<>(partial);
int removedRecords = getDriver().remove(getRecordClass(), query); final MountTable deleteEntry = getDriver().get(getRecordClass(), query);
boolean status = (removedRecords == 1);
boolean status = false;
if (deleteEntry != null) {
RouterPermissionChecker pc = RouterAdminServer.getPermissionChecker();
if (pc != null) {
pc.checkPermission(deleteEntry, FsAction.WRITE);
}
status = getDriver().remove(deleteEntry);
}
RemoveMountTableEntryResponse response = RemoveMountTableEntryResponse response =
RemoveMountTableEntryResponse.newInstance(); RemoveMountTableEntryResponse.newInstance();
response.setStatus(status); response.setStatus(status);
@ -88,12 +116,13 @@ public class MountTableStoreImpl extends MountTableStore {
@Override @Override
public GetMountTableEntriesResponse getMountTableEntries( public GetMountTableEntriesResponse getMountTableEntries(
GetMountTableEntriesRequest request) throws IOException { GetMountTableEntriesRequest request) throws IOException {
RouterPermissionChecker pc =
RouterAdminServer.getPermissionChecker();
// Get all values from the cache // Get all values from the cache
List<MountTable> records = getCachedRecords(); List<MountTable> records = getCachedRecords();
// Sort and filter // Sort and filter
Collections.sort(records); Collections.sort(records, MountTable.SOURCE_COMPARATOR);
String reqSrcPath = request.getSrcPath(); String reqSrcPath = request.getSrcPath();
if (reqSrcPath != null && !reqSrcPath.isEmpty()) { if (reqSrcPath != null && !reqSrcPath.isEmpty()) {
// Return only entries beneath this path // Return only entries beneath this path
@ -103,6 +132,15 @@ public class MountTableStoreImpl extends MountTableStore {
String srcPath = record.getSourcePath(); String srcPath = record.getSourcePath();
if (!srcPath.startsWith(reqSrcPath)) { if (!srcPath.startsWith(reqSrcPath)) {
it.remove(); it.remove();
} else if (pc != null) {
// do the READ permission check
try {
pc.checkPermission(record, FsAction.READ);
} catch (AccessControlException ignored) {
// Remove this mount table entry if it cannot
// be accessed by current user.
it.remove();
}
} }
} }
} }

View File

@ -28,9 +28,13 @@ import java.util.TreeMap;
import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.server.federation.resolver.RemoteLocation; import org.apache.hadoop.hdfs.server.federation.resolver.RemoteLocation;
import org.apache.hadoop.hdfs.server.federation.resolver.order.DestinationOrder; import org.apache.hadoop.hdfs.server.federation.resolver.order.DestinationOrder;
import org.apache.hadoop.hdfs.server.federation.router.RouterPermissionChecker;
import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreSerializer; import org.apache.hadoop.hdfs.server.federation.store.driver.StateStoreSerializer;
import org.apache.hadoop.hdfs.server.namenode.NameNode;
import org.apache.hadoop.security.UserGroupInformation;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -127,6 +131,15 @@ public abstract class MountTable extends BaseRecord {
// Set the serialized dest string // Set the serialized dest string
record.setDestinations(locations); record.setDestinations(locations);
// Set permission fields
UserGroupInformation ugi = NameNode.getRemoteUser();
record.setOwnerName(ugi.getShortUserName());
String group = ugi.getGroups().isEmpty() ? ugi.getShortUserName()
: ugi.getPrimaryGroupName();
record.setGroupName(group);
record.setMode(new FsPermission(
RouterPermissionChecker.MOUNT_TABLE_PERMISSION_DEFAULT));
// Validate // Validate
record.validate(); record.validate();
return record; return record;
@ -193,6 +206,48 @@ public abstract class MountTable extends BaseRecord {
*/ */
public abstract void setDestOrder(DestinationOrder order); public abstract void setDestOrder(DestinationOrder order);
/**
* Get owner name of this mount table entry.
*
* @return Owner name
*/
public abstract String getOwnerName();
/**
* Set owner name of this mount table entry.
*
* @param owner Owner name for mount table entry
*/
public abstract void setOwnerName(String owner);
/**
* Get group name of this mount table entry.
*
* @return Group name
*/
public abstract String getGroupName();
/**
* Set group name of this mount table entry.
*
* @param group Group name for mount table entry
*/
public abstract void setGroupName(String group);
/**
* Get permission of this mount table entry.
*
* @return FsPermission permission mode
*/
public abstract FsPermission getMode();
/**
* Set permission for this mount table entry.
*
* @param mode Permission for mount table entry
*/
public abstract void setMode(FsPermission mode);
/** /**
* Get the default location. * Get the default location.
* @return The default location. * @return The default location.
@ -235,6 +290,19 @@ public abstract class MountTable extends BaseRecord {
if (this.isReadOnly()) { if (this.isReadOnly()) {
sb.append("[RO]"); sb.append("[RO]");
} }
if (this.getOwnerName() != null) {
sb.append("[owner:").append(this.getOwnerName()).append("]");
}
if (this.getGroupName() != null) {
sb.append("[group:").append(this.getGroupName()).append("]");
}
if (this.getMode() != null) {
sb.append("[mode:").append(this.getMode()).append("]");
}
return sb.toString(); return sb.toString();
} }

View File

@ -21,6 +21,7 @@ import java.io.IOException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProtos.MountTableRecordProto; import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProtos.MountTableRecordProto;
import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProtos.MountTableRecordProto.Builder; import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProtos.MountTableRecordProto.Builder;
import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProtos.MountTableRecordProto.DestOrder; import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProtos.MountTableRecordProto.DestOrder;
@ -28,6 +29,8 @@ import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProt
import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProtos.RemoteLocationProto; import org.apache.hadoop.hdfs.federation.protocol.proto.HdfsServerFederationProtos.RemoteLocationProto;
import org.apache.hadoop.hdfs.server.federation.resolver.RemoteLocation; import org.apache.hadoop.hdfs.server.federation.resolver.RemoteLocation;
import org.apache.hadoop.hdfs.server.federation.resolver.order.DestinationOrder; import org.apache.hadoop.hdfs.server.federation.resolver.order.DestinationOrder;
import org.apache.hadoop.hdfs.server.federation.router.RouterAdminServer;
import org.apache.hadoop.hdfs.server.federation.router.RouterPermissionChecker;
import org.apache.hadoop.hdfs.server.federation.store.protocol.impl.pb.FederationProtocolPBTranslator; import org.apache.hadoop.hdfs.server.federation.store.protocol.impl.pb.FederationProtocolPBTranslator;
import org.apache.hadoop.hdfs.server.federation.store.records.MountTable; import org.apache.hadoop.hdfs.server.federation.store.records.MountTable;
@ -189,6 +192,64 @@ public class MountTablePBImpl extends MountTable implements PBRecord {
} }
} }
@Override
public String getOwnerName() {
MountTableRecordProtoOrBuilder proto = this.translator.getProtoOrBuilder();
if (!proto.hasOwnerName()) {
return RouterAdminServer.getSuperUser();
}
return proto.getOwnerName();
}
@Override
public void setOwnerName(String owner) {
Builder builder = this.translator.getBuilder();
if (owner == null) {
builder.clearOwnerName();
} else {
builder.setOwnerName(owner);
}
}
@Override
public String getGroupName() {
MountTableRecordProtoOrBuilder proto = this.translator.getProtoOrBuilder();
if (!proto.hasGroupName()) {
return RouterAdminServer.getSuperGroup();
}
return proto.getGroupName();
}
@Override
public void setGroupName(String group) {
Builder builder = this.translator.getBuilder();
if (group == null) {
builder.clearGroupName();
} else {
builder.setGroupName(group);
}
}
@Override
public FsPermission getMode() {
MountTableRecordProtoOrBuilder proto = this.translator.getProtoOrBuilder();
short mode = RouterPermissionChecker.MOUNT_TABLE_PERMISSION_DEFAULT;
if (proto.hasMode()) {
mode = (short) proto.getMode();
}
return new FsPermission(mode);
}
@Override
public void setMode(FsPermission mode) {
Builder builder = this.translator.getBuilder();
if (mode == null) {
builder.clearMode();
} else {
builder.setMode(mode.toShort());
}
}
private DestinationOrder convert(DestOrder order) { private DestinationOrder convert(DestOrder order) {
switch (order) { switch (order) {
case LOCAL: case LOCAL:

View File

@ -46,7 +46,7 @@ import org.apache.hadoop.security.UserGroupInformation;
* *
* Some of the helper methods are gaurded by {@link FSNamesystem#readLock()}. * Some of the helper methods are gaurded by {@link FSNamesystem#readLock()}.
*/ */
class FSPermissionChecker implements AccessControlEnforcer { public class FSPermissionChecker implements AccessControlEnforcer {
static final Log LOG = LogFactory.getLog(UserGroupInformation.class); static final Log LOG = LogFactory.getLog(UserGroupInformation.class);
private static String getPath(byte[][] components, int start, int end) { private static String getPath(byte[][] components, int start, int end) {
@ -86,7 +86,7 @@ class FSPermissionChecker implements AccessControlEnforcer {
private final INodeAttributeProvider attributeProvider; private final INodeAttributeProvider attributeProvider;
FSPermissionChecker(String fsOwner, String supergroup, protected FSPermissionChecker(String fsOwner, String supergroup,
UserGroupInformation callerUgi, UserGroupInformation callerUgi,
INodeAttributeProvider attributeProvider) { INodeAttributeProvider attributeProvider) {
this.fsOwner = fsOwner; this.fsOwner = fsOwner;

View File

@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured; import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.server.federation.resolver.MountTableManager; import org.apache.hadoop.hdfs.server.federation.resolver.MountTableManager;
@ -77,7 +78,7 @@ public class RouterAdmin extends Configured implements Tool {
public void printUsage() { public void printUsage() {
String usage = "Federation Admin Tools:\n" String usage = "Federation Admin Tools:\n"
+ "\t[-add <source> <nameservice> <destination> " + "\t[-add <source> <nameservice> <destination> "
+ "[-readonly]\n" + "[-readonly] -owner <owner> -group <group> -mode <mode>]\n"
+ "\t[-rm <source>]\n" + "\t[-rm <source>]\n"
+ "\t[-ls <path>]\n"; + "\t[-ls <path>]\n";
System.out.println(usage); System.out.println(usage);
@ -193,6 +194,9 @@ public class RouterAdmin extends Configured implements Tool {
// Optional parameters // Optional parameters
boolean readOnly = false; boolean readOnly = false;
String owner = null;
String group = null;
FsPermission mode = null;
DestinationOrder order = DestinationOrder.HASH; DestinationOrder order = DestinationOrder.HASH;
while (i < parameters.length) { while (i < parameters.length) {
if (parameters[i].equals("-readonly")) { if (parameters[i].equals("-readonly")) {
@ -204,11 +208,23 @@ public class RouterAdmin extends Configured implements Tool {
} catch(Exception e) { } catch(Exception e) {
System.err.println("Cannot parse order: " + parameters[i]); System.err.println("Cannot parse order: " + parameters[i]);
} }
} else if (parameters[i].equals("-owner")) {
i++;
owner = parameters[i];
} else if (parameters[i].equals("-group")) {
i++;
group = parameters[i];
} else if (parameters[i].equals("-mode")) {
i++;
short modeValue = Short.parseShort(parameters[i], 8);
mode = new FsPermission(modeValue);
} }
i++; i++;
} }
return addMount(mount, nss, dest, readOnly, order); return addMount(mount, nss, dest, readOnly, order,
new ACLEntity(owner, group, mode));
} }
/** /**
@ -219,11 +235,13 @@ public class RouterAdmin extends Configured implements Tool {
* @param dest Destination path. * @param dest Destination path.
* @param readonly If the mount point is read only. * @param readonly If the mount point is read only.
* @param order Order of the destination locations. * @param order Order of the destination locations.
* @param aclInfo the ACL info for mount point.
* @return If the mount point was added. * @return If the mount point was added.
* @throws IOException Error adding the mount point. * @throws IOException Error adding the mount point.
*/ */
public boolean addMount(String mount, String[] nss, String dest, public boolean addMount(String mount, String[] nss, String dest,
boolean readonly, DestinationOrder order) throws IOException { boolean readonly, DestinationOrder order, ACLEntity aclInfo)
throws IOException {
// Get the existing entry // Get the existing entry
MountTableManager mountTable = client.getMountTableManager(); MountTableManager mountTable = client.getMountTableManager();
GetMountTableEntriesRequest getRequest = GetMountTableEntriesRequest getRequest =
@ -251,6 +269,20 @@ public class RouterAdmin extends Configured implements Tool {
if (order != null) { if (order != null) {
newEntry.setDestOrder(order); newEntry.setDestOrder(order);
} }
// Set ACL info for mount table entry
if (aclInfo.getOwner() != null) {
newEntry.setOwnerName(aclInfo.getOwner());
}
if (aclInfo.getGroup() != null) {
newEntry.setGroupName(aclInfo.getGroup());
}
if (aclInfo.getMode() != null) {
newEntry.setMode(aclInfo.getMode());
}
AddMountTableEntryRequest request = AddMountTableEntryRequest request =
AddMountTableEntryRequest.newInstance(newEntry); AddMountTableEntryRequest.newInstance(newEntry);
AddMountTableEntryResponse addResponse = AddMountTableEntryResponse addResponse =
@ -273,6 +305,20 @@ public class RouterAdmin extends Configured implements Tool {
if (order != null) { if (order != null) {
existingEntry.setDestOrder(order); existingEntry.setDestOrder(order);
} }
// Update ACL info of mount table entry
if (aclInfo.getOwner() != null) {
existingEntry.setOwnerName(aclInfo.getOwner());
}
if (aclInfo.getGroup() != null) {
existingEntry.setGroupName(aclInfo.getGroup());
}
if (aclInfo.getMode() != null) {
existingEntry.setMode(aclInfo.getMode());
}
UpdateMountTableEntryRequest updateRequest = UpdateMountTableEntryRequest updateRequest =
UpdateMountTableEntryRequest.newInstance(existingEntry); UpdateMountTableEntryRequest.newInstance(existingEntry);
UpdateMountTableEntryResponse updateResponse = UpdateMountTableEntryResponse updateResponse =
@ -323,8 +369,8 @@ public class RouterAdmin extends Configured implements Tool {
private static void printMounts(List<MountTable> entries) { private static void printMounts(List<MountTable> entries) {
System.out.println("Mount Table Entries:"); System.out.println("Mount Table Entries:");
System.out.println(String.format( System.out.println(String.format(
"%-25s %-25s", "%-25s %-25s %-25s %-25s %-25s",
"Source", "Destinations")); "Source", "Destinations", "Owner", "Group", "Mode"));
for (MountTable entry : entries) { for (MountTable entry : entries) {
StringBuilder destBuilder = new StringBuilder(); StringBuilder destBuilder = new StringBuilder();
for (RemoteLocation location : entry.getDestinations()) { for (RemoteLocation location : entry.getDestinations()) {
@ -334,8 +380,38 @@ public class RouterAdmin extends Configured implements Tool {
destBuilder.append(String.format("%s->%s", location.getNameserviceId(), destBuilder.append(String.format("%s->%s", location.getNameserviceId(),
location.getDest())); location.getDest()));
} }
System.out.println(String.format("%-25s %-25s", entry.getSourcePath(), System.out.print(String.format("%-25s %-25s", entry.getSourcePath(),
destBuilder.toString())); destBuilder.toString()));
System.out.println(String.format(" %-25s %-25s %-25s",
entry.getOwnerName(), entry.getGroupName(), entry.getMode()));
}
}
/**
* Inner class that stores ACL info of mount table.
*/
static class ACLEntity {
private final String owner;
private final String group;
private final FsPermission mode;
ACLEntity(String owner, String group, FsPermission mode) {
this.owner = owner;
this.group = group;
this.mode = mode;
}
public String getOwner() {
return owner;
}
public String getGroup() {
return group;
}
public FsPermission getMode() {
return mode;
} }
} }
} }

View File

@ -129,6 +129,10 @@ message MountTableRecordProto {
RANDOM = 2; RANDOM = 2;
} }
optional DestOrder destOrder = 6 [default = HASH]; optional DestOrder destOrder = 6 [default = HASH];
optional string ownerName = 10;
optional string groupName = 11;
optional int32 mode = 12;
} }
message AddMountTableEntryRequestProto { message AddMountTableEntryRequestProto {

View File

@ -335,6 +335,9 @@
<th>Target path</th> <th>Target path</th>
<th>Order</th> <th>Order</th>
<th>Read only</th> <th>Read only</th>
<th>Owner</th>
<th>Group</th>
<th>Permission</th>
<th>Date Modified</th> <th>Date Modified</th>
<th>Date Created</th> <th>Date Created</th>
</tr> </tr>
@ -347,6 +350,9 @@
<td>{path}</td> <td>{path}</td>
<td>{order}</td> <td>{order}</td>
<td class="dfshealth-node-icon dfshealth-mount-read-only-{readonly}"/> <td class="dfshealth-node-icon dfshealth-mount-read-only-{readonly}"/>
<td>{ownerName}</td>
<td>{groupName}</td>
<td>{mode}</td>
<td>{dateModified}</td> <td>{dateModified}</td>
<td>{dateCreated}</td> <td>{dateCreated}</td>
</tr> </tr>

View File

@ -425,7 +425,7 @@ Runs the DFS router. See [Router](./HDFSRouterFederation.html#Router) for more i
Usage: Usage:
hdfs dfsrouteradmin hdfs dfsrouteradmin
[-add <source> <nameservice> <destination> [-readonly]] [-add <source> <nameservice> <destination> [-readonly] -owner <owner> -group <group> -mode <mode>]
[-rm <source>] [-rm <source>]
[-ls <path>] [-ls <path>]

View File

@ -190,6 +190,14 @@ It also supports mount points that disallow writes:
If a mount point is not set, the Router will map it to the default namespace `dfs.federation.router.default.nameserviceId`. If a mount point is not set, the Router will map it to the default namespace `dfs.federation.router.default.nameserviceId`.
Mount table have UNIX-like *permissions*, which restrict which users and groups have access to the mount point. Write permissions allow users to add
, update or remove mount point. Read permissions allow users to list mount point. Execute permissions are unused.
Mount table permission can be set by following command:
[hdfs]$ $HADOOP_HOME/bin/hdfs dfsrouteradmin -add /tmp ns1 /tmp -owner root -group supergroup -mode 0755
The option mode is UNIX-style permissions for the mount table. Permissions are specified in octal, e.g. 0755. By default, this is set to 0755.
Client configuration Client configuration
-------------------- --------------------

View File

@ -84,6 +84,9 @@ public class TestFederationMetrics extends TestMetricsBase {
json.getString("nameserviceId")); json.getString("nameserviceId"));
assertEquals(entry.getDefaultLocation().getDest(), assertEquals(entry.getDefaultLocation().getDest(),
json.getString("path")); json.getString("path"));
assertEquals(entry.getOwnerName(), json.getString("ownerName"));
assertEquals(entry.getGroupName(), json.getString("groupName"));
assertEquals(entry.getMode().toString(), json.getString("mode"));
assertNotNullAndNotEmpty(json.getString("dateCreated")); assertNotNullAndNotEmpty(json.getString("dateCreated"));
assertNotNullAndNotEmpty(json.getString("dateModified")); assertNotNullAndNotEmpty(json.getString("dateModified"));
match++; match++;

View File

@ -0,0 +1,182 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdfs.server.federation.router;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.server.federation.RouterConfigBuilder;
import org.apache.hadoop.hdfs.server.federation.RouterDFSCluster.RouterContext;
import org.apache.hadoop.hdfs.server.federation.StateStoreDFSCluster;
import org.apache.hadoop.hdfs.server.federation.store.StateStoreService;
import org.apache.hadoop.hdfs.server.federation.store.impl.MountTableStoreImpl;
import org.apache.hadoop.hdfs.server.federation.store.protocol.GetMountTableEntriesRequest;
import org.apache.hadoop.hdfs.server.federation.store.protocol.GetMountTableEntriesResponse;
import org.apache.hadoop.hdfs.server.federation.store.records.MountTable;
import org.apache.hadoop.hdfs.tools.federation.RouterAdmin;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.ToolRunner;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* Tests Router admin commands.
*/
public class TestRouterAdminCLI {
private static StateStoreDFSCluster cluster;
private static RouterContext routerContext;
private static StateStoreService stateStore;
private static RouterAdmin admin;
private static RouterClient client;
private static final String TEST_USER = "test-user";
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
private static final PrintStream OLD_OUT = System.out;
@BeforeClass
public static void globalSetUp() throws Exception {
cluster = new StateStoreDFSCluster(false, 1);
// Build and start a router with State Store + admin + RPC
Configuration conf = new RouterConfigBuilder()
.stateStore()
.admin()
.rpc()
.build();
cluster.addRouterOverrides(conf);
// Start routers
cluster.startRouters();
routerContext = cluster.getRandomRouter();
Router router = routerContext.getRouter();
stateStore = router.getStateStore();
Configuration routerConf = new Configuration();
InetSocketAddress routerSocket = router.getAdminServerAddress();
routerConf.setSocketAddr(DFSConfigKeys.DFS_ROUTER_ADMIN_ADDRESS_KEY,
routerSocket);
admin = new RouterAdmin(routerConf);
client = routerContext.getAdminClient();
}
@AfterClass
public static void tearDown() {
cluster.stopRouter(routerContext);
cluster.shutdown();
cluster = null;
}
@Test
public void testMountTableDefaultACL() throws Exception {
String[] argv = new String[] {"-add", "/testpath0", "ns0", "/testdir0"};
Assert.assertEquals(0, ToolRunner.run(admin, argv));
stateStore.loadCache(MountTableStoreImpl.class, true);
GetMountTableEntriesRequest getRequest = GetMountTableEntriesRequest
.newInstance("/testpath0");
GetMountTableEntriesResponse getResponse = client.getMountTableManager()
.getMountTableEntries(getRequest);
MountTable mountTable = getResponse.getEntries().get(0);
UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
String group = ugi.getGroups().isEmpty() ? ugi.getShortUserName()
: ugi.getPrimaryGroupName();
assertEquals(ugi.getShortUserName(), mountTable.getOwnerName());
assertEquals(group, mountTable.getGroupName());
assertEquals((short) 0755, mountTable.getMode().toShort());
}
@Test
public void testMountTablePermissions() throws Exception {
// re-set system out for testing
System.setOut(new PrintStream(out));
// use superuser to add new mount table with only read permission
String[] argv = new String[] {"-add", "/testpath2-1", "ns0", "/testdir2-1",
"-owner", TEST_USER, "-group", TEST_USER, "-mode", "0455"};
assertEquals(0, ToolRunner.run(admin, argv));
String superUser = UserGroupInformation.
getCurrentUser().getShortUserName();
// use normal user as current user to test
UserGroupInformation remoteUser = UserGroupInformation
.createRemoteUser(TEST_USER);
UserGroupInformation.setLoginUser(remoteUser);
// verify read permission by executing other commands
verifyExecutionResult("/testpath2-1", true, -1, -1);
// add new mount table with only write permission
argv = new String[] {"-add", "/testpath2-2", "ns0", "/testdir2-2",
"-owner", TEST_USER, "-group", TEST_USER, "-mode", "0255"};
assertEquals(0, ToolRunner.run(admin, argv));
verifyExecutionResult("/testpath2-2", false, 0, 0);
// set mount table entry with read and write permission
argv = new String[] {"-add", "/testpath2-3", "ns0", "/testdir2-3",
"-owner", TEST_USER, "-group", TEST_USER, "-mode", "0755"};
assertEquals(0, ToolRunner.run(admin, argv));
verifyExecutionResult("/testpath2-3", true, 0, 0);
// set back system out and login user
System.setOut(OLD_OUT);
remoteUser = UserGroupInformation.createRemoteUser(superUser);
UserGroupInformation.setLoginUser(remoteUser);
}
/**
* Verify router admin commands execution result.
*
* @param mount
* target mount table
* @param canRead
* whether can list mount tables under specified mount
* @param addCommandCode
* expected return code of add command executed for specified mount
* @param rmCommandCode
* expected return code of rm command executed for specified mount
* @throws Exception
*/
private void verifyExecutionResult(String mount, boolean canRead,
int addCommandCode, int rmCommandCode) throws Exception {
String[] argv = null;
stateStore.loadCache(MountTableStoreImpl.class, true);
out.reset();
// execute ls command
argv = new String[] {"-ls", mount};
assertEquals(0, ToolRunner.run(admin, argv));
assertEquals(canRead, out.toString().contains(mount));
// execute add/update command
argv = new String[] {"-add", mount, "ns0", mount + "newdir"};
assertEquals(addCommandCode, ToolRunner.run(admin, argv));
stateStore.loadCache(MountTableStoreImpl.class, true);
// execute remove command
argv = new String[] {"-rm", mount};
assertEquals(rmCommandCode, ToolRunner.run(admin, argv));
}
}