This is an automated email from the ASF dual-hosted git repository.

siyao pushed a commit to branch HDDS-2665-ofs
in repository https://gitbox.apache.org/repos/asf/hadoop-ozone.git


The following commit(s) were added to refs/heads/HDDS-2665-ofs by this push:
     new 0773a4a  HDDS-3494. Implement ofs://: Support volume and bucket 
deletion (#906)
0773a4a is described below

commit 0773a4aef90d27720c89cdc34dfcba5b0f7de5d5
Author: Siyao Meng <[email protected]>
AuthorDate: Mon May 18 09:55:45 2020 -0700

    HDDS-3494. Implement ofs://: Support volume and bucket deletion (#906)
---
 .../hadoop/fs/ozone/TestRootedOzoneFileSystem.java | 107 +++++++++++++++++++++
 .../hadoop/fs/ozone/BasicOzoneFileSystem.java      |   7 ++
 .../ozone/BasicRootedOzoneClientAdapterImpl.java   |   7 ++
 .../fs/ozone/BasicRootedOzoneFileSystem.java       |  75 +++++++++++++--
 .../java/org/apache/hadoop/fs/ozone/OFSPath.java   |  11 +++
 5 files changed, 199 insertions(+), 8 deletions(-)

diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
index b0550e1..4312905 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/fs/ozone/TestRootedOzoneFileSystem.java
@@ -25,6 +25,7 @@ import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.PathIsNotEmptyDirectoryException;
 import org.apache.hadoop.fs.contract.ContractTestUtils;
 import org.apache.hadoop.fs.permission.FsPermission;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
@@ -65,6 +66,7 @@ import static 
org.apache.hadoop.ozone.OzoneAcl.AclScope.ACCESS;
 import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER;
 import static org.apache.hadoop.ozone.om.OMConfigKeys.OZONE_OM_ADDRESS_KEY;
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_NOT_FOUND;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_FOUND;
 
 /**
  * Ozone file system tests that are not covered by contract tests.
@@ -766,4 +768,109 @@ public class TestRootedOzoneFileSystem {
         fileStatusesInDir1[0].getPath().toUri().getPath());
   }
 
+  /**
+   * Helper function. Check Ozone volume existence.
+   * @param volumeStr Name of the volume
+   * @return true if volume exists, false if not
+   */
+  private boolean volumeExist(String volumeStr) throws IOException {
+    try {
+      objectStore.getVolume(volumeStr);
+    } catch (OMException ex) {
+      if (ex.getResult() == VOLUME_NOT_FOUND) {
+        return false;
+      } else {
+        throw ex;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Helper function. Delete a path non-recursively and expect failure.
+   * @param f Path to delete.
+   * @throws IOException
+   */
+  private void deleteNonRecursivelyAndFail(Path f) throws IOException {
+    try {
+      fs.delete(f, false);
+      Assert.fail("Should have thrown PathIsNotEmptyDirectoryException!");
+    } catch (PathIsNotEmptyDirectoryException ignored) {
+    }
+  }
+
+  @Test
+  public void testDeleteEmptyVolume() throws IOException {
+    // Create volume
+    String volumeStr1 = getRandomNonExistVolumeName();
+    Path volumePath1 = new Path(OZONE_URI_DELIMITER + volumeStr1);
+    fs.mkdirs(volumePath1);
+    // Check volume creation
+    OzoneVolume volume1 = objectStore.getVolume(volumeStr1);
+    Assert.assertEquals(volumeStr1, volume1.getName());
+    // Delete empty volume non-recursively
+    Assert.assertTrue(fs.delete(volumePath1, false));
+    // Verify the volume is deleted
+    Assert.assertFalse(volumeStr1 + " should have been deleted!",
+        volumeExist(volumeStr1));
+  }
+
+  @Test
+  public void testDeleteVolumeAndBucket() throws IOException {
+    // Create volume and bucket
+    String volumeStr2 = getRandomNonExistVolumeName();
+    Path volumePath2 = new Path(OZONE_URI_DELIMITER + volumeStr2);
+    String bucketStr2 = "bucket2";
+    Path bucketPath2 = new Path(volumePath2, bucketStr2);
+    fs.mkdirs(bucketPath2);
+    // Check volume and bucket creation
+    OzoneVolume volume2 = objectStore.getVolume(volumeStr2);
+    Assert.assertEquals(volumeStr2, volume2.getName());
+    OzoneBucket bucket2 = volume2.getBucket(bucketStr2);
+    Assert.assertEquals(bucketStr2, bucket2.getName());
+    // Delete volume non-recursively should fail since it is not empty
+    deleteNonRecursivelyAndFail(volumePath2);
+    // Delete bucket first, then volume
+    Assert.assertTrue(fs.delete(bucketPath2, false));
+    Assert.assertTrue(fs.delete(volumePath2, false));
+    // Verify the volume is deleted
+    Assert.assertFalse(volumeExist(volumeStr2));
+  }
+
+  @Test
+  public void testDeleteVolumeBucketAndKey() throws IOException {
+    // Create test volume, bucket and key
+    String volumeStr3 = getRandomNonExistVolumeName();
+    Path volumePath3 = new Path(OZONE_URI_DELIMITER + volumeStr3);
+    String bucketStr3 = "bucket3";
+    Path bucketPath3 = new Path(volumePath3, bucketStr3);
+    String dirStr3 = "dir3";
+    Path dirPath3 = new Path(bucketPath3, dirStr3);
+    fs.mkdirs(dirPath3);
+    // Delete volume or bucket non-recursively, should fail
+    deleteNonRecursivelyAndFail(volumePath3);
+    deleteNonRecursivelyAndFail(bucketPath3);
+    // Delete key first, then bucket, then volume
+    Assert.assertTrue(fs.delete(dirPath3, false));
+    Assert.assertTrue(fs.delete(bucketPath3, false));
+    Assert.assertTrue(fs.delete(volumePath3, false));
+    // Verify the volume is deleted
+    Assert.assertFalse(volumeExist(volumeStr3));
+
+    // Test recursively delete volume
+    // Create test volume, bucket and key
+    fs.mkdirs(dirPath3);
+    // Delete volume recursively
+    Assert.assertTrue(fs.delete(volumePath3, true));
+    // Verify the volume is deleted
+    Assert.assertFalse(volumeExist(volumeStr3));
+  }
+
+  @Test
+  public void testFailToDeleteRoot() throws IOException {
+    // rm root should always fail for OFS
+    Assert.assertFalse(fs.delete(new Path("/"), false));
+    Assert.assertFalse(fs.delete(new Path("/"), true));
+  }
+
 }
diff --git 
a/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
 
b/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
index b7323ac..4706183 100644
--- 
a/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
+++ 
b/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicOzoneFileSystem.java
@@ -467,6 +467,13 @@ public class BasicOzoneFileSystem extends FileSystem {
     }
   }
 
+  /**
+   * {@inheritDoc}
+   *
+   * OFS supports volume and bucket deletion, recursive or non-recursive.
+   * e.g. delete(new Path("/volume1"), true)
+   * But root deletion is explicitly disallowed for safety concerns.
+   */
   @Override
   public boolean delete(Path f, boolean recursive) throws IOException {
     incrementCounter(Statistic.INVOCATION_DELETE);
diff --git 
a/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
 
b/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
index bf7b124..171937e 100644
--- 
a/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
+++ 
b/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneClientAdapterImpl.java
@@ -307,6 +307,9 @@ public class BasicRootedOzoneClientAdapterImpl
       boolean overWrite, boolean recursive) throws IOException {
     incrementCounter(Statistic.OBJECTS_CREATED);
     OFSPath ofsPath = new OFSPath(pathStr);
+    if (ofsPath.isRoot() || ofsPath.isVolume() || ofsPath.isBucket()) {
+      throw new IOException("Cannot create file under root or volume.");
+    }
     String key = ofsPath.getKeyName();
     try {
       // Hadoop CopyCommands class always sets recursive to true
@@ -660,6 +663,10 @@ public class BasicRootedOzoneClientAdapterImpl
 
   }
 
+  public ObjectStore getObjectStore() {
+    return objectStore;
+  }
+
   @Override
   public KeyProvider getKeyProvider() throws IOException {
     return objectStore.getKeyProvider();
diff --git 
a/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
 
b/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
index 39aeef1..fd6df55 100644
--- 
a/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
+++ 
b/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/BasicRootedOzoneFileSystem.java
@@ -37,6 +37,7 @@ import org.apache.hadoop.hdds.conf.ConfigurationSource;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.utils.LegacyHadoopConfigurationSource;
 import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneVolume;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.token.Token;
@@ -61,6 +62,8 @@ import static 
org.apache.hadoop.fs.ozone.Constants.OZONE_DEFAULT_USER;
 import static org.apache.hadoop.fs.ozone.Constants.OZONE_USER_DIR;
 import static org.apache.hadoop.ozone.OzoneConsts.OZONE_URI_DELIMITER;
 import static org.apache.hadoop.ozone.OzoneConsts.OZONE_OFS_URI_SCHEME;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.BUCKET_NOT_EMPTY;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.VOLUME_NOT_EMPTY;
 
 /**
  * The minimal Ozone Filesystem implementation.
@@ -84,6 +87,7 @@ public class BasicRootedOzoneFileSystem extends FileSystem {
   private String userName;
   private Path workingDir;
   private OzoneClientAdapter adapter;
+  private BasicRootedOzoneClientAdapterImpl adapterImpl;
 
   private static final String URI_EXCEPTION_TEXT =
       "URL should be one of the following formats: " +
@@ -149,6 +153,7 @@ public class BasicRootedOzoneFileSystem extends FileSystem {
           createAdapter(source,
               omHostOrServiceId, omPort,
               isolatedClassloader);
+      this.adapterImpl = (BasicRootedOzoneClientAdapterImpl) this.adapter;
 
       try {
         this.userName =
@@ -388,7 +393,7 @@ public class BasicRootedOzoneFileSystem extends FileSystem {
   }
 
   private class DeleteIterator extends OzoneListingIterator {
-    private boolean recursive;
+    final private boolean recursive;
     private final OzoneBucket bucket;
     private final BasicRootedOzoneClientAdapterImpl adapterImpl;
 
@@ -409,12 +414,12 @@ public class BasicRootedOzoneFileSystem extends 
FileSystem {
     }
 
     @Override
-    boolean processKeyPath(String keyPath) throws IOException {
+    boolean processKeyPath(String keyPath) {
       if (keyPath.equals("")) {
         LOG.trace("Skipping deleting root directory");
         return true;
       } else {
-        LOG.trace("deleting key path:" + keyPath);
+        LOG.trace("Deleting: {}", keyPath);
         boolean succeed = adapterImpl.deleteObject(this.bucket, keyPath);
         // if recursive delete is requested ignore the return value of
         // deleteObject and issue deletes for other keys.
@@ -457,19 +462,73 @@ public class BasicRootedOzoneFileSystem extends 
FileSystem {
       return false;
     }
 
+    if (status == null) {
+      return false;
+    }
+
     String key = pathToKey(f);
     boolean result;
 
     if (status.isDirectory()) {
       LOG.debug("delete: Path is a directory: {}", f);
-      key = addTrailingSlashIfNeeded(key);
-
-      if (key.equals("/")) {
-        LOG.warn("Cannot delete root directory.");
+      OFSPath ofsPath = new OFSPath(key);
+
+      // Handle rm root
+      if (ofsPath.isRoot()) {
+        // Intentionally drop support for rm root
+        // because it is too dangerous and doesn't provide much value
+        LOG.warn("delete: OFS does not support rm root. "
+            + "To wipe the cluster, please re-init OM instead.");
         return false;
       }
 
+      // Handle delete volume
+      if (ofsPath.isVolume()) {
+        String volumeName = ofsPath.getVolumeName();
+        if (recursive) {
+          // Delete all buckets first
+          OzoneVolume volume =
+              adapterImpl.getObjectStore().getVolume(volumeName);
+          Iterator<? extends OzoneBucket> it = volume.listBuckets("");
+          String prefixVolumePathStr = addTrailingSlashIfNeeded(f.toString());
+          while (it.hasNext()) {
+            OzoneBucket bucket = it.next();
+            String nextBucket = prefixVolumePathStr + bucket.getName();
+            delete(new Path(nextBucket), true);
+          }
+        }
+        try {
+          adapterImpl.getObjectStore().deleteVolume(volumeName);
+          return true;
+        } catch (OMException ex) {
+          // volume is not empty
+          if (ex.getResult() == VOLUME_NOT_EMPTY) {
+            throw new PathIsNotEmptyDirectoryException(f.toString());
+          } else {
+            throw ex;
+          }
+        }
+      }
+
       result = innerDelete(f, recursive);
+
+      // Handle delete bucket
+      if (ofsPath.isBucket()) {
+        OzoneVolume volume =
+            adapterImpl.getObjectStore().getVolume(ofsPath.getVolumeName());
+        try {
+          volume.deleteBucket(ofsPath.getBucketName());
+          return result;
+        } catch (OMException ex) {
+          // bucket is not empty
+          if (ex.getResult() == BUCKET_NOT_EMPTY) {
+            throw new PathIsNotEmptyDirectoryException(f.toString());
+          } else {
+            throw ex;
+          }
+        }
+      }
+
     } else {
       LOG.debug("delete: Path is a file: {}", f);
       result = adapter.deleteObject(key);
@@ -477,7 +536,7 @@ public class BasicRootedOzoneFileSystem extends FileSystem {
 
     if (result) {
       // If this delete operation removes all files/directories from the
-      // parent direcotry, then an empty parent directory must be created.
+      // parent directory, then an empty parent directory must be created.
       createFakeParentDirectory(f);
     }
 
diff --git 
a/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/OFSPath.java 
b/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/OFSPath.java
index e970168..88f89fc 100644
--- a/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/OFSPath.java
+++ b/hadoop-ozone/ozonefs/src/main/java/org/apache/hadoop/fs/ozone/OFSPath.java
@@ -190,6 +190,17 @@ class OFSPath {
     return this.getBucketName().isEmpty() && !this.getVolumeName().isEmpty();
   }
 
+  /**
+   * If key name is empty but volume and bucket names are not, the given path
+   * it bucket.
+   * e.g. /volume1/bucket2
+   */
+  public boolean isBucket() {
+    return this.getKeyName().isEmpty() &&
+        !this.getBucketName().isEmpty() &&
+        !this.getVolumeName().isEmpty();
+  }
+
   private static String md5Hex(String input) {
     try {
       MessageDigest md = MessageDigest.getInstance("MD5");


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to