瀏覽代碼

Merge branch 'trunk' into ozone-0.4.1

Anu Engineer 6 年之前
父節點
當前提交
20b2cd6695
共有 100 個文件被更改,包括 4188 次插入448 次删除
  1. 5 1
      hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/AclCommands.java
  2. 0 2
      hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/IOUtils.java
  3. 2 2
      hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
  4. 1 1
      hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md
  5. 33 11
      hadoop-common-project/hadoop-common/src/site/markdown/filesystem/filesystem.md
  6. 1 1
      hadoop-common-project/hadoop-common/src/site/markdown/filesystem/fsdatainputstreambuilder.md
  7. 1 1
      hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/contract/AbstractContractRootDirectoryTest.java
  8. 26 4
      hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/contract/ContractTestUtils.java
  9. 4 0
      hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java
  10. 1 0
      hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java
  11. 8 0
      hadoop-hdds/common/src/main/resources/ozone-default.xml
  12. 52 8
      hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/TopologySubcommand.java
  13. 10 3
      hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/router/FederationUtil.java
  14. 69 0
      hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/router/TestFederationUtil.java
  15. 3 1
      hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormatPBINode.java
  16. 16 0
      hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java
  17. 4 0
      hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java
  18. 34 0
      hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/common/TestJspHelper.java
  19. 1 1
      hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/hdfs/NNBench.java
  20. 0 1
      hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
  21. 2 0
      hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
  22. 3 0
      hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java
  23. 2 1
      hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
  24. 2 16
      hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerHAProtocol.java
  25. 2 7
      hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto
  26. 2 0
      hadoop-ozone/dist/src/main/compose/ozone-topology/test.sh
  27. 32 0
      hadoop-ozone/dist/src/main/smoketest/topology/scmcli.robot
  28. 7 0
      hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneCluster.java
  29. 49 5
      hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java
  30. 2 0
      hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestSecureOzoneContainer.java
  31. 2 0
      hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/server/TestSecureContainerServer.java
  32. 189 0
      hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMRatisSnapshots.java
  33. 6 1
      hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerHA.java
  34. 0 11
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java
  35. 4 6
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java
  36. 1 1
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java
  37. 19 3
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java
  38. 283 98
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
  39. 3 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerDoubleBuffer.java
  40. 13 2
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerRatisServer.java
  41. 71 59
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerStateMachine.java
  42. 12 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java
  43. 193 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/bucket/S3BucketDeleteRequest.java
  44. 213 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3InitiateMultipartUploadRequest.java
  45. 173 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadAbortRequest.java
  46. 217 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCommitPartRequest.java
  47. 23 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/package-info.java
  48. 1 3
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/volume/OMVolumeSetOwnerRequest.java
  49. 1 2
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/volume/OMVolumeSetQuotaRequest.java
  50. 7 1
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/bucket/S3BucketCreateResponse.java
  51. 55 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/bucket/S3BucketDeleteResponse.java
  52. 80 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/multipart/S3InitiateMultipartUploadResponse.java
  53. 83 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/multipart/S3MultipartUploadAbortResponse.java
  54. 109 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/multipart/S3MultipartUploadCommitPartResponse.java
  55. 22 0
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/multipart/package-info.java
  56. 1 1
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/OzoneManagerSnapshotProvider.java
  57. 10 8
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerHARequestHandlerImpl.java
  58. 0 35
      hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
  59. 85 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestOMRequestUtils.java
  60. 167 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/bucket/TestS3BucketDeleteRequest.java
  61. 153 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3InitiateMultipartUploadRequest.java
  62. 178 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartRequest.java
  63. 158 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartUploadAbortRequest.java
  64. 209 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartUploadCommitPartRequest.java
  65. 24 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/package-info.java
  66. 42 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/TestOMResponseUtils.java
  67. 3 38
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/bucket/TestS3BucketCreateResponse.java
  68. 73 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/bucket/TestS3BucketDeleteResponse.java
  69. 63 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/multipart/TestS3InitiateMultipartUploadResponse.java
  70. 143 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/multipart/TestS3MultipartResponse.java
  71. 129 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/multipart/TestS3MultipartUploadAbortResponse.java
  72. 24 0
      hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/multipart/package-info.java
  73. 2 1
      hadoop-project/pom.xml
  74. 2 2
      hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java
  75. 7 20
      hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AFileSystem.java
  76. 5 6
      hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/DDBPathMetadata.java
  77. 31 10
      hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/DirListingMetadata.java
  78. 15 8
      hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/DynamoDBMetadataStore.java
  79. 29 21
      hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/LocalMetadataStore.java
  80. 56 6
      hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/PathMetadata.java
  81. 9 4
      hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/S3Guard.java
  82. 2 2
      hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md
  83. 2 2
      hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/performance.md
  84. 1 5
      hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/testing.md
  85. 1 1
      hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/troubleshooting_s3a.md
  86. 6 3
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/contract/s3a/ITestS3AContractRootDir.java
  87. 152 3
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3GuardEmptyDirs.java
  88. 45 1
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3GuardOutOfBandOperations.java
  89. 70 0
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3GuardTtl.java
  90. 26 0
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/S3ATestUtils.java
  91. 1 1
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestDynamoDBMetadataStore.java
  92. 10 0
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestS3GuardConcurrentOps.java
  93. 1 1
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestS3GuardDDBRootOperations.java
  94. 3 2
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/MetadataStoreTestBase.java
  95. 39 5
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/TestDirListingMetadata.java
  96. 40 0
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/TestLocalMetadataStore.java
  97. 8 6
      hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/TestS3Guard.java
  98. 2 2
      hadoop-tools/hadoop-aws/src/test/resources/core-site.xml
  99. 1 1
      hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/constants/FileSystemConfigurations.java
  100. 6 0
      hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/TestAbfsConfigurationFieldsValidation.java

+ 5 - 1
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/AclCommands.java

@@ -173,7 +173,11 @@ class AclCommands extends FsCommand {
         + "  -x :Remove specified ACL entries. Other ACL entries are retained.\n"
         + "  --set :Fully replace the ACL, discarding all existing entries."
         + " The <acl_spec> must include entries for user, group, and others"
-        + " for compatibility with permission bits.\n"
+        + " for compatibility with permission bits. If the ACL spec contains"
+        + " only access entries, then the existing default entries are retained"
+        + ". If the ACL spec contains only default entries, then the existing"
+        + " access entries are retained. If the ACL spec contains both access"
+        + " and default entries, then both are replaced.\n"
         + "  <acl_spec>: Comma separated list of ACL entries.\n"
         + "  <path>: File or directory to modify.\n";
 

+ 0 - 2
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/io/IOUtils.java

@@ -508,8 +508,6 @@ public class IOUtils {
       Throwable t = ctor.newInstance(msg);
       return (T) (t.initCause(exception));
     } catch (Throwable e) {
-      LOG.warn("Unable to wrap exception of type " +
-          clazz + ": it has no (String) constructor", e);
       throw exception;
     }
   }

+ 2 - 2
hadoop-common-project/hadoop-common/src/main/resources/core-default.xml

@@ -1353,7 +1353,7 @@
 
 <property>
   <name>fs.s3a.multipart.size</name>
-  <value>100M</value>
+  <value>64M</value>
   <description>How big (in bytes) to split upload or copy operations up into.
     A suffix from the set {K,M,G,T,P} may be used to scale the numeric value.
   </description>
@@ -1361,7 +1361,7 @@
 
 <property>
   <name>fs.s3a.multipart.threshold</name>
-  <value>2147483647</value>
+  <value>128M</value>
   <description>How big (in bytes) to split upload or copy operations up into.
     This also controls the partition size in renamed files, as rename() involves
     copying the source file(s).

+ 1 - 1
hadoop-common-project/hadoop-common/src/site/markdown/FileSystemShell.md

@@ -638,7 +638,7 @@ Options:
 * -R: Apply operations to all files and directories recursively.
 * -m: Modify ACL. New entries are added to the ACL, and existing entries are retained.
 * -x: Remove specified ACL entries. Other ACL entries are retained.
-* ``--set``: Fully replace the ACL, discarding all existing entries. The *acl\_spec* must include entries for user, group, and others for compatibility with permission bits.
+* ``--set``: Fully replace the ACL, discarding all existing entries. The *acl\_spec* must include entries for user, group, and others for compatibility with permission bits. If the ACL spec contains only access entries, then the existing default entries are retained. If the ACL spec contains only default entries, then the existing access entries are retained. If the ACL spec contains both access and default entries, then both are replaced.
 * *acl\_spec*: Comma separated list of ACL entries.
 * *path*: File or directory to modify.
 

+ 33 - 11
hadoop-common-project/hadoop-common/src/site/markdown/filesystem/filesystem.md

@@ -220,21 +220,21 @@ directory contains many thousands of files.
 
 Consider a directory `"/d"` with the contents:
 
-	a
-	part-0000001
-	part-0000002
-	...
-	part-9999999
+    a
+    part-0000001
+    part-0000002
+    ...
+    part-9999999
 
 
 If the number of files is such that HDFS returns a partial listing in each
 response, then, if a listing `listStatus("/d")` takes place concurrently with the operation
 `rename("/d/a","/d/z"))`, the result may be one of:
 
-	[a, part-0000001, ... , part-9999999]
-	[part-0000001, ... , part-9999999, z]
-	[a, part-0000001, ... , part-9999999, z]
-	[part-0000001, ... , part-9999999]
+    [a, part-0000001, ... , part-9999999]
+    [part-0000001, ... , part-9999999, z]
+    [a, part-0000001, ... , part-9999999, z]
+    [part-0000001, ... , part-9999999]
 
 While this situation is likely to be a rare occurrence, it MAY happen. In HDFS
 these inconsistent views are only likely when listing a directory with many children.
@@ -964,7 +964,7 @@ A path referring to a file is removed, return value: `True`
 Deleting an empty root does not change the filesystem state
 and may return true or false.
 
-    if isDir(FS, p) and isRoot(p) and children(FS, p) == {} :
+    if isRoot(p) and children(FS, p) == {} :
         FS ' = FS
         result = (undetermined)
 
@@ -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.
 
+*Object Stores*: see [Object Stores: root directory deletion](#object-stores-rm-root).
+
+
 ##### Empty (non-root) directory `recursive == False`
 
 Deleting an empty directory that is not root will remove the path from the FS and
@@ -986,7 +989,7 @@ return true.
 ##### Recursive delete of non-empty root directory
 
 Deleting a root path with children and `recursive==True`
- can do one of two things.
+can generally have three outcomes:
 
 1. The POSIX model assumes that if the user has
 the correct permissions to delete everything,
@@ -1004,6 +1007,8 @@ filesystem is desired.
             FS' = FS
             result = False
 
+1. Object Stores: see [Object Stores: root directory deletion](#object-stores-rm-root).
+
 HDFS has the notion of *Protected Directories*, which are declared in
 the option `fs.protected.directories`. Any attempt to delete such a directory
 or a parent thereof raises an `AccessControlException`. Accordingly, any
@@ -1019,6 +1024,23 @@ 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.
 
+
+### <a name="object-stores-rm-root"></a> Object Stores: root directory deletion
+
+Some of the object store based filesystem implementations always return
+false when deleting the root, leaving the state of the store unchanged.
+
+    if isRoot(p) :
+        FS ' = FS
+        result = False
+
+This is irrespective of the recursive flag status or the state of the directory.
+
+This is a simplification which avoids the inevitably non-atomic scan and delete
+of the contents of the store. It also avoids any confusion about whether
+the operation actually deletes that specific store/container itself, and
+adverse consequences of the simpler permissions models of stores.
+
 ##### Recursive delete of non-root directory
 
 Deleting a non-root path with children `recursive==true`

+ 1 - 1
hadoop-common-project/hadoop-common/src/site/markdown/filesystem/fsdatainputstreambuilder.md

@@ -54,7 +54,7 @@ of `FileSystem`.
 
 ```java
 out = fs.openFile(path)
-    .opt("fs.s3a.experimental.fadvise", "random")
+    .opt("fs.s3a.experimental.input.fadvise", "random")
     .must("fs.s3a.readahead.range", 256 * 1024)
     .build()
     .get();

+ 1 - 1
hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/contract/AbstractContractRootDirectoryTest.java

@@ -189,7 +189,7 @@ public abstract class AbstractContractRootDirectoryTest extends AbstractFSContra
     Path root = new Path("/");
     FileStatus[] statuses = fs.listStatus(root);
     for (FileStatus status : statuses) {
-      ContractTestUtils.assertDeleted(fs, status.getPath(), true);
+      ContractTestUtils.assertDeleted(fs, status.getPath(), false, true, false);
     }
     FileStatus[] rootListStatus = fs.listStatus(root);
     assertEquals("listStatus on empty root-directory returned found: "

+ 26 - 4
hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/contract/ContractTestUtils.java

@@ -674,7 +674,8 @@ public class ContractTestUtils extends Assert {
   /**
    * Delete a file/dir and assert that delete() returned true
    * <i>and</i> that the path no longer exists. This variant rejects
-   * all operations on root directories.
+   * all operations on root directories and requires the target path
+   * to exist before the deletion operation.
    * @param fs filesystem
    * @param file path to delete
    * @param recursive flag to enable recursive delete
@@ -688,20 +689,41 @@ public class ContractTestUtils extends Assert {
 
   /**
    * Delete a file/dir and assert that delete() returned true
-   * <i>and</i> that the path no longer exists. This variant rejects
-   * all operations on root directories
+   * <i>and</i> that the path no longer exists.
+   * This variant requires the target path
+   * to exist before the deletion operation.
+   * @param fs filesystem
+   * @param file path to delete
+   * @param recursive flag to enable recursive delete
+   * @param allowRootOperations can the root dir be deleted?
+   * @throws IOException IO problems
+   */
+  public static void assertDeleted(FileSystem fs,
+      Path file,
+      boolean recursive,
+      boolean allowRootOperations) throws IOException {
+    assertDeleted(fs, file, true, recursive, allowRootOperations);
+  }
+
+  /**
+   * Delete a file/dir and assert that delete() returned true
+   * <i>and</i> that the path no longer exists.
    * @param fs filesystem
    * @param file path to delete
+   * @param requirePathToExist check for the path existing first?
    * @param recursive flag to enable recursive delete
    * @param allowRootOperations can the root dir be deleted?
    * @throws IOException IO problems
    */
   public static void assertDeleted(FileSystem fs,
       Path file,
+      boolean requirePathToExist,
       boolean recursive,
       boolean allowRootOperations) throws IOException {
     rejectRootOperation(file, allowRootOperations);
-    assertPathExists(fs, "about to be deleted file", file);
+    if (requirePathToExist) {
+      assertPathExists(fs, "about to be deleted file", file);
+    }
     boolean deleted = fs.delete(file, recursive);
     String dir = ls(fs, file.getParent());
     assertTrue("Delete failed on " + file + ": " + dir, deleted);

+ 4 - 0
hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/XceiverClientGrpc.java

@@ -54,6 +54,7 @@ import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -269,6 +270,9 @@ public class XceiverClientGrpc extends XceiverClientSpi {
       datanodeList = pipeline.getNodesInOrder();
     } else {
       datanodeList = pipeline.getNodes();
+      // Shuffle datanode list so that clients do not read in the same order
+      // every time.
+      Collections.shuffle(datanodeList);
     }
     for (DatanodeDetails dn : datanodeList) {
       try {

+ 1 - 0
hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java

@@ -119,6 +119,7 @@ public final class OzoneConsts {
   public static final String DN_CONTAINER_DB = "-dn-"+ CONTAINER_DB_SUFFIX;
   public static final String DELETED_BLOCK_DB = "deletedBlock.db";
   public static final String OM_DB_NAME = "om.db";
+  public static final String OM_DB_BACKUP_PREFIX = "om.db.backup.";
   public static final String OM_DB_CHECKPOINTS_DIR_NAME = "om.db.checkpoints";
   public static final String OZONE_MANAGER_TOKEN_DB_NAME = "om-token.db";
   public static final String SCM_DB_NAME = "scm.db";

+ 8 - 0
hadoop-hdds/common/src/main/resources/ozone-default.xml

@@ -1630,6 +1630,14 @@
     <description>Byte limit for Raft's Log Worker queue.
     </description>
   </property>
+  <property>
+    <name>ozone.om.ratis.log.purge.gap</name>
+    <value>1000000</value>
+    <tag>OZONE, OM, RATIS</tag>
+    <description>The minimum gap between log indices for Raft server to purge
+      its log segments after taking snapshot.
+    </description>
+  </property>
 
   <property>
     <name>ozone.om.ratis.snapshot.auto.trigger.threshold</name>

+ 52 - 8
hadoop-hdds/tools/src/main/java/org/apache/hadoop/hdds/scm/cli/TopologySubcommand.java

@@ -19,9 +19,11 @@
 package org.apache.hadoop.hdds.scm.cli;
 
 import org.apache.hadoop.hdds.cli.HddsVersionProvider;
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import org.apache.hadoop.hdds.scm.client.ScmClient;
 import picocli.CommandLine;
+
 import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState.DEAD;
 import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState.DECOMMISSIONED;
 import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState.DECOMMISSIONING;
@@ -29,7 +31,11 @@ import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState.HEALTHY
 import static org.apache.hadoop.hdds.protocol.proto.HddsProtos.NodeState.STALE;
 
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.TreeSet;
 import java.util.concurrent.Callable;
 
 /**
@@ -55,6 +61,10 @@ public class TopologySubcommand implements Callable<Void> {
     stateArray.add(DECOMMISSIONED);
   }
 
+  @CommandLine.Option(names = {"-o", "--order"},
+      description = "Print Topology ordered by network location")
+  private boolean order;
+
   @Override
   public Void call() throws Exception {
     try (ScmClient scmClient = parent.createScmClient()) {
@@ -64,17 +74,51 @@ public class TopologySubcommand implements Callable<Void> {
         if (nodes != null && nodes.size() > 0) {
           // show node state
           System.out.println("State = " + state.toString());
-          // format "hostname/ipAddress    networkLocation"
-          nodes.forEach(node -> {
-            System.out.print(node.getNodeID().getHostName() + "/" +
-                node.getNodeID().getIpAddress());
-            System.out.println("    " +
-                (node.getNodeID().getNetworkLocation() != null ?
-                    node.getNodeID().getNetworkLocation() : "NA"));
-          });
+          if (order) {
+            printOrderedByLocation(nodes);
+          } else {
+            printNodesWithLocation(nodes);
+          }
         }
       }
       return null;
     }
   }
+
+  // Format
+  // Location: rack1
+  //  ipAddress(hostName)
+  private void printOrderedByLocation(List<HddsProtos.Node> nodes) {
+    HashMap<String, TreeSet<DatanodeDetails>> tree =
+        new HashMap<>();
+    for (HddsProtos.Node node : nodes) {
+      String location = node.getNodeID().getNetworkLocation();
+      if (location != null && !tree.containsKey(location)) {
+        tree.put(location, new TreeSet<>());
+      }
+      tree.get(location).add(DatanodeDetails.getFromProtoBuf(node.getNodeID()));
+    }
+    ArrayList<String> locations = new ArrayList<>(tree.keySet());
+    Collections.sort(locations);
+
+    locations.forEach(location -> {
+      System.out.println("Location: " + location);
+      tree.get(location).forEach(node -> {
+        System.out.println(" " + node.getIpAddress() + "(" + node.getHostName()
+            + ")");
+      });
+    });
+  }
+
+
+  // Format "ipAddress(hostName)    networkLocation"
+  private void printNodesWithLocation(Collection<HddsProtos.Node> nodes) {
+    nodes.forEach(node -> {
+      System.out.print(" " + node.getNodeID().getIpAddress() + "(" +
+          node.getNodeID().getHostName() + ")");
+      System.out.println("    " +
+          (node.getNodeID().getNetworkLocation() != null ?
+              node.getNodeID().getNetworkLocation() : "NA"));
+    });
+  }
 }

+ 10 - 3
hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/router/FederationUtil.java

@@ -149,9 +149,16 @@ public final class FederationUtil {
       final R context, final Class<R> contextClass, final Class<T> clazz) {
     try {
       if (contextClass == null) {
-        // Default constructor if no context
-        Constructor<T> constructor = clazz.getConstructor();
-        return constructor.newInstance();
+        if (conf == null) {
+          // Default constructor if no context
+          Constructor<T> constructor = clazz.getConstructor();
+          return constructor.newInstance();
+        } else {
+          // Constructor with configuration but no context
+          Constructor<T> constructor = clazz.getConstructor(
+              Configuration.class);
+          return constructor.newInstance(conf);
+        }
       } else {
         // Constructor with context
         Constructor<T> constructor = clazz.getConstructor(

+ 69 - 0
hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/router/TestFederationUtil.java

@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdfs.server.federation.router;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdfs.HdfsConfiguration;
+import org.apache.hadoop.hdfs.server.federation.MockResolver;
+import org.apache.hadoop.hdfs.server.federation.resolver.ActiveNamenodeResolver;
+import org.apache.hadoop.hdfs.server.federation.resolver.FileSubclusterResolver;
+import org.apache.hadoop.hdfs.server.federation.store.StateStoreService;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.apache.hadoop.hdfs.server.federation.router.RBFConfigKeys.FEDERATION_FILE_RESOLVER_CLIENT_CLASS;
+import static org.apache.hadoop.hdfs.server.federation.router.RBFConfigKeys.FEDERATION_NAMENODE_RESOLVER_CLIENT_CLASS;
+
+/**
+ * Tests Router federation utility methods.
+ */
+public class TestFederationUtil {
+
+  @Test
+  public void testInstanceCreation() {
+    Configuration conf = new HdfsConfiguration();
+
+    // Use mock resolver classes
+    conf.setClass(FEDERATION_NAMENODE_RESOLVER_CLIENT_CLASS,
+        MockResolver.class, ActiveNamenodeResolver.class);
+    conf.setClass(FEDERATION_FILE_RESOLVER_CLIENT_CLASS,
+        MockResolver.class, FileSubclusterResolver.class);
+
+    Router router = new Router();
+    StateStoreService stateStore = new StateStoreService();
+
+    ActiveNamenodeResolver namenodeResolverWithContext =
+        FederationUtil.newActiveNamenodeResolver(conf, stateStore);
+
+    ActiveNamenodeResolver namenodeResolverWithoutContext =
+        FederationUtil.newActiveNamenodeResolver(conf, null);
+
+    FileSubclusterResolver subclusterResolverWithContext =
+        FederationUtil.newFileSubclusterResolver(conf, router);
+
+    FileSubclusterResolver subclusterResolverWithoutContext =
+        FederationUtil.newFileSubclusterResolver(conf, null);
+
+    // Check all instances are created successfully.
+    assertNotNull(namenodeResolverWithContext);
+    assertNotNull(namenodeResolverWithoutContext);
+    assertNotNull(subclusterResolverWithContext);
+    assertNotNull(subclusterResolverWithoutContext);
+  }
+}

+ 3 - 1
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSImageFormatPBINode.java

@@ -269,7 +269,7 @@ public final class FSImageFormatPBINode {
             + "name before upgrading to this release.");
       }
       // NOTE: This does not update space counts for parents
-      if (!parent.addChild(child)) {
+      if (!parent.addChildAtLoading(child)) {
         return;
       }
       dir.cacheName(child);
@@ -551,6 +551,8 @@ public final class FSImageFormatPBINode {
               ++numImageErrors;
             }
             if (!inode.isReference()) {
+              // Serialization must ensure that children are in order, related
+              // to HDFS-13693
               b.addChildren(inode.getId());
             } else {
               refList.add(inode.asReference());

+ 16 - 0
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/INodeDirectory.java

@@ -572,6 +572,22 @@ public class INodeDirectory extends INodeWithAdditionalFields
     return true;
   }
 
+  /**
+   * During image loading, the search is unnecessary since the insert position
+   * should always be at the end of the map given the sequence they are
+   * serialized on disk.
+   */
+  public boolean addChildAtLoading(INode node) {
+    int pos;
+    if (!node.isReference()) {
+      pos = (children == null) ? (-1) : (-children.size() - 1);
+      addChild(node, pos);
+      return true;
+    } else {
+      return addChild(node);
+    }
+  }
+
   /**
    * Add the node to the children list at the given insertion point.
    * The basic add method which actually calls children.add(..).

+ 4 - 0
hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java

@@ -682,6 +682,10 @@ public class NameNode extends ReconfigurableBase implements
   @Override
   public void verifyToken(DelegationTokenIdentifier id, byte[] password)
       throws IOException {
+    // during startup namesystem is null, let client retry
+    if (namesystem == null) {
+      throw new RetriableException("Namenode is in startup mode");
+    }
     namesystem.verifyToken(id, password);
   }
 

+ 34 - 0
hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/common/TestJspHelper.java

@@ -21,12 +21,14 @@ import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hdfs.DFSConfigKeys;
 import org.apache.hadoop.hdfs.HdfsConfiguration;
 import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
+import org.apache.hadoop.hdfs.server.namenode.NameNode;
 import org.apache.hadoop.hdfs.server.namenode.NameNodeHttpServer;
 import org.apache.hadoop.hdfs.web.resources.DoAsParam;
 import org.apache.hadoop.hdfs.web.resources.UserParam;
 import org.apache.hadoop.io.DataInputBuffer;
 import org.apache.hadoop.io.DataOutputBuffer;
 import org.apache.hadoop.io.Text;
+import org.apache.hadoop.ipc.RetriableException;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
 import org.apache.hadoop.security.authorize.AuthorizationException;
@@ -36,9 +38,11 @@ import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.hadoop.security.token.Token;
 import org.apache.hadoop.security.token.TokenIdentifier;
 import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
+import org.apache.hadoop.test.LambdaTestUtils;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpServletRequest;
@@ -371,8 +375,38 @@ public class TestJspHelper {
     }
   }
 
+  @Test
+  public void testGetUgiDuringStartup() throws Exception {
+    conf.set(DFSConfigKeys.FS_DEFAULT_NAME_KEY, "hdfs://localhost:4321/");
+    ServletContext context = mock(ServletContext.class);
+    String realUser = "TheDoctor";
+    String user = "TheNurse";
+    conf.set(DFSConfigKeys.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
+    UserGroupInformation.setConfiguration(conf);
+    HttpServletRequest request;
 
+    Text ownerText = new Text(user);
+    DelegationTokenIdentifier dtId = new DelegationTokenIdentifier(
+        ownerText, ownerText, new Text(realUser));
+    Token<DelegationTokenIdentifier> token =
+        new Token<DelegationTokenIdentifier>(dtId,
+            new DummySecretManager(0, 0, 0, 0));
+    String tokenString = token.encodeToUrlString();
 
+    // token with auth-ed user
+    request = getMockRequest(realUser, null, null);
+    when(request.getParameter(JspHelper.DELEGATION_PARAMETER_NAME)).thenReturn(
+        tokenString);
+
+    NameNode mockNN = mock(NameNode.class);
+    Mockito.doCallRealMethod().when(mockNN)
+        .verifyToken(Mockito.any(), Mockito.any());
+    when(context.getAttribute("name.node")).thenReturn(mockNN);
+
+    LambdaTestUtils.intercept(RetriableException.class,
+        "Namenode is in startup mode",
+        () -> JspHelper.getUGI(context, request, conf));
+  }
 
   private HttpServletRequest getMockRequest(String remoteUser, String user, String doAs) {
     HttpServletRequest request = mock(HttpServletRequest.class);

+ 1 - 1
hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/java/org/apache/hadoop/hdfs/NNBench.java

@@ -668,7 +668,7 @@ public class NNBench extends Configured implements Tool {
       long startTime = getConf().getLong("test.nnbench.starttime", 0l);
       long currentTime = System.currentTimeMillis();
       long sleepTime = startTime - currentTime;
-      boolean retVal = false;
+      boolean retVal = true;
       
       // If the sleep time is greater than 0, then sleep and return
       if (sleepTime > 0) {

+ 0 - 1
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java

@@ -221,7 +221,6 @@ public final class OmUtils {
     case GetDelegationToken:
     case RenewDelegationToken:
     case CancelDelegationToken:
-    case ApplyInitiateMultiPartUpload:
     case CreateDirectory:
     case CreateFile:
     case RemoveAcl:

+ 2 - 0
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java

@@ -41,6 +41,7 @@ public enum OMAction implements AuditAction {
 
   // S3 Bucket
   CREATE_S3_BUCKET,
+  DELETE_S3_BUCKET,
 
   // READ Actions
   CHECK_VOLUME_ACCESS,
@@ -55,6 +56,7 @@ public enum OMAction implements AuditAction {
   COMMIT_MULTIPART_UPLOAD_PARTKEY,
   COMPLETE_MULTIPART_UPLOAD,
   LIST_MULTIPART_UPLOAD_PARTS,
+  ABORT_MULTIPART_UPLOAD,
 
   //FS Actions
   GET_FILE_STATUS,

+ 3 - 0
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/OMConfigKeys.java

@@ -123,6 +123,9 @@ public final class OMConfigKeys {
       "ozone.om.ratis.log.appender.queue.byte-limit";
   public static final String
       OZONE_OM_RATIS_LOG_APPENDER_QUEUE_BYTE_LIMIT_DEFAULT = "32MB";
+  public static final String OZONE_OM_RATIS_LOG_PURGE_GAP =
+      "ozone.om.ratis.log.purge.gap";
+  public static final int OZONE_OM_RATIS_LOG_PURGE_GAP_DEFAULT = 1000000;
 
   // OM Snapshot configurations
   public static final String OZONE_OM_RATIS_SNAPSHOT_AUTO_TRIGGER_THRESHOLD_KEY

+ 2 - 1
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java

@@ -203,7 +203,8 @@ public class OMException extends IOException {
 
     PREFIX_NOT_FOUND,
 
-    S3_BUCKET_INVALID_LENGTH
+    S3_BUCKET_INVALID_LENGTH,
 
+    RATIS_ERROR // Error in Ratis server
   }
 }

+ 2 - 16
hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerHAProtocol.java

@@ -18,9 +18,6 @@
 
 package org.apache.hadoop.ozone.om.protocol;
 
-import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
-import org.apache.hadoop.ozone.om.helpers.OmMultipartInfo;
-
 import java.io.IOException;
 
 /**
@@ -32,21 +29,10 @@ public interface OzoneManagerHAProtocol {
   /**
    * Store the snapshot index i.e. the raft log index, corresponding to the
    * last transaction applied to the OM RocksDB, in OM metadata dir on disk.
+   * @param flush flush the OM DB to disk if true
    * @return the snapshot index
    * @throws IOException
    */
-  long saveRatisSnapshot() throws IOException;
-
-  /**
-   * Initiate multipart upload for the specified key.
-   *
-   * This will be called only from applyTransaction.
-   * @param omKeyArgs
-   * @param multipartUploadID
-   * @return OmMultipartInfo
-   * @throws IOException
-   */
-  OmMultipartInfo applyInitiateMultipartUpload(OmKeyArgs omKeyArgs,
-      String multipartUploadID) throws IOException;
+  long saveRatisSnapshot(boolean flush) throws IOException;
 
 }

+ 2 - 7
hadoop-ozone/common/src/main/proto/OzoneManagerProtocol.proto

@@ -74,8 +74,6 @@ enum Type {
 
   ServiceList = 51;
 
-  ApplyInitiateMultiPartUpload = 52;
-
   GetDelegationToken = 61;
   RenewDelegationToken = 62;
   CancelDelegationToken = 63;
@@ -138,7 +136,6 @@ message OMRequest {
   optional MultipartUploadListPartsRequest  listMultipartUploadPartsRequest = 50;
 
   optional ServiceListRequest               serviceListRequest             = 51;
-  optional MultipartInfoApplyInitiateRequest initiateMultiPartUploadApplyRequest = 52;
 
   optional hadoop.common.GetDelegationTokenRequestProto getDelegationTokenRequest = 61;
   optional hadoop.common.RenewDelegationTokenRequestProto renewDelegationTokenRequest= 62;
@@ -281,6 +278,8 @@ enum Status {
     PREFIX_NOT_FOUND=50;
 
     S3_BUCKET_INVALID_LENGTH = 51; // s3 bucket invalid length.
+
+    RATIS_ERROR = 52;
 }
 
 
@@ -909,11 +908,7 @@ message S3ListBucketsResponse {
 
 message MultipartInfoInitiateRequest {
     required KeyArgs keyArgs = 1;
-}
 
-message MultipartInfoApplyInitiateRequest {
-    required KeyArgs keyArgs = 1;
-    required string multipartUploadID = 2;
 }
 
 message MultipartInfoInitiateResponse {

+ 2 - 0
hadoop-ozone/dist/src/main/compose/ozone-topology/test.sh

@@ -30,6 +30,8 @@ execute_robot_test om auditparser
 
 execute_robot_test scm basic/basic.robot
 
+execute_robot_test scm topology/scmcli.robot
+
 stop_docker_env
 
 generate_report

+ 32 - 0
hadoop-ozone/dist/src/main/smoketest/topology/scmcli.robot

@@ -0,0 +1,32 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation       Smoketest ozone cluster startup
+Library             OperatingSystem
+Library             BuiltIn
+Resource            ../commonlib.robot
+
+*** Variables ***
+
+
+*** Test Cases ***
+Run printTopology
+    ${output} =         Execute          ozone scmcli printTopology
+                        Should contain   ${output}         10.5.0.7(ozone-topology_datanode_4_1.ozone-topology_net)    /rack2
+Run printTopology -o
+    ${output} =         Execute          ozone scmcli printTopology -o
+                        Should contain   ${output}         Location: /rack2
+                        Should contain   ${output}         10.5.0.7(ozone-topology_datanode_4_1.ozone-topology_net)

+ 7 - 0
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneCluster.java

@@ -234,6 +234,7 @@ public interface MiniOzoneCluster {
 
     protected static final int DEFAULT_HB_INTERVAL_MS = 1000;
     protected static final int DEFAULT_HB_PROCESSOR_INTERVAL_MS = 100;
+    protected static final int ACTIVE_OMS_NOT_SET = -1;
 
     protected final OzoneConfiguration conf;
     protected final String path;
@@ -241,6 +242,7 @@ public interface MiniOzoneCluster {
     protected String clusterId;
     protected String omServiceId;
     protected int numOfOMs;
+    protected int numOfActiveOMs = ACTIVE_OMS_NOT_SET;
 
     protected Optional<Boolean> enableTrace = Optional.of(false);
     protected Optional<Integer> hbInterval = Optional.empty();
@@ -440,6 +442,11 @@ public interface MiniOzoneCluster {
       return this;
     }
 
+    public Builder setNumOfActiveOMs(int numActiveOMs) {
+      this.numOfActiveOMs = numActiveOMs;
+      return this;
+    }
+
     public Builder setStreamBufferSizeUnit(StorageUnit unit) {
       this.streamBufferSizeUnit = Optional.of(unit);
       return this;

+ 49 - 5
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/MiniOzoneHAClusterImpl.java

@@ -53,6 +53,10 @@ public final class MiniOzoneHAClusterImpl extends MiniOzoneClusterImpl {
   private Map<String, OzoneManager> ozoneManagerMap;
   private List<OzoneManager> ozoneManagers;
 
+  // Active OMs denote OMs which are up and running
+  private List<OzoneManager> activeOMs;
+  private List<OzoneManager> inactiveOMs;
+
   private static final Random RANDOM = new Random();
   private static final int RATIS_LEADER_ELECTION_TIMEOUT = 1000; // 1 seconds
 
@@ -67,11 +71,15 @@ public final class MiniOzoneHAClusterImpl extends MiniOzoneClusterImpl {
   private MiniOzoneHAClusterImpl(
       OzoneConfiguration conf,
       Map<String, OzoneManager> omMap,
+      List<OzoneManager> activeOMList,
+      List<OzoneManager> inactiveOMList,
       StorageContainerManager scm,
       List<HddsDatanodeService> hddsDatanodes) {
     super(conf, scm, hddsDatanodes);
     this.ozoneManagerMap = omMap;
     this.ozoneManagers = new ArrayList<>(omMap.values());
+    this.activeOMs = activeOMList;
+    this.inactiveOMs = inactiveOMList;
   }
 
   /**
@@ -83,6 +91,10 @@ public final class MiniOzoneHAClusterImpl extends MiniOzoneClusterImpl {
     return this.ozoneManagers.get(0);
   }
 
+  public boolean isOMActive(String omNodeId) {
+    return activeOMs.contains(ozoneManagerMap.get(omNodeId));
+  }
+
   public OzoneManager getOzoneManager(int index) {
     return this.ozoneManagers.get(index);
   }
@@ -91,6 +103,20 @@ public final class MiniOzoneHAClusterImpl extends MiniOzoneClusterImpl {
     return this.ozoneManagerMap.get(omNodeId);
   }
 
+  /**
+   * Start a previously inactive OM.
+   */
+  public void startInactiveOM(String omNodeID) throws IOException {
+    OzoneManager ozoneManager = ozoneManagerMap.get(omNodeID);
+    if (!inactiveOMs.contains(ozoneManager)) {
+      throw new IOException("OM is already active.");
+    } else {
+      ozoneManager.start();
+      activeOMs.add(ozoneManager);
+      inactiveOMs.remove(ozoneManager);
+    }
+  }
+
   @Override
   public void restartOzoneManager() throws IOException {
     for (OzoneManager ozoneManager : ozoneManagers) {
@@ -125,6 +151,8 @@ public final class MiniOzoneHAClusterImpl extends MiniOzoneClusterImpl {
   public static class Builder extends MiniOzoneClusterImpl.Builder {
 
     private final String nodeIdBaseStr = "omNode-";
+    private List<OzoneManager> activeOMs = new ArrayList<>();
+    private List<OzoneManager> inactiveOMs = new ArrayList<>();
 
     /**
      * Creates a new Builder.
@@ -137,6 +165,15 @@ public final class MiniOzoneHAClusterImpl extends MiniOzoneClusterImpl {
 
     @Override
     public MiniOzoneCluster build() throws IOException {
+      if (numOfActiveOMs > numOfOMs) {
+        throw new IllegalArgumentException("Number of active OMs cannot be " +
+            "more than the total number of OMs");
+      }
+
+      // If num of ActiveOMs is not set, set it to numOfOMs.
+      if (numOfActiveOMs == ACTIVE_OMS_NOT_SET) {
+        numOfActiveOMs = numOfOMs;
+      }
       DefaultMetricsSystem.setMiniClusterMode(true);
       initializeConfiguration();
       StorageContainerManager scm;
@@ -150,8 +187,8 @@ public final class MiniOzoneHAClusterImpl extends MiniOzoneClusterImpl {
       }
 
       final List<HddsDatanodeService> hddsDatanodes = createHddsDatanodes(scm);
-      MiniOzoneHAClusterImpl cluster = new MiniOzoneHAClusterImpl(conf, omMap,
-          scm, hddsDatanodes);
+      MiniOzoneHAClusterImpl cluster = new MiniOzoneHAClusterImpl(
+          conf, omMap, activeOMs, inactiveOMs, scm, hddsDatanodes);
       if (startDataNodes) {
         cluster.startHddsDatanodes();
       }
@@ -215,9 +252,16 @@ public final class MiniOzoneHAClusterImpl extends MiniOzoneClusterImpl {
             om.setCertClient(certClient);
             omMap.put(nodeId, om);
 
-            om.start();
-            LOG.info("Started OzoneManager RPC server at " +
-                om.getOmRpcServerAddr());
+            if (i <= numOfActiveOMs) {
+              om.start();
+              activeOMs.add(om);
+              LOG.info("Started OzoneManager RPC server at " +
+                  om.getOmRpcServerAddr());
+            } else {
+              inactiveOMs.add(om);
+              LOG.info("Intialized OzoneManager at " + om.getOmRpcServerAddr()
+                  + ". This OM is currently inactive (not running).");
+            }
           }
 
           // Set default OM address to point to the first OM. Clients would

+ 2 - 0
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/ozoneimpl/TestSecureOzoneContainer.java

@@ -26,6 +26,7 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos.BlockTokenSecretProto.Ac
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
 import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier;
 import org.apache.hadoop.hdds.security.x509.SecurityConfig;
+import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.ozone.OzoneConfigKeys;
 import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.ozone.client.CertificateClientTestImpl;
@@ -110,6 +111,7 @@ public class TestSecureOzoneContainer {
 
   @Before
   public void setup() throws Exception {
+    DefaultMetricsSystem.setMiniClusterMode(true);
     conf = new OzoneConfiguration();
     String ozoneMetaPath =
         GenericTestUtils.getTempPath("ozoneMeta");

+ 2 - 0
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/container/server/TestSecureContainerServer.java

@@ -34,6 +34,7 @@ import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
 import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier;
 import org.apache.hadoop.hdds.security.x509.SecurityConfig;
+import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.ozone.OzoneConfigKeys;
 import org.apache.hadoop.ozone.RatisTestHelper;
 import org.apache.hadoop.ozone.client.CertificateClientTestImpl;
@@ -97,6 +98,7 @@ public class TestSecureContainerServer {
 
   @BeforeClass
   static public void setup() throws Exception {
+    DefaultMetricsSystem.setMiniClusterMode(true);
     CONF.set(HddsConfigKeys.HDDS_METADATA_DIR_NAME, TEST_DIR);
     CONF.setBoolean(OZONE_SECURITY_ENABLED_KEY, true);
     CONF.setBoolean(HDDS_BLOCK_TOKEN_ENABLED, true);

+ 189 - 0
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOMRatisSnapshots.java

@@ -0,0 +1,189 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.hadoop.ozone.om;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.MiniOzoneHAClusterImpl;
+import org.apache.hadoop.ozone.client.ObjectStore;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneClientFactory;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.client.VolumeArgs;
+import org.apache.hadoop.ozone.om.ratis.OzoneManagerRatisServer;
+import org.apache.hadoop.utils.db.DBCheckpoint;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.Timeout;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import static org.apache.hadoop.ozone.om.TestOzoneManagerHA.createKey;
+
+/**
+ * Tests the Ratis snaphsots feature in OM.
+ */
+public class TestOMRatisSnapshots {
+
+  private MiniOzoneHAClusterImpl cluster = null;
+  private ObjectStore objectStore;
+  private OzoneConfiguration conf;
+  private String clusterId;
+  private String scmId;
+  private int numOfOMs = 3;
+  private static final long SNAPSHOT_THRESHOLD = 50;
+  private static final int LOG_PURGE_GAP = 50;
+
+  @Rule
+  public ExpectedException exception = ExpectedException.none();
+
+  @Rule
+  public Timeout timeout = new Timeout(500_000);
+
+  /**
+   * Create a MiniOzoneCluster for testing. The cluster initially has one
+   * inactive OM. So at the start of the cluster, there will be 2 active and 1
+   * inactive OM.
+   *
+   * @throws IOException
+   */
+  @Before
+  public void init() throws Exception {
+    conf = new OzoneConfiguration();
+    clusterId = UUID.randomUUID().toString();
+    scmId = UUID.randomUUID().toString();
+    conf.setLong(
+        OMConfigKeys.OZONE_OM_RATIS_SNAPSHOT_AUTO_TRIGGER_THRESHOLD_KEY,
+        SNAPSHOT_THRESHOLD);
+    conf.setInt(OMConfigKeys.OZONE_OM_RATIS_LOG_PURGE_GAP, LOG_PURGE_GAP);
+    cluster = (MiniOzoneHAClusterImpl) MiniOzoneCluster.newHABuilder(conf)
+        .setClusterId(clusterId)
+        .setScmId(scmId)
+        .setOMServiceId("om-service-test1")
+        .setNumOfOzoneManagers(numOfOMs)
+        .setNumOfActiveOMs(2)
+        .build();
+    cluster.waitForClusterToBeReady();
+    objectStore = OzoneClientFactory.getRpcClient(conf).getObjectStore();
+  }
+
+  /**
+   * Shutdown MiniDFSCluster.
+   */
+  @After
+  public void shutdown() {
+    if (cluster != null) {
+      cluster.shutdown();
+    }
+  }
+
+  @Test
+  public void testInstallSnapshot() throws Exception {
+    // Get the leader OM
+    String leaderOMNodeId = objectStore.getClientProxy().getOMProxyProvider()
+        .getCurrentProxyOMNodeId();
+    OzoneManager leaderOM = cluster.getOzoneManager(leaderOMNodeId);
+    OzoneManagerRatisServer leaderRatisServer = leaderOM.getOmRatisServer();
+
+    // Find the inactive OM
+    String followerNodeId = leaderOM.getPeerNodes().get(0).getOMNodeId();
+    if (cluster.isOMActive(followerNodeId)) {
+      followerNodeId = leaderOM.getPeerNodes().get(1).getOMNodeId();
+    }
+    OzoneManager followerOM = cluster.getOzoneManager(followerNodeId);
+
+    // Do some transactions so that the log index increases
+    String userName = "user" + RandomStringUtils.randomNumeric(5);
+    String adminName = "admin" + RandomStringUtils.randomNumeric(5);
+    String volumeName = "volume" + RandomStringUtils.randomNumeric(5);
+    String bucketName = "bucket" + RandomStringUtils.randomNumeric(5);
+
+    VolumeArgs createVolumeArgs = VolumeArgs.newBuilder()
+        .setOwner(userName)
+        .setAdmin(adminName)
+        .build();
+
+    objectStore.createVolume(volumeName, createVolumeArgs);
+    OzoneVolume retVolumeinfo = objectStore.getVolume(volumeName);
+
+    retVolumeinfo.createBucket(bucketName);
+    OzoneBucket ozoneBucket = retVolumeinfo.getBucket(bucketName);
+
+    long leaderOMappliedLogIndex =
+        leaderRatisServer.getStateMachineLastAppliedIndex();
+    leaderOM.getOmRatisServer().getStateMachineLastAppliedIndex();
+
+    List<String> keys = new ArrayList<>();
+    while (leaderOMappliedLogIndex < 2000) {
+      keys.add(createKey(ozoneBucket));
+      leaderOMappliedLogIndex =
+          leaderRatisServer.getStateMachineLastAppliedIndex();
+    }
+
+    // Get the latest db checkpoint from the leader OM.
+    long leaderOMSnaphsotIndex = leaderOM.saveRatisSnapshot(true);
+    DBCheckpoint leaderDbCheckpoint =
+        leaderOM.getMetadataManager().getStore().getCheckpoint(false);
+
+    // Start the inactive OM
+    cluster.startInactiveOM(followerNodeId);
+
+    // The recently started OM should be lagging behind the leader OM.
+    long followerOMLastAppliedIndex =
+        followerOM.getOmRatisServer().getStateMachineLastAppliedIndex();
+    Assert.assertTrue(
+        followerOMLastAppliedIndex < leaderOMSnaphsotIndex);
+
+    // Install leader OM's db checkpoint on the lagging OM.
+    followerOM.getOmRatisServer().getOmStateMachine().pause();
+    followerOM.getMetadataManager().getStore().close();
+    followerOM.replaceOMDBWithCheckpoint(
+        leaderOMSnaphsotIndex, leaderDbCheckpoint.getCheckpointLocation());
+
+    // Reload the follower OM with new DB checkpoint from the leader OM.
+    followerOM.reloadOMState(leaderOMSnaphsotIndex);
+    followerOM.getOmRatisServer().getOmStateMachine().unpause(
+        leaderOMSnaphsotIndex);
+
+    // After the new checkpoint is loaded and state machine is unpaused, the
+    // follower OM lastAppliedIndex must match the snapshot index of the
+    // checkpoint.
+    followerOMLastAppliedIndex = followerOM.getOmRatisServer()
+        .getStateMachineLastAppliedIndex();
+    Assert.assertEquals(leaderOMSnaphsotIndex, followerOMLastAppliedIndex);
+
+    // Verify that the follower OM's DB contains the transactions which were
+    // made while it was inactive.
+    OMMetadataManager followerOMMetaMngr = followerOM.getMetadataManager();
+    Assert.assertNotNull(followerOMMetaMngr.getVolumeTable().get(
+        followerOMMetaMngr.getVolumeKey(volumeName)));
+    Assert.assertNotNull(followerOMMetaMngr.getBucketTable().get(
+        followerOMMetaMngr.getBucketKey(volumeName, bucketName)));
+    for (String key : keys) {
+      Assert.assertNotNull(followerOMMetaMngr.getKeyTable().get(
+          followerOMMetaMngr.getOzoneKey(volumeName, bucketName, key)));
+    }
+  }
+}

+ 6 - 1
hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/om/TestOzoneManagerHA.java

@@ -829,7 +829,11 @@ public class TestOzoneManagerHA {
 
   }
 
-  private void createKey(OzoneBucket ozoneBucket) throws IOException {
+  /**
+   * Create a key in the bucket.
+   * @return the key name.
+   */
+  static String createKey(OzoneBucket ozoneBucket) throws IOException {
     String keyName = "key" + RandomStringUtils.randomNumeric(5);
     String data = "data" + RandomStringUtils.randomNumeric(5);
     OzoneOutputStream ozoneOutputStream = ozoneBucket.createKey(keyName,
@@ -837,5 +841,6 @@ public class TestOzoneManagerHA {
         ReplicationFactor.ONE, new HashMap<>());
     ozoneOutputStream.write(data.getBytes(), 0, data.length());
     ozoneOutputStream.close();
+    return keyName;
   }
 }

+ 0 - 11
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManager.java

@@ -201,17 +201,6 @@ public interface KeyManager extends OzoneManagerFS, IOzoneAcl {
    */
   OmMultipartInfo initiateMultipartUpload(OmKeyArgs keyArgs) throws IOException;
 
-  /**
-   * Initiate multipart upload for the specified key.
-   *
-   * @param keyArgs
-   * @param multipartUploadID
-   * @return MultipartInfo
-   * @throws IOException
-   */
-  OmMultipartInfo applyInitiateMultipartUpload(OmKeyArgs keyArgs,
-      String multipartUploadID) throws IOException;
-
   /**
    * Commit Multipart upload part file.
    * @param omKeyArgs

+ 4 - 6
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/KeyManagerImpl.java

@@ -874,15 +874,13 @@ public class KeyManagerImpl implements KeyManager {
   @Override
   public OmMultipartInfo initiateMultipartUpload(OmKeyArgs omKeyArgs) throws
       IOException {
-    long time = Time.monotonicNowNanos();
-    String uploadID = UUID.randomUUID().toString() + "-" + time;
-    return applyInitiateMultipartUpload(omKeyArgs, uploadID);
+    Preconditions.checkNotNull(omKeyArgs);
+    String uploadID = UUID.randomUUID().toString() + "-" + UniqueId.next();
+    return createMultipartInfo(omKeyArgs, uploadID);
   }
 
-  public OmMultipartInfo applyInitiateMultipartUpload(OmKeyArgs keyArgs,
+  private OmMultipartInfo createMultipartInfo(OmKeyArgs keyArgs,
       String multipartUploadID) throws IOException {
-    Preconditions.checkNotNull(keyArgs);
-    Preconditions.checkNotNull(multipartUploadID);
     String volumeName = keyArgs.getVolumeName();
     String bucketName = keyArgs.getBucketName();
     String keyName = keyArgs.getKeyName();

+ 1 - 1
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMDBCheckpointServlet.java

@@ -126,7 +126,7 @@ public class OMDBCheckpointServlet extends HttpServlet {
         // ratis snapshot first. This step also included flushing the OM DB.
         // Hence, we can set flush to false.
         flush = false;
-        ratisSnapshotIndex = om.saveRatisSnapshot();
+        ratisSnapshotIndex = om.saveRatisSnapshot(true);
       } else {
         ratisSnapshotIndex = om.loadRatisSnapshotIndex();
       }

+ 19 - 3
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OMMetrics.java

@@ -129,6 +129,8 @@ public class OMMetrics {
 
   private @Metric MutableCounterLong numS3BucketCreates;
   private @Metric MutableCounterLong numS3BucketCreateFails;
+  private @Metric MutableCounterLong numS3BucketDeletes;
+  private @Metric MutableCounterLong numS3BucketDeleteFails;
 
 
   public OMMetrics() {
@@ -150,6 +152,17 @@ public class OMMetrics {
     numS3BucketCreateFails.incr();
   }
 
+  public void incNumS3BucketDeletes() {
+    numBucketOps.incr();
+    numS3BucketDeletes.incr();
+  }
+
+  public void incNumS3BucketDeleteFails() {
+    numBucketOps.incr();
+    numS3BucketDeleteFails.incr();
+  }
+
+
   public void incNumS3Buckets() {
     numS3Buckets.incr();
   }
@@ -183,15 +196,18 @@ public class OMMetrics {
   }
 
   public void setNumVolumes(long val) {
-    this.numVolumes.incr(val);
+    long oldVal = this.numVolumes.value();
+    this.numVolumes.incr(val - oldVal);
   }
 
   public void setNumBuckets(long val) {
-    this.numBuckets.incr(val);
+    long oldVal = this.numBuckets.value();
+    this.numBuckets.incr(val - oldVal);
   }
 
   public void setNumKeys(long val) {
-    this.numKeys.incr(val);
+    long oldVal = this.numKeys.value();
+    this.numKeys.incr(val- oldVal);
   }
 
   public long getNumVolumes() {

+ 283 - 98
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java

@@ -25,6 +25,7 @@ import com.google.common.base.Preconditions;
 import com.google.protobuf.BlockingService;
 
 import java.net.InetAddress;
+import java.nio.file.Path;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.KeyPair;
@@ -143,6 +144,10 @@ import org.apache.hadoop.util.KMSUtil;
 import org.apache.hadoop.util.ReflectionUtils;
 import org.apache.hadoop.util.ShutdownHookManager;
 import org.apache.hadoop.utils.RetriableTask;
+import org.apache.hadoop.utils.db.DBCheckpoint;
+import org.apache.hadoop.utils.db.DBStore;
+import org.apache.ratis.server.protocol.TermIndex;
+import org.apache.ratis.util.FileUtils;
 import org.apache.ratis.util.LifeCycle;
 import org.bouncycastle.pkcs.PKCS10CertificationRequest;
 import org.slf4j.Logger;
@@ -236,18 +241,20 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
   private RPC.Server omRpcServer;
   private InetSocketAddress omRpcAddress;
   private String omId;
-  private final OMMetadataManager metadataManager;
-  private final VolumeManager volumeManager;
-  private final BucketManager bucketManager;
-  private final KeyManager keyManager;
-  private final PrefixManagerImpl prefixManager;
+
+  private OMMetadataManager metadataManager;
+  private VolumeManager volumeManager;
+  private BucketManager bucketManager;
+  private KeyManager keyManager;
+  private PrefixManagerImpl prefixManager;
+  private S3BucketManager s3BucketManager;
+
   private final OMMetrics metrics;
   private OzoneManagerHttpServer httpServer;
   private final OMStorage omStorage;
   private final ScmBlockLocationProtocol scmBlockClient;
   private final StorageContainerLocationProtocol scmContainerClient;
   private ObjectName omInfoBeanName;
-  private final S3BucketManager s3BucketManager;
   private Timer metricsTimer;
   private ScheduleOMMetricsWriteTask scheduleOMMetricsWriteTask;
   private static final ObjectWriter WRITER =
@@ -258,7 +265,7 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
   private final Runnable shutdownHook;
   private final File omMetaDir;
   private final boolean isAclEnabled;
-  private final IAccessAuthorizer accessAuthorizer;
+  private IAccessAuthorizer accessAuthorizer;
   private JvmPauseMonitor jvmPauseMonitor;
   private final SecurityConfig secConfig;
   private S3SecretManager s3SecretManager;
@@ -308,12 +315,37 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
       throw new OMException("OM not initialized.",
           ResultCodes.OM_NOT_INITIALIZED);
     }
+
+    // Read configuration and set values.
+    ozAdmins = conf.getTrimmedStringCollection(OZONE_ADMINISTRATORS);
+    omMetaDir = OmUtils.getOmDbDir(configuration);
+    this.isAclEnabled = conf.getBoolean(OZONE_ACL_ENABLED,
+        OZONE_ACL_ENABLED_DEFAULT);
+    this.scmBlockSize = (long) conf.getStorageSize(OZONE_SCM_BLOCK_SIZE,
+        OZONE_SCM_BLOCK_SIZE_DEFAULT, StorageUnit.BYTES);
+    this.preallocateBlocksMax = conf.getInt(
+        OZONE_KEY_PREALLOCATION_BLOCKS_MAX,
+        OZONE_KEY_PREALLOCATION_BLOCKS_MAX_DEFAULT);
+    this.grpcBlockTokenEnabled = conf.getBoolean(HDDS_BLOCK_TOKEN_ENABLED,
+        HDDS_BLOCK_TOKEN_ENABLED_DEFAULT);
+    this.useRatisForReplication = conf.getBoolean(
+        DFS_CONTAINER_RATIS_ENABLED_KEY, DFS_CONTAINER_RATIS_ENABLED_DEFAULT);
+    // TODO: This is a temporary check. Once fully implemented, all OM state
+    //  change should go through Ratis - be it standalone (for non-HA) or
+    //  replicated (for HA).
+    isRatisEnabled = configuration.getBoolean(
+        OMConfigKeys.OZONE_OM_RATIS_ENABLE_KEY,
+        OMConfigKeys.OZONE_OM_RATIS_ENABLE_DEFAULT);
+
     // Load HA related configurations
     loadOMHAConfigs(configuration);
+    InetSocketAddress omNodeRpcAddr = omNodeDetails.getRpcAddress();
+    omRpcAddressTxt = new Text(omNodeDetails.getRpcAddressString());
 
     scmContainerClient = getScmContainerClient(configuration);
     // verifies that the SCM info in the OM Version file is correct.
     scmBlockClient = getScmBlockClient(configuration);
+    this.scmClient = new ScmClient(scmBlockClient, scmContainerClient);
 
     // For testing purpose only, not hit scm from om as Hadoop UGI can't login
     // two principals in the same JVM.
@@ -329,16 +361,32 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
     RPC.setProtocolEngine(configuration, OzoneManagerProtocolPB.class,
         ProtobufRpcEngine.class);
 
-    metadataManager = new OmMetadataManagerImpl(configuration);
+    secConfig = new SecurityConfig(configuration);
+    // Create the KMS Key Provider
+    try {
+      kmsProvider = createKeyProviderExt(configuration);
+    } catch (IOException ioe) {
+      kmsProvider = null;
+      LOG.error("Fail to create Key Provider");
+    }
+    if (secConfig.isSecurityEnabled()) {
+      omComponent = OM_DAEMON + "-" + omId;
+      if(omStorage.getOmCertSerialId() == null) {
+        throw new RuntimeException("OzoneManager started in secure mode but " +
+            "doesn't have SCM signed certificate.");
+      }
+      certClient = new OMCertificateClient(new SecurityConfig(conf),
+          omStorage.getOmCertSerialId());
+    }
+    if (secConfig.isBlockTokenEnabled()) {
+      blockTokenMgr = createBlockTokenSecretManager(configuration);
+    }
+
+    instantiateServices();
+
+    initializeRatisServer();
+    initializeRatisClient();
 
-    // This is a temporary check. Once fully implemented, all OM state change
-    // should go through Ratis - be it standalone (for non-HA) or replicated
-    // (for HA).
-    isRatisEnabled = configuration.getBoolean(
-        OMConfigKeys.OZONE_OM_RATIS_ENABLE_KEY,
-        OMConfigKeys.OZONE_OM_RATIS_ENABLE_DEFAULT);
-    startRatisServer();
-    startRatisClient();
     if (isRatisEnabled) {
       // Create Ratis storage dir
       String omRatisDirectory = OmUtils.getOMRatisDirectory(configuration);
@@ -361,59 +409,44 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
         OM_RATIS_SNAPSHOT_INDEX);
     this.snapshotIndex = loadRatisSnapshotIndex();
 
-    InetSocketAddress omNodeRpcAddr = omNodeDetails.getRpcAddress();
-    omRpcAddressTxt = new Text(omNodeDetails.getRpcAddressString());
-    secConfig = new SecurityConfig(configuration);
-    volumeManager = new VolumeManagerImpl(metadataManager, configuration);
+    metrics = OMMetrics.create();
 
-    // Create the KMS Key Provider
-    try {
-      kmsProvider = createKeyProviderExt(configuration);
-    } catch (IOException ioe) {
-      kmsProvider = null;
-      LOG.error("Fail to create Key Provider");
-    }
+    // Start Om Rpc Server.
+    omRpcServer = getRpcServer(conf);
+    omRpcAddress = updateRPCListenAddress(configuration,
+        OZONE_OM_ADDRESS_KEY, omNodeRpcAddr, omRpcServer);
+
+    shutdownHook = () -> {
+      saveOmMetrics();
+    };
+    ShutdownHookManager.get().addShutdownHook(shutdownHook,
+        SHUTDOWN_HOOK_PRIORITY);
+  }
 
+  /**
+   * Instantiate services which are dependent on the OM DB state.
+   * When OM state is reloaded, these services are re-initialized with the
+   * new OM state.
+   */
+  private void instantiateServices() throws IOException {
+
+    metadataManager = new OmMetadataManagerImpl(configuration);
+    volumeManager = new VolumeManagerImpl(metadataManager, configuration);
     bucketManager = new BucketManagerImpl(metadataManager, getKmsProvider(),
         isRatisEnabled);
-    metrics = OMMetrics.create();
-
     s3BucketManager = new S3BucketManagerImpl(configuration, metadataManager,
         volumeManager, bucketManager);
     if (secConfig.isSecurityEnabled()) {
-      omComponent = OM_DAEMON + "-" + omId;
-      if(omStorage.getOmCertSerialId() == null) {
-        throw new RuntimeException("OzoneManager started in secure mode but " +
-            "doesn't have SCM signed certificate.");
-      }
-      certClient = new OMCertificateClient(new SecurityConfig(conf),
-          omStorage.getOmCertSerialId());
       s3SecretManager = new S3SecretManagerImpl(configuration, metadataManager);
       delegationTokenMgr = createDelegationTokenSecretManager(configuration);
     }
-    if (secConfig.isBlockTokenEnabled()) {
-      blockTokenMgr = createBlockTokenSecretManager(configuration);
-    }
-
-    omRpcServer = getRpcServer(conf);
-    omRpcAddress = updateRPCListenAddress(configuration,
-        OZONE_OM_ADDRESS_KEY, omNodeRpcAddr, omRpcServer);
-
-    this.scmClient = new ScmClient(scmBlockClient, scmContainerClient);
 
     prefixManager = new PrefixManagerImpl(metadataManager);
     keyManager = new KeyManagerImpl(this, scmClient, configuration,
         omStorage.getOmId());
 
-    shutdownHook = () -> {
-      saveOmMetrics();
-    };
-    ShutdownHookManager.get().addShutdownHook(shutdownHook,
-        SHUTDOWN_HOOK_PRIORITY);
-    isAclEnabled = conf.getBoolean(OZONE_ACL_ENABLED,
-            OZONE_ACL_ENABLED_DEFAULT);
     if (isAclEnabled) {
-      accessAuthorizer = getACLAuthorizerInstance(conf);
+      accessAuthorizer = getACLAuthorizerInstance(configuration);
       if (accessAuthorizer instanceof OzoneNativeAuthorizer) {
         OzoneNativeAuthorizer authorizer =
             (OzoneNativeAuthorizer) accessAuthorizer;
@@ -425,17 +458,6 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
     } else {
       accessAuthorizer = null;
     }
-    ozAdmins = conf.getTrimmedStringCollection(OZONE_ADMINISTRATORS);
-    omMetaDir = OmUtils.getOmDbDir(configuration);
-    this.scmBlockSize = (long) conf.getStorageSize(OZONE_SCM_BLOCK_SIZE,
-        OZONE_SCM_BLOCK_SIZE_DEFAULT, StorageUnit.BYTES);
-    this.preallocateBlocksMax = conf.getInt(
-        OZONE_KEY_PREALLOCATION_BLOCKS_MAX,
-        OZONE_KEY_PREALLOCATION_BLOCKS_MAX_DEFAULT);
-    this.grpcBlockTokenEnabled = conf.getBoolean(HDDS_BLOCK_TOKEN_ENABLED,
-        HDDS_BLOCK_TOKEN_ENABLED_DEFAULT);
-    this.useRatisForReplication = conf.getBoolean(
-        DFS_CONTAINER_RATIS_ENABLED_KEY, DFS_CONTAINER_RATIS_ENABLED_DEFAULT);
   }
 
   /**
@@ -1235,6 +1257,14 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
 
     DefaultMetricsSystem.initialize("OzoneManager");
 
+    // Start Ratis services
+    if (omRatisServer != null) {
+      omRatisServer.start();
+    }
+    if (omRatisClient != null) {
+      omRatisClient.connect();
+    }
+
     metadataManager.start(configuration);
     startSecretManagerIfNecessary();
 
@@ -1305,8 +1335,14 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
     omRpcServer.start();
     isOmRpcServerRunning = true;
 
-    startRatisServer();
-    startRatisClient();
+    initializeRatisServer();
+    if (omRatisServer != null) {
+      omRatisServer.start();
+    }
+    initializeRatisClient();
+    if (omRatisClient != null) {
+      omRatisClient.connect();
+    }
 
     try {
       httpServer = new OzoneManagerHttpServer(configuration, this);
@@ -1353,15 +1389,13 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
   /**
    * Creates an instance of ratis server.
    */
-  private void startRatisServer() throws IOException {
+  private void initializeRatisServer() throws IOException {
     if (isRatisEnabled) {
       if (omRatisServer == null) {
         omRatisServer = OzoneManagerRatisServer.newOMRatisServer(
             configuration, this, omNodeDetails, peerNodes);
       }
-      omRatisServer.start();
-
-      LOG.info("OzoneManager Ratis server started at port {}",
+      LOG.info("OzoneManager Ratis server initialized at port {}",
           omRatisServer.getServerPort());
     } else {
       omRatisServer = null;
@@ -1371,14 +1405,13 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
   /**
    * Creates an instance of ratis client.
    */
-  private void startRatisClient() throws IOException {
+  private void initializeRatisClient() throws IOException {
     if (isRatisEnabled) {
       if (omRatisClient == null) {
         omRatisClient = OzoneManagerRatisClient.newOzoneManagerRatisClient(
             omNodeDetails.getOMNodeId(), omRatisServer.getRaftGroup(),
             configuration);
       }
-      omRatisClient.connect();
     } else {
       omRatisClient = null;
     }
@@ -1398,11 +1431,13 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
   }
 
   @Override
-  public long saveRatisSnapshot() throws IOException {
+  public long saveRatisSnapshot(boolean flush) throws IOException {
     snapshotIndex = omRatisServer.getStateMachineLastAppliedIndex();
 
-    // Flush the OM state to disk
-    getMetadataManager().getStore().flush();
+    if (flush) {
+      // Flush the OM state to disk
+      metadataManager.getStore().flush();
+    }
 
     PersistentLongFile.writeFile(ratisSnapshotFile, snapshotIndex);
     LOG.info("Saved Ratis Snapshot on the OM with snapshotIndex {}",
@@ -2697,29 +2732,6 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
       }
     }
   }
-
-
-  @Override
-  public OmMultipartInfo applyInitiateMultipartUpload(OmKeyArgs keyArgs,
-      String multipartUploadID) throws IOException {
-    OmMultipartInfo multipartInfo;
-    metrics.incNumInitiateMultipartUploads();
-    try {
-      multipartInfo = keyManager.applyInitiateMultipartUpload(keyArgs,
-          multipartUploadID);
-      AUDIT.logWriteSuccess(buildAuditMessageForSuccess(
-          OMAction.INITIATE_MULTIPART_UPLOAD, (keyArgs == null) ? null :
-              keyArgs.toAuditMap()));
-    } catch (IOException ex) {
-      AUDIT.logWriteFailure(buildAuditMessageForFailure(
-          OMAction.INITIATE_MULTIPART_UPLOAD,
-          (keyArgs == null) ? null : keyArgs.toAuditMap(), ex));
-      metrics.incNumInitiateMultipartUploadFails();
-      throw ex;
-    }
-    return multipartInfo;
-  }
-
   @Override
   public OmMultipartInfo initiateMultipartUpload(OmKeyArgs keyArgs) throws
       IOException {
@@ -3091,6 +3103,179 @@ public final class OzoneManager extends ServiceRuntimeInfoImpl
     }
   }
 
+  /**
+   * Download and install latest checkpoint from leader OM.
+   * If the download checkpoints snapshot index is greater than this OM's
+   * last applied transaction index, then re-initialize the OM state via this
+   * checkpoint. Before re-initializing OM state, the OM Ratis server should
+   * be stopped so that no new transactions can be applied.
+   * @param leaderId peerNodeID of the leader OM
+   * @return If checkpoint is installed, return the corresponding termIndex.
+   * Otherwise, return null.
+   */
+  public TermIndex installSnapshot(String leaderId) {
+    if (omSnapshotProvider == null) {
+      LOG.error("OM Snapshot Provider is not configured as there are no peer " +
+          "nodes.");
+      return null;
+    }
+
+    DBCheckpoint omDBcheckpoint = getDBCheckpointFromLeader(leaderId);
+    Path newDBlocation = omDBcheckpoint.getCheckpointLocation();
+
+    // Check if current ratis log index is smaller than the downloaded
+    // snapshot index. If yes, proceed by stopping the ratis server so that
+    // the OM state can be re-initialized. If no, then do not proceed with
+    // installSnapshot.
+    long lastAppliedIndex = omRatisServer.getStateMachineLastAppliedIndex();
+    long checkpointSnapshotIndex = omDBcheckpoint.getRatisSnapshotIndex();
+    if (checkpointSnapshotIndex <= lastAppliedIndex) {
+      LOG.error("Failed to install checkpoint from OM leader: {}. The last " +
+          "applied index: {} is greater than or equal to the checkpoint's " +
+          "snapshot index: {}. Deleting the downloaded checkpoint {}", leaderId,
+          lastAppliedIndex, checkpointSnapshotIndex,
+          newDBlocation);
+      try {
+        FileUtils.deleteFully(newDBlocation);
+      } catch (IOException e) {
+        LOG.error("Failed to fully delete the downloaded DB checkpoint {} " +
+            "from OM leader {}.", newDBlocation,
+            leaderId, e);
+      }
+      return null;
+    }
+
+    // Pause the State Machine so that no new transactions can be applied.
+    // This action also clears the OM Double Buffer so that if there are any
+    // pending transactions in the buffer, they are discarded.
+    // TODO: The Ratis server should also be paused here. This is required
+    //  because a leader election might happen while the snapshot
+    //  installation is in progress and the new leader might start sending
+    //  append log entries to the ratis server.
+    omRatisServer.getOmStateMachine().pause();
+
+    File dbBackup;
+    try {
+      dbBackup = replaceOMDBWithCheckpoint(lastAppliedIndex, newDBlocation);
+    } catch (Exception e) {
+      LOG.error("OM DB checkpoint replacement with new downloaded checkpoint " +
+          "failed.", e);
+      return null;
+    }
+
+    // Reload the OM DB store with the new checkpoint.
+    // Restart (unpause) the state machine and update its last applied index
+    // to the installed checkpoint's snapshot index.
+    try {
+      reloadOMState(checkpointSnapshotIndex);
+      omRatisServer.getOmStateMachine().unpause(checkpointSnapshotIndex);
+    } catch (IOException e) {
+      LOG.error("Failed to reload OM state with new DB checkpoint.", e);
+      return null;
+    }
+
+    // Delete the backup DB
+    try {
+      FileUtils.deleteFully(dbBackup);
+    } catch (IOException e) {
+      LOG.error("Failed to delete the backup of the original DB {}", dbBackup);
+    }
+
+    // TODO: We should only return the snpashotIndex to the leader.
+    //  Should be fixed after RATIS-586
+    TermIndex newTermIndex = TermIndex.newTermIndex(0,
+        checkpointSnapshotIndex);
+
+    return newTermIndex;
+  }
+
+  /**
+   * Download the latest OM DB checkpoint from the leader OM.
+   * @param leaderId OMNodeID of the leader OM node.
+   * @return latest DB checkpoint from leader OM.
+   */
+  private DBCheckpoint getDBCheckpointFromLeader(String leaderId) {
+    LOG.info("Downloading checkpoint from leader OM {} and reloading state " +
+        "from the checkpoint.", leaderId);
+
+    try {
+      return omSnapshotProvider.getOzoneManagerDBSnapshot(leaderId);
+    } catch (IOException e) {
+      LOG.error("Failed to download checkpoint from OM leader {}", leaderId, e);
+    }
+    return null;
+  }
+
+  /**
+   * Replace the current OM DB with the new DB checkpoint.
+   * @param lastAppliedIndex the last applied index in the current OM DB.
+   * @param checkpointPath path to the new DB checkpoint
+   * @return location of the backup of the original DB
+   * @throws Exception
+   */
+  File replaceOMDBWithCheckpoint(long lastAppliedIndex, Path checkpointPath)
+      throws Exception {
+    // Stop the DB first
+    DBStore store = metadataManager.getStore();
+    store.close();
+
+    // Take a backup of the current DB
+    File db = store.getDbLocation();
+    String dbBackupName = OzoneConsts.OM_DB_BACKUP_PREFIX +
+        lastAppliedIndex + "_" + System.currentTimeMillis();
+    File dbBackup = new File(db.getParentFile(), dbBackupName);
+
+    try {
+      Files.move(db.toPath(), dbBackup.toPath());
+    } catch (IOException e) {
+      LOG.error("Failed to create a backup of the current DB. Aborting " +
+          "snapshot installation.");
+      throw e;
+    }
+
+    // Move the new DB checkpoint into the om metadata dir
+    try {
+      Files.move(checkpointPath, db.toPath());
+    } catch (IOException e) {
+      LOG.error("Failed to move downloaded DB checkpoint {} to metadata " +
+          "directory {}. Resetting to original DB.", checkpointPath,
+          db.toPath());
+      Files.move(dbBackup.toPath(), db.toPath());
+      throw e;
+    }
+    return dbBackup;
+  }
+
+  /**
+   * Re-instantiate MetadataManager with new DB checkpoint.
+   * All the classes which use/ store MetadataManager should also be updated
+   * with the new MetadataManager instance.
+   */
+  void reloadOMState(long newSnapshotIndex) throws IOException {
+
+    instantiateServices();
+
+    // Restart required services
+    metadataManager.start(configuration);
+    keyManager.start(configuration);
+
+    // Set metrics and start metrics back ground thread
+    metrics.setNumVolumes(metadataManager.countRowsInTable(metadataManager
+        .getVolumeTable()));
+    metrics.setNumBuckets(metadataManager.countRowsInTable(metadataManager
+        .getBucketTable()));
+
+    // Delete the omMetrics file if it exists and save the a new metrics file
+    // with new data
+    Files.deleteIfExists(getMetricsStorageFile().toPath());
+    saveOmMetrics();
+
+    // Update OM snapshot index with the new snapshot index (from the new OM
+    // DB state) and save the snapshot index to disk
+    this.snapshotIndex = newSnapshotIndex;
+    saveRatisSnapshot(false);
+  }
+
   public static  Logger getLogger() {
     return LOG;
   }

+ 3 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerDoubleBuffer.java

@@ -176,6 +176,9 @@ public class OzoneManagerDoubleBuffer {
     omMetadataManager.getKeyTable().cleanupCache(lastRatisTransactionIndex);
     omMetadataManager.getDeletedTable().cleanupCache(lastRatisTransactionIndex);
     omMetadataManager.getS3Table().cleanupCache(lastRatisTransactionIndex);
+    omMetadataManager.getMultipartInfoTable().cleanupCache(
+        lastRatisTransactionIndex);
+
   }
 
   /**

+ 13 - 2
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerRatisServer.java

@@ -308,10 +308,15 @@ public final class OzoneManagerRatisServer {
   }
 
   /**
-   * Returns OzoneManager StateMachine.
+   * Initializes and returns OzoneManager StateMachine.
    */
   private OzoneManagerStateMachine getStateMachine() {
-    return  new OzoneManagerStateMachine(this);
+    return new OzoneManagerStateMachine(this);
+  }
+
+  @VisibleForTesting
+  public OzoneManagerStateMachine getOmStateMachine() {
+    return omStateMachine;
   }
 
   public OzoneManager getOzoneManager() {
@@ -387,6 +392,12 @@ public final class OzoneManagerRatisServer {
         SizeInBytes.valueOf(logAppenderQueueByteLimit));
     RaftServerConfigKeys.Log.setPreallocatedSize(properties,
         SizeInBytes.valueOf(raftSegmentPreallocatedSize));
+    RaftServerConfigKeys.Log.Appender.setInstallSnapshotEnabled(properties,
+        false);
+    final int logPurgeGap = conf.getInt(
+        OMConfigKeys.OZONE_OM_RATIS_LOG_PURGE_GAP,
+        OMConfigKeys.OZONE_OM_RATIS_LOG_PURGE_GAP_DEFAULT);
+    RaftServerConfigKeys.Log.setPurgeGap(properties, logPurgeGap);
 
     // For grpc set the maximum message size
     // TODO: calculate the optimal max message size

+ 71 - 59
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerStateMachine.java

@@ -23,7 +23,6 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.protobuf.ServiceException;
 import java.io.IOException;
 import java.util.Collection;
-import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ThreadFactory;
@@ -32,28 +31,28 @@ import java.util.concurrent.TimeUnit;
 import org.apache.hadoop.ozone.container.common.transport.server.ratis
     .ContainerStateMachine;
 import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.OMRatisHelper;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
-    .MultipartInfoApplyInitiateRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
     .OMRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
     .OMResponse;
 import org.apache.hadoop.ozone.protocolPB.OzoneManagerHARequestHandler;
 import org.apache.hadoop.ozone.protocolPB.OzoneManagerHARequestHandlerImpl;
-import org.apache.hadoop.util.Time;
 import org.apache.hadoop.util.concurrent.HadoopExecutors;
 import org.apache.ratis.proto.RaftProtos;
 import org.apache.ratis.protocol.Message;
 import org.apache.ratis.protocol.RaftClientRequest;
 import org.apache.ratis.protocol.RaftGroupId;
+import org.apache.ratis.protocol.RaftPeerId;
 import org.apache.ratis.server.RaftServer;
+import org.apache.ratis.server.protocol.TermIndex;
 import org.apache.ratis.server.storage.RaftStorage;
 import org.apache.ratis.statemachine.TransactionContext;
 import org.apache.ratis.statemachine.impl.BaseStateMachine;
 import org.apache.ratis.statemachine.impl.SimpleStateMachineStorage;
 import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
+import org.apache.ratis.util.LifeCycle;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -73,8 +72,9 @@ public class OzoneManagerStateMachine extends BaseStateMachine {
   private OzoneManagerHARequestHandler handler;
   private RaftGroupId raftGroupId;
   private long lastAppliedIndex = 0;
-  private final OzoneManagerDoubleBuffer ozoneManagerDoubleBuffer;
+  private OzoneManagerDoubleBuffer ozoneManagerDoubleBuffer;
   private final ExecutorService executorService;
+  private final ExecutorService installSnapshotExecutor;
 
   public OzoneManagerStateMachine(OzoneManagerRatisServer ratisServer) {
     this.omRatisServer = ratisServer;
@@ -87,19 +87,20 @@ public class OzoneManagerStateMachine extends BaseStateMachine {
     ThreadFactory build = new ThreadFactoryBuilder().setDaemon(true)
         .setNameFormat("OM StateMachine ApplyTransaction Thread - %d").build();
     this.executorService = HadoopExecutors.newSingleThreadExecutor(build);
+    this.installSnapshotExecutor = HadoopExecutors.newSingleThreadExecutor();
   }
 
   /**
    * Initializes the State Machine with the given server, group and storage.
-   * TODO: Load the latest snapshot from the file system.
    */
   @Override
-  public void initialize(
-      RaftServer server, RaftGroupId id, RaftStorage raftStorage)
-      throws IOException {
-    super.initialize(server, id, raftStorage);
-    this.raftGroupId = id;
-    storage.init(raftStorage);
+  public void initialize(RaftServer server, RaftGroupId id,
+      RaftStorage raftStorage) throws IOException {
+    lifeCycle.startAndTransition(() -> {
+      super.initialize(server, id, raftStorage);
+      this.raftGroupId = id;
+      storage.init(raftStorage);
+    });
   }
 
   /**
@@ -190,6 +191,27 @@ public class OzoneManagerStateMachine extends BaseStateMachine {
     }
   }
 
+  @Override
+  public void pause() {
+    lifeCycle.transition(LifeCycle.State.PAUSING);
+    lifeCycle.transition(LifeCycle.State.PAUSED);
+    ozoneManagerDoubleBuffer.stop();
+  }
+
+  /**
+   * Unpause the StateMachine, re-initialize the DoubleBuffer and update the
+   * lastAppliedIndex. This should be done after uploading new state to the
+   * StateMachine.
+   */
+  public void unpause(long newLastAppliedSnaphsotIndex) {
+    lifeCycle.startAndTransition(() -> {
+      this.ozoneManagerDoubleBuffer =
+          new OzoneManagerDoubleBuffer(ozoneManager.getMetadataManager(),
+              this::updateLastAppliedIndex);
+      this.updateLastAppliedIndex(newLastAppliedSnaphsotIndex);
+    });
+  }
+
   /**
    * Take OM Ratis snapshot. Write the snapshot index to file. Snapshot index
    * is the log index corresponding to the last applied transaction on the OM
@@ -202,11 +224,44 @@ public class OzoneManagerStateMachine extends BaseStateMachine {
   public long takeSnapshot() throws IOException {
     LOG.info("Saving Ratis snapshot on the OM.");
     if (ozoneManager != null) {
-      return ozoneManager.saveRatisSnapshot();
+      return ozoneManager.saveRatisSnapshot(true);
     }
     return 0;
   }
 
+  /**
+   * Leader OM has purged entries from its log. To catch up, OM must download
+   * the latest checkpoint from the leader OM and install it.
+   * @param roleInfoProto the leader node information
+   * @param firstTermIndexInLog TermIndex of the first append entry available
+   *                           in the Leader's log.
+   * @return the last term index included in the installed snapshot.
+   */
+  @Override
+  public CompletableFuture<TermIndex> notifyInstallSnapshotFromLeader(
+      RaftProtos.RoleInfoProto roleInfoProto, TermIndex firstTermIndexInLog) {
+
+    String leaderNodeId = RaftPeerId.valueOf(roleInfoProto.getSelf().getId())
+        .toString();
+
+    LOG.info("Received install snapshot notificaiton form OM leader: {} with " +
+            "term index: {}", leaderNodeId, firstTermIndexInLog);
+
+    if (!roleInfoProto.getRole().equals(RaftProtos.RaftPeerRole.LEADER)) {
+      // A non-leader Ratis server should not send this notification.
+      LOG.error("Received Install Snapshot notification from non-leader OM " +
+          "node: {}. Ignoring the notification.", leaderNodeId);
+      return completeExceptionally(new OMException("Received notification to " +
+          "install snaphost from non-leader OM node",
+          OMException.ResultCodes.RATIS_ERROR));
+    }
+
+    CompletableFuture<TermIndex> future = CompletableFuture.supplyAsync(
+        () -> ozoneManager.installSnapshot(leaderNodeId),
+        installSnapshotExecutor);
+    return future;
+  }
+
   /**
    * Notifies the state machine that the raft peer is no longer leader.
    */
@@ -225,53 +280,11 @@ public class OzoneManagerStateMachine extends BaseStateMachine {
   private TransactionContext handleStartTransactionRequests(
       RaftClientRequest raftClientRequest, OMRequest omRequest) {
 
-    switch (omRequest.getCmdType()) {
-    case InitiateMultiPartUpload:
-      return handleInitiateMultipartUpload(raftClientRequest, omRequest);
-    default:
-      return TransactionContext.newBuilder()
-          .setClientRequest(raftClientRequest)
-          .setStateMachine(this)
-          .setServerRole(RaftProtos.RaftPeerRole.LEADER)
-          .setLogData(raftClientRequest.getMessage().getContent())
-          .build();
-    }
-  }
-
-  private TransactionContext handleInitiateMultipartUpload(
-      RaftClientRequest raftClientRequest, OMRequest omRequest) {
-
-    // Generate a multipart uploadID, and create a new request.
-    // When applyTransaction happen's all OM's use the same multipartUploadID
-    // for the key.
-
-    long time = Time.monotonicNowNanos();
-    String multipartUploadID = UUID.randomUUID().toString() + "-" + time;
-
-    MultipartInfoApplyInitiateRequest multipartInfoApplyInitiateRequest =
-        MultipartInfoApplyInitiateRequest.newBuilder()
-            .setKeyArgs(omRequest.getInitiateMultiPartUploadRequest()
-                .getKeyArgs()).setMultipartUploadID(multipartUploadID).build();
-
-    OMRequest.Builder newOmRequest =
-        OMRequest.newBuilder().setCmdType(
-            OzoneManagerProtocolProtos.Type.ApplyInitiateMultiPartUpload)
-            .setInitiateMultiPartUploadApplyRequest(
-                multipartInfoApplyInitiateRequest)
-            .setClientId(omRequest.getClientId());
-
-    if (omRequest.hasTraceID()) {
-      newOmRequest.setTraceID(omRequest.getTraceID());
-    }
-
-    ByteString messageContent =
-        ByteString.copyFrom(newOmRequest.build().toByteArray());
-
     return TransactionContext.newBuilder()
         .setClientRequest(raftClientRequest)
         .setStateMachine(this)
         .setServerRole(RaftProtos.RaftPeerRole.LEADER)
-        .setLogData(messageContent)
+        .setLogData(raftClientRequest.getMessage().getContent())
         .build();
   }
 
@@ -323,10 +336,9 @@ public class OzoneManagerStateMachine extends BaseStateMachine {
     this.raftGroupId = raftGroupId;
   }
 
-
   public void stop() {
     ozoneManagerDoubleBuffer.stop();
     HadoopExecutors.shutdown(executorService, LOG, 5, TimeUnit.SECONDS);
+    HadoopExecutors.shutdown(installSnapshotExecutor, LOG, 5, TimeUnit.SECONDS);
   }
-
 }

+ 12 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/utils/OzoneManagerRatisUtils.java

@@ -32,6 +32,10 @@ import org.apache.hadoop.ozone.om.request.key.OMKeyDeleteRequest;
 import org.apache.hadoop.ozone.om.request.key.OMKeyPurgeRequest;
 import org.apache.hadoop.ozone.om.request.key.OMKeyRenameRequest;
 import org.apache.hadoop.ozone.om.request.s3.bucket.S3BucketCreateRequest;
+import org.apache.hadoop.ozone.om.request.s3.bucket.S3BucketDeleteRequest;
+import org.apache.hadoop.ozone.om.request.s3.multipart.S3InitiateMultipartUploadRequest;
+import org.apache.hadoop.ozone.om.request.s3.multipart.S3MultipartUploadAbortRequest;
+import org.apache.hadoop.ozone.om.request.s3.multipart.S3MultipartUploadCommitPartRequest;
 import org.apache.hadoop.ozone.om.request.volume.OMVolumeCreateRequest;
 import org.apache.hadoop.ozone.om.request.volume.OMVolumeDeleteRequest;
 import org.apache.hadoop.ozone.om.request.volume.OMVolumeSetOwnerRequest;
@@ -102,6 +106,14 @@ public final class OzoneManagerRatisUtils {
       return new OMKeyPurgeRequest(omRequest);
     case CreateS3Bucket:
       return new S3BucketCreateRequest(omRequest);
+    case DeleteS3Bucket:
+      return new S3BucketDeleteRequest(omRequest);
+    case InitiateMultiPartUpload:
+      return new S3InitiateMultipartUploadRequest(omRequest);
+    case CommitMultiPartUpload:
+      return new S3MultipartUploadCommitPartRequest(omRequest);
+    case AbortMultiPartUpload:
+      return new S3MultipartUploadAbortRequest(omRequest);
     default:
       // TODO: will update once all request types are implemented.
       return null;

+ 193 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/bucket/S3BucketDeleteRequest.java

@@ -0,0 +1,193 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.bucket;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.common.base.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.audit.OMAction;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OMMetrics;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.request.volume.OMVolumeRequest;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.response.s3.bucket.S3BucketDeleteResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .S3DeleteBucketRequest;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.apache.hadoop.utils.db.cache.CacheKey;
+import org.apache.hadoop.utils.db.cache.CacheValue;
+
+import static org.apache.hadoop.ozone.OzoneConsts.S3_BUCKET_MAX_LENGTH;
+import static org.apache.hadoop.ozone.OzoneConsts.S3_BUCKET_MIN_LENGTH;
+import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK;
+import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.S3_BUCKET_LOCK;
+
+/**
+ * Handle Create S3Bucket request.
+ */
+public class S3BucketDeleteRequest extends OMVolumeRequest {
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(S3BucketDeleteRequest.class);
+
+  public S3BucketDeleteRequest(OMRequest omRequest) {
+    super(omRequest);
+  }
+
+  public OMRequest preExecute(OzoneManager ozoneManager) throws IOException {
+    S3DeleteBucketRequest s3DeleteBucketRequest =
+        getOmRequest().getDeleteS3BucketRequest();
+
+    // TODO: Do we need to enforce the bucket rules in this code path?
+    // https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html
+
+    // For now only checked the length.
+    int bucketLength = s3DeleteBucketRequest.getS3BucketName().length();
+    if (bucketLength < S3_BUCKET_MIN_LENGTH ||
+        bucketLength >= S3_BUCKET_MAX_LENGTH) {
+      throw new OMException("S3BucketName must be at least 3 and not more " +
+          "than 63 characters long",
+          OMException.ResultCodes.S3_BUCKET_INVALID_LENGTH);
+    }
+
+    return getOmRequest().toBuilder().setUserInfo(getUserInfo()).build();
+
+  }
+
+  @Override
+  public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
+      long transactionLogIndex) {
+    S3DeleteBucketRequest s3DeleteBucketRequest =
+        getOmRequest().getDeleteS3BucketRequest();
+
+    String s3BucketName = s3DeleteBucketRequest.getS3BucketName();
+
+    OMResponse.Builder omResponse = OMResponse.newBuilder().setCmdType(
+        OzoneManagerProtocolProtos.Type.DeleteS3Bucket).setStatus(
+        OzoneManagerProtocolProtos.Status.OK).setSuccess(true);
+
+    OMMetrics omMetrics = ozoneManager.getMetrics();
+    omMetrics.incNumS3BucketDeletes();
+    IOException exception = null;
+    boolean acquiredS3Lock = false;
+    boolean acquiredBucketLock = false;
+    String volumeName = null;
+    OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
+    try {
+      // check Acl
+      if (ozoneManager.getAclsEnabled()) {
+        checkAcls(ozoneManager, OzoneObj.ResourceType.BUCKET,
+            OzoneObj.StoreType.S3, IAccessAuthorizer.ACLType.DELETE, null,
+            s3BucketName, null);
+      }
+
+      acquiredS3Lock = omMetadataManager.getLock().acquireLock(S3_BUCKET_LOCK,
+          s3BucketName);
+
+      String s3Mapping = omMetadataManager.getS3Table().get(s3BucketName);
+
+      if (s3Mapping == null) {
+        throw new OMException("S3Bucket " + s3BucketName + " not found",
+            OMException.ResultCodes.S3_BUCKET_NOT_FOUND);
+      } else {
+        volumeName = getOzoneVolumeName(s3Mapping);
+
+        acquiredBucketLock =
+            omMetadataManager.getLock().acquireLock(BUCKET_LOCK, volumeName,
+                s3BucketName);
+
+        String bucketKey = omMetadataManager.getBucketKey(volumeName,
+            s3BucketName);
+
+        // Update bucket table cache and s3 table cache.
+        omMetadataManager.getBucketTable().addCacheEntry(
+            new CacheKey<>(bucketKey),
+            new CacheValue<>(Optional.absent(), transactionLogIndex));
+        omMetadataManager.getS3Table().addCacheEntry(
+            new CacheKey<>(s3BucketName),
+            new CacheValue<>(Optional.absent(), transactionLogIndex));
+      }
+    } catch (IOException ex) {
+      exception = ex;
+    } finally {
+      if (acquiredBucketLock) {
+        omMetadataManager.getLock().releaseLock(BUCKET_LOCK, volumeName,
+            s3BucketName);
+      }
+      if (acquiredS3Lock) {
+        omMetadataManager.getLock().releaseLock(S3_BUCKET_LOCK, s3BucketName);
+      }
+    }
+
+    // Performing audit logging outside of the lock.
+    auditLog(ozoneManager.getAuditLogger(),
+        buildAuditMessage(OMAction.DELETE_S3_BUCKET,
+            buildAuditMap(s3BucketName), exception,
+            getOmRequest().getUserInfo()));
+
+    if (exception == null) {
+      // Decrement s3 bucket and ozone bucket count. As S3 bucket is mapped to
+      // ozonevolume/ozone bucket.
+      LOG.debug("S3Bucket {} successfully deleted", s3BucketName);
+      omMetrics.decNumS3Buckets();
+      omMetrics.decNumBuckets();
+      omResponse.setDeleteS3BucketResponse(
+          OzoneManagerProtocolProtos.S3DeleteBucketResponse.newBuilder());
+      return new S3BucketDeleteResponse(s3BucketName, volumeName,
+          omResponse.build());
+    } else {
+      LOG.error("S3Bucket Deletion failed for S3Bucket:{}", s3BucketName,
+          exception);
+      omMetrics.incNumS3BucketDeleteFails();
+      return new S3BucketDeleteResponse(null, null,
+          createErrorOMResponse(omResponse, exception));
+    }
+  }
+
+  /**
+   * Extract volumeName from s3Mapping.
+   * @param s3Mapping
+   * @return volumeName
+   * @throws IOException
+   */
+  private String getOzoneVolumeName(String s3Mapping) throws IOException {
+    return s3Mapping.split("/")[0];
+  }
+
+  private Map<String, String> buildAuditMap(String s3BucketName) {
+    Map<String, String> auditMap = new HashMap<>();
+    auditMap.put(s3BucketName, OzoneConsts.S3_BUCKET);
+    return auditMap;
+  }
+
+}

+ 213 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3InitiateMultipartUploadRequest.java

@@ -0,0 +1,213 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.multipart;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import org.apache.hadoop.ozone.audit.OMAction;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.om.request.key.OMKeyRequest;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.response.s3.multipart.S3InitiateMultipartUploadResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.MultipartInfoInitiateRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.MultipartInfoInitiateResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.apache.hadoop.util.Time;
+import org.apache.hadoop.utils.UniqueId;
+import org.apache.hadoop.utils.db.cache.CacheKey;
+import org.apache.hadoop.utils.db.cache.CacheValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.UUID;
+
+import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK;
+
+/**
+ * Handles initiate multipart upload request.
+ */
+public class S3InitiateMultipartUploadRequest extends OMKeyRequest {
+
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(S3InitiateMultipartUploadRequest.class);
+
+  public S3InitiateMultipartUploadRequest(OMRequest omRequest) {
+    super(omRequest);
+  }
+
+  @Override
+  public OMRequest preExecute(OzoneManager ozoneManager) {
+    MultipartInfoInitiateRequest multipartInfoInitiateRequest =
+        getOmRequest().getInitiateMultiPartUploadRequest();
+    Preconditions.checkNotNull(multipartInfoInitiateRequest);
+
+    OzoneManagerProtocolProtos.KeyArgs.Builder newKeyArgs =
+        multipartInfoInitiateRequest.getKeyArgs().toBuilder()
+            .setMultipartUploadID(UUID.randomUUID().toString() + "-" +
+                UniqueId.next()).setModificationTime(Time.now());
+
+    return getOmRequest().toBuilder()
+        .setUserInfo(getUserInfo())
+        .setInitiateMultiPartUploadRequest(
+            multipartInfoInitiateRequest.toBuilder().setKeyArgs(newKeyArgs))
+        .build();
+  }
+
+  @Override
+  public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
+      long transactionLogIndex) {
+    MultipartInfoInitiateRequest multipartInfoInitiateRequest =
+        getOmRequest().getInitiateMultiPartUploadRequest();
+
+    OzoneManagerProtocolProtos.KeyArgs keyArgs =
+        multipartInfoInitiateRequest.getKeyArgs();
+
+    Preconditions.checkNotNull(keyArgs.getMultipartUploadID());
+
+    String volumeName = keyArgs.getVolumeName();
+    String bucketName = keyArgs.getBucketName();
+    String keyName = keyArgs.getKeyName();
+
+    OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
+
+    ozoneManager.getMetrics().incNumInitiateMultipartUploads();
+    boolean acquiredBucketLock = false;
+    IOException exception = null;
+    OmMultipartKeyInfo multipartKeyInfo = null;
+    OmKeyInfo omKeyInfo = null;
+    try {
+      // check Acl
+      if (ozoneManager.getAclsEnabled()) {
+        checkAcls(ozoneManager, OzoneObj.ResourceType.KEY,
+            OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.WRITE,
+            volumeName, bucketName, keyName);
+      }
+
+      acquiredBucketLock =
+          omMetadataManager.getLock().acquireLock(BUCKET_LOCK, volumeName,
+              bucketName);
+
+      validateBucketAndVolume(omMetadataManager, volumeName, bucketName);
+
+      // We are adding uploadId to key, because if multiple users try to
+      // perform multipart upload on the same key, each will try to upload, who
+      // ever finally commit the key, we see that key in ozone. Suppose if we
+      // don't add id, and use the same key /volume/bucket/key, when multiple
+      // users try to upload the key, we update the parts of the key's from
+      // multiple users to same key, and the key output can be a mix of the
+      // parts from multiple users.
+
+      // So on same key if multiple time multipart upload is initiated we
+      // store multiple entries in the openKey Table.
+      // Checked AWS S3, when we try to run multipart upload, each time a
+      // new uploadId is returned. And also even if a key exist when initiate
+      // multipart upload request is received, it returns multipart upload id
+      // for the key.
+
+      String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+          bucketName, keyName, keyArgs.getMultipartUploadID());
+
+      // Not checking if there is an already key for this in the keyTable, as
+      // during final complete multipart upload we take care of this. AWS S3
+      // behavior is also like this, even when key exists in a bucket, user
+      // can still initiate MPU.
+
+
+      multipartKeyInfo = new OmMultipartKeyInfo(
+          keyArgs.getMultipartUploadID(), new HashMap<>());
+
+      omKeyInfo = new OmKeyInfo.Builder()
+          .setVolumeName(keyArgs.getVolumeName())
+          .setBucketName(keyArgs.getBucketName())
+          .setKeyName(keyArgs.getKeyName())
+          .setCreationTime(keyArgs.getModificationTime())
+          .setModificationTime(keyArgs.getModificationTime())
+          .setReplicationType(keyArgs.getType())
+          .setReplicationFactor(keyArgs.getFactor())
+          .setOmKeyLocationInfos(Collections.singletonList(
+              new OmKeyLocationInfoGroup(0, new ArrayList<>())))
+          .setAcls(keyArgs.getAclsList())
+          .build();
+
+
+      // Add to cache
+      omMetadataManager.getOpenKeyTable().addCacheEntry(
+          new CacheKey<>(multipartKey),
+          new CacheValue<>(Optional.of(omKeyInfo), transactionLogIndex));
+      omMetadataManager.getMultipartInfoTable().addCacheEntry(
+          new CacheKey<>(multipartKey),
+          new CacheValue<>(Optional.of(multipartKeyInfo), transactionLogIndex));
+
+    } catch (IOException ex) {
+      exception = ex;
+    } finally {
+      if (acquiredBucketLock) {
+        omMetadataManager.getLock().releaseLock(BUCKET_LOCK, volumeName,
+            bucketName);
+      }
+    }
+
+
+    OMResponse.Builder omResponse = OMResponse.newBuilder()
+        .setCmdType(OzoneManagerProtocolProtos.Type.InitiateMultiPartUpload)
+        .setStatus(OzoneManagerProtocolProtos.Status.OK)
+        .setSuccess(true);
+
+    // audit log
+    auditLog(ozoneManager.getAuditLogger(), buildAuditMessage(
+        OMAction.INITIATE_MULTIPART_UPLOAD, buildKeyArgsAuditMap(keyArgs),
+        exception, getOmRequest().getUserInfo()));
+
+    if (exception == null) {
+      LOG.debug("S3 InitiateMultipart Upload request for Key {} in " +
+          "Volume/Bucket {}/{} is successfully completed", keyName,
+          volumeName, bucketName);
+
+      return new S3InitiateMultipartUploadResponse(multipartKeyInfo, omKeyInfo,
+          omResponse.setInitiateMultiPartUploadResponse(
+              MultipartInfoInitiateResponse.newBuilder()
+                  .setVolumeName(volumeName)
+                  .setBucketName(bucketName)
+                  .setKeyName(keyName)
+                  .setMultipartUploadID(keyArgs.getMultipartUploadID()))
+              .build());
+
+    } else {
+      ozoneManager.getMetrics().incNumInitiateMultipartUploadFails();
+      LOG.error("S3 InitiateMultipart Upload request for Key {} in " +
+              "Volume/Bucket {}/{} is failed", keyName, volumeName, bucketName,
+          exception);
+      return new S3InitiateMultipartUploadResponse(null, null,
+          createErrorOMResponse(omResponse, exception));
+    }
+  }
+}

+ 173 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadAbortRequest.java

@@ -0,0 +1,173 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.multipart;
+
+import java.io.IOException;
+
+import com.google.common.base.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hadoop.ozone.audit.OMAction;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.om.request.key.OMKeyRequest;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.response.s3.multipart
+    .S3MultipartUploadAbortResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .KeyArgs;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .MultipartUploadAbortResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMResponse;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.apache.hadoop.util.Time;
+import org.apache.hadoop.utils.db.cache.CacheKey;
+import org.apache.hadoop.utils.db.cache.CacheValue;
+
+import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK;
+
+/**
+ * Handles Abort of multipart upload request.
+ */
+public class S3MultipartUploadAbortRequest extends OMKeyRequest {
+
+  private static final Logger LOG =
+      LoggerFactory.getLogger(S3MultipartUploadAbortRequest.class);
+
+  public S3MultipartUploadAbortRequest(OMRequest omRequest) {
+    super(omRequest);
+  }
+
+  @Override
+  public OMRequest preExecute(OzoneManager ozoneManager) throws IOException {
+    KeyArgs keyArgs =
+        getOmRequest().getAbortMultiPartUploadRequest().getKeyArgs();
+
+    return getOmRequest().toBuilder().setAbortMultiPartUploadRequest(
+        getOmRequest().getAbortMultiPartUploadRequest().toBuilder()
+            .setKeyArgs(keyArgs.toBuilder().setModificationTime(Time.now())))
+        .setUserInfo(getUserInfo()).build();
+
+  }
+
+  @Override
+  public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
+      long transactionLogIndex) {
+    OzoneManagerProtocolProtos.KeyArgs keyArgs =
+        getOmRequest().getAbortMultiPartUploadRequest().getKeyArgs();
+
+    String volumeName = keyArgs.getVolumeName();
+    String bucketName = keyArgs.getBucketName();
+    String keyName = keyArgs.getKeyName();
+
+    OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
+    boolean acquiredLock = false;
+    IOException exception = null;
+    OmMultipartKeyInfo multipartKeyInfo = null;
+    String multipartKey = null;
+    try {
+      // check Acl
+      if (ozoneManager.getAclsEnabled()) {
+        checkAcls(ozoneManager, OzoneObj.ResourceType.KEY,
+            OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.WRITE,
+            volumeName, bucketName, keyName);
+      }
+
+      acquiredLock =
+          omMetadataManager.getLock().acquireLock(BUCKET_LOCK, volumeName,
+              bucketName);
+
+      validateBucketAndVolume(omMetadataManager, volumeName, bucketName);
+
+      multipartKey = omMetadataManager.getMultipartKey(volumeName,
+          bucketName, keyName, keyArgs.getMultipartUploadID());
+
+      OmKeyInfo omKeyInfo =
+          omMetadataManager.getOpenKeyTable().get(multipartKey);
+
+      // If there is no entry in openKeyTable, then there is no multipart
+      // upload initiated for this key.
+      if (omKeyInfo == null) {
+        throw new OMException("Abort Multipart Upload Failed: volume: " +
+            volumeName + "bucket: " + bucketName + "key: " + keyName,
+            OMException.ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR);
+      } else {
+        multipartKeyInfo = omMetadataManager
+            .getMultipartInfoTable().get(multipartKey);
+
+
+        // Update cache of openKeyTable and multipartInfo table.
+        // No need to add the cache entries to delete table, as the entries
+        // in delete table are not used by any read/write operations.
+        omMetadataManager.getOpenKeyTable().addCacheEntry(
+            new CacheKey<>(multipartKey),
+            new CacheValue<>(Optional.absent(), transactionLogIndex));
+        omMetadataManager.getMultipartInfoTable().addCacheEntry(
+            new CacheKey<>(multipartKey),
+            new CacheValue<>(Optional.absent(), transactionLogIndex));
+      }
+
+    } catch (IOException ex) {
+      exception = ex;
+    } finally {
+      if (acquiredLock) {
+        omMetadataManager.getLock().releaseLock(BUCKET_LOCK, volumeName,
+            bucketName);
+      }
+    }
+
+    // audit log
+    auditLog(ozoneManager.getAuditLogger(), buildAuditMessage(
+        OMAction.ABORT_MULTIPART_UPLOAD, buildKeyArgsAuditMap(keyArgs),
+        exception, getOmRequest().getUserInfo()));
+
+    OMResponse.Builder omResponse = OMResponse.newBuilder()
+        .setCmdType(OzoneManagerProtocolProtos.Type.AbortMultiPartUpload)
+        .setStatus(OzoneManagerProtocolProtos.Status.OK)
+        .setSuccess(true);
+
+
+    if (exception == null) {
+      LOG.debug("Abort Multipart request is successfully completed for " +
+          "KeyName {} in VolumeName/Bucket {}/{}", keyName, volumeName,
+          bucketName);
+      return new S3MultipartUploadAbortResponse(multipartKey,
+          keyArgs.getModificationTime(), multipartKeyInfo,
+          omResponse.setAbortMultiPartUploadResponse(
+              MultipartUploadAbortResponse.newBuilder()).build());
+    } else {
+      LOG.error("Abort Multipart request is failed for " +
+          "KeyName {} in VolumeName/Bucket {}/{}", keyName, volumeName,
+          bucketName, exception);
+      return new S3MultipartUploadAbortResponse(multipartKey,
+          keyArgs.getModificationTime(), multipartKeyInfo,
+          createErrorOMResponse(omResponse, exception));
+    }
+
+  }
+}

+ 217 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/S3MultipartUploadCommitPartRequest.java

@@ -0,0 +1,217 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.multipart;
+
+import com.google.common.base.Optional;
+import org.apache.hadoop.ozone.audit.OMAction;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.om.request.key.OMKeyRequest;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.om.response.s3.multipart
+    .S3MultipartUploadCommitPartResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .MultipartCommitUploadPartRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .MultipartCommitUploadPartResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMResponse;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.apache.hadoop.util.Time;
+import org.apache.hadoop.utils.db.cache.CacheKey;
+import org.apache.hadoop.utils.db.cache.CacheValue;
+
+import java.io.IOException;
+import java.util.stream.Collectors;
+
+import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.KEY_NOT_FOUND;
+import static org.apache.hadoop.ozone.om.lock.OzoneManagerLock.Resource.BUCKET_LOCK;
+
+/**
+ * Handle Multipart upload commit upload part file.
+ */
+public class S3MultipartUploadCommitPartRequest extends OMKeyRequest {
+
+  public S3MultipartUploadCommitPartRequest(OMRequest omRequest) {
+    super(omRequest);
+  }
+
+  @Override
+  public OMRequest preExecute(OzoneManager ozoneManager) {
+    MultipartCommitUploadPartRequest multipartCommitUploadPartRequest =
+        getOmRequest().getCommitMultiPartUploadRequest();
+
+    return getOmRequest().toBuilder().setCommitMultiPartUploadRequest(
+        multipartCommitUploadPartRequest.toBuilder()
+            .setKeyArgs(multipartCommitUploadPartRequest.getKeyArgs()
+                .toBuilder().setModificationTime(Time.now())))
+        .setUserInfo(getUserInfo()).build();
+  }
+
+  @Override
+  public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager,
+      long transactionLogIndex) {
+    MultipartCommitUploadPartRequest multipartCommitUploadPartRequest =
+        getOmRequest().getCommitMultiPartUploadRequest();
+
+    OzoneManagerProtocolProtos.KeyArgs keyArgs =
+        multipartCommitUploadPartRequest.getKeyArgs();
+
+    String volumeName = keyArgs.getVolumeName();
+    String bucketName = keyArgs.getBucketName();
+    String keyName = keyArgs.getKeyName();
+
+    OMMetadataManager omMetadataManager = ozoneManager.getMetadataManager();
+    ozoneManager.getMetrics().incNumCommitMultipartUploadParts();
+
+    boolean acquiredLock = false;
+    OmMultipartKeyInfo multipartKeyInfo = null;
+    OmKeyInfo omKeyInfo = null;
+    String openKey = null;
+    String multipartKey = null;
+    OzoneManagerProtocolProtos.PartKeyInfo oldPartKeyInfo = null;
+    IOException exception = null;
+    String partName = null;
+    try {
+      // check Acl
+      if (ozoneManager.getAclsEnabled()) {
+        checkAcls(ozoneManager, OzoneObj.ResourceType.KEY,
+            OzoneObj.StoreType.OZONE, IAccessAuthorizer.ACLType.WRITE,
+            volumeName, bucketName, keyName);
+      }
+
+      acquiredLock =
+          omMetadataManager.getLock().acquireLock(BUCKET_LOCK, volumeName,
+              bucketName);
+
+      validateBucketAndVolume(omMetadataManager, volumeName, bucketName);
+
+      String uploadID = keyArgs.getMultipartUploadID();
+      multipartKey = omMetadataManager.getMultipartKey(volumeName, bucketName,
+          keyName, uploadID);
+
+      multipartKeyInfo = omMetadataManager
+          .getMultipartInfoTable().get(multipartKey);
+
+      long clientID = multipartCommitUploadPartRequest.getClientID();
+
+      openKey = omMetadataManager.getOpenKey(
+          volumeName, bucketName, keyName, clientID);
+
+      omKeyInfo = omMetadataManager.getOpenKeyTable().get(openKey);
+
+
+      if (omKeyInfo == null) {
+        throw new OMException("Failed to commit Multipart Upload key, as " +
+            openKey + "entry is not found in the openKey table", KEY_NOT_FOUND);
+      }
+
+      // set the data size and location info list
+      omKeyInfo.setDataSize(keyArgs.getDataSize());
+      omKeyInfo.updateLocationInfoList(keyArgs.getKeyLocationsList().stream()
+          .map(OmKeyLocationInfo::getFromProtobuf)
+          .collect(Collectors.toList()));
+      // Set Modification time
+      omKeyInfo.setModificationTime(keyArgs.getModificationTime());
+
+      partName = omMetadataManager.getOzoneKey(volumeName, bucketName,
+          keyName) + clientID;
+
+      if (multipartKeyInfo == null) {
+        // This can occur when user started uploading part by the time commit
+        // of that part happens, in between the user might have requested
+        // abort multipart upload. If we just throw exception, then the data
+        // will not be garbage collected, so move this part to delete table
+        // and throw error
+        // Move this part to delete table.
+        throw new OMException("No such Multipart upload is with specified " +
+            "uploadId " + uploadID,
+            OMException.ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR);
+      } else {
+        int partNumber = keyArgs.getMultipartNumber();
+        oldPartKeyInfo = multipartKeyInfo.getPartKeyInfo(partNumber);
+
+        // Build this multipart upload part info.
+        OzoneManagerProtocolProtos.PartKeyInfo.Builder partKeyInfo =
+            OzoneManagerProtocolProtos.PartKeyInfo.newBuilder();
+        partKeyInfo.setPartName(partName);
+        partKeyInfo.setPartNumber(partNumber);
+        partKeyInfo.setPartKeyInfo(omKeyInfo.getProtobuf());
+
+        // Add this part information in to multipartKeyInfo.
+        multipartKeyInfo.addPartKeyInfo(partNumber, partKeyInfo.build());
+
+        // Add to cache.
+
+        // Delete from open key table and add it to multipart info table.
+        // No need to add cache entries to delete table, as no
+        // read/write requests that info for validation.
+        omMetadataManager.getMultipartInfoTable().addCacheEntry(
+            new CacheKey<>(multipartKey),
+            new CacheValue<>(Optional.of(multipartKeyInfo),
+                transactionLogIndex));
+
+        omMetadataManager.getOpenKeyTable().addCacheEntry(
+            new CacheKey<>(openKey),
+            new CacheValue<>(Optional.absent(), transactionLogIndex));
+      }
+
+    } catch (IOException ex) {
+      exception = ex;
+    } finally {
+      if (acquiredLock) {
+        omMetadataManager.getLock().releaseLock(BUCKET_LOCK, volumeName,
+            bucketName);
+      }
+    }
+
+    // audit log
+    auditLog(ozoneManager.getAuditLogger(), buildAuditMessage(
+        OMAction.COMMIT_MULTIPART_UPLOAD_PARTKEY, buildKeyArgsAuditMap(keyArgs),
+        exception, getOmRequest().getUserInfo()));
+
+    OMResponse.Builder omResponse = OMResponse.newBuilder()
+        .setCmdType(OzoneManagerProtocolProtos.Type.CommitMultiPartUpload)
+        .setStatus(OzoneManagerProtocolProtos.Status.OK)
+        .setSuccess(true);
+
+    if (exception == null) {
+      omResponse.setCommitMultiPartUploadResponse(
+          MultipartCommitUploadPartResponse.newBuilder().setPartName(partName));
+      return new S3MultipartUploadCommitPartResponse(multipartKey, openKey,
+          keyArgs.getModificationTime(), omKeyInfo, multipartKeyInfo,
+          oldPartKeyInfo, omResponse.build());
+    } else {
+      ozoneManager.getMetrics().incNumCommitMultipartUploadPartFails();
+      return new S3MultipartUploadCommitPartResponse(multipartKey, openKey,
+          keyArgs.getModificationTime(), omKeyInfo, multipartKeyInfo,
+          oldPartKeyInfo, createErrorOMResponse(omResponse, exception));
+
+    }
+  }
+}
+

+ 23 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/multipart/package-info.java

@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * Package contains classes related to S3 multipart upload  requests.
+ */
+package org.apache.hadoop.ozone.om.request.s3.multipart;

+ 1 - 3
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/volume/OMVolumeSetOwnerRequest.java

@@ -21,7 +21,6 @@ package org.apache.hadoop.ozone.om.request.volume;
 import java.io.IOException;
 import java.util.Map;
 
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import com.google.common.base.Optional;
@@ -36,7 +35,6 @@ import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
-import org.apache.hadoop.ozone.om.response.volume.OMVolumeCreateResponse;
 import org.apache.hadoop.ozone.om.response.volume.OMVolumeSetOwnerResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
@@ -110,7 +108,7 @@ public class OMVolumeSetOwnerRequest extends OMVolumeRequest {
       omMetrics.incNumVolumeUpdateFails();
       auditLog(auditLogger, buildAuditMessage(OMAction.SET_OWNER, auditMap,
           ex, userInfo));
-      return new OMVolumeCreateResponse(null, null,
+      return new OMVolumeSetOwnerResponse(null, null, null, null,
           createErrorOMResponse(omResponse, ex));
     }
 

+ 1 - 2
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/volume/OMVolumeSetQuotaRequest.java

@@ -36,7 +36,6 @@ import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
 import org.apache.hadoop.ozone.om.response.volume.OMVolumeSetQuotaResponse;
-import org.apache.hadoop.ozone.om.response.volume.OMVolumeCreateResponse;
 import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
@@ -112,7 +111,7 @@ public class OMVolumeSetQuotaRequest extends OMVolumeRequest {
       omMetrics.incNumVolumeUpdateFails();
       auditLog(auditLogger, buildAuditMessage(OMAction.SET_QUOTA, auditMap,
           ex, userInfo));
-      return new OMVolumeCreateResponse(null, null,
+      return new OMVolumeSetQuotaResponse(null,
           createErrorOMResponse(omResponse, ex));
     }
 

+ 7 - 1
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/bucket/S3BucketCreateResponse.java

@@ -22,6 +22,8 @@ import javax.annotation.Nullable;
 import java.io.IOException;
 
 import com.google.common.base.Preconditions;
+import com.google.common.annotations.VisibleForTesting;
+
 import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
 import org.apache.hadoop.ozone.om.response.bucket.OMBucketCreateResponse;
@@ -69,5 +71,9 @@ public class S3BucketCreateResponse extends OMClientResponse {
           s3Mapping);
     }
   }
-}
 
+  @VisibleForTesting
+  public String getS3Mapping() {
+    return s3Mapping;
+  }
+}

+ 55 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/bucket/S3BucketDeleteResponse.java

@@ -0,0 +1,55 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership.  The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.hadoop.ozone.om.response.s3.bucket;
+
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
+import org.apache.hadoop.utils.db.BatchOperation;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+
+/**
+ * Response for S3Bucket Delete request.
+ */
+public class S3BucketDeleteResponse extends OMClientResponse {
+
+  private String s3BucketName;
+  private String volumeName;
+  public S3BucketDeleteResponse(@Nullable String s3BucketName,
+      @Nullable String volumeName, @Nonnull OMResponse omResponse) {
+    super(omResponse);
+    this.s3BucketName = s3BucketName;
+    this.volumeName = volumeName;
+  }
+
+  @Override
+  public void addToDBBatch(OMMetadataManager omMetadataManager,
+      BatchOperation batchOperation) throws IOException {
+
+    if (getOMResponse().getStatus() == OzoneManagerProtocolProtos.Status.OK) {
+      omMetadataManager.getBucketTable().deleteWithBatch(batchOperation,
+          omMetadataManager.getBucketKey(volumeName, s3BucketName));
+      omMetadataManager.getS3Table().deleteWithBatch(batchOperation,
+          s3BucketName);
+    }
+  }
+}

+ 80 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/multipart/S3InitiateMultipartUploadResponse.java

@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.ozone.om.response.s3.multipart;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.utils.db.BatchOperation;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.IOException;
+
+/**
+ * Response for S3 Initiate Multipart Upload request.
+ */
+public class S3InitiateMultipartUploadResponse extends OMClientResponse {
+
+  private OmMultipartKeyInfo omMultipartKeyInfo;
+  private OmKeyInfo omKeyInfo;
+
+  public S3InitiateMultipartUploadResponse(
+      @Nullable OmMultipartKeyInfo omMultipartKeyInfo,
+      @Nullable OmKeyInfo omKeyInfo,
+      @Nonnull OzoneManagerProtocolProtos.OMResponse omResponse) {
+    super(omResponse);
+    this.omMultipartKeyInfo = omMultipartKeyInfo;
+    this.omKeyInfo = omKeyInfo;
+  }
+
+  @Override
+  public void addToDBBatch(OMMetadataManager omMetadataManager,
+      BatchOperation batchOperation) throws IOException {
+
+    // For OmResponse with failure, this should do nothing. This method is
+    // not called in failure scenario in OM code.
+    if (getOMResponse().getStatus() == OzoneManagerProtocolProtos.Status.OK) {
+
+      String multipartKey =
+          omMetadataManager.getMultipartKey(omKeyInfo.getVolumeName(),
+              omKeyInfo.getBucketName(), omKeyInfo.getKeyName(),
+              omMultipartKeyInfo.getUploadID());
+
+      omMetadataManager.getOpenKeyTable().putWithBatch(batchOperation,
+          multipartKey, omKeyInfo);
+      omMetadataManager.getMultipartInfoTable().putWithBatch(batchOperation,
+          multipartKey, omMultipartKeyInfo);
+    }
+  }
+
+  @VisibleForTesting
+  public OmMultipartKeyInfo getOmMultipartKeyInfo() {
+    return omMultipartKeyInfo;
+  }
+
+  @VisibleForTesting
+  public OmKeyInfo getOmKeyInfo() {
+    return omKeyInfo;
+  }
+}

+ 83 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/multipart/S3MultipartUploadAbortResponse.java

@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.om.response.s3.multipart;
+
+import org.apache.hadoop.ozone.OmUtils;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .PartKeyInfo;
+import org.apache.hadoop.utils.db.BatchOperation;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Response for Multipart Abort Request.
+ */
+public class S3MultipartUploadAbortResponse extends OMClientResponse {
+
+  private String multipartKey;
+  private long timeStamp;
+  private OmMultipartKeyInfo omMultipartKeyInfo;
+
+  public S3MultipartUploadAbortResponse(String multipartKey,
+      long timeStamp,
+      OmMultipartKeyInfo omMultipartKeyInfo,
+      OMResponse omResponse) {
+    super(omResponse);
+    this.multipartKey = multipartKey;
+    this.timeStamp = timeStamp;
+    this.omMultipartKeyInfo = omMultipartKeyInfo;
+  }
+
+  @Override
+  public void addToDBBatch(OMMetadataManager omMetadataManager,
+      BatchOperation batchOperation) throws IOException {
+
+    if (getOMResponse().getStatus() == OzoneManagerProtocolProtos.Status.OK) {
+
+      // Delete from openKey table and multipart info table.
+      omMetadataManager.getOpenKeyTable().deleteWithBatch(batchOperation,
+          multipartKey);
+      omMetadataManager.getMultipartInfoTable().deleteWithBatch(batchOperation,
+          multipartKey);
+
+      // Move all the parts to delete table
+      TreeMap<Integer, PartKeyInfo > partKeyInfoMap =
+          omMultipartKeyInfo.getPartKeyInfoMap();
+      for (Map.Entry<Integer, PartKeyInfo > partKeyInfoEntry :
+          partKeyInfoMap.entrySet()) {
+        PartKeyInfo partKeyInfo = partKeyInfoEntry.getValue();
+        OmKeyInfo currentKeyPartInfo =
+            OmKeyInfo.getFromProtobuf(partKeyInfo.getPartKeyInfo());
+        omMetadataManager.getDeletedTable().putWithBatch(batchOperation,
+            OmUtils.getDeletedKeyName(partKeyInfo.getPartName(), timeStamp),
+            currentKeyPartInfo);
+      }
+
+    }
+  }
+}

+ 109 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/multipart/S3MultipartUploadCommitPartResponse.java

@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.om.response.s3.multipart;
+
+import org.apache.hadoop.ozone.OmUtils;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMResponse;
+import org.apache.hadoop.utils.db.BatchOperation;
+
+import java.io.IOException;
+import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .Status.NO_SUCH_MULTIPART_UPLOAD_ERROR;
+import static org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .Status.OK;
+
+/**
+ * Response for S3MultipartUploadCommitPart request.
+ */
+public class S3MultipartUploadCommitPartResponse extends OMClientResponse {
+
+  private String multipartKey;
+  private String openKey;
+  private long deleteTimeStamp;
+  private OmKeyInfo deletePartKeyInfo;
+  private OmMultipartKeyInfo omMultipartKeyInfo;
+  private OzoneManagerProtocolProtos.PartKeyInfo oldMultipartKeyInfo;
+
+
+  public S3MultipartUploadCommitPartResponse(String multipartKey,
+      String openKey, long deleteTimeStamp,
+      OmKeyInfo deletePartKeyInfo, OmMultipartKeyInfo omMultipartKeyInfo,
+      OzoneManagerProtocolProtos.PartKeyInfo oldPartKeyInfo,
+      OMResponse omResponse) {
+    super(omResponse);
+    this.multipartKey = multipartKey;
+    this.openKey = openKey;
+    this.deleteTimeStamp = deleteTimeStamp;
+    this.deletePartKeyInfo = deletePartKeyInfo;
+    this.omMultipartKeyInfo = omMultipartKeyInfo;
+    this.oldMultipartKeyInfo = oldPartKeyInfo;
+  }
+
+
+  @Override
+  public void addToDBBatch(OMMetadataManager omMetadataManager,
+      BatchOperation batchOperation) throws IOException {
+
+
+    if (getOMResponse().getStatus() == NO_SUCH_MULTIPART_UPLOAD_ERROR) {
+      // Means by the time we try to commit part, some one has aborted this
+      // multipart upload. So, delete this part information.
+      omMetadataManager.getDeletedTable().putWithBatch(batchOperation,
+          OmUtils.getDeletedKeyName(openKey, deleteTimeStamp),
+          deletePartKeyInfo);
+    }
+
+    if (getOMResponse().getStatus() == OK) {
+
+      // If we have old part info:
+      // Need to do 3 steps:
+      //   1. add old part to delete table
+      //   2. Commit multipart info which has information about this new part.
+      //   3. delete this new part entry from open key table.
+
+      // This means for this multipart upload part upload, we have an old
+      // part information, so delete it.
+      if (oldMultipartKeyInfo != null) {
+        omMetadataManager.getDeletedTable().putWithBatch(batchOperation,
+            OmUtils.getDeletedKeyName(oldMultipartKeyInfo.getPartName(),
+                deleteTimeStamp),
+            OmKeyInfo.getFromProtobuf(oldMultipartKeyInfo.getPartKeyInfo()));
+      }
+
+
+      omMetadataManager.getMultipartInfoTable().putWithBatch(batchOperation,
+          multipartKey, omMultipartKeyInfo);
+
+      //  This information has been added to multipartKeyInfo. So, we can
+      //  safely delete part key info from open key table.
+      omMetadataManager.getOpenKeyTable().deleteWithBatch(batchOperation,
+          openKey);
+
+
+    }
+  }
+
+}
+

+ 22 - 0
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/multipart/package-info.java

@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+/**
+ * Package contains classes related to S3 multipart upload responses.
+ */
+package org.apache.hadoop.ozone.om.response.s3.multipart;

+ 1 - 1
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/snapshot/OzoneManagerSnapshotProvider.java

@@ -149,7 +149,7 @@ public class OzoneManagerSnapshotProvider {
    * @param leaderOMNodeID leader OM Node ID.
    * @return the DB checkpoint (including the ratis snapshot index)
    */
-  protected DBCheckpoint getOzoneManagerDBSnapshot(String leaderOMNodeID)
+  public DBCheckpoint getOzoneManagerDBSnapshot(String leaderOMNodeID)
       throws IOException {
     String snapshotFileName = OM_SNAPSHOT_DB + "_" + System.currentTimeMillis();
     File targetFile = new File(omSnapshotDir, snapshotFileName + ".tar.gz");

+ 10 - 8
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerHARequestHandlerImpl.java

@@ -26,8 +26,6 @@ import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
     .OMRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
     .OMResponse;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
-    .Status;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
     .Type;
 
@@ -68,6 +66,10 @@ public class OzoneManagerHARequestHandlerImpl
     case CreateFile:
     case PurgeKeys:
     case CreateS3Bucket:
+    case DeleteS3Bucket:
+    case InitiateMultiPartUpload:
+    case CommitMultiPartUpload:
+    case AbortMultiPartUpload:
       //TODO: We don't need to pass transactionID, this will be removed when
       // complete write requests is changed to new model. And also we can
       // return OMClientResponse, then adding to doubleBuffer can be taken
@@ -79,12 +81,12 @@ public class OzoneManagerHARequestHandlerImpl
           omClientRequest.validateAndUpdateCache(getOzoneManager(),
               transactionLogIndex);
 
-      // If any error we have got when validateAndUpdateCache, OMResponse
-      // Status is set with Error Code other than OK, in that case don't
-      // add this to double buffer.
-      if (omClientResponse.getOMResponse().getStatus() == Status.OK) {
-        ozoneManagerDoubleBuffer.add(omClientResponse, transactionLogIndex);
-      }
+
+      // Add OMClient Response to double buffer.
+      // Each OMClient Response should handle what needs to be done in error
+      // case.
+      ozoneManagerDoubleBuffer.add(omClientResponse, transactionLogIndex);
+
       return omClientResponse.getOMResponse();
     default:
       // As all request types are not changed so we need to call handle

+ 0 - 35
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java

@@ -49,8 +49,6 @@ import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetFile
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetFileStatusResponse;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AllocateBlockRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AllocateBlockResponse;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
-    .MultipartInfoApplyInitiateRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CancelDelegationTokenResponseProto;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CheckVolumeAccessRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.CheckVolumeAccessResponse;
@@ -270,13 +268,6 @@ public class OzoneManagerRequestHandler implements RequestHandler {
         responseBuilder.setInitiateMultiPartUploadResponse(
             multipartInfoInitiateResponse);
         break;
-      case ApplyInitiateMultiPartUpload:
-        MultipartInfoInitiateResponse response =
-            applyInitiateMultiPartUpload(
-                request.getInitiateMultiPartUploadApplyRequest());
-        responseBuilder.setInitiateMultiPartUploadResponse(
-            response);
-        break;
       case CommitMultiPartUpload:
         MultipartCommitUploadPartResponse commitUploadPartResponse =
             commitMultipartUploadPart(
@@ -810,32 +801,6 @@ public class OzoneManagerRequestHandler implements RequestHandler {
     return resp.build();
   }
 
-  private MultipartInfoInitiateResponse applyInitiateMultiPartUpload(
-      MultipartInfoApplyInitiateRequest request) throws IOException {
-    MultipartInfoInitiateResponse.Builder resp = MultipartInfoInitiateResponse
-        .newBuilder();
-
-    KeyArgs keyArgs = request.getKeyArgs();
-    OmKeyArgs omKeyArgs = new OmKeyArgs.Builder()
-        .setVolumeName(keyArgs.getVolumeName())
-        .setBucketName(keyArgs.getBucketName())
-        .setKeyName(keyArgs.getKeyName())
-        .setType(keyArgs.getType())
-        .setAcls(keyArgs.getAclsList().stream().map(a ->
-            OzoneAcl.fromProtobuf(a)).collect(Collectors.toList()))
-        .setFactor(keyArgs.getFactor())
-        .build();
-    OmMultipartInfo multipartInfo =
-        impl.applyInitiateMultipartUpload(omKeyArgs,
-            request.getMultipartUploadID());
-    resp.setVolumeName(multipartInfo.getVolumeName());
-    resp.setBucketName(multipartInfo.getBucketName());
-    resp.setKeyName(multipartInfo.getKeyName());
-    resp.setMultipartUploadID(multipartInfo.getUploadID());
-
-    return resp.build();
-  }
-
   private MultipartCommitUploadPartResponse commitMultipartUploadPart(
       MultipartCommitUploadPartRequest request) throws IOException {
     MultipartCommitUploadPartResponse.Builder resp =

+ 85 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestOMRequestUtils.java

@@ -35,6 +35,14 @@ import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
 import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
 import org.apache.hadoop.ozone.om.request.s3.bucket.S3BucketCreateRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .MultipartUploadAbortRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .MultipartCommitUploadPartRequest;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .KeyArgs;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .MultipartInfoInitiateRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
     .OMRequest;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
@@ -203,6 +211,17 @@ public final class TestOMRequestUtils {
         .setClientId(UUID.randomUUID().toString()).build();
   }
 
+  public static OzoneManagerProtocolProtos.OMRequest deleteS3BucketRequest(
+      String s3BucketName) {
+    OzoneManagerProtocolProtos.S3DeleteBucketRequest request =
+        OzoneManagerProtocolProtos.S3DeleteBucketRequest.newBuilder()
+            .setS3BucketName(s3BucketName).build();
+    return OzoneManagerProtocolProtos.OMRequest.newBuilder()
+        .setDeleteS3BucketRequest(request)
+        .setCmdType(OzoneManagerProtocolProtos.Type.DeleteS3Bucket)
+        .setClientId(UUID.randomUUID().toString()).build();
+  }
+
   public static List< HddsProtos.KeyValue> getMetadataList() {
     List<HddsProtos.KeyValue> metadataList = new ArrayList<>();
     metadataList.add(HddsProtos.KeyValue.newBuilder().setKey("key1").setValue(
@@ -281,4 +300,70 @@ public final class TestOMRequestUtils {
     return deletedKeyName;
   }
 
+  /**
+   * Create OMRequest which encapsulates InitiateMultipartUpload request.
+   * @param volumeName
+   * @param bucketName
+   * @param keyName
+   */
+  public static OMRequest createInitiateMPURequest(String volumeName,
+      String bucketName, String keyName) {
+    MultipartInfoInitiateRequest
+        multipartInfoInitiateRequest =
+        MultipartInfoInitiateRequest.newBuilder().setKeyArgs(
+            KeyArgs.newBuilder().setVolumeName(volumeName).setKeyName(keyName)
+                .setBucketName(bucketName)).build();
+
+    return OMRequest.newBuilder().setClientId(UUID.randomUUID().toString())
+        .setCmdType(OzoneManagerProtocolProtos.Type.InitiateMultiPartUpload)
+        .setInitiateMultiPartUploadRequest(multipartInfoInitiateRequest)
+        .build();
+  }
+
+  /**
+   * Create OMRequest which encapsulates InitiateMultipartUpload request.
+   * @param volumeName
+   * @param bucketName
+   * @param keyName
+   */
+  public static OMRequest createCommitPartMPURequest(String volumeName,
+      String bucketName, String keyName, long clientID, long size,
+      String multipartUploadID, int partNumber) {
+
+    // Just set dummy size.
+    KeyArgs.Builder keyArgs =
+        KeyArgs.newBuilder().setVolumeName(volumeName).setKeyName(keyName)
+            .setBucketName(bucketName)
+            .setDataSize(size)
+            .setMultipartNumber(partNumber)
+            .setMultipartUploadID(multipartUploadID)
+            .addAllKeyLocations(new ArrayList<>());
+    // Just adding dummy list. As this is for UT only.
+
+    MultipartCommitUploadPartRequest multipartCommitUploadPartRequest =
+        MultipartCommitUploadPartRequest.newBuilder()
+            .setKeyArgs(keyArgs).setClientID(clientID).build();
+
+    return OMRequest.newBuilder().setClientId(UUID.randomUUID().toString())
+        .setCmdType(OzoneManagerProtocolProtos.Type.CommitMultiPartUpload)
+        .setCommitMultiPartUploadRequest(multipartCommitUploadPartRequest)
+        .build();
+  }
+
+  public static OMRequest createAbortMPURequest(String volumeName,
+      String bucketName, String keyName, String multipartUploadID) {
+    KeyArgs.Builder keyArgs =
+        KeyArgs.newBuilder().setVolumeName(volumeName)
+            .setKeyName(keyName)
+            .setBucketName(bucketName)
+            .setMultipartUploadID(multipartUploadID);
+
+    MultipartUploadAbortRequest multipartUploadAbortRequest =
+        MultipartUploadAbortRequest.newBuilder().setKeyArgs(keyArgs).build();
+
+    return OMRequest.newBuilder().setClientId(UUID.randomUUID().toString())
+        .setCmdType(OzoneManagerProtocolProtos.Type.AbortMultiPartUpload)
+        .setAbortMultiPartUploadRequest(multipartUploadAbortRequest).build();
+  }
+
 }

+ 167 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/bucket/TestS3BucketDeleteRequest.java

@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.bucket;
+
+import java.util.UUID;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.audit.AuditMessage;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OMMetrics;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.request.TestOMRequestUtils;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMRequest;
+import org.apache.hadoop.test.GenericTestUtils;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests S3BucketDelete Request.
+ */
+public class TestS3BucketDeleteRequest {
+  @Rule
+  public TemporaryFolder folder = new TemporaryFolder();
+
+  private OzoneManager ozoneManager;
+  private OMMetrics omMetrics;
+  private OMMetadataManager omMetadataManager;
+  private AuditLogger auditLogger;
+
+
+  @Before
+  public void setup() throws Exception {
+
+    ozoneManager = Mockito.mock(OzoneManager.class);
+    omMetrics = OMMetrics.create();
+    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
+    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
+        folder.newFolder().getAbsolutePath());
+    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration);
+    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
+    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
+    auditLogger = Mockito.mock(AuditLogger.class);
+    when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
+    Mockito.doNothing().when(auditLogger).logWrite(any(AuditMessage.class));
+  }
+
+  @After
+  public void stop() {
+    omMetrics.unRegister();
+    Mockito.framework().clearInlineMocks();
+  }
+
+  @Test
+  public void testPreExecute() throws Exception {
+    String s3BucketName = UUID.randomUUID().toString();
+    doPreExecute(s3BucketName);
+  }
+
+  @Test
+  public void testValidateAndUpdateCache() throws Exception {
+    String s3BucketName = UUID.randomUUID().toString();
+    OMRequest omRequest = doPreExecute(s3BucketName);
+
+    // Add s3Bucket to s3Bucket table.
+    TestOMRequestUtils.addS3BucketToDB("ozone", s3BucketName,
+        omMetadataManager);
+
+    S3BucketDeleteRequest s3BucketDeleteRequest =
+        new S3BucketDeleteRequest(omRequest);
+
+    OMClientResponse s3BucketDeleteResponse =
+        s3BucketDeleteRequest.validateAndUpdateCache(ozoneManager, 1L);
+
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        s3BucketDeleteResponse.getOMResponse().getStatus());
+  }
+
+  @Test
+  public void testValidateAndUpdateCacheWithS3BucketNotFound()
+      throws Exception {
+    String s3BucketName = UUID.randomUUID().toString();
+    OMRequest omRequest = doPreExecute(s3BucketName);
+
+    S3BucketDeleteRequest s3BucketDeleteRequest =
+        new S3BucketDeleteRequest(omRequest);
+
+    OMClientResponse s3BucketDeleteResponse =
+        s3BucketDeleteRequest.validateAndUpdateCache(ozoneManager, 1L);
+
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.S3_BUCKET_NOT_FOUND,
+        s3BucketDeleteResponse.getOMResponse().getStatus());
+  }
+
+  @Test
+  public void testPreExecuteInvalidBucketLength() throws Exception {
+    // set bucket name which is less than 3 characters length
+    String s3BucketName = RandomStringUtils.randomAlphabetic(2);
+
+    try {
+      doPreExecute(s3BucketName);
+      fail("testPreExecuteInvalidBucketLength failed");
+    } catch (OMException ex) {
+      GenericTestUtils.assertExceptionContains("S3_BUCKET_INVALID_LENGTH", ex);
+    }
+
+    // set bucket name which is less than 3 characters length
+    s3BucketName = RandomStringUtils.randomAlphabetic(65);
+
+    try {
+      doPreExecute(s3BucketName);
+      fail("testPreExecuteInvalidBucketLength failed");
+    } catch (OMException ex) {
+      GenericTestUtils.assertExceptionContains("S3_BUCKET_INVALID_LENGTH", ex);
+    }
+  }
+
+  private OMRequest doPreExecute(String s3BucketName) throws Exception {
+    OMRequest omRequest =
+        TestOMRequestUtils.deleteS3BucketRequest(s3BucketName);
+
+    S3BucketDeleteRequest s3BucketDeleteRequest =
+        new S3BucketDeleteRequest(omRequest);
+
+    OMRequest modifiedOMRequest =
+        s3BucketDeleteRequest.preExecute(ozoneManager);
+
+    // As user name will be set both should not be equal.
+    Assert.assertNotEquals(omRequest, modifiedOMRequest);
+
+    return modifiedOMRequest;
+  }
+}

+ 153 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3InitiateMultipartUploadRequest.java

@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.multipart;
+
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.hadoop.ozone.om.request.TestOMRequestUtils;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+
+/**
+ * Tests S3 Initiate Multipart Upload request.
+ */
+public class TestS3InitiateMultipartUploadRequest
+    extends TestS3MultipartRequest {
+
+  @Test
+  public void testPreExecute() {
+    doPreExecuteInitiateMPU(UUID.randomUUID().toString(),
+        UUID.randomUUID().toString(), UUID.randomUUID().toString());
+  }
+
+
+  @Test
+  public void testValidateAndUpdateCache() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    // Add volume and bucket to DB.
+    TestOMRequestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager);
+
+    OMRequest modifiedRequest = doPreExecuteInitiateMPU(volumeName,
+        bucketName, keyName);
+
+    S3InitiateMultipartUploadRequest s3InitiateMultipartUploadRequest =
+        new S3InitiateMultipartUploadRequest(modifiedRequest);
+
+    OMClientResponse omClientResponse =
+        s3InitiateMultipartUploadRequest.validateAndUpdateCache(ozoneManager,
+            100L);
+
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omClientResponse.getOMResponse().getStatus());
+
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, modifiedRequest.getInitiateMultiPartUploadRequest()
+            .getKeyArgs().getMultipartUploadID());
+
+    Assert.assertNotNull(omMetadataManager.getOpenKeyTable().get(multipartKey));
+    Assert.assertNotNull(omMetadataManager.getMultipartInfoTable()
+        .get(multipartKey));
+
+    Assert.assertEquals(modifiedRequest.getInitiateMultiPartUploadRequest()
+            .getKeyArgs().getMultipartUploadID(),
+        omMetadataManager.getMultipartInfoTable().get(multipartKey)
+            .getUploadID());
+
+    Assert.assertEquals(modifiedRequest.getInitiateMultiPartUploadRequest()
+        .getKeyArgs().getModificationTime(),
+        omMetadataManager.getOpenKeyTable().get(multipartKey)
+        .getModificationTime());
+    Assert.assertEquals(modifiedRequest.getInitiateMultiPartUploadRequest()
+            .getKeyArgs().getModificationTime(),
+        omMetadataManager.getOpenKeyTable().get(multipartKey)
+            .getCreationTime());
+
+  }
+
+
+  @Test
+  public void testValidateAndUpdateCacheWithBucketNotFound() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    TestOMRequestUtils.addVolumeToDB(volumeName, omMetadataManager);
+
+    OMRequest modifiedRequest = doPreExecuteInitiateMPU(
+        volumeName, bucketName, keyName);
+
+    S3InitiateMultipartUploadRequest s3InitiateMultipartUploadRequest =
+        new S3InitiateMultipartUploadRequest(modifiedRequest);
+
+    OMClientResponse omClientResponse =
+        s3InitiateMultipartUploadRequest.validateAndUpdateCache(ozoneManager,
+            100L);
+
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.BUCKET_NOT_FOUND,
+        omClientResponse.getOMResponse().getStatus());
+
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, modifiedRequest.getInitiateMultiPartUploadRequest()
+            .getKeyArgs().getMultipartUploadID());
+
+    Assert.assertNull(omMetadataManager.getOpenKeyTable().get(multipartKey));
+    Assert.assertNull(omMetadataManager.getMultipartInfoTable()
+        .get(multipartKey));
+
+  }
+
+  @Test
+  public void testValidateAndUpdateCacheWithVolumeNotFound() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+
+    OMRequest modifiedRequest = doPreExecuteInitiateMPU(volumeName, bucketName,
+        keyName);
+
+    S3InitiateMultipartUploadRequest s3InitiateMultipartUploadRequest =
+        new S3InitiateMultipartUploadRequest(modifiedRequest);
+
+    OMClientResponse omClientResponse =
+        s3InitiateMultipartUploadRequest.validateAndUpdateCache(ozoneManager,
+            100L);
+
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.VOLUME_NOT_FOUND,
+        omClientResponse.getOMResponse().getStatus());
+
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, modifiedRequest.getInitiateMultiPartUploadRequest()
+            .getKeyArgs().getMultipartUploadID());
+
+    Assert.assertNull(omMetadataManager.getOpenKeyTable().get(multipartKey));
+    Assert.assertNull(omMetadataManager.getMultipartInfoTable()
+        .get(multipartKey));
+
+  }
+}

+ 178 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartRequest.java

@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.multipart;
+
+import java.io.IOException;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.audit.AuditMessage;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OMMetrics;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.request.TestOMRequestUtils;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMRequest;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+/**
+ * Base test class for S3 Multipart upload request.
+ */
+@SuppressWarnings("visibilitymodifier")
+public class TestS3MultipartRequest {
+  @Rule
+  public TemporaryFolder folder = new TemporaryFolder();
+
+  protected OzoneManager ozoneManager;
+  protected OMMetrics omMetrics;
+  protected OMMetadataManager omMetadataManager;
+  protected AuditLogger auditLogger;
+
+
+  @Before
+  public void setup() throws Exception {
+    ozoneManager = Mockito.mock(OzoneManager.class);
+    omMetrics = OMMetrics.create();
+    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
+    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
+        folder.newFolder().getAbsolutePath());
+    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration);
+    when(ozoneManager.getMetrics()).thenReturn(omMetrics);
+    when(ozoneManager.getMetadataManager()).thenReturn(omMetadataManager);
+    auditLogger = Mockito.mock(AuditLogger.class);
+    when(ozoneManager.getAuditLogger()).thenReturn(auditLogger);
+    Mockito.doNothing().when(auditLogger).logWrite(any(AuditMessage.class));
+  }
+
+
+  @After
+  public void stop() {
+    omMetrics.unRegister();
+    Mockito.framework().clearInlineMocks();
+  }
+
+  /**
+   * Perform preExecute of Initiate Multipart upload request for given
+   * volume, bucket and key name.
+   * @param volumeName
+   * @param bucketName
+   * @param keyName
+   * @return OMRequest - returned from preExecute.
+   */
+  protected OMRequest doPreExecuteInitiateMPU(
+      String volumeName, String bucketName, String keyName) {
+    OMRequest omRequest =
+        TestOMRequestUtils.createInitiateMPURequest(volumeName, bucketName,
+            keyName);
+
+    S3InitiateMultipartUploadRequest s3InitiateMultipartUploadRequest =
+        new S3InitiateMultipartUploadRequest(omRequest);
+
+    OMRequest modifiedRequest =
+        s3InitiateMultipartUploadRequest.preExecute(ozoneManager);
+
+    Assert.assertNotEquals(omRequest, modifiedRequest);
+    Assert.assertTrue(modifiedRequest.hasInitiateMultiPartUploadRequest());
+    Assert.assertNotNull(modifiedRequest.getInitiateMultiPartUploadRequest()
+        .getKeyArgs().getMultipartUploadID());
+    Assert.assertTrue(modifiedRequest.getInitiateMultiPartUploadRequest()
+        .getKeyArgs().getModificationTime() > 0);
+
+    return modifiedRequest;
+  }
+
+  /**
+   * Perform preExecute of Commit Multipart Upload request for given volume,
+   * bucket and keyName.
+   * @param volumeName
+   * @param bucketName
+   * @param keyName
+   * @param clientID
+   * @param multipartUploadID
+   * @param partNumber
+   * @return OMRequest - returned from preExecute.
+   */
+  protected OMRequest doPreExecuteCommitMPU(
+      String volumeName, String bucketName, String keyName,
+      long clientID, String multipartUploadID, int partNumber) {
+
+    // Just set dummy size
+    long dataSize = 100L;
+    OMRequest omRequest =
+        TestOMRequestUtils.createCommitPartMPURequest(volumeName, bucketName,
+            keyName, clientID, dataSize, multipartUploadID, partNumber);
+    S3MultipartUploadCommitPartRequest s3MultipartUploadCommitPartRequest =
+        new S3MultipartUploadCommitPartRequest(omRequest);
+
+
+    OMRequest modifiedRequest =
+        s3MultipartUploadCommitPartRequest.preExecute(ozoneManager);
+
+    // UserInfo and modification time is set.
+    Assert.assertNotEquals(omRequest, modifiedRequest);
+
+    return modifiedRequest;
+  }
+
+  /**
+   * Perform preExecute of Abort Multipart Upload request for given volume,
+   * bucket and keyName.
+   * @param volumeName
+   * @param bucketName
+   * @param keyName
+   * @param multipartUploadID
+   * @return OMRequest - returned from preExecute.
+   * @throws IOException
+   */
+  protected OMRequest doPreExecuteAbortMPU(
+      String volumeName, String bucketName, String keyName,
+      String multipartUploadID) throws IOException {
+
+    OMRequest omRequest =
+        TestOMRequestUtils.createAbortMPURequest(volumeName, bucketName,
+            keyName, multipartUploadID);
+
+
+    S3MultipartUploadAbortRequest s3MultipartUploadAbortRequest =
+        new S3MultipartUploadAbortRequest(omRequest);
+
+    OMRequest modifiedRequest =
+        s3MultipartUploadAbortRequest.preExecute(ozoneManager);
+
+    // UserInfo and modification time is set.
+    Assert.assertNotEquals(omRequest, modifiedRequest);
+
+    return modifiedRequest;
+
+  }
+
+
+}

+ 158 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartUploadAbortRequest.java

@@ -0,0 +1,158 @@
+package org.apache.hadoop.ozone.om.request.s3.multipart;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.hadoop.ozone.om.request.TestOMRequestUtils;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMRequest;
+
+/**
+ * Test Multipart upload abort request.
+ */
+public class TestS3MultipartUploadAbortRequest extends TestS3MultipartRequest {
+
+
+  @Test
+  public void testPreExecute() throws IOException {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    doPreExecuteAbortMPU(volumeName, bucketName, keyName,
+        UUID.randomUUID().toString());
+  }
+
+  @Test
+  public void testValidateAndUpdateCache() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    TestOMRequestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager);
+
+    OMRequest initiateMPURequest = doPreExecuteInitiateMPU(volumeName,
+        bucketName, keyName);
+
+    S3InitiateMultipartUploadRequest s3InitiateMultipartUploadRequest =
+        new S3InitiateMultipartUploadRequest(initiateMPURequest);
+
+    OMClientResponse omClientResponse =
+        s3InitiateMultipartUploadRequest.validateAndUpdateCache(ozoneManager,
+            1L);
+
+    String multipartUploadID = omClientResponse.getOMResponse()
+        .getInitiateMultiPartUploadResponse().getMultipartUploadID();
+
+    OMRequest abortMPURequest =
+        doPreExecuteAbortMPU(volumeName, bucketName, keyName,
+            multipartUploadID);
+
+    S3MultipartUploadAbortRequest s3MultipartUploadAbortRequest =
+        new S3MultipartUploadAbortRequest(abortMPURequest);
+
+    omClientResponse =
+        s3MultipartUploadAbortRequest.validateAndUpdateCache(ozoneManager, 2L);
+
+
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, multipartUploadID);
+
+    // Check table and response.
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omClientResponse.getOMResponse().getStatus());
+    Assert.assertNull(
+        omMetadataManager.getMultipartInfoTable().get(multipartKey));
+    Assert.assertNull(omMetadataManager.getOpenKeyTable().get(multipartKey));
+
+  }
+
+  @Test
+  public void testValidateAndUpdateCacheMultipartNotFound() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    TestOMRequestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager);
+
+    String multipartUploadID = "randomMPU";
+
+    OMRequest abortMPURequest =
+        doPreExecuteAbortMPU(volumeName, bucketName, keyName,
+            multipartUploadID);
+
+    S3MultipartUploadAbortRequest s3MultipartUploadAbortRequest =
+        new S3MultipartUploadAbortRequest(abortMPURequest);
+
+    OMClientResponse omClientResponse =
+        s3MultipartUploadAbortRequest.validateAndUpdateCache(ozoneManager, 2L);
+
+    // Check table and response.
+    Assert.assertEquals(
+        OzoneManagerProtocolProtos.Status.NO_SUCH_MULTIPART_UPLOAD_ERROR,
+        omClientResponse.getOMResponse().getStatus());
+
+  }
+
+
+  @Test
+  public void testValidateAndUpdateCacheVolumeNotFound() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+
+    String multipartUploadID = "randomMPU";
+
+    OMRequest abortMPURequest =
+        doPreExecuteAbortMPU(volumeName, bucketName, keyName,
+            multipartUploadID);
+
+    S3MultipartUploadAbortRequest s3MultipartUploadAbortRequest =
+        new S3MultipartUploadAbortRequest(abortMPURequest);
+
+    OMClientResponse omClientResponse =
+        s3MultipartUploadAbortRequest.validateAndUpdateCache(ozoneManager, 2L);
+
+    // Check table and response.
+    Assert.assertEquals(
+        OzoneManagerProtocolProtos.Status.VOLUME_NOT_FOUND,
+        omClientResponse.getOMResponse().getStatus());
+
+  }
+
+  @Test
+  public void testValidateAndUpdateCacheBucketNotFound() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+
+    TestOMRequestUtils.addVolumeToDB(volumeName, omMetadataManager);
+
+    String multipartUploadID = "randomMPU";
+
+    OMRequest abortMPURequest =
+        doPreExecuteAbortMPU(volumeName, bucketName, keyName,
+            multipartUploadID);
+
+    S3MultipartUploadAbortRequest s3MultipartUploadAbortRequest =
+        new S3MultipartUploadAbortRequest(abortMPURequest);
+
+    OMClientResponse omClientResponse =
+        s3MultipartUploadAbortRequest.validateAndUpdateCache(ozoneManager, 2L);
+
+    // Check table and response.
+    Assert.assertEquals(
+        OzoneManagerProtocolProtos.Status.BUCKET_NOT_FOUND,
+        omClientResponse.getOMResponse().getStatus());
+
+  }
+}

+ 209 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/TestS3MultipartUploadCommitPartRequest.java

@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.multipart;
+
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.ozone.om.request.TestOMRequestUtils;
+import org.apache.hadoop.ozone.om.response.OMClientResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMRequest;
+import org.apache.hadoop.util.Time;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.UUID;
+
+/**
+ * Tests S3 Multipart upload commit part request.
+ */
+public class TestS3MultipartUploadCommitPartRequest
+    extends TestS3MultipartRequest {
+
+  @Test
+  public void testPreExecute() {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    doPreExecuteCommitMPU(volumeName, bucketName, keyName, Time.now(),
+        UUID.randomUUID().toString(), 1);
+  }
+
+
+  @Test
+  public void testValidateAndUpdateCacheSuccess() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    TestOMRequestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager);
+
+    OMRequest initiateMPURequest = doPreExecuteInitiateMPU(volumeName,
+        bucketName, keyName);
+
+    S3InitiateMultipartUploadRequest s3InitiateMultipartUploadRequest =
+        new S3InitiateMultipartUploadRequest(initiateMPURequest);
+
+    OMClientResponse omClientResponse =
+        s3InitiateMultipartUploadRequest.validateAndUpdateCache(ozoneManager,
+        1L);
+
+    long clientID = Time.now();
+    String multipartUploadID = omClientResponse.getOMResponse()
+        .getInitiateMultiPartUploadResponse().getMultipartUploadID();
+
+    OMRequest commitMultipartRequest = doPreExecuteCommitMPU(volumeName,
+        bucketName, keyName, clientID, multipartUploadID, 1);
+
+    S3MultipartUploadCommitPartRequest s3MultipartUploadCommitPartRequest =
+        new S3MultipartUploadCommitPartRequest(commitMultipartRequest);
+
+    // Add key to open key table.
+    TestOMRequestUtils.addKeyToTable(true, volumeName, bucketName,
+        keyName, clientID, HddsProtos.ReplicationType.RATIS,
+        HddsProtos.ReplicationFactor.ONE, omMetadataManager);
+
+    omClientResponse =
+        s3MultipartUploadCommitPartRequest.validateAndUpdateCache(ozoneManager,
+        2L);
+
+
+    Assert.assertTrue(omClientResponse.getOMResponse().getStatus()
+        == OzoneManagerProtocolProtos.Status.OK);
+
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, multipartUploadID);
+
+    Assert.assertNotNull(
+        omMetadataManager.getMultipartInfoTable().get(multipartKey));
+    Assert.assertTrue(omMetadataManager.getMultipartInfoTable()
+        .get(multipartKey).getPartKeyInfoMap().size() == 1);
+    Assert.assertNull(omMetadataManager.getOpenKeyTable()
+        .get(omMetadataManager.getOpenKey(volumeName, bucketName, keyName,
+            clientID)));
+
+  }
+
+  @Test
+  public void testValidateAndUpdateCacheMultipartNotFound() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    TestOMRequestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager);
+
+
+    long clientID = Time.now();
+    String multipartUploadID = UUID.randomUUID().toString();
+
+    OMRequest commitMultipartRequest = doPreExecuteCommitMPU(volumeName,
+        bucketName, keyName, clientID, multipartUploadID, 1);
+
+    S3MultipartUploadCommitPartRequest s3MultipartUploadCommitPartRequest =
+        new S3MultipartUploadCommitPartRequest(commitMultipartRequest);
+
+    // Add key to open key table.
+    TestOMRequestUtils.addKeyToTable(true, volumeName, bucketName,
+        keyName, clientID, HddsProtos.ReplicationType.RATIS,
+        HddsProtos.ReplicationFactor.ONE, omMetadataManager);
+
+    OMClientResponse omClientResponse =
+        s3MultipartUploadCommitPartRequest.validateAndUpdateCache(ozoneManager,
+            2L);
+
+
+    Assert.assertTrue(omClientResponse.getOMResponse().getStatus()
+        == OzoneManagerProtocolProtos.Status.NO_SUCH_MULTIPART_UPLOAD_ERROR);
+
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, multipartUploadID);
+
+    Assert.assertNull(
+        omMetadataManager.getMultipartInfoTable().get(multipartKey));
+
+  }
+
+  @Test
+  public void testValidateAndUpdateCacheKeyNotFound() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    TestOMRequestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager);
+
+
+    long clientID = Time.now();
+    String multipartUploadID = UUID.randomUUID().toString();
+
+    OMRequest commitMultipartRequest = doPreExecuteCommitMPU(volumeName,
+        bucketName, keyName, clientID, multipartUploadID, 1);
+
+    // Don't add key to open table entry, and we are trying to commit this MPU
+    // part. It will fail with KEY_NOT_FOUND
+
+    S3MultipartUploadCommitPartRequest s3MultipartUploadCommitPartRequest =
+        new S3MultipartUploadCommitPartRequest(commitMultipartRequest);
+
+
+    OMClientResponse omClientResponse =
+        s3MultipartUploadCommitPartRequest.validateAndUpdateCache(ozoneManager,
+            2L);
+
+    Assert.assertTrue(omClientResponse.getOMResponse().getStatus()
+        == OzoneManagerProtocolProtos.Status.KEY_NOT_FOUND);
+
+  }
+
+
+  @Test
+  public void testValidateAndUpdateCacheBucketFound() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+
+    TestOMRequestUtils.addVolumeToDB(volumeName, omMetadataManager);
+
+
+    long clientID = Time.now();
+    String multipartUploadID = UUID.randomUUID().toString();
+
+    OMRequest commitMultipartRequest = doPreExecuteCommitMPU(volumeName,
+        bucketName, keyName, clientID, multipartUploadID, 1);
+
+    // Don't add key to open table entry, and we are trying to commit this MPU
+    // part. It will fail with KEY_NOT_FOUND
+
+    S3MultipartUploadCommitPartRequest s3MultipartUploadCommitPartRequest =
+        new S3MultipartUploadCommitPartRequest(commitMultipartRequest);
+
+
+    OMClientResponse omClientResponse =
+        s3MultipartUploadCommitPartRequest.validateAndUpdateCache(ozoneManager,
+            2L);
+
+    Assert.assertTrue(omClientResponse.getOMResponse().getStatus()
+        == OzoneManagerProtocolProtos.Status.BUCKET_NOT_FOUND);
+
+  }
+}

+ 24 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/multipart/package-info.java

@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * Package contains test classes for S3 MPU requests.
+ */
+
+package org.apache.hadoop.ozone.om.request.s3.multipart;

+ 42 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/TestOMResponseUtils.java

@@ -20,6 +20,12 @@
 package org.apache.hadoop.ozone.om.response;
 
 import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
+import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
+import org.apache.hadoop.ozone.om.request.s3.bucket.S3BucketCreateRequest;
+import org.apache.hadoop.ozone.om.response.bucket.OMBucketCreateResponse;
+import org.apache.hadoop.ozone.om.response.s3.bucket.S3BucketCreateResponse;
+import org.apache.hadoop.ozone.om.response.volume.OMVolumeCreateResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
 import org.apache.hadoop.util.Time;
 
 /**
@@ -37,4 +43,40 @@ public final class TestOMResponseUtils {
             "key1", "value1").build();
 
   }
+
+  public static S3BucketCreateResponse createS3BucketResponse(String userName,
+      String volumeName, String s3BucketName) {
+    OzoneManagerProtocolProtos.OMResponse omResponse =
+        OzoneManagerProtocolProtos.OMResponse.newBuilder()
+            .setCmdType(OzoneManagerProtocolProtos.Type.CreateS3Bucket)
+            .setStatus(OzoneManagerProtocolProtos.Status.OK)
+            .setSuccess(true)
+            .setCreateS3BucketResponse(
+                OzoneManagerProtocolProtos.S3CreateBucketResponse
+                    .getDefaultInstance())
+            .build();
+
+    OzoneManagerProtocolProtos.VolumeList volumeList =
+        OzoneManagerProtocolProtos.VolumeList.newBuilder()
+            .addVolumeNames(volumeName).build();
+
+    OmVolumeArgs omVolumeArgs = OmVolumeArgs.newBuilder()
+        .setOwnerName(userName).setAdminName(userName)
+        .setVolume(volumeName).setCreationTime(Time.now()).build();
+
+    OMVolumeCreateResponse omVolumeCreateResponse =
+        new OMVolumeCreateResponse(omVolumeArgs, volumeList, omResponse);
+
+
+    OmBucketInfo omBucketInfo = TestOMResponseUtils.createBucket(
+        volumeName, s3BucketName);
+    OMBucketCreateResponse omBucketCreateResponse =
+        new OMBucketCreateResponse(omBucketInfo, omResponse);
+
+    String s3Mapping = S3BucketCreateRequest.formatS3MappingName(volumeName,
+        s3BucketName);
+    return
+        new S3BucketCreateResponse(omVolumeCreateResponse,
+            omBucketCreateResponse, s3BucketName, s3Mapping, omResponse);
+  }
 }

+ 3 - 38
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/bucket/TestS3BucketCreateResponse.java

@@ -31,14 +31,8 @@ import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.ozone.om.OMConfigKeys;
 import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
-import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
-import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
 import org.apache.hadoop.ozone.om.request.s3.bucket.S3BucketCreateRequest;
 import org.apache.hadoop.ozone.om.response.TestOMResponseUtils;
-import org.apache.hadoop.ozone.om.response.bucket.OMBucketCreateResponse;
-import org.apache.hadoop.ozone.om.response.volume.OMVolumeCreateResponse;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
-import org.apache.hadoop.util.Time;
 import org.apache.hadoop.utils.db.BatchOperation;
 
 /**
@@ -66,40 +60,11 @@ public class TestS3BucketCreateResponse {
   public void testAddToDBBatch() throws Exception {
     String userName = UUID.randomUUID().toString();
     String s3BucketName = UUID.randomUUID().toString();
-
-    OzoneManagerProtocolProtos.OMResponse omResponse =
-        OzoneManagerProtocolProtos.OMResponse.newBuilder()
-            .setCmdType(OzoneManagerProtocolProtos.Type.CreateS3Bucket)
-            .setStatus(OzoneManagerProtocolProtos.Status.OK)
-            .setSuccess(true)
-            .setCreateS3BucketResponse(
-                OzoneManagerProtocolProtos.S3CreateBucketResponse
-                    .getDefaultInstance())
-            .build();
-
     String volumeName = S3BucketCreateRequest.formatOzoneVolumeName(userName);
-    OzoneManagerProtocolProtos.VolumeList volumeList =
-        OzoneManagerProtocolProtos.VolumeList.newBuilder()
-            .addVolumeNames(volumeName).build();
-
-    OmVolumeArgs omVolumeArgs = OmVolumeArgs.newBuilder()
-        .setOwnerName(userName).setAdminName(userName)
-        .setVolume(volumeName).setCreationTime(Time.now()).build();
-
-    OMVolumeCreateResponse omVolumeCreateResponse =
-        new OMVolumeCreateResponse(omVolumeArgs, volumeList, omResponse);
-
-
-    OmBucketInfo omBucketInfo = TestOMResponseUtils.createBucket(
-        volumeName, s3BucketName);
-    OMBucketCreateResponse omBucketCreateResponse =
-        new OMBucketCreateResponse(omBucketInfo, omResponse);
 
-    String s3Mapping = S3BucketCreateRequest.formatS3MappingName(volumeName,
-        s3BucketName);
     S3BucketCreateResponse s3BucketCreateResponse =
-        new S3BucketCreateResponse(omVolumeCreateResponse,
-            omBucketCreateResponse, s3BucketName, s3Mapping, omResponse);
+        TestOMResponseUtils.createS3BucketResponse(userName, volumeName,
+            s3BucketName);
 
     s3BucketCreateResponse.addToDBBatch(omMetadataManager, batchOperation);
 
@@ -107,7 +72,7 @@ public class TestS3BucketCreateResponse {
     omMetadataManager.getStore().commitBatchOperation(batchOperation);
 
     Assert.assertNotNull(omMetadataManager.getS3Table().get(s3BucketName));
-    Assert.assertEquals(s3Mapping,
+    Assert.assertEquals(s3BucketCreateResponse.getS3Mapping(),
         omMetadataManager.getS3Table().get(s3BucketName));
     Assert.assertNotNull(omMetadataManager.getVolumeTable().get(
         omMetadataManager.getVolumeKey(volumeName)));

+ 73 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/bucket/TestS3BucketDeleteResponse.java

@@ -0,0 +1,73 @@
+package org.apache.hadoop.ozone.om.response.s3.bucket;
+
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.ozone.om.request.s3.bucket.S3BucketCreateRequest;
+import org.apache.hadoop.ozone.om.response.TestOMResponseUtils;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .S3DeleteBucketResponse;
+import org.apache.hadoop.utils.db.BatchOperation;
+
+
+
+/**
+ * Tests S3BucketDeleteResponse.
+ */
+public class TestS3BucketDeleteResponse {
+  @Rule
+  public TemporaryFolder folder = new TemporaryFolder();
+
+  private OMMetadataManager omMetadataManager;
+  private BatchOperation batchOperation;
+
+  @Before
+  public void setup() throws Exception {
+    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
+    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
+        folder.newFolder().getAbsolutePath());
+    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration);
+    batchOperation = omMetadataManager.getStore().initBatchOperation();
+  }
+
+  @Test
+  public void testAddToDBBatch() throws Exception {
+    String s3BucketName = UUID.randomUUID().toString();
+    String userName = "ozone";
+    String volumeName = S3BucketCreateRequest.formatOzoneVolumeName(userName);
+    S3BucketCreateResponse s3BucketCreateResponse =
+        TestOMResponseUtils.createS3BucketResponse(userName, volumeName,
+            s3BucketName);
+
+    s3BucketCreateResponse.addToDBBatch(omMetadataManager, batchOperation);
+
+    OMResponse omResponse = OMResponse.newBuilder().setCmdType(
+        OzoneManagerProtocolProtos.Type.DeleteS3Bucket).setStatus(
+        OzoneManagerProtocolProtos.Status.OK).setSuccess(true)
+        .setDeleteS3BucketResponse(S3DeleteBucketResponse.newBuilder()).build();
+
+    S3BucketDeleteResponse s3BucketDeleteResponse =
+        new S3BucketDeleteResponse(s3BucketName, volumeName, omResponse);
+
+    s3BucketDeleteResponse.addToDBBatch(omMetadataManager, batchOperation);
+
+    omMetadataManager.getStore().commitBatchOperation(batchOperation);
+
+    // Check now s3 bucket exists or not.
+    Assert.assertNull(omMetadataManager.getS3Table().get(s3BucketName));
+    Assert.assertNull(omMetadataManager.getBucketTable().get(
+        omMetadataManager.getBucketKey(volumeName, s3BucketName)));
+  }
+}

+ 63 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/multipart/TestS3InitiateMultipartUploadResponse.java

@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.ozone.om.response.s3.multipart;
+
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Class tests S3 Initiate MPU response.
+ */
+public class TestS3InitiateMultipartUploadResponse
+    extends TestS3MultipartResponse {
+
+  @Test
+  public void addDBToBatch() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+    String multipartUploadID = UUID.randomUUID().toString();
+
+    S3InitiateMultipartUploadResponse s3InitiateMultipartUploadResponse =
+        createS3InitiateMPUResponse(volumeName, bucketName, keyName,
+            multipartUploadID);
+
+
+    s3InitiateMultipartUploadResponse.addToDBBatch(omMetadataManager,
+        batchOperation);
+
+    // Do manual commit and see whether addToBatch is successful or not.
+    omMetadataManager.getStore().commitBatchOperation(batchOperation);
+
+
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, multipartUploadID);
+
+    Assert.assertNotNull(omMetadataManager.getOpenKeyTable().get(multipartKey));
+    Assert.assertNotNull(omMetadataManager.getMultipartInfoTable()
+        .get(multipartKey));
+
+    Assert.assertEquals(multipartUploadID,
+        omMetadataManager.getMultipartInfoTable().get(multipartKey)
+            .getUploadID());
+  }
+}

+ 143 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/multipart/TestS3MultipartResponse.java

@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.hadoop.ozone.om.response.s3.multipart;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.UUID;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .KeyInfo;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .OMResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .MultipartUploadAbortResponse;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .PartKeyInfo;
+import org.apache.hadoop.util.Time;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
+import org.apache.hadoop.ozone.om.OMConfigKeys;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OmMetadataManagerImpl;
+import org.apache.hadoop.utils.db.BatchOperation;
+
+/**
+ * Base test class for S3 MPU response.
+ */
+
+@SuppressWarnings("VisibilityModifier")
+public class TestS3MultipartResponse {
+
+  @Rule
+  public TemporaryFolder folder = new TemporaryFolder();
+
+  protected OMMetadataManager omMetadataManager;
+  protected BatchOperation batchOperation;
+
+  @Before
+  public void setup() throws Exception {
+    OzoneConfiguration ozoneConfiguration = new OzoneConfiguration();
+    ozoneConfiguration.set(OMConfigKeys.OZONE_OM_DB_DIRS,
+        folder.newFolder().getAbsolutePath());
+    omMetadataManager = new OmMetadataManagerImpl(ozoneConfiguration);
+    batchOperation = omMetadataManager.getStore().initBatchOperation();
+  }
+
+
+  public S3InitiateMultipartUploadResponse createS3InitiateMPUResponse(
+      String volumeName, String bucketName, String keyName,
+      String multipartUploadID) {
+    OmMultipartKeyInfo multipartKeyInfo = new OmMultipartKeyInfo(
+        multipartUploadID, new HashMap<>());
+
+    OmKeyInfo omKeyInfo = new OmKeyInfo.Builder()
+        .setVolumeName(volumeName)
+        .setBucketName(bucketName)
+        .setKeyName(keyName)
+        .setCreationTime(Time.now())
+        .setModificationTime(Time.now())
+        .setReplicationType(HddsProtos.ReplicationType.RATIS)
+        .setReplicationFactor(HddsProtos.ReplicationFactor.ONE)
+        .setOmKeyLocationInfos(Collections.singletonList(
+            new OmKeyLocationInfoGroup(0, new ArrayList<>())))
+        .build();
+
+    OMResponse omResponse = OMResponse.newBuilder()
+            .setCmdType(OzoneManagerProtocolProtos.Type.InitiateMultiPartUpload)
+            .setStatus(OzoneManagerProtocolProtos.Status.OK)
+            .setSuccess(true).setInitiateMultiPartUploadResponse(
+            OzoneManagerProtocolProtos.MultipartInfoInitiateResponse
+                .newBuilder().setVolumeName(volumeName)
+                .setBucketName(bucketName)
+                .setKeyName(keyName)
+                .setMultipartUploadID(multipartUploadID)).build();
+
+    return new S3InitiateMultipartUploadResponse(multipartKeyInfo, omKeyInfo,
+            omResponse);
+  }
+
+  public S3MultipartUploadAbortResponse createS3AbortMPUResponse(
+      String multipartKey, long timeStamp,
+      OmMultipartKeyInfo omMultipartKeyInfo) {
+    OMResponse omResponse = OMResponse.newBuilder()
+        .setCmdType(OzoneManagerProtocolProtos.Type.AbortMultiPartUpload)
+        .setStatus(OzoneManagerProtocolProtos.Status.OK)
+        .setSuccess(true)
+        .setAbortMultiPartUploadResponse(
+            MultipartUploadAbortResponse.newBuilder().build()).build();
+
+    return new S3MultipartUploadAbortResponse(multipartKey, Time.now(),
+            omMultipartKeyInfo,
+            omResponse);
+  }
+
+
+  public void addPart(int partNumber, PartKeyInfo partKeyInfo,
+      OmMultipartKeyInfo omMultipartKeyInfo) {
+    omMultipartKeyInfo.addPartKeyInfo(partNumber, partKeyInfo);
+  }
+
+  public PartKeyInfo createPartKeyInfo(
+      String volumeName, String bucketName, String keyName, int partNumber) {
+    return PartKeyInfo.newBuilder()
+        .setPartNumber(partNumber)
+        .setPartName(omMetadataManager.getMultipartKey(volumeName,
+            bucketName, keyName, UUID.randomUUID().toString()))
+        .setPartKeyInfo(KeyInfo.newBuilder()
+            .setVolumeName(volumeName)
+            .setBucketName(bucketName)
+            .setKeyName(keyName)
+            .setDataSize(100L) // Just set dummy size for testing
+            .setCreationTime(Time.now())
+            .setModificationTime(Time.now())
+            .setType(HddsProtos.ReplicationType.RATIS)
+            .setFactor(HddsProtos.ReplicationFactor.ONE).build()).build();
+  }
+}

+ 129 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/multipart/TestS3MultipartUploadAbortResponse.java

@@ -0,0 +1,129 @@
+package org.apache.hadoop.ozone.om.response.s3.multipart;
+
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.hadoop.ozone.OmUtils;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos
+    .PartKeyInfo;
+import org.apache.hadoop.util.Time;
+
+/**
+ * Test multipart upload abort response.
+ */
+public class TestS3MultipartUploadAbortResponse
+    extends TestS3MultipartResponse {
+
+
+  @Test
+  public void testAddDBToBatch() throws Exception {
+
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+    String multipartUploadID = UUID.randomUUID().toString();
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, multipartUploadID);
+
+    S3InitiateMultipartUploadResponse s3InitiateMultipartUploadResponse =
+        createS3InitiateMPUResponse(volumeName, bucketName, keyName,
+            multipartUploadID);
+
+    s3InitiateMultipartUploadResponse.addToDBBatch(omMetadataManager,
+        batchOperation);
+
+    S3MultipartUploadAbortResponse s3MultipartUploadAbortResponse =
+        createS3AbortMPUResponse(multipartKey, Time.now(),
+            s3InitiateMultipartUploadResponse.getOmMultipartKeyInfo());
+
+    s3MultipartUploadAbortResponse.addToDBBatch(omMetadataManager,
+        batchOperation);
+
+    omMetadataManager.getStore().commitBatchOperation(batchOperation);
+
+    Assert.assertNull(omMetadataManager.getOpenKeyTable().get(multipartKey));
+    Assert.assertNull(
+        omMetadataManager.getMultipartInfoTable().get(multipartKey));
+
+    // As no parts are created, so no entries should be there in delete table.
+    Assert.assertTrue(omMetadataManager.countRowsInTable(
+        omMetadataManager.getDeletedTable()) == 0);
+  }
+
+  @Test
+  public void testAddDBToBatchWithParts() throws Exception {
+
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+    String keyName = UUID.randomUUID().toString();
+    String multipartUploadID = UUID.randomUUID().toString();
+    String multipartKey = omMetadataManager.getMultipartKey(volumeName,
+        bucketName, keyName, multipartUploadID);
+
+    S3InitiateMultipartUploadResponse s3InitiateMultipartUploadResponse =
+        createS3InitiateMPUResponse(volumeName, bucketName, keyName,
+            multipartUploadID);
+
+    s3InitiateMultipartUploadResponse.addToDBBatch(omMetadataManager,
+        batchOperation);
+
+
+    // Add some dummy parts for testing.
+    // Not added any key locations, as this just test is to see entries are
+    // adding to delete table or not.
+
+    OmMultipartKeyInfo omMultipartKeyInfo =
+        s3InitiateMultipartUploadResponse.getOmMultipartKeyInfo();
+
+    PartKeyInfo part1 = createPartKeyInfo(volumeName, bucketName,
+        keyName, 1);
+    PartKeyInfo part2 = createPartKeyInfo(volumeName, bucketName,
+        keyName, 1);
+
+    addPart(1, part1, omMultipartKeyInfo);
+    addPart(2, part2, omMultipartKeyInfo);
+
+
+    long timeStamp = Time.now();
+    S3MultipartUploadAbortResponse s3MultipartUploadAbortResponse =
+        createS3AbortMPUResponse(multipartKey, timeStamp,
+            s3InitiateMultipartUploadResponse.getOmMultipartKeyInfo());
+
+    s3MultipartUploadAbortResponse.addToDBBatch(omMetadataManager,
+        batchOperation);
+
+    omMetadataManager.getStore().commitBatchOperation(batchOperation);
+
+    Assert.assertNull(omMetadataManager.getOpenKeyTable().get(multipartKey));
+    Assert.assertNull(
+        omMetadataManager.getMultipartInfoTable().get(multipartKey));
+
+    // As 2 parts are created, so 2 entries should be there in delete table.
+    Assert.assertTrue(omMetadataManager.countRowsInTable(
+        omMetadataManager.getDeletedTable()) == 2);
+
+    String part1DeletedKeyName = OmUtils.getDeletedKeyName(
+        omMultipartKeyInfo.getPartKeyInfo(1).getPartName(),
+        timeStamp);
+
+    String part2DeletedKeyName = OmUtils.getDeletedKeyName(
+        omMultipartKeyInfo.getPartKeyInfo(2).getPartName(),
+        timeStamp);
+
+    Assert.assertNotNull(omMetadataManager.getDeletedTable().get(
+        part1DeletedKeyName));
+    Assert.assertNotNull(omMetadataManager.getDeletedTable().get(
+        part2DeletedKeyName));
+
+    Assert.assertEquals(OmKeyInfo.getFromProtobuf(part1.getPartKeyInfo()),
+        omMetadataManager.getDeletedTable().get(part1DeletedKeyName));
+
+    Assert.assertEquals(OmKeyInfo.getFromProtobuf(part2.getPartKeyInfo()),
+        omMetadataManager.getDeletedTable().get(part2DeletedKeyName));
+  }
+
+}

+ 24 - 0
hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/response/s3/multipart/package-info.java

@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/**
+ * Package contains test classes for S3 MPU responses.
+ */
+
+package org.apache.hadoop.ozone.om.response.s3.multipart;

+ 2 - 1
hadoop-project/pom.xml

@@ -70,6 +70,7 @@
     <!-- jackson versions -->
     <jackson.version>1.9.13</jackson.version>
     <jackson2.version>2.9.9</jackson2.version>
+    <jackson2.databind.version>2.9.9.1</jackson2.databind.version>
 
     <!-- httpcomponents versions -->
     <httpclient.version>4.5.6</httpclient.version>
@@ -1078,7 +1079,7 @@
       <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-databind</artifactId>
-        <version>${jackson2.version}</version>
+        <version>${jackson2.databind.version}</version>
       </dependency>
       <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>

+ 2 - 2
hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/Constants.java

@@ -202,12 +202,12 @@ public final class Constants {
 
   // size of each of or multipart pieces in bytes
   public static final String MULTIPART_SIZE = "fs.s3a.multipart.size";
-  public static final long DEFAULT_MULTIPART_SIZE = 104857600; // 100 MB
+  public static final long DEFAULT_MULTIPART_SIZE = 67108864; // 64M
 
   // minimum size in bytes before we start a multipart uploads or copy
   public static final String MIN_MULTIPART_THRESHOLD =
       "fs.s3a.multipart.threshold";
-  public static final long DEFAULT_MIN_MULTIPART_THRESHOLD = Integer.MAX_VALUE;
+  public static final long DEFAULT_MIN_MULTIPART_THRESHOLD = 134217728; // 128M
 
   //enable multiobject-delete calls?
   public static final String ENABLE_MULTI_DELETE =

+ 7 - 20
hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/S3AFileSystem.java

@@ -2307,30 +2307,16 @@ public class S3AFileSystem extends FileSystem implements StreamCapabilities,
    * Implements the specific logic to reject root directory deletion.
    * 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.
+   * directories is never allowed.
    * @param status filesystem status
    * @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);
-    }
+      boolean recursive) {
+    LOG.error("S3A: Cannot delete the {} root directory. Path: {}. Recursive: "
+            + "{}", bucket, status.getPath(), recursive);
+    return false;
   }
 
   /**
@@ -2623,7 +2609,8 @@ public class S3AFileSystem extends FileSystem implements StreamCapabilities,
     // Check MetadataStore, if any.
     PathMetadata pm = null;
     if (hasMetadataStore()) {
-      pm = S3Guard.getWithTtl(metadataStore, path, ttlTimeProvider);
+      pm = S3Guard.getWithTtl(metadataStore, path, ttlTimeProvider,
+          needEmptyDirectoryFlag);
     }
     Set<Path> tombstones = Collections.emptySet();
     if (pm != null) {

+ 5 - 6
hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/DDBPathMetadata.java

@@ -31,7 +31,8 @@ public class DDBPathMetadata extends PathMetadata {
   private boolean isAuthoritativeDir;
 
   public DDBPathMetadata(PathMetadata pmd) {
-    super(pmd.getFileStatus(), pmd.isEmptyDirectory(), pmd.isDeleted());
+    super(pmd.getFileStatus(), pmd.isEmptyDirectory(), pmd.isDeleted(),
+        pmd.getLastUpdated());
     this.isAuthoritativeDir = false;
     this.setLastUpdated(pmd.getLastUpdated());
   }
@@ -42,16 +43,15 @@ public class DDBPathMetadata extends PathMetadata {
   }
 
   public DDBPathMetadata(S3AFileStatus fileStatus, Tristate isEmptyDir,
-      boolean isDeleted) {
-    super(fileStatus, isEmptyDir, isDeleted);
+      boolean isDeleted, long lastUpdated) {
+    super(fileStatus, isEmptyDir, isDeleted, lastUpdated);
     this.isAuthoritativeDir = false;
   }
 
   public DDBPathMetadata(S3AFileStatus fileStatus, Tristate isEmptyDir,
       boolean isDeleted, boolean isAuthoritativeDir, long lastUpdated) {
-    super(fileStatus, isEmptyDir, isDeleted);
+    super(fileStatus, isEmptyDir, isDeleted, lastUpdated);
     this.isAuthoritativeDir = isAuthoritativeDir;
-    this.setLastUpdated(lastUpdated);
   }
 
   public boolean isAuthoritativeDir() {
@@ -74,7 +74,6 @@ public class DDBPathMetadata extends PathMetadata {
   @Override public String toString() {
     return "DDBPathMetadata{" +
         "isAuthoritativeDir=" + isAuthoritativeDir +
-        ", lastUpdated=" + this.getLastUpdated() +
         ", PathMetadata=" + super.toString() +
         '}';
   }

+ 31 - 10
hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/DirListingMetadata.java

@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -62,7 +63,7 @@ public class DirListingMetadata extends ExpirableMetadata {
    * Create a directory listing metadata container.
    *
    * @param path Path of the directory. If this path has a host component, then
-   *     all paths added later via {@link #put(S3AFileStatus)} must also have
+   *     all paths added later via {@link #put(PathMetadata)} must also have
    *     the same host.
    * @param listing Entries in the directory.
    * @param isAuthoritative true iff listing is the full contents of the
@@ -203,9 +204,9 @@ public class DirListingMetadata extends ExpirableMetadata {
    * Replace an entry with a tombstone.
    * @param childPath path of entry to replace.
    */
-  public void markDeleted(Path childPath) {
+  public void markDeleted(Path childPath, long lastUpdated) {
     checkChildPath(childPath);
-    listMap.put(childPath, PathMetadata.tombstone(childPath));
+    listMap.put(childPath, PathMetadata.tombstone(childPath, lastUpdated));
   }
 
   /**
@@ -222,16 +223,17 @@ public class DirListingMetadata extends ExpirableMetadata {
    * Add an entry to the directory listing.  If this listing already contains a
    * {@code FileStatus} with the same path, it will be replaced.
    *
-   * @param childFileStatus entry to add to this directory listing.
+   * @param childPathMetadata entry to add to this directory listing.
    * @return true if the status was added or replaced with a new value. False
    * if the same FileStatus value was already present.
    */
-  public boolean put(S3AFileStatus childFileStatus) {
-    Preconditions.checkNotNull(childFileStatus,
-        "childFileStatus must be non-null");
-    Path childPath = childStatusToPathKey(childFileStatus);
-    PathMetadata newValue = new PathMetadata(childFileStatus);
-    PathMetadata oldValue = listMap.put(childPath, newValue);
+  public boolean put(PathMetadata childPathMetadata) {
+    Preconditions.checkNotNull(childPathMetadata,
+        "childPathMetadata must be non-null");
+    final S3AFileStatus fileStatus = childPathMetadata.getFileStatus();
+    Path childPath = childStatusToPathKey(fileStatus);
+    PathMetadata newValue = childPathMetadata;
+    PathMetadata oldValue = listMap.put(childPath, childPathMetadata);
     return oldValue == null || !oldValue.equals(newValue);
   }
 
@@ -245,6 +247,25 @@ public class DirListingMetadata extends ExpirableMetadata {
         '}';
   }
 
+  /**
+   * Remove expired entries from the listing based on TTL.
+   * @param ttl the ttl time
+   * @param now the current time
+   */
+  public synchronized void removeExpiredEntriesFromListing(long ttl,
+      long now) {
+    final Iterator<Map.Entry<Path, PathMetadata>> iterator =
+        listMap.entrySet().iterator();
+    while (iterator.hasNext()) {
+      final Map.Entry<Path, PathMetadata> entry = iterator.next();
+      // we filter iff the lastupdated is not 0 and the entry is expired
+      if (entry.getValue().getLastUpdated() != 0
+          && (entry.getValue().getLastUpdated() + ttl) <= now) {
+        iterator.remove();
+      }
+    }
+  }
+
   /**
    * Log contents to supplied StringBuilder in a pretty fashion.
    * @param sb target StringBuilder

+ 15 - 8
hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/DynamoDBMetadataStore.java

@@ -585,9 +585,8 @@ public class DynamoDBMetadataStore implements MetadataStore,
     if (tombstone) {
       Preconditions.checkArgument(ttlTimeProvider != null, "ttlTimeProvider "
           + "must not be null");
-      final PathMetadata pmTombstone = PathMetadata.tombstone(path);
-      // update the last updated field of record when putting a tombstone
-      pmTombstone.setLastUpdated(ttlTimeProvider.getNow());
+      final PathMetadata pmTombstone = PathMetadata.tombstone(path,
+          ttlTimeProvider.getNow());
       Item item = PathMetadataDynamoDBTranslation.pathMetadataToItem(
           new DDBPathMetadata(pmTombstone));
       writeOp.retry(
@@ -785,8 +784,16 @@ public class DynamoDBMetadataStore implements MetadataStore,
           // get a null in DDBPathMetadata.
           DDBPathMetadata dirPathMeta = get(path);
 
-          return getDirListingMetadataFromDirMetaAndList(path, metas,
-              dirPathMeta);
+          // Filter expired entries.
+          final DirListingMetadata dirListing =
+              getDirListingMetadataFromDirMetaAndList(path, metas,
+                  dirPathMeta);
+          if(dirListing != null) {
+            dirListing.removeExpiredEntriesFromListing(
+                ttlTimeProvider.getMetadataTtl(),
+                ttlTimeProvider.getNow());
+          }
+          return dirListing;
         });
   }
 
@@ -947,7 +954,7 @@ public class DynamoDBMetadataStore implements MetadataStore,
         S3AFileStatus status = makeDirStatus(username, parent);
         LOG.debug("Adding new ancestor entry {}", status);
         DDBPathMetadata meta = new DDBPathMetadata(status, Tristate.FALSE,
-            false);
+            false, ttlTimeProvider.getNow());
         newDirs.add(meta);
         // Do not update ancestor state here, as it
         // will happen in the innerPut() call. Were we to add it
@@ -1039,8 +1046,8 @@ public class DynamoDBMetadataStore implements MetadataStore,
       for (Path meta : pathsToDelete) {
         Preconditions.checkArgument(ttlTimeProvider != null, "ttlTimeProvider"
             + " must not be null");
-        final PathMetadata pmTombstone = PathMetadata.tombstone(meta);
-        pmTombstone.setLastUpdated(ttlTimeProvider.getNow());
+        final PathMetadata pmTombstone = PathMetadata.tombstone(meta,
+            ttlTimeProvider.getNow());
         tombstones.add(new DDBPathMetadata(pmTombstone));
       }
       // sort all the tombstones lowest first.

+ 29 - 21
hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/LocalMetadataStore.java

@@ -129,32 +129,31 @@ public class LocalMetadataStore implements MetadataStore {
   @Override
   public void delete(Path p)
       throws IOException {
-    doDelete(p, false, true, ttlTimeProvider);
+    doDelete(p, false, true);
   }
 
   @Override
   public void forgetMetadata(Path p) throws IOException {
-    doDelete(p, false, false, null);
+    doDelete(p, false, false);
   }
 
   @Override
   public void deleteSubtree(Path path)
       throws IOException {
-    doDelete(path, true, true, ttlTimeProvider);
+    doDelete(path, true, true);
   }
 
   private synchronized void doDelete(Path p, boolean recursive,
-      boolean tombstone, ITtlTimeProvider ttlTp) {
+      boolean tombstone) {
 
     Path path = standardize(p);
 
     // Delete entry from file cache, then from cached parent directory, if any
-
-    deleteCacheEntries(path, tombstone, ttlTp);
+    deleteCacheEntries(path, tombstone);
 
     if (recursive) {
       // Remove all entries that have this dir as path prefix.
-      deleteEntryByAncestor(path, localCache, tombstone, ttlTp);
+      deleteEntryByAncestor(path, localCache, tombstone, ttlTimeProvider);
     }
   }
 
@@ -202,8 +201,16 @@ public class LocalMetadataStore implements MetadataStore {
       LOG.debug("listChildren({}) -> {}", path,
           listing == null ? "null" : listing.prettyPrint());
     }
-    // Make a copy so callers can mutate without affecting our state
-    return listing == null ? null : new DirListingMetadata(listing);
+
+    if (listing != null) {
+      listing.removeExpiredEntriesFromListing(
+          ttlTimeProvider.getMetadataTtl(), ttlTimeProvider.getNow());
+      LOG.debug("listChildren [after removing expired entries] ({}) -> {}",
+          path, listing.prettyPrint());
+      // Make a copy so callers can mutate without affecting our state
+      return new DirListingMetadata(listing);
+    }
+    return null;
   }
 
   @Override
@@ -309,15 +316,17 @@ public class LocalMetadataStore implements MetadataStore {
           DirListingMetadata parentDirMeta =
               new DirListingMetadata(parentPath, DirListingMetadata.EMPTY_DIR,
                   false);
+          parentDirMeta.setLastUpdated(meta.getLastUpdated());
           parentMeta.setDirListingMetadata(parentDirMeta);
         }
 
-        // Add the child status to the listing
-        parentMeta.getDirListingMeta().put(status);
+        // Add the child pathMetadata to the listing
+        parentMeta.getDirListingMeta().put(meta);
 
         // Mark the listing entry as deleted if the meta is set to deleted
         if(meta.isDeleted()) {
-          parentMeta.getDirListingMeta().markDeleted(path);
+          parentMeta.getDirListingMeta().markDeleted(path,
+              ttlTimeProvider.getNow());
         }
       }
     }
@@ -463,8 +472,8 @@ public class LocalMetadataStore implements MetadataStore {
           if(meta.hasDirMeta()){
             cache.invalidate(path);
           } else if(tombstone && meta.hasPathMeta()){
-            final PathMetadata pmTombstone = PathMetadata.tombstone(path);
-            pmTombstone.setLastUpdated(ttlTimeProvider.getNow());
+            final PathMetadata pmTombstone = PathMetadata.tombstone(path,
+                ttlTimeProvider.getNow());
             meta.setPathMetadata(pmTombstone);
           } else {
             cache.invalidate(path);
@@ -489,8 +498,7 @@ public class LocalMetadataStore implements MetadataStore {
    * Update fileCache and dirCache to reflect deletion of file 'f'.  Call with
    * lock held.
    */
-  private void deleteCacheEntries(Path path, boolean tombstone,
-      ITtlTimeProvider ttlTp) {
+  private void deleteCacheEntries(Path path, boolean tombstone) {
     LocalMetadataEntry entry = localCache.getIfPresent(path);
     // If there's no entry, delete should silently succeed
     // (based on MetadataStoreTestBase#testDeleteNonExisting)
@@ -503,8 +511,8 @@ public class LocalMetadataStore implements MetadataStore {
     LOG.debug("delete file entry for {}", path);
     if(entry.hasPathMeta()){
       if (tombstone) {
-        PathMetadata pmd = PathMetadata.tombstone(path);
-        pmd.setLastUpdated(ttlTp.getNow());
+        PathMetadata pmd = PathMetadata.tombstone(path,
+            ttlTimeProvider.getNow());
         entry.setPathMetadata(pmd);
       } else {
         entry.setPathMetadata(null);
@@ -530,8 +538,7 @@ public class LocalMetadataStore implements MetadataStore {
       if (dir != null) {
         LOG.debug("removing parent's entry for {} ", path);
         if (tombstone) {
-          dir.markDeleted(path);
-          dir.setLastUpdated(ttlTp.getNow());
+          dir.markDeleted(path, ttlTimeProvider.getNow());
         } else {
           dir.remove(path);
         }
@@ -613,7 +620,8 @@ public class LocalMetadataStore implements MetadataStore {
       if (directory == null || directory.isDeleted()) {
         S3AFileStatus status = new S3AFileStatus(Tristate.FALSE, parent,
             username);
-        PathMetadata meta = new PathMetadata(status, Tristate.FALSE, false);
+        PathMetadata meta = new PathMetadata(status, Tristate.FALSE, false,
+            ttlTimeProvider.getNow());
         newDirs.add(meta);
       } else {
         break;

+ 56 - 6
hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/PathMetadata.java

@@ -27,7 +27,9 @@ import org.apache.hadoop.fs.s3a.Tristate;
 
 /**
  * {@code PathMetadata} models path metadata stored in the
- * {@link MetadataStore}.
+ * {@link MetadataStore}. The lastUpdated field is implicitly set to 0 in the
+ * constructors without that parameter to show that it will be initialized
+ * with 0 if not set otherwise.
  */
 @InterfaceAudience.Private
 @InterfaceStability.Evolving
@@ -39,38 +41,85 @@ public class PathMetadata extends ExpirableMetadata {
 
   /**
    * Create a tombstone from the current time.
+   * It is mandatory to set the lastUpdated field to update when the
+   * tombstone state has changed to set when the entry got deleted.
+   *
    * @param path path to tombstone
+   * @param lastUpdated last updated time on which expiration is based.
    * @return the entry.
    */
-  public static PathMetadata tombstone(Path path) {
+  public static PathMetadata tombstone(Path path, long lastUpdated) {
     S3AFileStatus s3aStatus = new S3AFileStatus(0,
         System.currentTimeMillis(), path, 0, null,
         null, null);
-    return new PathMetadata(s3aStatus, Tristate.UNKNOWN, true);
+    return new PathMetadata(s3aStatus, Tristate.UNKNOWN, true, lastUpdated);
   }
 
   /**
    * Creates a new {@code PathMetadata} containing given {@code FileStatus}.
+   * lastUpdated field will be updated to 0 implicitly in this constructor.
+   *
    * @param fileStatus file status containing an absolute path.
    */
   public PathMetadata(S3AFileStatus fileStatus) {
-    this(fileStatus, Tristate.UNKNOWN, false);
+    this(fileStatus, Tristate.UNKNOWN, false, 0);
   }
 
+  /**
+   * Creates a new {@code PathMetadata} containing given {@code FileStatus}.
+   *
+   * @param fileStatus file status containing an absolute path.
+   * @param lastUpdated last updated time on which expiration is based.
+   */
+  public PathMetadata(S3AFileStatus fileStatus, long lastUpdated) {
+    this(fileStatus, Tristate.UNKNOWN, false, lastUpdated);
+  }
+
+  /**
+   * Creates a new {@code PathMetadata}.
+   * lastUpdated field will be updated to 0 implicitly in this constructor.
+   *
+   * @param fileStatus file status containing an absolute path.
+   * @param isEmptyDir empty directory {@link Tristate}
+   */
   public PathMetadata(S3AFileStatus fileStatus, Tristate isEmptyDir) {
-    this(fileStatus, isEmptyDir, false);
+    this(fileStatus, isEmptyDir, false, 0);
   }
 
+  /**
+   * Creates a new {@code PathMetadata}.
+   * lastUpdated field will be updated to 0 implicitly in this constructor.
+   *
+   * @param fileStatus file status containing an absolute path.
+   * @param isEmptyDir empty directory {@link Tristate}
+   * @param isDeleted deleted / tombstoned flag
+   */
+  public PathMetadata(S3AFileStatus fileStatus, Tristate isEmptyDir,
+      boolean isDeleted) {
+    this(fileStatus, isEmptyDir, isDeleted, 0);
+  }
+
+  /**
+   * Creates a new {@code PathMetadata}.
+   *
+   * @param fileStatus file status containing an absolute path.
+   * @param isEmptyDir empty directory {@link Tristate}
+   * @param isDeleted deleted / tombstoned flag
+   * @param lastUpdated last updated time on which expiration is based.
+   */
   public PathMetadata(S3AFileStatus fileStatus, Tristate isEmptyDir, boolean
-      isDeleted) {
+      isDeleted, long lastUpdated) {
     Preconditions.checkNotNull(fileStatus, "fileStatus must be non-null");
     Preconditions.checkNotNull(fileStatus.getPath(), "fileStatus path must be" +
         " non-null");
     Preconditions.checkArgument(fileStatus.getPath().isAbsolute(), "path must" +
         " be absolute");
+    Preconditions.checkArgument(lastUpdated >=0, "lastUpdated parameter must "
+        + "be greater or equal to 0.");
     this.fileStatus = fileStatus;
     this.isEmptyDirectory = isEmptyDir;
     this.isDeleted = isDeleted;
+    this.setLastUpdated(lastUpdated);
   }
 
   /**
@@ -122,6 +171,7 @@ public class PathMetadata extends ExpirableMetadata {
         "fileStatus=" + fileStatus +
         "; isEmptyDirectory=" + isEmptyDirectory +
         "; isDeleted=" + isDeleted +
+        "; lastUpdated=" + super.getLastUpdated() +
         '}';
   }
 

+ 9 - 4
hadoop-tools/hadoop-aws/src/main/java/org/apache/hadoop/fs/s3a/s3guard/S3Guard.java

@@ -296,12 +296,14 @@ public final class S3Guard {
         continue;
       }
 
+      final PathMetadata pathMetadata = new PathMetadata(s);
+
       if (!isAuthoritative){
         FileStatus status = dirMetaMap.get(s.getPath());
         if (status != null
             && s.getModificationTime() > status.getModificationTime()) {
           LOG.debug("Update ms with newer metadata of: {}", status);
-          S3Guard.putWithTtl(ms, new PathMetadata(s), timeProvider, null);
+          S3Guard.putWithTtl(ms, pathMetadata, timeProvider, null);
         }
       }
 
@@ -312,7 +314,7 @@ public final class S3Guard {
       // Any FileSystem has similar race conditions, but we could persist
       // a stale entry longer.  We could expose an atomic
       // DirListingMetadata#putIfNotPresent()
-      boolean updated = dirMeta.put(s);
+      boolean updated = dirMeta.put(pathMetadata);
       changed = changed || updated;
     }
 
@@ -712,12 +714,15 @@ public final class S3Guard {
    * @param ms metastore
    * @param path path to look up.
    * @param timeProvider nullable time provider
+   * @param needEmptyDirectoryFlag if true, implementation will
+   * return known state of directory emptiness.
    * @return the metadata or null if there as no entry.
    * @throws IOException failure.
    */
   public static PathMetadata getWithTtl(MetadataStore ms, Path path,
-      @Nullable ITtlTimeProvider timeProvider) throws IOException {
-    final PathMetadata pathMetadata = ms.get(path);
+      @Nullable ITtlTimeProvider timeProvider,
+      final boolean needEmptyDirectoryFlag) throws IOException {
+    final PathMetadata pathMetadata = ms.get(path, needEmptyDirectoryFlag);
     // if timeProvider is null let's return with what the ms has
     if (timeProvider == null) {
       LOG.debug("timeProvider is null, returning pathMetadata as is");

+ 2 - 2
hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/index.md

@@ -860,7 +860,7 @@ options are covered in [Testing](./testing.md).
 
 <property>
   <name>fs.s3a.multipart.size</name>
-  <value>100M</value>
+  <value>64M</value>
   <description>How big (in bytes) to split upload or copy operations up into.
     A suffix from the set {K,M,G,T,P} may be used to scale the numeric value.
   </description>
@@ -868,7 +868,7 @@ options are covered in [Testing](./testing.md).
 
 <property>
   <name>fs.s3a.multipart.threshold</name>
-  <value>2147483647</value>
+  <value>128MB</value>
   <description>How big (in bytes) to split upload or copy operations up into.
     This also controls the partition size in renamed files, as rename() involves
     copying the source file(s).

+ 2 - 2
hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/performance.md

@@ -281,7 +281,7 @@ killer.
 `fs.s3a.threads.max` and `fs.s3a.connection.maximum`.
 
 1. Make sure that the bucket is using `sequential` or `normal` fadvise seek policies,
-that is, `fs.s3a.experimental.fadvise` is not set to `random`
+that is, `fs.s3a.experimental.input.fadvise` is not set to `random`
 
 1. Perform listings in parallel by setting `-numListstatusThreads`
 to a higher number. Make sure that `fs.s3a.connection.maximum`
@@ -314,7 +314,7 @@ the S3 bucket/shard.
 </property>
 
 <property>
-  <name>fs.s3a.experimental.fadvise</name>
+  <name>fs.s3a.experimental.input.fadvise</name>
   <value>normal</value>
 </property>
 

+ 1 - 5
hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/testing.md

@@ -854,13 +854,9 @@ and, for test runs in or near the S3/DDB stores, throttling events.
 
 If you want to manage capacity, use `s3guard set-capacity` to increase it
 (performance) or decrease it (costs).
-For remote `hadoop-aws` test runs, the read/write capacities of "10" each should suffice;
+For remote `hadoop-aws` test runs, the read/write capacities of "0" each should suffice;
 increase it if parallel test run logs warn of throttling.
 
-Tip: for agility, use DynamoDB autoscaling, setting the minimum to something very low (e.g 5 units), the maximum to the largest amount you are willing to pay.
-This will automatically reduce capacity when you are not running tests against
-the bucket, slowly increase it over multiple test runs, if the load justifies it.
-
 ## <a name="tips"></a> Tips
 
 ### How to keep your credentials really safe

+ 1 - 1
hadoop-tools/hadoop-aws/src/site/markdown/tools/hadoop-aws/troubleshooting_s3a.md

@@ -1129,7 +1129,7 @@ shows that it is generally more efficient to abort the TCP connection and initia
 a new one than read to the end of a large file.
 
 Note: the threshold when data is read rather than the stream aborted can be tuned
-by `fs.s3a.readahead.range`; seek policy in `fs.s3a.experimental.fadvise`.
+by `fs.s3a.readahead.range`; seek policy in `fs.s3a.experimental.input.fadvise`.
 
 ### <a name="no_such_bucket"></a> `FileNotFoundException` Bucket does not exist.
 

+ 6 - 3
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/contract/s3a/ITestS3AContractRootDir.java

@@ -26,6 +26,7 @@ import org.apache.hadoop.fs.contract.AbstractContractRootDirectoryTest;
 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;
 
@@ -62,6 +63,11 @@ public class ITestS3AContractRootDir extends
     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.
@@ -69,9 +75,6 @@ public class ITestS3AContractRootDir extends
   @Override
   public void testListEmptyRootDirectory() throws IOException {
     int maxAttempts = 10;
-    if (getFileSystem().hasMetadataStore()) {
-      maxAttempts = 1;
-    }
     describe("Listing root directory; for consistency allowing "
         + maxAttempts + " attempts");
     for (int attempt = 1; attempt <= maxAttempts; ++attempt) {

+ 152 - 3
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3GuardEmptyDirs.java

@@ -20,16 +20,30 @@ package org.apache.hadoop.fs.s3a;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.stream.Stream;
+
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.model.ListObjectsV2Request;
+import com.amazonaws.services.s3.model.ListObjectsV2Result;
+import com.amazonaws.services.s3.model.PutObjectRequest;
+import com.amazonaws.services.s3.model.S3ObjectSummary;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
 
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.fs.s3a.impl.StoreContext;
+import org.apache.hadoop.fs.s3a.s3guard.DDBPathMetadata;
+import org.apache.hadoop.fs.s3a.s3guard.DynamoDBMetadataStore;
 import org.apache.hadoop.fs.s3a.s3guard.MetadataStore;
 import org.apache.hadoop.fs.s3a.s3guard.NullMetadataStore;
-import org.junit.Assume;
-import org.junit.Test;
 
 import static org.apache.hadoop.fs.contract.ContractTestUtils.assertRenameOutcome;
 import static org.apache.hadoop.fs.contract.ContractTestUtils.touch;
 import static org.apache.hadoop.test.LambdaTestUtils.intercept;
+import static org.apache.hadoop.fs.s3a.S3ATestUtils.assume;
+import static org.apache.hadoop.fs.s3a.S3ATestUtils.assumeFilesystemHasMetadatastore;
+import static org.apache.hadoop.fs.s3a.S3ATestUtils.getStatusWithEmptyDirFlag;
 
 /**
  * Test logic around whether or not a directory is empty, with S3Guard enabled.
@@ -84,7 +98,7 @@ public class ITestS3GuardEmptyDirs extends AbstractS3ATestBase {
   @Test
   public void testEmptyDirs() throws Exception {
     S3AFileSystem fs = getFileSystem();
-    Assume.assumeTrue(fs.hasMetadataStore());
+    assumeFilesystemHasMetadatastore(getFileSystem());
     MetadataStore configuredMs = fs.getMetadataStore();
     Path existingDir = path("existing-dir");
     Path existingFile = path("existing-dir/existing-file");
@@ -126,4 +140,139 @@ public class ITestS3GuardEmptyDirs extends AbstractS3ATestBase {
       configuredMs.forgetMetadata(existingDir);
     }
   }
+
+  /**
+   * Test tombstones don't get in the way of a listing of the
+   * root dir.
+   * This test needs to create a path which appears first in the listing,
+   * and an entry which can come later. To allow the test to proceed
+   * while other tests are running, the filename "0000" is used for that
+   * deleted entry.
+   */
+  @Test
+  public void testTombstonesAndEmptyDirectories() throws Throwable {
+    S3AFileSystem fs = getFileSystem();
+    assumeFilesystemHasMetadatastore(getFileSystem());
+
+    // Create the first and last files.
+    Path base = path(getMethodName());
+    // use something ahead of all the ASCII alphabet characters so
+    // even during parallel test runs, this test is expected to work.
+    String first = "0000";
+    Path firstPath = new Path(base, first);
+
+    // this path is near the bottom of the ASCII string space.
+    // This isn't so critical.
+    String last = "zzzz";
+    Path lastPath = new Path(base, last);
+    touch(fs, firstPath);
+    touch(fs, lastPath);
+    // Delete first entry (+assert tombstone)
+    assertDeleted(firstPath, false);
+    DynamoDBMetadataStore ddbMs = getRequiredDDBMetastore(fs);
+    DDBPathMetadata firstMD = ddbMs.get(firstPath);
+    assertNotNull("No MD for " + firstPath, firstMD);
+    assertTrue("Not a tombstone " + firstMD,
+        firstMD.isDeleted());
+    // PUT child to store going past the FS entirely.
+    // This is not going to show up on S3Guard.
+    Path child = new Path(firstPath, "child");
+    StoreContext ctx = fs.createStoreContext();
+    String childKey = ctx.pathToKey(child);
+    String baseKey = ctx.pathToKey(base) + "/";
+    AmazonS3 s3 = fs.getAmazonS3ClientForTesting("LIST");
+    String bucket = ctx.getBucket();
+    try {
+      createEmptyObject(fs, childKey);
+
+      // Do a list
+      ListObjectsV2Request listReq = new ListObjectsV2Request()
+          .withBucketName(bucket)
+          .withPrefix(baseKey)
+          .withMaxKeys(10)
+          .withDelimiter("/");
+      ListObjectsV2Result listing = s3.listObjectsV2(listReq);
+
+      // the listing has the first path as a prefix, because of the child
+      Assertions.assertThat(listing.getCommonPrefixes())
+          .describedAs("The prefixes of a LIST of %s", base)
+          .contains(baseKey + first + "/");
+
+      // and the last file is one of the files
+      Stream<String> files = listing.getObjectSummaries()
+          .stream()
+          .map(S3ObjectSummary::getKey);
+      Assertions.assertThat(files)
+          .describedAs("The files of a LIST of %s", base)
+          .contains(baseKey + last);
+
+      // verify absolutely that the last file exists
+      assertPathExists("last file", lastPath);
+
+      boolean isDDB = fs.getMetadataStore() instanceof DynamoDBMetadataStore;
+      // if DDB is the metastore, then we expect no FS requests to be made
+      // at all.
+      S3ATestUtils.MetricDiff listMetric = new S3ATestUtils.MetricDiff(fs,
+          Statistic.OBJECT_LIST_REQUESTS);
+      S3ATestUtils.MetricDiff getMetric = new S3ATestUtils.MetricDiff(fs,
+          Statistic.OBJECT_METADATA_REQUESTS);
+      // do a getFile status with empty dir flag
+      S3AFileStatus status = getStatusWithEmptyDirFlag(fs, base);
+      assertNonEmptyDir(status);
+      if (isDDB) {
+        listMetric.assertDiffEquals(
+            "FileSystem called S3 LIST rather than use DynamoDB",
+            0);
+        getMetric.assertDiffEquals(
+            "FileSystem called S3 GET rather than use DynamoDB",
+            0);
+        LOG.info("Verified that DDB directory status was accepted");
+      }
+
+    } finally {
+      // try to recover from the defective state.
+      s3.deleteObject(bucket, childKey);
+      fs.delete(lastPath, true);
+      ddbMs.forgetMetadata(firstPath);
+    }
+  }
+
+  protected void assertNonEmptyDir(final S3AFileStatus status) {
+    assertEquals("Should not be empty dir: " + status, Tristate.FALSE,
+        status.isEmptyDirectory());
+  }
+
+  /**
+   * Get the DynamoDB metastore; assume false if it is of a different
+   * type.
+   * @return extracted and cast metadata store.
+   */
+  @SuppressWarnings("ConstantConditions")
+  private DynamoDBMetadataStore getRequiredDDBMetastore(S3AFileSystem fs) {
+    MetadataStore ms = fs.getMetadataStore();
+    assume("Not a DynamoDBMetadataStore: " + ms,
+        ms instanceof DynamoDBMetadataStore);
+    return (DynamoDBMetadataStore) ms;
+  }
+
+  /**
+   * From {@code S3AFileSystem.createEmptyObject()}.
+   * @param fs filesystem
+   * @param key key
+   */
+  private void createEmptyObject(S3AFileSystem fs, String key) {
+    final InputStream im = new InputStream() {
+      @Override
+      public int read() {
+        return -1;
+      }
+    };
+
+    PutObjectRequest putObjectRequest = fs.newPutObjectRequest(key,
+        fs.newObjectMetadata(0L),
+        im);
+    AmazonS3 s3 = fs.getAmazonS3ClientForTesting("PUT");
+    s3.putObject(putObjectRequest);
+  }
+
 }

+ 45 - 1
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3GuardOutOfBandOperations.java

@@ -27,6 +27,7 @@ import java.util.UUID;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
 
+import org.apache.hadoop.test.LambdaTestUtils;
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,6 +48,7 @@ import org.apache.hadoop.fs.s3a.s3guard.DirListingMetadata;
 import org.apache.hadoop.fs.s3a.s3guard.MetadataStore;
 import org.apache.hadoop.fs.s3a.s3guard.PathMetadata;
 import org.apache.hadoop.fs.s3a.s3guard.ITtlTimeProvider;
+import org.apache.hadoop.fs.contract.ContractTestUtils;
 
 import static org.apache.hadoop.fs.contract.ContractTestUtils.readBytesToString;
 import static org.apache.hadoop.fs.contract.ContractTestUtils.touch;
@@ -61,6 +63,7 @@ import static org.apache.hadoop.fs.s3a.S3ATestUtils.metadataStorePersistsAuthori
 import static org.apache.hadoop.fs.s3a.S3ATestUtils.removeBaseAndBucketOverrides;
 import static org.apache.hadoop.test.LambdaTestUtils.eventually;
 import static org.apache.hadoop.test.LambdaTestUtils.intercept;
+
 import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -288,7 +291,7 @@ public class ITestS3GuardOutOfBandOperations extends AbstractS3ATestBase {
   }
 
   /**
-   * Tests that tombstone expiry is implemented, so if a file is created raw
+   * Tests that tombstone expiry is implemented. If a file is created raw
    * while the tombstone exist in ms for with the same name then S3Guard will
    * check S3 for the file.
    *
@@ -538,6 +541,47 @@ public class ITestS3GuardOutOfBandOperations extends AbstractS3ATestBase {
     }
   }
 
+  /**
+   * Test that a tombstone won't hide an entry after it's expired in the
+   * listing.
+   */
+  @Test
+  public void testRootTombstones() throws Exception {
+    long ttl = 10L;
+    ITtlTimeProvider mockTimeProvider = mock(ITtlTimeProvider.class);
+    when(mockTimeProvider.getMetadataTtl()).thenReturn(ttl);
+    when(mockTimeProvider.getNow()).thenReturn(100L);
+    ITtlTimeProvider originalTimeProvider = guardedFs.getTtlTimeProvider();
+    guardedFs.setTtlTimeProvider(mockTimeProvider);
+
+    Path base = path(getMethodName() + UUID.randomUUID());
+    Path testFile = new Path(base, "test.file");
+
+    try {
+      touch(guardedFs, testFile);
+      ContractTestUtils.assertDeleted(guardedFs, testFile, false);
+
+      touch(rawFS, testFile);
+      awaitFileStatus(rawFS, testFile);
+
+      // the rawFS will include the file=
+      LambdaTestUtils.eventually(5000, 1000, () -> {
+        checkListingContainsPath(rawFS, testFile);
+      });
+
+      // it will be hidden because of the tombstone
+      checkListingDoesNotContainPath(guardedFs, testFile);
+
+      // the tombstone is expired, so we should detect the file
+      when(mockTimeProvider.getNow()).thenReturn(100 + ttl);
+      checkListingContainsPath(guardedFs, testFile);
+    } finally {
+      // cleanup
+      guardedFs.delete(base, true);
+      guardedFs.setTtlTimeProvider(originalTimeProvider);
+    }
+  }
+
   /**
    * Perform an out-of-band delete.
    * @param testFilePath filename

+ 70 - 0
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/ITestS3GuardTtl.java

@@ -18,9 +18,12 @@
 
 package org.apache.hadoop.fs.s3a;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.FileStatus;
@@ -30,6 +33,7 @@ import org.apache.hadoop.fs.s3a.s3guard.ITtlTimeProvider;
 import org.apache.hadoop.fs.s3a.s3guard.MetadataStore;
 import org.apache.hadoop.fs.s3a.s3guard.S3Guard;
 
+import org.assertj.core.api.Assertions;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -286,4 +290,70 @@ public class ITestS3GuardTtl extends AbstractS3ATestBase {
     }
   }
 
+  /**
+   * Test that listing of metadatas is filtered from expired items.
+   */
+  @Test
+  public void testListingFilteredExpiredItems() throws Exception {
+    LOG.info("Authoritative mode: {}", authoritative);
+    final S3AFileSystem fs = getFileSystem();
+
+    long oldTime = 100L;
+    long newTime = 110L;
+    long ttl = 9L;
+    final String basedir = "testListingFilteredExpiredItems";
+    final Path tombstonedPath = path(basedir + "/tombstonedPath");
+    final Path baseDirPath = path(basedir);
+    final List<Path> filesToCreate = new ArrayList<>();
+    final MetadataStore ms = fs.getMetadataStore();
+
+    for (int i = 0; i < 10; i++) {
+      filesToCreate.add(path(basedir + "/file" + i));
+    }
+
+    ITtlTimeProvider mockTimeProvider = mock(ITtlTimeProvider.class);
+    ITtlTimeProvider originalTimeProvider = fs.getTtlTimeProvider();
+
+    try {
+      fs.setTtlTimeProvider(mockTimeProvider);
+      when(mockTimeProvider.getMetadataTtl()).thenReturn(ttl);
+
+      // add and delete entry with the oldtime
+      when(mockTimeProvider.getNow()).thenReturn(oldTime);
+      touch(fs, tombstonedPath);
+      fs.delete(tombstonedPath, false);
+
+      // create items with newTime
+      when(mockTimeProvider.getNow()).thenReturn(newTime);
+      for (Path path : filesToCreate) {
+        touch(fs, path);
+      }
+
+      // listing will contain the tombstone with oldtime
+      when(mockTimeProvider.getNow()).thenReturn(oldTime);
+      final DirListingMetadata fullDLM = ms.listChildren(baseDirPath);
+      List<Path> containedPaths = fullDLM.getListing().stream()
+          .map(pm -> pm.getFileStatus().getPath())
+          .collect(Collectors.toList());
+      Assertions.assertThat(containedPaths)
+          .describedAs("Full listing of path %s", baseDirPath)
+          .hasSize(11)
+          .contains(tombstonedPath);
+
+      // listing will be filtered, and won't contain the tombstone with oldtime
+      when(mockTimeProvider.getNow()).thenReturn(newTime);
+      final DirListingMetadata filteredDLM = ms.listChildren(baseDirPath);
+      containedPaths = filteredDLM.getListing().stream()
+          .map(pm -> pm.getFileStatus().getPath())
+          .collect(Collectors.toList());
+      Assertions.assertThat(containedPaths)
+          .describedAs("Full listing of path %s", baseDirPath)
+          .hasSize(10)
+          .doesNotContain(tombstonedPath);
+    } finally {
+      fs.delete(baseDirPath, true);
+      fs.setTtlTimeProvider(originalTimeProvider);
+    }
+  }
+
 }

+ 26 - 0
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/S3ATestUtils.java

@@ -505,6 +505,16 @@ public final class S3ATestUtils {
         Constants.DEFAULT_METADATASTORE_AUTHORITATIVE);
   }
 
+  /**
+   * Require a filesystem to have a metadata store; skip test
+   * if not.
+   * @param fs filesystem to check
+   */
+  public static void assumeFilesystemHasMetadatastore(S3AFileSystem fs) {
+    assume("Filesystem does not have a metastore",
+        fs.hasMetadataStore());
+  }
+
   /**
    * Reset all metrics in a list.
    * @param metrics metrics to reset
@@ -818,6 +828,22 @@ public final class S3ATestUtils {
     return null;
   }
 
+  /**
+   * Get a file status from S3A with the {@code needEmptyDirectoryFlag}
+   * state probed.
+   * This accesses a package-private method in the
+   * S3A filesystem.
+   * @param fs filesystem
+   * @param dir directory
+   * @return a status
+   * @throws IOException
+   */
+  public static S3AFileStatus getStatusWithEmptyDirFlag(
+      final S3AFileSystem fs,
+      final Path dir) throws IOException {
+    return fs.innerGetFileStatus(dir, true);
+  }
+
   /**
    * Helper class to do diffs of metrics.
    */

+ 1 - 1
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestDynamoDBMetadataStore.java

@@ -1021,7 +1021,7 @@ public class ITestDynamoDBMetadataStore extends MetadataStoreTestBase {
     }
 
     // Test with non-authoritative listing, non-empty dir
-    dlm.put(basicFileStatus(fileToPut, 1, false));
+    dlm.put(new PathMetadata(basicFileStatus(fileToPut, 1, false)));
     ms.put(dlm, null);
     final PathMetadata pmdResultNotEmpty = ms.get(dirToPut, true);
     assertEquals(Tristate.FALSE, pmdResultNotEmpty.isEmptyDirectory());

+ 10 - 0
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestS3GuardConcurrentOps.java

@@ -46,6 +46,8 @@ import org.apache.hadoop.fs.s3a.Constants;
 import org.apache.hadoop.fs.s3a.S3AFileSystem;
 
 import static org.apache.hadoop.fs.s3a.Constants.S3GUARD_DDB_REGION_KEY;
+import static org.apache.hadoop.fs.s3a.Constants.S3GUARD_DDB_TABLE_CAPACITY_READ_KEY;
+import static org.apache.hadoop.fs.s3a.Constants.S3GUARD_DDB_TABLE_CAPACITY_WRITE_KEY;
 
 /**
  * Tests concurrent operations on S3Guard.
@@ -55,6 +57,14 @@ public class ITestS3GuardConcurrentOps extends AbstractS3ATestBase {
   @Rule
   public final Timeout timeout = new Timeout(5 * 60 * 1000);
 
+  protected Configuration createConfiguration() {
+    Configuration conf =  super.createConfiguration();
+    //patch the read/write capacity
+    conf.setInt(S3GUARD_DDB_TABLE_CAPACITY_READ_KEY, 0);
+    conf.setInt(S3GUARD_DDB_TABLE_CAPACITY_WRITE_KEY, 0);
+    return conf;
+  }
+
   private void failIfTableExists(DynamoDB db, String tableName) {
     boolean tableExists = true;
     try {

+ 1 - 1
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/ITestS3GuardDDBRootOperations.java

@@ -234,7 +234,7 @@ public class ITestS3GuardDDBRootOperations extends AbstractS3ATestBase {
       assertDeleted(file, false);
 
 
-      assertTrue("Root directory delete failed",
+      assertFalse("Root directory delete failed",
           fs.delete(root, true));
 
       ContractTestUtils.touch(fs, file2);

+ 3 - 2
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/MetadataStoreTestBase.java

@@ -109,7 +109,7 @@ public abstract class MetadataStoreTestBase extends HadoopTestBase {
   /** The MetadataStore contract used to test against. */
   private AbstractMSContract contract;
 
-  private MetadataStore ms;
+  protected MetadataStore ms;
 
   /**
    * @return reference to the test contract.
@@ -554,7 +554,8 @@ public abstract class MetadataStoreTestBase extends HadoopTestBase {
 
     DirListingMetadata dirMeta = ms.listChildren(strToPath("/a1/b1"));
     dirMeta.setAuthoritative(true);
-    dirMeta.put(makeFileStatus("/a1/b1/file_new", 100));
+    dirMeta.put(new PathMetadata(
+        makeFileStatus("/a1/b1/file_new", 100)));
     ms.put(dirMeta, null);
 
     dirMeta = ms.listChildren(strToPath("/a1/b1"));

+ 39 - 5
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/TestDirListingMetadata.java

@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import org.assertj.core.api.Assertions;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -198,7 +199,7 @@ public class TestDirListingMetadata {
     assertFalse(meta.isAuthoritative());
     PathMetadata pathMeta4 = new PathMetadata(
         new S3AFileStatus(true, new Path(path, "dir3"), TEST_OWNER));
-    meta.put(pathMeta4.getFileStatus());
+    meta.put(pathMeta4);
     assertTrue(meta.getListing().contains(pathMeta4));
     assertEquals(pathMeta4, meta.get(pathMeta4.getFileStatus().getPath()));
   }
@@ -218,7 +219,7 @@ public class TestDirListingMetadata {
     DirListingMetadata meta = new DirListingMetadata(path, null, false);
     exception.expect(NullPointerException.class);
     exception.expectMessage(notNullValue(String.class));
-    meta.put(new S3AFileStatus(true, null, TEST_OWNER));
+    meta.put(new PathMetadata(new S3AFileStatus(true, null, TEST_OWNER)));
   }
 
   @Test
@@ -227,7 +228,8 @@ public class TestDirListingMetadata {
     DirListingMetadata meta = new DirListingMetadata(path, null, false);
     exception.expect(IllegalArgumentException.class);
     exception.expectMessage(notNullValue(String.class));
-    meta.put(new S3AFileStatus(true, new Path("/"), TEST_OWNER));
+    meta.put(new PathMetadata(new S3AFileStatus(true, new Path("/"),
+        TEST_OWNER)));
   }
 
   @Test
@@ -236,8 +238,8 @@ public class TestDirListingMetadata {
     DirListingMetadata meta = new DirListingMetadata(path, null, false);
     exception.expect(IllegalArgumentException.class);
     exception.expectMessage(notNullValue(String.class));
-    meta.put(new S3AFileStatus(true, new Path("/different/ancestor"),
-        TEST_OWNER));
+    meta.put(new PathMetadata(
+        new S3AFileStatus(true, new Path("/different/ancestor"), TEST_OWNER)));
   }
 
   @Test
@@ -291,6 +293,38 @@ public class TestDirListingMetadata {
     meta.remove(new Path("/different/ancestor"));
   }
 
+
+  @Test
+  public void testRemoveExpiredEntriesFromListing() {
+    long ttl = 9;
+    long oldTime = 100;
+    long newTime = 110;
+    long now = 110;
+
+    Path path = new Path("/path");
+    PathMetadata pathMeta1 = new PathMetadata(
+        new S3AFileStatus(true, new Path(path, "dir1"), TEST_OWNER));
+    PathMetadata pathMeta2 = new PathMetadata(
+        new S3AFileStatus(true, new Path(path, "dir2"), TEST_OWNER));
+    PathMetadata pathMeta3 = new PathMetadata(
+        new S3AFileStatus(123, 456, new Path(path, "file1"), 8192, TEST_OWNER,
+            TEST_ETAG, TEST_VERSION_ID));
+    pathMeta1.setLastUpdated(oldTime);
+    pathMeta2.setLastUpdated(0);
+    pathMeta3.setLastUpdated(newTime);
+
+    List<PathMetadata> listing = Arrays.asList(pathMeta1, pathMeta2, pathMeta3);
+    DirListingMetadata meta = new DirListingMetadata(path, listing, false);
+
+    meta.removeExpiredEntriesFromListing(ttl, now);
+
+    Assertions.assertThat(meta.getListing())
+        .describedAs("Metadata listing for %s", path)
+        .doesNotContain(pathMeta1)
+        .contains(pathMeta2)
+        .contains(pathMeta3);
+  }
+
   /*
    * Create DirListingMetadata with two dirs and one file living in directory
    * 'parent'

+ 40 - 0
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/TestLocalMetadataStore.java

@@ -19,11 +19,13 @@
 package org.apache.hadoop.fs.s3a.s3guard;
 
 import java.io.IOException;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
 import com.google.common.base.Ticker;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
+import org.junit.Assume;
 import org.junit.Test;
 
 import org.apache.hadoop.conf.Configuration;
@@ -34,6 +36,9 @@ import org.apache.hadoop.fs.s3a.S3AFileStatus;
 import org.apache.hadoop.fs.s3a.S3ATestUtils;
 import org.apache.hadoop.fs.s3a.Tristate;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 /**
  * MetadataStore unit test for {@link LocalMetadataStore}.
  */
@@ -164,6 +169,41 @@ public class TestLocalMetadataStore extends MetadataStoreTestBase {
     assertNull("PathMetadata should be null after eviction", pm1);
   }
 
+
+  @Test
+  public void testUpdateParentLastUpdatedOnPutNewParent() throws Exception {
+    Assume.assumeTrue("This test only applies if metadatastore does not allow"
+        + " missing values (skip for NullMS).", !allowMissing());
+
+    ITtlTimeProvider tp = mock(ITtlTimeProvider.class);
+    ITtlTimeProvider originalTimeProvider = getTtlTimeProvider();
+
+    long now = 100L;
+
+    final String parent = "/parentUpdated-" + UUID.randomUUID();
+    final String child = parent + "/file1";
+
+    try {
+      when(tp.getNow()).thenReturn(now);
+
+      // create a file
+      ms.put(new PathMetadata(makeFileStatus(child, 100), tp.getNow()),
+          null);
+      final PathMetadata fileMeta = ms.get(strToPath(child));
+      assertEquals("lastUpdated field of first file should be equal to the "
+          + "mocked value", now, fileMeta.getLastUpdated());
+
+      final DirListingMetadata listing = ms.listChildren(strToPath(parent));
+      assertEquals("Listing lastUpdated field should be equal to the mocked "
+          + "time value.", now, listing.getLastUpdated());
+
+    } finally {
+      ms.setTtlTimeProvider(originalTimeProvider);
+    }
+
+  }
+
+
   private static void populateMap(Cache<Path, LocalMetadataEntry> cache,
       String prefix) {
     populateEntry(cache, new Path(prefix + "/dirA/dirB/"));

+ 8 - 6
hadoop-tools/hadoop-aws/src/test/java/org/apache/hadoop/fs/s3a/s3guard/TestS3Guard.java

@@ -154,7 +154,7 @@ public class TestS3Guard extends Assert {
     pm.setLastUpdated(100L);
 
     MetadataStore ms = mock(MetadataStore.class);
-    when(ms.get(path)).thenReturn(pm);
+    when(ms.get(path, false)).thenReturn(pm);
 
     ITtlTimeProvider timeProvider =
         mock(ITtlTimeProvider.class);
@@ -162,7 +162,8 @@ public class TestS3Guard extends Assert {
     when(timeProvider.getMetadataTtl()).thenReturn(1L);
 
     // act
-    final PathMetadata pmExpired = S3Guard.getWithTtl(ms, path, timeProvider);
+    final PathMetadata pmExpired = S3Guard.getWithTtl(ms, path, timeProvider,
+        false);
 
     // assert
     assertNull(pmExpired);
@@ -178,7 +179,7 @@ public class TestS3Guard extends Assert {
     pm.setLastUpdated(100L);
 
     MetadataStore ms = mock(MetadataStore.class);
-    when(ms.get(path)).thenReturn(pm);
+    when(ms.get(path, false)).thenReturn(pm);
 
     ITtlTimeProvider timeProvider =
         mock(ITtlTimeProvider.class);
@@ -187,7 +188,7 @@ public class TestS3Guard extends Assert {
 
     // act
     final PathMetadata pmNotExpired =
-        S3Guard.getWithTtl(ms, path, timeProvider);
+        S3Guard.getWithTtl(ms, path, timeProvider, false);
 
     // assert
     assertNotNull(pmNotExpired);
@@ -205,7 +206,7 @@ public class TestS3Guard extends Assert {
     pm.setLastUpdated(0L);
 
     MetadataStore ms = mock(MetadataStore.class);
-    when(ms.get(path)).thenReturn(pm);
+    when(ms.get(path, false)).thenReturn(pm);
 
     ITtlTimeProvider timeProvider =
         mock(ITtlTimeProvider.class);
@@ -213,7 +214,8 @@ public class TestS3Guard extends Assert {
     when(timeProvider.getMetadataTtl()).thenReturn(2L);
 
     // act
-    final PathMetadata pmExpired = S3Guard.getWithTtl(ms, path, timeProvider);
+    final PathMetadata pmExpired = S3Guard.getWithTtl(ms, path, timeProvider,
+        false);
 
     // assert
     assertNotNull(pmExpired);

+ 2 - 2
hadoop-tools/hadoop-aws/src/test/resources/core-site.xml

@@ -153,11 +153,11 @@
   <!-- Reduce DDB capacity on auto-created tables, to keep bills down. -->
   <property>
     <name>fs.s3a.s3guard.ddb.table.capacity.read</name>
-    <value>10</value>
+    <value>0</value>
   </property>
   <property>
     <name>fs.s3a.s3guard.ddb.table.capacity.write</name>
-    <value>10</value>
+    <value>0</value>
   </property>
 
   <!--

+ 1 - 1
hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/constants/FileSystemConfigurations.java

@@ -44,7 +44,7 @@ public final class FileSystemConfigurations {
   public static final int DEFAULT_READ_BUFFER_SIZE = 4 * ONE_MB;  // 4 MB
   public static final int MIN_BUFFER_SIZE = 16 * ONE_KB;  // 16 KB
   public static final int MAX_BUFFER_SIZE = 100 * ONE_MB;  // 100 MB
-  public static final long MAX_AZURE_BLOCK_SIZE = 512 * 1024 * 1024L;
+  public static final long MAX_AZURE_BLOCK_SIZE = 256 * 1024 * 1024L; // changing default abfs blocksize to 256MB
   public static final String AZURE_BLOCK_LOCATION_HOST_DEFAULT = "localhost";
 
   public static final int MAX_CONCURRENT_READ_THREADS = 12;

+ 6 - 0
hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/TestAbfsConfigurationFieldsValidation.java

@@ -143,6 +143,12 @@ public class TestAbfsConfigurationFieldsValidation {
     assertEquals(AZURE_BLOCK_LOCATION_HOST_DEFAULT, abfsConfiguration.getAzureBlockLocationHost());
   }
 
+  @Test
+  public void testConfigBlockSizeInitialized() throws Exception {
+    // test the block size annotated field has been initialized in the constructor
+    assertEquals(MAX_AZURE_BLOCK_SIZE, abfsConfiguration.getAzureBlockSize());
+  }
+
   @Test
   public void testGetAccountKey() throws Exception {
     String accountKey = abfsConfiguration.getStorageAccountKey();

部分文件因文件數量過多而無法顯示