Skip to content

HADOOP-16380. S3A non-recursive deletion of root to return false #1106

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

Closed
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 @@ -973,6 +973,9 @@ There is no consistent return code from an attempt to delete the root directory.
Implementations SHOULD return true; this avoids code which checks for a false
return value from overreacting.

Note: Some of the object store based filesystem implementations always return
false when deleting the root.

##### Empty (non-root) directory `recursive == False`

Deleting an empty directory that is not root will remove the path from the FS and
Expand Down Expand Up @@ -1019,6 +1022,9 @@ Any filesystem client which interacts with a remote filesystem which lacks
such a security model, MAY reject calls to `delete("/", true)` on the basis
that it makes it too easy to lose data.

Note: Some of the object store based filesystem implementations always return
false when deleting the root.

##### Recursive delete of non-root directory

Deleting a non-root path with children `recursive==true`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2195,6 +2195,10 @@ void removeKeys(
public boolean delete(Path f, boolean recursive) throws IOException {
try {
entryPoint(INVOCATION_DELETE);
if (f.isRoot()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

you're going to have to qualify the path. this is done in getFilestatus() in the main delete codepath, but now you'll have to do it first (you can reuse that path in L2199 and L2202, obviously)

Thinking about this, all we need is your change to rejectRootDirectoryDelete(), without needing any other changes to the delete/innerDelete codepath. Yes, we'll do a needless getFileStatus and return the wrong empty flag if there's a tombstone, but that "feature" is still there no matter what. And I don't care about performance here given the operation will always be rejected

return rejectRootDirectoryDelete(f, recursive);
}

boolean outcome = innerDelete(innerGetFileStatus(f, true), recursive);
if (outcome) {
try {
Expand Down Expand Up @@ -2250,10 +2254,6 @@ private boolean innerDelete(S3AFileStatus status, boolean recursive)
key = key + "/";
}

if (key.equals("/")) {
return rejectRootDirectoryDelete(status, recursive);
}

if (!recursive && status.isEmptyDirectory() == Tristate.FALSE) {
throw new PathIsNotEmptyDirectoryException(f.toString());
}
Expand Down Expand Up @@ -2308,29 +2308,16 @@ private boolean innerDelete(S3AFileStatus status, boolean recursive)
* The caller must return the result of this call, rather than
* attempt to continue with the delete operation: deleting root
* directories is never allowed. This method simply implements
* the policy of when to return an exit code versus raise an exception.
* @param status filesystem status
* the policy of when to return false versus raise an exception.
* @param path path for deletion
* @param recursive recursive flag from command
* @return a return code for the operation
* @throws PathIOException if the operation was explicitly rejected.
*/
private boolean rejectRootDirectoryDelete(S3AFileStatus status,
boolean recursive) throws IOException {
LOG.info("s3a delete the {} root directory. Path: {}. Recursive: {}",
bucket, status.getPath(), recursive);
boolean emptyRoot = status.isEmptyDirectory() == Tristate.TRUE;
if (emptyRoot) {
return true;
}
if (recursive) {
LOG.error("Cannot delete root path: {}", status.getPath());
return false;
} else {
// reject
String msg = "Cannot delete root path: " + status.getPath();
LOG.error(msg);
throw new PathIOException(bucket, msg);
}
private boolean rejectRootDirectoryDelete(Path path, boolean recursive) {
LOG.error("S3A: Cannot delete the {} root directory. Path: {}. Recursive: "
+ "{}", bucket, path, recursive);
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import org.apache.hadoop.fs.contract.AbstractFSContract;
import org.apache.hadoop.fs.s3a.S3AFileSystem;

import org.junit.Ignore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -62,6 +63,11 @@ public S3AFileSystem getFileSystem() {
return (S3AFileSystem) super.getFileSystem();
}

@Override
@Ignore("S3 always return false when non-recursively remove root dir")
public void testRmNonEmptyRootDirNonRecursive() throws Throwable {
}

/**
* This is overridden to allow for eventual consistency on listings,
* but only if the store does not have S3Guard protecting it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public void test_400_rm_root_recursive() throws Throwable {
assertDeleted(file, false);


assertTrue("Root directory delete failed",
assertFalse("Root directory delete failed",
fs.delete(root, true));

ContractTestUtils.touch(fs, file2);
Expand Down