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

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


The following commit(s) were added to refs/heads/master by this push:
     new 4b9a1dc590 HDDS-9318. Prevent bucket from being deleted if incomplete 
MPUs exist (#5326)
4b9a1dc590 is described below

commit 4b9a1dc5908bababe85d750525b1bf3d01d7af55
Author: Ivan Andika <[email protected]>
AuthorDate: Wed Nov 8 20:05:21 2023 +0800

    HDDS-9318. Prevent bucket from being deleted if incomplete MPUs exist 
(#5326)
---
 .../dist/src/main/smoketest/s3/bucketdelete.robot  | 19 +++++++
 .../dist/src/main/smoketest/s3/commonawslib.robot  |  1 +
 .../apache/hadoop/ozone/om/OMMetadataManager.java  | 10 ++++
 .../hadoop/ozone/om/OmMetadataManagerImpl.java     | 19 +++++++
 .../om/request/bucket/OMBucketDeleteRequest.java   | 11 ++++
 .../request/bucket/TestOMBucketDeleteRequest.java  | 63 ++++++++++++++++++++++
 6 files changed, 123 insertions(+)

diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/bucketdelete.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/bucketdelete.robot
index 354832fbca..607a7dee96 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/bucketdelete.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/bucketdelete.robot
@@ -43,3 +43,22 @@ Delete non-existent bucket
     ${randStr} =   Generate Ozone String
     ${result} =    Execute AWSS3APICli and checkrc    delete-bucket --bucket 
nosuchbucket-${randStr}    255
                    Should contain                     ${result}                
              NoSuchBucket
+
+Delete bucket with incomplete multipart uploads
+    [tags]    no-bucket-type
+    ${bucket} =                Create bucket
+
+    # initiate incomplete multipart uploads (multipart upload is initiated but 
not completed/aborted)
+    ${initiate_result} =       Execute AWSS3APICli     create-multipart-upload 
--bucket ${bucket} --key incomplete-multipartkey
+    ${uploadID} =              Execute                 echo 
'${initiate_result}' | jq -r '.UploadId'
+                               Should contain          ${initiate_result}    
${bucket}
+                               Should contain          ${initiate_result}    
incomplete-multipartkey
+                               Should contain          ${initiate_result}    
UploadId
+
+    # bucket deletion should fail since there is still incomplete multipart 
upload
+    ${delete_fail_result} =    Execute AWSS3APICli and checkrc    
delete-bucket --bucket ${bucket}    255
+                               Should contain                     
${delete_fail_result}               BucketNotEmpty
+
+    # after aborting the multipart upload, the bucket deletion should succeed
+    ${abort_result} =          Execute AWSS3APICli and checkrc    
abort-multipart-upload --bucket ${bucket} --key incomplete-multipartkey 
--upload-id ${uploadID}   0
+    ${delete_result} =         Execute AWSS3APICli and checkrc    
delete-bucket --bucket ${bucket}    0
\ No newline at end of file
diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot
index afcec38e47..ae57bf82a8 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/commonawslib.robot
@@ -34,6 +34,7 @@ Execute AWSS3APICli
     ${output} =       Execute                    aws s3api --endpoint-url 
${ENDPOINT_URL} ${command}
     [return]          ${output}
 
+# For possible AWS CLI return codes see: 
https://docs.aws.amazon.com/cli/latest/topic/return-codes.html
 Execute AWSS3APICli and checkrc
     [Arguments]       ${command}                 ${expected_error_code}
     ${output} =       Execute and checkrc        aws s3api --endpoint-url 
${ENDPOINT_URL} ${command}  ${expected_error_code}
diff --git 
a/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
 
b/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
index 4dfb135dd7..6ce7e31764 100644
--- 
a/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
+++ 
b/hadoop-ozone/interface-storage/src/main/java/org/apache/hadoop/ozone/om/OMMetadataManager.java
@@ -577,4 +577,14 @@ public interface OMMetadataManager extends 
DBStoreHAManager {
    * @return {@link BlockGroup}
    */
   List<BlockGroup> getBlocksForKeyDelete(String deletedKey) throws IOException;
+
+  /**
+   * Given a volume/bucket, check whether it contains incomplete MPUs.
+   *
+   * @param volume - Volume name
+   * @param bucket - Bucket name
+   * @return true if the bucket is empty
+   */
+  boolean containsIncompleteMPUs(String volume, String bucket)
+      throws IOException;
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
index 5616048ffd..950a17829b 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
@@ -2169,4 +2169,23 @@ public class OmMetadataManagerImpl implements 
OMMetadataManager,
     }
     return result;
   }
+
+  @Override
+  public boolean containsIncompleteMPUs(String volume, String bucket)
+      throws IOException {
+    String keyPrefix =
+        OmMultipartUpload.getDbKey(volume, bucket, "");
+
+    // First check in table cache
+    if (isKeyPresentInTableCache(keyPrefix, multipartInfoTable)) {
+      return true;
+    }
+
+    // Check in table
+    if (isKeyPresentInTable(keyPrefix, multipartInfoTable)) {
+      return true;
+    }
+
+    return false;
+  }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketDeleteRequest.java
index 36105c899f..ecdcdc49b3 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/bucket/OMBucketDeleteRequest.java
@@ -25,6 +25,7 @@ import java.util.Map;
 
 import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.hdds.utils.db.TableIterator;
+import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.OmBucketInfo;
 import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
@@ -141,6 +142,16 @@ public class OMBucketDeleteRequest extends OMClientRequest 
{
             OMException.ResultCodes.BUCKET_NOT_EMPTY);
       }
 
+      // Check if bucket does not contain incomplete MPUs
+      if (omMetadataManager.containsIncompleteMPUs(volumeName, bucketName)) {
+        LOG.debug("Volume '{}', Bucket '{}' can't be deleted when it has " +
+                "incomplete multipart uploads", volumeName, bucketName);
+        throw new OMException(
+            String.format("Volume %s, Bucket %s can't be deleted when it " +
+                "has incomplete multipart uploads", volumeName, bucketName),
+            ResultCodes.BUCKET_NOT_EMPTY);
+      }
+
       // appending '/' to end to eliminate cases where 2 buckets start with 
same
       // characters.
       String snapshotBucketKey = bucketKey + OzoneConsts.OM_KEY_PREFIX;
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketDeleteRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketDeleteRequest.java
index 090d3fd12c..475f20f193 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketDeleteRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/bucket/TestOMBucketDeleteRequest.java
@@ -21,7 +21,14 @@ package org.apache.hadoop.ozone.om.request.bucket;
 
 import java.util.UUID;
 
+import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
+import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
+import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmMultipartKeyInfo;
 import org.apache.hadoop.ozone.om.request.OMRequestTestUtils;
+import org.apache.hadoop.ozone.om.request.util.OMMultipartUploadUtils;
+import org.apache.hadoop.util.Time;
 import org.junit.Assert;
 import org.junit.Test;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
@@ -97,6 +104,62 @@ public class TestOMBucketDeleteRequest extends 
TestBucketRequest {
         omMetadataManager);
   }
 
+  @Test
+  public void testBucketContainsIncompleteMPUs() throws Exception {
+    String volumeName = UUID.randomUUID().toString();
+    String bucketName = UUID.randomUUID().toString();
+
+    OMRequest omRequest =
+        createDeleteBucketRequest(volumeName, bucketName);
+
+    OMRequestTestUtils.addVolumeAndBucketToDB(volumeName, bucketName,
+        omMetadataManager);
+
+    OMBucketDeleteRequest omBucketDeleteRequest =
+        new OMBucketDeleteRequest(omRequest);
+
+    // Create a MPU key in the MPU table to simulate incomplete MPU
+    long creationTime = Time.now();
+    String uploadId = OMMultipartUploadUtils.getMultipartUploadId();
+    final OmKeyInfo keyInfo = OMRequestTestUtils.createOmKeyInfo(volumeName,
+        bucketName, UUID.randomUUID().toString(),
+        HddsProtos.ReplicationType.RATIS, HddsProtos.ReplicationFactor.ONE,
+        0L, creationTime, true);
+    final OmMultipartKeyInfo multipartKeyInfo = OMRequestTestUtils.
+        createOmMultipartKeyInfo(uploadId, Time.now(),
+            HddsProtos.ReplicationType.RATIS,
+            HddsProtos.ReplicationFactor.ONE, 0L);
+    OMRequestTestUtils.addMultipartInfoToTable(false, keyInfo,
+        multipartKeyInfo, 0L, omMetadataManager);
+
+    // Bucket delete request should fail since there are still incomplete MPUs
+    OMClientResponse omClientResponse =
+        omBucketDeleteRequest.validateAndUpdateCache(ozoneManager, 1L,
+            ozoneManagerDoubleBufferHelper);
+
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.BUCKET_NOT_EMPTY,
+        omClientResponse.getOMResponse().getStatus());
+    Assert.assertNotNull(omMetadataManager.getBucketTable().get(
+        omMetadataManager.getBucketKey(volumeName, bucketName)));
+
+    // Remove the MPU keys to simulate MPU aborts / completes
+    String multipartKey = omMetadataManager.getMultipartKey(
+        volumeName, bucketName, keyInfo.getKeyName(), uploadId);
+    omMetadataManager.getMultipartInfoTable().addCacheEntry(
+        new CacheKey<>(multipartKey), CacheValue.get(2L));
+    omMetadataManager.getMultipartInfoTable().delete(multipartKey);
+
+    // Bucket delete request should succeed now
+    omClientResponse =
+        omBucketDeleteRequest.validateAndUpdateCache(ozoneManager, 3L,
+            ozoneManagerDoubleBufferHelper);
+
+    Assert.assertEquals(OzoneManagerProtocolProtos.Status.OK,
+        omClientResponse.getOMResponse().getStatus());
+    Assert.assertNull(omMetadataManager.getBucketTable().get(
+        omMetadataManager.getBucketKey(volumeName, bucketName)));
+  }
+
   private OMRequest createDeleteBucketRequest(String volumeName,
       String bucketName) {
     return OMRequest.newBuilder().setDeleteBucketRequest(


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

Reply via email to