Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public Writable newInstance() {
private DataChecksum.Type checksumType;
private String keyProviderUri;
private byte storagepolicyId;
private boolean snapshotTrashRootEnabled;

public FsServerDefaults() {
}
Expand Down Expand Up @@ -83,6 +84,18 @@ public FsServerDefaults(long blockSize, int bytesPerChecksum,
boolean encryptDataTransfer, long trashInterval,
DataChecksum.Type checksumType,
String keyProviderUri, byte storagepolicy) {
this(blockSize, bytesPerChecksum, writePacketSize, replication,
fileBufferSize, encryptDataTransfer, trashInterval,
checksumType, keyProviderUri, storagepolicy,
false);
}

public FsServerDefaults(long blockSize, int bytesPerChecksum,
int writePacketSize, short replication, int fileBufferSize,
boolean encryptDataTransfer, long trashInterval,
DataChecksum.Type checksumType,
String keyProviderUri, byte storagepolicy,
boolean snapshotTrashRootEnabled) {
this.blockSize = blockSize;
this.bytesPerChecksum = bytesPerChecksum;
this.writePacketSize = writePacketSize;
Expand All @@ -93,6 +106,7 @@ public FsServerDefaults(long blockSize, int bytesPerChecksum,
this.checksumType = checksumType;
this.keyProviderUri = keyProviderUri;
this.storagepolicyId = storagepolicy;
this.snapshotTrashRootEnabled = snapshotTrashRootEnabled;
}

public long getBlockSize() {
Expand Down Expand Up @@ -139,6 +153,10 @@ public byte getDefaultStoragePolicyId() {
return storagepolicyId;
}

public boolean getSnapshotTrashRootEnabled() {
return snapshotTrashRootEnabled;
}

// /////////////////////////////////////////
// Writable
// /////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3149,6 +3149,32 @@ boolean isHDFSEncryptionEnabled() throws IOException {
return getKeyProviderUri() != null;
}

boolean isSnapshotTrashRootEnabled() throws IOException {
return getServerDefaults().getSnapshotTrashRootEnabled();
}

/**
* Get the snapshot root of a given file or directory if it exists.
* e.g. if /snapdir1 is a snapshottable directory and path given is
* /snapdir1/path/to/file, this method would return /snapdir1
* @param path Path to a file or a directory.
* @return Not null if found in a snapshot root directory.
* @throws IOException
*/
String getSnapshotRoot(Path path) throws IOException {
SnapshottableDirectoryStatus[] dirStatusList = getSnapshottableDirListing();
if (dirStatusList == null) {
return null;
}
for (SnapshottableDirectoryStatus dirStatus : dirStatusList) {
String currDir = dirStatus.getFullPath().toString();
if (path.toUri().getPath().startsWith(currDir)) {
return currDir;
}
}
return null;
}

/**
* Returns the SaslDataTransferClient configured for this DFSClient.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1040,4 +1040,16 @@ public static String getEZTrashRoot(EncryptionZone ez,
return (ezpath.equals("/") ? ezpath : ezpath + Path.SEPARATOR)
+ FileSystem.TRASH_PREFIX + Path.SEPARATOR + ugi.getShortUserName();
}

/**
* Returns trash root in a snapshottable directory.
* @param ssRoot String of path to a snapshottable directory root.
* @param ugi user of trash owner.
* @return unqualified path of trash root.
*/
public static String getSnapshotTrashRoot(String ssRoot,
UserGroupInformation ugi) {
return (ssRoot.equals("/") ? ssRoot : ssRoot + Path.SEPARATOR)
+ FileSystem.TRASH_PREFIX + Path.SEPARATOR + ugi.getShortUserName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,12 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;

import static org.apache.hadoop.fs.impl.PathCapabilitiesSupport.validatePathCapabilityArgs;

Expand Down Expand Up @@ -3273,8 +3275,11 @@ public ECTopologyVerifierResult getECTopologyResultForPolicies(
/**
* Get the root directory of Trash for a path in HDFS.
* 1. File in encryption zone returns /ez1/.Trash/username
* 2. File not in encryption zone, or encountered exception when checking
* the encryption zone of the path, returns /users/username/.Trash
* 2. File in snapshottable directory returns /snapdir1/.Trash/username
* if dfs.namenode.snapshot.trashroot.enabled is set to true.
* 3. In other cases, or encountered exception when checking the encryption
* zone or when checking snapshot root of the path, returns
* /users/username/.Trash
* Caller appends either Current or checkpoint timestamp for trash destination
* @param path the trash root of the path to be determined.
* @return trash root
Expand All @@ -3283,41 +3288,89 @@ public ECTopologyVerifierResult getECTopologyResultForPolicies(
public Path getTrashRoot(Path path) {
statistics.incrementReadOps(1);
storageStatistics.incrementOpCounter(OpType.GET_TRASH_ROOT);
if (path == null) {
return super.getTrashRoot(null);
}

// Snapshottable directory trash root, not null if path is inside a
// snapshottable directory and isSnapshotTrashRootEnabled is true from NN.
String ssTrashRoot = null;
try {
if ((path == null) || !dfs.isHDFSEncryptionEnabled()) {
return super.getTrashRoot(path);
if (dfs.isSnapshotTrashRootEnabled()) {
String ssRoot = dfs.getSnapshotRoot(path);
if (ssRoot != null) {
ssTrashRoot = DFSUtilClient.getSnapshotTrashRoot(ssRoot, dfs.ugi);
}
}
} catch (IOException ioe) {
DFSClient.LOG.warn("Exception while checking whether the path is in a "
+ "snapshottable directory", ioe);
}

try {
if (!dfs.isHDFSEncryptionEnabled()) {
if (ssTrashRoot == null) {
// the path is not in a snapshottable directory and EZ is not enabled
return super.getTrashRoot(path);
} else {
return this.makeQualified(new Path(ssTrashRoot));
}
}
} catch (IOException ioe) {
DFSClient.LOG.warn("Exception while checking whether encryption zone is "
+ "supported", ioe);
}

String parentSrc = path.isRoot()?
path.toUri().getPath():path.getParent().toUri().getPath();
// HDFS encryption is enabled on the cluster at this point, does not
// necessary mean the given path is in an EZ hence the check.
String parentSrc = path.isRoot() ?
path.toUri().getPath() : path.getParent().toUri().getPath();
String ezTrashRoot = null;
try {
EncryptionZone ez = dfs.getEZForPath(parentSrc);
if ((ez != null)) {
return this.makeQualified(
new Path(DFSUtilClient.getEZTrashRoot(ez, dfs.ugi)));
ezTrashRoot = DFSUtilClient.getEZTrashRoot(ez, dfs.ugi);
}
} catch (IOException e) {
DFSClient.LOG.warn("Exception in checking the encryption zone for the " +
"path " + parentSrc + ". " + e.getMessage());
}
return super.getTrashRoot(path);

if (ssTrashRoot == null) {
if (ezTrashRoot == null) {
// The path is neither in a snapshottable directory nor in an EZ
return super.getTrashRoot(path);
} else {
return this.makeQualified(new Path(ezTrashRoot));
}
} else {
if (ezTrashRoot == null) {
return this.makeQualified(new Path(ssTrashRoot));
} else {
// The path is in EZ and in a snapshottable directory
return this.makeQualified(new Path(
ssTrashRoot.length() > ezTrashRoot.length() ?
ssTrashRoot : ezTrashRoot));
}
}
}

/**
* Get all the trash roots of HDFS for current user or for all the users.
* 1. File deleted from non-encryption zone /user/username/.Trash
* 2. File deleted from encryption zones
* 1. File deleted from encryption zones
* e.g., ez1 rooted at /ez1 has its trash root at /ez1/.Trash/$USER
* 2. File deleted from snapshottable directories
* if dfs.namenode.snapshot.trashroot.enabled is set to true.
* e.g., snapshottable directory /snapdir1 has its trash root
* at /snapdir1/.Trash/$USER
* 3. File deleted from other directories
* /user/username/.Trash
* @param allUsers return trashRoots of all users if true, used by emptier
* @return trash roots of HDFS
*/
@Override
public Collection<FileStatus> getTrashRoots(boolean allUsers) {
List<FileStatus> ret = new ArrayList<>();
Set<FileStatus> ret = new HashSet<>();
// Get normal trash roots
ret.addAll(super.getTrashRoots(allUsers));

Expand Down Expand Up @@ -3348,6 +3401,39 @@ public Collection<FileStatus> getTrashRoots(boolean allUsers) {
} catch (IOException e){
DFSClient.LOG.warn("Cannot get all encrypted trash roots", e);
}

try {
// Get snapshottable directory trash roots
if (dfs.isSnapshotTrashRootEnabled()) {
SnapshottableDirectoryStatus[] lst = dfs.getSnapshottableDirListing();
if (lst != null) {
for (SnapshottableDirectoryStatus dirStatus : lst) {
String ssDir = dirStatus.getFullPath().toString();
Path ssTrashRoot = new Path(ssDir, FileSystem.TRASH_PREFIX);
if (!exists(ssTrashRoot)) {
continue;
}
if (allUsers) {
for (FileStatus candidate : listStatus(ssTrashRoot)) {
if (exists(candidate.getPath())) {
ret.add(candidate);
}
}
} else {
Path userTrash = new Path(DFSUtilClient.getSnapshotTrashRoot(
ssDir, dfs.ugi));
try {
ret.add(getFileStatus(userTrash));
} catch (FileNotFoundException ignored) {
}
}
}
}
}
} catch (IOException e) {
DFSClient.LOG.warn("Cannot get snapshot trash roots", e);
}

return ret;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2124,7 +2124,8 @@ public static FsServerDefaults convert(FsServerDefaultsProto fs) {
fs.getTrashInterval(),
convert(fs.getChecksumType()),
fs.hasKeyProviderUri() ? fs.getKeyProviderUri() : null,
(byte) fs.getPolicyId());
(byte) fs.getPolicyId(),
fs.getSnapshotTrashRootEnabled());
}

public static List<CryptoProtocolVersionProto> convert(
Expand Down Expand Up @@ -2298,7 +2299,8 @@ public static FsServerDefaultsProto convert(FsServerDefaults fs) {
.setEncryptDataTransfer(fs.getEncryptDataTransfer())
.setTrashInterval(fs.getTrashInterval())
.setChecksumType(convert(fs.getChecksumType()))
.setPolicyId(fs.getDefaultStoragePolicyId());
.setPolicyId(fs.getDefaultStoragePolicyId())
.setSnapshotTrashRootEnabled(fs.getSnapshotTrashRootEnabled());
if (fs.getKeyProviderUri() != null) {
builder.setKeyProviderUri(fs.getKeyProviderUri());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ message FsServerDefaultsProto {
optional ChecksumTypeProto checksumType = 8 [default = CHECKSUM_CRC32];
optional string keyProviderUri = 9;
optional uint32 policyId = 10 [default = 0];
optional bool snapshotTrashRootEnabled = 11 [default = false];
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,12 @@ public class FSNamesystem implements Namesystem, FSNamesystemMBean,

public static final org.slf4j.Logger LOG = LoggerFactory
.getLogger(FSNamesystem.class.getName());

// The following are private configurations
static final String DFS_NAMENODE_SNAPSHOT_TRASHROOT_ENABLED =
"dfs.namenode.snapshot.trashroot.enabled";
static final boolean DFS_NAMENODE_SNAPSHOT_TRASHROOT_ENABLED_DEFAULT = false;

private final MetricsRegistry registry = new MetricsRegistry("FSNamesystem");
@Metric final MutableRatesWithAggregation detailedLockHoldTimeMetrics =
registry.newRatesWithAggregation("detailedLockHoldTimeMetrics");
Expand Down Expand Up @@ -902,7 +908,9 @@ static FSNamesystem loadFromDisk(Configuration conf) throws IOException {
conf.getTrimmed(
CommonConfigurationKeysPublic.HADOOP_SECURITY_KEY_PROVIDER_PATH,
""),
blockManager.getStoragePolicySuite().getDefaultPolicy().getId());
blockManager.getStoragePolicySuite().getDefaultPolicy().getId(),
conf.getBoolean(DFS_NAMENODE_SNAPSHOT_TRASHROOT_ENABLED,
DFS_NAMENODE_SNAPSHOT_TRASHROOT_ENABLED_DEFAULT));

this.maxFsObjects = conf.getLong(DFS_NAMENODE_MAX_OBJECTS_KEY,
DFS_NAMENODE_MAX_OBJECTS_DEFAULT);
Expand Down
Loading