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]