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

neilj pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 5dd389b7cb HDDS-5866. Discrepancy in Trash directory in ofs vs o3fs. 
(#3906)
5dd389b7cb is described below

commit 5dd389b7cb9d6dd8ba425c5c4fd8678a2bc5c7b1
Author: Sadanand Shenoy <[email protected]>
AuthorDate: Thu Nov 3 20:59:15 2022 +0530

    HDDS-5866. Discrepancy in Trash directory in ofs vs o3fs. (#3906)
    
    * HDDS-5866.Discrepancy in Trash directory in ofs vs o3fs
    
    * add new line
    
    * fix test and set default trash policy
    
    * fix test
    
    * rebase and address comment
---
 .../org/apache/hadoop/ozone/OzoneConfigKeys.java   |   5 +
 .../common/src/main/resources/ozone-default.xml    |  10 ++
 .../hadoop/fs/ozone/TestRootedOzoneFileSystem.java |  57 +++--------
 .../hadoop/ozone/shell/TestOzoneShellHA.java       |  19 ++--
 .../apache/hadoop/ozone/om/TrashPolicyOzone.java   | 105 ++++++++++++++++++++-
 5 files changed, 140 insertions(+), 56 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
index 733cce1ec2..80c531550f 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConfigKeys.java
@@ -533,6 +533,11 @@ public final class OzoneConfigKeys {
   public static final String
       OZONE_FS_LISTING_PAGE_SIZE_MAX = "ozone.fs.listing.page.size.max";
 
+
+  public static final String FS_TRASH_CLASSNAME = "fs.trash.classname";
+  public static final String FS_TRASH_CLASSNAME_DEFAULT =
+      "org.apache.hadoop.ozone.om.TrashPolicyOzone";
+
   /**
    * There is no need to instantiate this class.
    */
diff --git a/hadoop-hdds/common/src/main/resources/ozone-default.xml 
b/hadoop-hdds/common/src/main/resources/ozone-default.xml
index 7b9132b2aa..2176f0a8df 100644
--- a/hadoop-hdds/common/src/main/resources/ozone-default.xml
+++ b/hadoop-hdds/common/src/main/resources/ozone-default.xml
@@ -2274,6 +2274,16 @@
       Iterate batch size of delete when use BasicOzoneFileSystem.
     </description>
   </property>
+
+  <property>
+    <name>fs.trash.classname</name>
+    <value>org.apache.hadoop.ozone.om.TrashPolicyOzone</value>
+    <tag>OZONE, OZONEFS, CLIENT</tag>
+    <description>
+      Trash Policy to be used.
+    </description>
+  </property>
+
   <property>
     <name>ozone.manager.db.checkpoint.transfer.bandwidthPerSec</name>
     <value>0</value>
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 96ac47f065..437e30b8d8 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
@@ -64,12 +64,10 @@ import org.apache.hadoop.ozone.security.acl.OzoneAclConfig;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.ozone.test.GenericTestUtils;
 import org.apache.ozone.test.LambdaTestUtils;
-import org.apache.ozone.test.tag.Flaky;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.Timeout;
@@ -255,8 +253,6 @@ public class TestRootedOzoneFileSystem {
     conf.setInt(OZONE_FS_ITERATE_BATCH_SIZE, 5);
     // fs.ofs.impl would be loaded from META-INF, no need to manually set it
     fs = FileSystem.get(conf);
-    conf.setClass("fs.trash.classname", TrashPolicyOzone.class,
-        TrashPolicy.class);
     trash = new Trash(conf);
     ofs = (RootedOzoneFileSystem) fs;
     adapter = (BasicRootedOzoneClientAdapterImpl) ofs.getAdapter();
@@ -1523,39 +1519,6 @@ public class TestRootedOzoneFileSystem {
     Assert.assertTrue(volume1.setOwner(prevOwner));
   }
 
-  /**
-   * Check that  files are moved to trash since it is enabled by
-   * fs.rename(src, dst, options).
-   */
-  @Test
-  @Flaky({"HDDS-5819", "HDDS-6451"})
-  public void testRenameToTrashEnabled() throws IOException {
-    // Create a file
-    String testKeyName = "testKey2";
-    Path path = new Path(bucketPath, testKeyName);
-    try (FSDataOutputStream stream = fs.create(path)) {
-      stream.write(1);
-    }
-
-    // Call moveToTrash. We can't call protected fs.rename() directly
-    trash.moveToTrash(path);
-
-    // Construct paths
-    String username = UserGroupInformation.getCurrentUser().getShortUserName();
-    Path trashRoot = new Path(bucketPath, TRASH_PREFIX);
-    Path userTrash = new Path(trashRoot, username);
-    Path userTrashCurrent = new Path(userTrash, "Current");
-    String key = path.toString().substring(1);
-    Path trashPath = new Path(userTrashCurrent, key);
-    // Trash Current directory should still have been created.
-    Assert.assertTrue(ofs.exists(userTrashCurrent));
-    // Check under trash, the key should be present
-    Assert.assertTrue(ofs.exists(trashPath));
-
-    // Cleanup
-    ofs.delete(trashRoot, true);
-  }
-
   @Test
   public void testFileDelete() throws Exception {
     Path grandparent = new Path(bucketPath, "testBatchDelete");
@@ -1600,7 +1563,6 @@ public class TestRootedOzoneFileSystem {
    * 3.Create a second Key in different bucket and verify deletion.
    * @throws Exception
    */
-  @Ignore
   @Test
   public void testTrash() throws Exception {
     String testKeyName = "keyToBeDeleted";
@@ -1635,21 +1597,26 @@ public class TestRootedOzoneFileSystem {
     long prevNumTrashAtomicDirRenames = getOMMetrics()
         .getNumTrashAtomicDirRenames();
 
-    // Call moveToTrash. We can't call protected fs.rename() directly
-    trash.moveToTrash(keyPath1);
-    // for key in second bucket
-    trash.moveToTrash(keyPath2);
-
     // Construct paths for first key
     String username = UserGroupInformation.getCurrentUser().getShortUserName();
     Path trashRoot = new Path(bucketPath, TRASH_PREFIX);
     Path userTrash = new Path(trashRoot, username);
-    Path trashPath = getTrashKeyPath(keyPath1, userTrash);
+    Path userTrashCurrent = new Path(userTrash, "Current");
+    Path trashPath = new Path(userTrashCurrent, testKeyName);
 
     // Construct paths for second key in different bucket
     Path trashRoot2 = new Path(bucketPath2, TRASH_PREFIX);
     Path userTrash2 = new Path(trashRoot2, username);
-    Path trashPath2 = getTrashKeyPath(keyPath2, userTrash2);
+    Path trashPath2 = new Path(userTrashCurrent, testKeyName + "1");
+
+    // Call moveToTrash. We can't call protected fs.rename() directly
+    trash.moveToTrash(keyPath1);
+    // for key in second bucket
+    trash.moveToTrash(keyPath2);
+
+    // key should either be present in Current or checkpointDir
+    Assert.assertTrue(ofs.exists(trashPath)
+        || ofs.listStatus(ofs.listStatus(userTrash)[0].getPath()).length > 0);
 
 
     // Wait until the TrashEmptier purges the keys
diff --git 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
index 81a01190d1..88f387afee 100644
--- 
a/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
+++ 
b/hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/shell/TestOzoneShellHA.java
@@ -581,19 +581,23 @@ public class TestOzoneShellHA {
         getClientConfForOFS(hostPrefix, cluster.getConf());
     OzoneFsShell shell = new OzoneFsShell(clientConf);
     FileSystem fs = FileSystem.get(clientConf);
-    final String strDir1 = hostPrefix + "/volumed2t/bucket1/dir1";
+    String ofsPrefix = hostPrefix + "/volumed2t/bucket1";
+    String dir1 = "/dir1";
+    final String strDir1 = ofsPrefix + dir1;
     // Note: CURRENT is also privately defined in TrashPolicyDefault
     final Path trashCurrent = new Path("Current");
 
     final String strKey1 = strDir1 + "/key1";
     final Path pathKey1 = new Path(strKey1);
-    final Path trashPathKey1 = Path.mergePaths(new Path(
-        new OFSPath(strKey1).getTrashRoot(), trashCurrent), pathKey1);
+    final Path trashPathKey1 = Path.mergePaths(
+        new Path(new OFSPath(strKey1).getTrashRoot(), trashCurrent),
+        new Path(dir1, "key1"));
 
     final String strKey2 = strDir1 + "/key2";
     final Path pathKey2 = new Path(strKey2);
-    final Path trashPathKey2 = Path.mergePaths(new Path(
-        new OFSPath(strKey2).getTrashRoot(), trashCurrent), pathKey2);
+    final Path trashPathKey2 = Path.mergePaths(
+        new Path(new OFSPath(strKey2).getTrashRoot(), trashCurrent),
+        new Path(dir1, "key2"));
 
     int res;
     try {
@@ -659,7 +663,8 @@ public class TestOzoneShellHA {
 
     // create volume: vol1 with bucket: bucket1
     final String testVolBucket = "/vol1/bucket1";
-    final String testKey = testVolBucket + "/key1";
+    String keyName = "/key1";
+    final String testKey = testVolBucket + keyName;
 
     final String[] volBucketArgs = new String[] {"-mkdir", "-p", 
testVolBucket};
     final String[] keyArgs = new String[] {"-touch", testKey};
@@ -690,7 +695,7 @@ public class TestOzoneShellHA {
                                                testVolBucket + "/.Trash"};
     final Path trashPathKey1 = Path.mergePaths(new Path(
             new OFSPath(testKey).getTrashRoot(), new Path("Current")),
-            new Path(testKey));
+            new Path(keyName));
     FileSystem fs = FileSystem.get(clientConf);
 
     try {
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/TrashPolicyOzone.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/TrashPolicyOzone.java
index e1138afc8e..da42265438 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/TrashPolicyOzone.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/TrashPolicyOzone.java
@@ -38,6 +38,7 @@ import org.apache.hadoop.fs.permission.FsAction;
 import org.apache.hadoop.fs.permission.FsPermission;
 import org.apache.hadoop.fs.InvalidPathException;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.OzoneConsts;
 import org.apache.hadoop.ozone.conf.OMClientConfig;
 import org.apache.hadoop.ozone.om.helpers.OzoneFSUtils;
 import org.apache.hadoop.ozone.OFSPath;
@@ -123,7 +124,100 @@ public class TrashPolicyOzone extends TrashPolicyDefault {
 
   @Override
   public boolean moveToTrash(Path path) throws IOException {
-    this.fs.getFileStatus(path);
+    if (validatePath(path)) {
+      if (!isEnabled()) {
+        return false;
+      }
+
+      if (!path.isAbsolute()) {                  // make path absolute
+        path = new Path(fs.getWorkingDirectory(), path);
+      }
+
+      // check that path exists
+      fs.getFileStatus(path);
+      String qpath = fs.makeQualified(path).toString();
+
+      Path trashRoot = fs.getTrashRoot(path);
+      Path trashCurrent = new Path(trashRoot, CURRENT);
+      if (qpath.startsWith(trashRoot.toString())) {
+        return false;                               // already in trash
+      }
+
+      if (trashRoot.getParent().toString().startsWith(qpath)) {
+        throw new IOException("Cannot move \"" + path
+            + "\" to the trash, as it contains the trash");
+      }
+
+      Path trashPath;
+      Path baseTrashPath;
+      if (fs.getUri().getScheme().equals(OzoneConsts.OZONE_OFS_URI_SCHEME)) {
+        OFSPath ofsPath = new OFSPath(path);
+        // trimming volume and bucket in order to be compatible with o3fs
+        // Also including volume and bucket name in the path is redundant as
+        // the key is already in a particular volume and bucket.
+        Path trimmedVolumeAndBucket =
+            new Path(OzoneConsts.OZONE_URI_DELIMITER
+                + ofsPath.getKeyName());
+        trashPath = makeTrashRelativePath(trashCurrent, 
trimmedVolumeAndBucket);
+        baseTrashPath = makeTrashRelativePath(trashCurrent,
+            trimmedVolumeAndBucket.getParent());
+      } else {
+        trashPath = makeTrashRelativePath(trashCurrent, path);
+        baseTrashPath = makeTrashRelativePath(trashCurrent, path.getParent());
+      }
+
+      IOException cause = null;
+
+      // try twice, in case checkpoint between the mkdirs() & rename()
+      for (int i = 0; i < 2; i++) {
+        try {
+          if (!fs.mkdirs(baseTrashPath, PERMISSION)) {      // create current
+            LOG.warn("Can't create(mkdir) trash directory: " + baseTrashPath);
+            return false;
+          }
+        } catch (FileAlreadyExistsException e) {
+          // find the path which is not a directory, and modify baseTrashPath
+          // & trashPath, then mkdirs
+          Path existsFilePath = baseTrashPath;
+          while (!fs.exists(existsFilePath)) {
+            existsFilePath = existsFilePath.getParent();
+          }
+          baseTrashPath = new Path(baseTrashPath.toString()
+              .replace(existsFilePath.toString(),
+                  existsFilePath.toString() + Time.now()));
+          trashPath = new Path(baseTrashPath, trashPath.getName());
+          // retry, ignore current failure
+          --i;
+          continue;
+        } catch (IOException e) {
+          LOG.warn("Can't create trash directory: " + baseTrashPath, e);
+          cause = e;
+          break;
+        }
+        try {
+          // if the target path in Trash already exists, then append with
+          // a current time in millisecs.
+          String orig = trashPath.toString();
+
+          while (fs.exists(trashPath)) {
+            trashPath = new Path(orig + Time.now());
+          }
+
+          // move to current trash
+          fs.rename(path, trashPath);
+          LOG.info("Moved: '" + path + "' to trash at: " + trashPath);
+          return true;
+        } catch (IOException e) {
+          cause = e;
+        }
+      }
+      throw (IOException) new IOException("Failed to move to trash: " + path)
+          .initCause(cause);
+    }
+    return false;
+  }
+
+  private boolean validatePath(Path path) throws IOException {
     String key = path.toUri().getPath();
     // Check to see if bucket is path item to be deleted.
     // Cannot moveToTrash if bucket is deleted,
@@ -146,11 +240,14 @@ public class TrashPolicyOzone extends TrashPolicyDefault {
     // first condition tests when length key is <= length trash
     // and second when length key > length trash
     if ((key.contains(this.fs.TRASH_PREFIX)) && (trashRootKey.startsWith(key))
-            || key.startsWith(trashRootKey)) {
+        || key.startsWith(trashRootKey)) {
       return false;
-    } else {
-      return super.moveToTrash(path);
     }
+    return true;
+  }
+
+  private Path makeTrashRelativePath(Path basePath, Path rmFilePath) {
+    return Path.mergePaths(basePath, rmFilePath);
   }
 
   protected class Emptier implements Runnable {


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

Reply via email to