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

Gargi-jais11 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 72f1f9a612e HDDS-15608. Return BucketAlreadyOwnedByYou when the same 
S3 owner recreates an existing bucket (#10814).
72f1f9a612e is described below

commit 72f1f9a612efac13fdf5a48cdb771ef13c645f82
Author: Gargi Jaiswal <[email protected]>
AuthorDate: Fri Jul 24 14:01:42 2026 +0530

    HDDS-15608. Return BucketAlreadyOwnedByYou when the same S3 owner recreates 
an existing bucket (#10814).
---
 .../dist/src/main/smoketest/s3/bucketcreate.robot  |  2 +-
 .../ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java   | 30 +++++++++++
 .../ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java   | 31 +++++++++++
 .../hadoop/ozone/s3/endpoint/BucketEndpoint.java   |  3 ++
 .../hadoop/ozone/s3/endpoint/EndpointBase.java     | 32 ++++++++++++
 .../hadoop/ozone/s3/exception/S3ErrorTable.java    |  4 ++
 .../hadoop/ozone/client/OzoneVolumeStub.java       |  1 +
 .../hadoop/ozone/s3/endpoint/TestBucketPut.java    | 60 ++++++++++++++++++++--
 8 files changed, 157 insertions(+), 6 deletions(-)

diff --git a/hadoop-ozone/dist/src/main/smoketest/s3/bucketcreate.robot 
b/hadoop-ozone/dist/src/main/smoketest/s3/bucketcreate.robot
index 4ca7a1d7bfb..3b86e610f3d 100644
--- a/hadoop-ozone/dist/src/main/smoketest/s3/bucketcreate.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/s3/bucketcreate.robot
@@ -35,7 +35,7 @@ Create new bucket
 Create bucket which already exists
     ${bucket} =         Create bucket
     ${result} =         Execute AWSS3APICli and checkrc         create-bucket 
--bucket ${bucket}   255
-                        Should contain          ${result}           
BucketAlreadyExists
+                        Should contain          ${result}           
BucketAlreadyOwnedByYou
 
 Create bucket with invalid bucket name
     ${randStr} =        Generate Ozone String
diff --git 
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
 
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
index 514834d243d..7dc5c8c5ad2 100644
--- 
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
+++ 
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
@@ -129,6 +129,7 @@
 import org.apache.hadoop.hdds.client.ReplicationFactor;
 import org.apache.hadoop.hdds.client.ReplicationType;
 import org.apache.hadoop.ozone.MiniOzoneCluster;
+import org.apache.hadoop.ozone.client.BucketArgs;
 import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneBucket;
 import org.apache.hadoop.ozone.client.OzoneClient;
@@ -254,6 +255,35 @@ public void testCreateBucket() {
     assertTrue(isBucketEmpty(b));
   }
 
+  /**
+   * s3-tests: test_bucket_create_exists.
+   */
+  @Test
+  public void testCreateBucketAlreadyOwnedByYou() {
+    final String bucketName = getBucketName("owned-by-you");
+    s3Client.createBucket(bucketName);
+
+    AmazonServiceException ase = assertThrows(AmazonServiceException.class,
+        () -> s3Client.createBucket(bucketName));
+    assertEquals(409, ase.getStatusCode());
+    assertEquals(S3ErrorTable.BUCKET_ALREADY_OWNED_BY_YOU.getCode(), 
ase.getErrorCode());
+  }
+
+  @Test
+  public void testCreateBucketAlreadyExistsDifferentOwner() throws IOException 
{
+    final String bucketName = getBucketName("other-owner");
+    final String otherOwner = "other-s3-owner";
+    try (OzoneClient ozoneClient = cluster.newClient()) {
+      ozoneClient.getObjectStore().getS3Volume().createBucket(bucketName,
+          BucketArgs.newBuilder().setOwner(otherOwner).build());
+    }
+
+    AmazonServiceException ase = assertThrows(AmazonServiceException.class,
+        () -> s3Client.createBucket(bucketName));
+    assertEquals(409, ase.getStatusCode());
+    assertEquals(S3ErrorTable.BUCKET_ALREADY_EXISTS.getCode(), 
ase.getErrorCode());
+  }
+
   @Test
   public void testBucketACLOperations() {
     // TODO HDDS-11738: Uncomment assertions when bucket S3 ACL logic has been 
fixed
diff --git 
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
 
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
index 34cccaca4a9..750e5742156 100644
--- 
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
+++ 
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
@@ -233,6 +233,37 @@ void closeClient() {
     }
   }
 
+  /**
+   * s3-tests: test_bucket_create_exists.
+   */
+  @Test
+  public void testCreateBucketAlreadyOwnedByYou() {
+    final String bucketName = getBucketName("owned-by-you");
+    s3Client.createBucket(b -> b.bucket(bucketName));
+
+    S3Exception exception = assertThrows(S3Exception.class,
+        () -> s3Client.createBucket(b -> b.bucket(bucketName)));
+    assertEquals(409, exception.statusCode());
+    assertEquals(S3ErrorTable.BUCKET_ALREADY_OWNED_BY_YOU.getCode(),
+        exception.awsErrorDetails().errorCode());
+  }
+
+  @Test
+  public void testCreateBucketAlreadyExistsDifferentOwner() throws IOException 
{
+    final String bucketName = getBucketName("other-owner");
+    final String otherOwner = "other-s3-owner";
+    try (OzoneClient ozoneClient = cluster.newClient()) {
+      ozoneClient.getObjectStore().getS3Volume().createBucket(bucketName,
+          BucketArgs.newBuilder().setOwner(otherOwner).build());
+    }
+
+    S3Exception exception = assertThrows(S3Exception.class,
+        () -> s3Client.createBucket(b -> b.bucket(bucketName)));
+    assertEquals(409, exception.statusCode());
+    assertEquals(S3ErrorTable.BUCKET_ALREADY_EXISTS.getCode(),
+        exception.awsErrorDetails().errorCode());
+  }
+
   @Test
   public void testPutObject() {
     final String bucketName = getBucketName();
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java
index 438ed93670c..ad545667fea 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java
@@ -272,6 +272,9 @@ public Response put(
     try {
       return handler.handlePutRequest(context, bucketName, body);
     } catch (OMException ex) {
+      if (ex.getResult() == ResultCodes.BUCKET_ALREADY_EXISTS) {
+        throw newDuplicateBucketError(bucketName, ex);
+      }
       throw newError(bucketName, ex);
     }
   }
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
index 452303dc4c0..60ed3a29fa7 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
@@ -28,6 +28,8 @@
 import static org.apache.hadoop.ozone.OzoneConsts.KB;
 import static 
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_CLIENT_BUFFER_SIZE_DEFAULT;
 import static 
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_CLIENT_BUFFER_SIZE_KEY;
+import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.BUCKET_ALREADY_EXISTS;
+import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.BUCKET_ALREADY_OWNED_BY_YOU;
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_ARGUMENT;
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_REQUEST;
 import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_TAG;
@@ -107,6 +109,7 @@
 import org.apache.hadoop.ozone.s3.signature.SignatureInfo;
 import org.apache.hadoop.ozone.s3.util.AuditUtils;
 import org.apache.hadoop.ozone.s3.util.S3Utils;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.http.NameValuePair;
 import org.apache.http.client.utils.URLEncodedUtils;
 import org.apache.ratis.util.function.CheckedRunnable;
@@ -243,6 +246,35 @@ protected OzoneVolume getVolume() throws IOException {
     return client.getObjectStore().getS3Volume();
   }
 
+  /**
+   * Maps a duplicate bucket create to the S3 error expected by AWS when the
+   * requester already owns the bucket name.
+   */
+  protected OS3Exception newDuplicateBucketError(String bucketName, 
OMException cause) {
+    try {
+      OzoneBucket existingBucket = getVolume().getBucket(bucketName);
+      if (isSameBucketOwner(existingBucket.getOwner())) {
+        return newError(BUCKET_ALREADY_OWNED_BY_YOU, bucketName, cause);
+      }
+    } catch (IOException ex) {
+      LOG.debug("Could not resolve duplicate bucket owner for {}", bucketName, 
ex);
+    }
+    return newError(BUCKET_ALREADY_EXISTS, bucketName, cause);
+  }
+
+  private boolean isSameBucketOwner(String bucketOwner) {
+    String requestOwner = getRequestOwner();
+    return requestOwner != null && requestOwner.equals(bucketOwner);
+  }
+
+  private String getRequestOwner() {
+    if (s3Auth == null || s3Auth.getUserPrincipal() == null) {
+      return null;
+    }
+    return UserGroupInformation.createRemoteUser(s3Auth.getUserPrincipal())
+        .getShortUserName();
+  }
+
   /**
    * Returns Iterator to iterate over all buckets for a specific user.
    * The result can be restricted using bucket prefix, will return all
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
index b8240ba8088..d59aa36db0e 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java
@@ -134,6 +134,10 @@ public enum S3ErrorTable {
       "BucketAlreadyExists", "The requested bucket name is not available" +
       " as it already exists.", HTTP_CONFLICT),
 
+  BUCKET_ALREADY_OWNED_BY_YOU(
+      "BucketAlreadyOwnedByYou", "Your previous request to create the named" +
+      " bucket succeeded and you already own it.", HTTP_CONFLICT),
+
   INVALID_TAG(
       "InvalidTag", "Your request contains tag input that is not valid.", 
HTTP_BAD_REQUEST),
 
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneVolumeStub.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneVolumeStub.java
index cb01d287642..414c397aeee 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneVolumeStub.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/OzoneVolumeStub.java
@@ -124,6 +124,7 @@ public void createBucket(String bucketName, BucketArgs 
bucketArgs) throws OMExce
     buckets.put(bucketName, OzoneBucketStub.newBuilder()
         .setVolumeName(getName())
         .setName(bucketName)
+        .setOwner(bucketArgs.getOwner())
         .setDefaultReplicationConfig(new DefaultReplicationConfig(
             RatisReplicationConfig.getInstance(
                 HddsProtos.ReplicationFactor.THREE)))
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java
index 32af79a6270..e5464e179aa 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestBucketPut.java
@@ -20,13 +20,19 @@
 import static java.net.HttpURLConnection.HTTP_OK;
 import static 
org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertErrorResponse;
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.BUCKET_ALREADY_EXISTS;
+import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.BUCKET_ALREADY_OWNED_BY_YOU;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import javax.ws.rs.core.Response;
+import org.apache.hadoop.ozone.OzoneConfigKeys;
 import org.apache.hadoop.ozone.OzoneConsts;
+import org.apache.hadoop.ozone.client.BucketArgs;
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.s3.signature.SignatureInfo;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -35,12 +41,16 @@
  */
 public class TestBucketPut {
 
+  private static final String BUCKET_OWNER = "my-s3-owner";
+  private static final String OTHER_BUCKET_OWNER = "other-s3-owner";
+
   private String bucketName = OzoneConsts.BUCKET;
   private BucketEndpoint bucketEndpoint;
+  private OzoneClient clientStub;
 
   @BeforeEach
   public void setup() throws Exception {
-    OzoneClient clientStub = new OzoneClientStub();
+    clientStub = new OzoneClientStub();
 
     bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder()
         .setClient(clientStub)
@@ -48,13 +58,53 @@ public void setup() throws Exception {
   }
 
   @Test
-  public void testCreateBucketAndFailOnDuplicate() throws Exception {
+  public void testCreateBucketAndFailOnDuplicateWithSameOwner() throws 
Exception {
+    BucketEndpoint endpoint = newBucketEndpointWithRequestOwner(BUCKET_OWNER);
+    
clientStub.getObjectStore().createVolume(OzoneConfigKeys.OZONE_S3_VOLUME_NAME_DEFAULT);
+    clientStub.getObjectStore().getS3Volume().createBucket(bucketName,
+        BucketArgs.newBuilder().setOwner(BUCKET_OWNER).build());
+
+    assertErrorResponse(BUCKET_ALREADY_OWNED_BY_YOU,
+        () -> endpoint.put(bucketName, null));
+  }
+
+  @Test
+  public void testCreateBucketAndFailOnDuplicateWithUnknownRequestOwner() 
throws Exception {
+    
clientStub.getObjectStore().createVolume(OzoneConfigKeys.OZONE_S3_VOLUME_NAME_DEFAULT);
+    clientStub.getObjectStore().getS3Volume().createBucket(bucketName);
+
+    assertErrorResponse(BUCKET_ALREADY_EXISTS,
+        () -> bucketEndpoint.put(bucketName, null));
+  }
+
+  @Test
+  public void testCreateBucketAndFailOnDuplicateWithDifferentOwner() throws 
Exception {
+    BucketEndpoint endpoint = newBucketEndpointWithRequestOwner(BUCKET_OWNER);
+    
clientStub.getObjectStore().createVolume(OzoneConfigKeys.OZONE_S3_VOLUME_NAME_DEFAULT);
+    clientStub.getObjectStore().getS3Volume().createBucket(bucketName,
+        BucketArgs.newBuilder().setOwner(OTHER_BUCKET_OWNER).build());
+
+    assertErrorResponse(BUCKET_ALREADY_EXISTS,
+        () -> endpoint.put(bucketName, null));
+  }
+
+  @Test
+  public void testCreateBucketSuccess() throws Exception {
     Response response = bucketEndpoint.put(bucketName, null);
     assertEquals(HTTP_OK, response.getStatus());
     assertNotNull(response.getLocation());
+  }
 
-    // Create-bucket on an existing bucket fails
-    assertErrorResponse(BUCKET_ALREADY_EXISTS,
-        () -> bucketEndpoint.put(bucketName, null));
+  private BucketEndpoint newBucketEndpointWithRequestOwner(String 
requestOwner) {
+    SignatureInfo signatureInfo = mock(SignatureInfo.class);
+    when(signatureInfo.isSignPayload()).thenReturn(true);
+    when(signatureInfo.getAwsAccessId()).thenReturn(requestOwner);
+    when(signatureInfo.getStringToSign()).thenReturn("");
+    when(signatureInfo.getSignature()).thenReturn("");
+
+    return EndpointBuilder.newBucketEndpointBuilder()
+        .setClient(clientStub)
+        .setSignatureInfo(signatureInfo)
+        .build();
   }
 }


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

Reply via email to