Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HDFS-15689. allow/disallowSnapshot on EZ roots shouldn't fail due to trash provisioning/emptiness check #2472

Merged
merged 4 commits into from
Nov 25, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -3158,6 +3158,14 @@ boolean isHDFSEncryptionEnabled() throws IOException {
return getKeyProviderUri() != null;
}

/**
* @return true if p is an encryption zone root
*/
boolean isEZRoot(Path p) throws IOException {
EncryptionZone ez = getEZForPath(p.toUri().getPath());
return ez.getPath().equals(p.toString());
}

boolean isSnapshotTrashRootEnabled() throws IOException {
return getServerDefaults().getSnapshotTrashRootEnabled();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2122,6 +2122,12 @@ public Void next(final FileSystem fs, final Path p)
* @param p Path to a directory.
*/
private void checkTrashRootAndRemoveIfEmpty(final Path p) throws IOException {
// If p is EZ root, skip the check
if (dfs.isHDFSEncryptionEnabled() && dfs.isEZRoot(p)) {
DFSClient.LOG.debug("{} is an encryption zone root. "
+ "Skipping empty trash root check.", p);
return;
}
Path trashRoot = new Path(p, FileSystem.TRASH_PREFIX);
try {
// listStatus has 4 possible outcomes here:
Expand All @@ -2139,9 +2145,10 @@ private void checkTrashRootAndRemoveIfEmpty(final Path p) throws IOException {
} else {
if (fileStatuses.length == 1
&& !fileStatuses[0].isDirectory()
&& !fileStatuses[0].getPath().equals(p)) {
&& fileStatuses[0].getPath().toUri().getPath().equals(
trashRoot.toString())) {
// Ignore the trash path because it is not a directory.
DFSClient.LOG.warn("{} is not a directory.", trashRoot);
DFSClient.LOG.warn("{} is not a directory. Ignored.", trashRoot);
} else {
throw new IOException("Found non-empty trash root at " +
trashRoot + ". Rename or delete it, then try again.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.hadoop.crypto.key.KeyProvider;
import org.apache.hadoop.fs.BlockStoragePolicySpi;
import org.apache.hadoop.fs.CacheFlag;
import org.apache.hadoop.fs.FileAlreadyExistsException;
import org.apache.hadoop.fs.FileEncryptionInfo;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
Expand All @@ -48,6 +49,8 @@
import org.apache.hadoop.hdfs.protocol.OpenFilesIterator.OpenFilesType;
import org.apache.hadoop.hdfs.protocol.ZoneReencryptionStatus;
import org.apache.hadoop.security.AccessControlException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileNotFoundException;
import java.io.IOException;
Expand All @@ -67,7 +70,7 @@
@InterfaceAudience.Public
@InterfaceStability.Evolving
public class HdfsAdmin {

private static final Logger LOG = LoggerFactory.getLogger(HdfsAdmin.class);
final private DistributedFileSystem dfs;
public static final FsPermission TRASH_PERMISSION = new FsPermission(
FsAction.ALL, FsAction.ALL, FsAction.ALL, true);
Expand Down Expand Up @@ -171,7 +174,15 @@ public void clearQuotaByStorageType(Path src, StorageType type) throws IOExcepti
public void allowSnapshot(Path path) throws IOException {
dfs.allowSnapshot(path);
if (dfs.isSnapshotTrashRootEnabled()) {
dfs.provisionSnapshotTrash(path, TRASH_PERMISSION);
try {
dfs.provisionSnapshotTrash(path, TRASH_PERMISSION);
} catch (FileAlreadyExistsException ex) {
// Don't throw on FileAlreadyExistsException since it is likely due to
// admin allowing snapshot on an EZ root.
LOG.warn(ex.getMessage());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are 2 approaches instead of just ignoring the FileAlreadyExists exception:

  1. we can just validate whether the existing Trash path is a directory and validate the permissions and not throw FileAlreadyExists exception itself in DistributedFileSystem.java#provisionSnapshotTrash
  2. If the trash path already exists with right permissions, we can check if the path is an encryption zone as well and throw FileAlreadyExists exception only if its not an encryption zone. Similar change will be required for making an snapshottable dir an encryption zone.

I am ok with either of the above approaches. I think just ignoring the exception here will not work in case. the existing path is not a directory or has right permissions.

Copy link
Contributor Author

@smengcl smengcl Nov 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bshashikant for the comment.

I was intending to not change the behavior of DFS#provisionSnapshotTrash but as I think again changing it should be fine since 3.4.0 is not released yet.

I'm in favor of (1). It makes sense to reuse the trash if it is configured correctly. I will update the PR a bit later.

LOG.info("You can safely ignore the above warning if you are allowing "
+ "snapshot on an encryption zone, as the EZ already has a trash.");
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -925,6 +926,26 @@ private void verifyNodesAndCorruptBlocks(
.getECBlockGroupStats().getHighestPriorityLowRedundancyBlocks());
}

@Test
public void testAllowSnapshotWhenTrashExists() throws Exception {
final Path dirPath = new Path("/ssdir3");
final Path trashRoot = new Path(dirPath, ".Trash");
final DistributedFileSystem dfs = cluster.getFileSystem();
final DFSAdmin dfsAdmin = new DFSAdmin(conf);

dfs.mkdirs(trashRoot);
// allowSnapshot should still succeed even when trash exists
assertEquals(0, ToolRunner.run(dfsAdmin,
new String[]{"-allowSnapshot", dirPath.toString()}));
// disallowSnapshot should remove the empty trash
assertEquals(0, ToolRunner.run(dfsAdmin,
new String[]{"-disallowSnapshot", dirPath.toString()}));
assertFalse(dfs.exists(trashRoot));

// Cleanup
dfs.delete(dirPath, true);
}

@Test
public void testAllowDisallowSnapshot() throws Exception {
final Path dirPath = new Path("/ssdir1");
Expand Down