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]