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 037a367ceb1 HDDS-16650. Reuse bucket metadata in S3 lifecycle PUT and 
DELETE (#11372)
037a367ceb1 is described below

commit 037a367ceb14c13d2f6892ab328cbbffd26145df
Author: KUAN-HAO HUANG <[email protected]>
AuthorDate: Fri Oct 2 15:50:21 2026 +0800

    HDDS-16650. Reuse bucket metadata in S3 lifecycle PUT and DELETE (#11372)
---
 .../ozone/s3/endpoint/BucketLifecycleHandler.java  |   6 +-
 .../hadoop/ozone/s3/endpoint/S3RequestContext.java |  12 ++
 .../TestS3LifecycleConfigurationDelete.java        | 125 +++++++++++++++++++++
 .../endpoint/TestS3LifecycleConfigurationPut.java  |  90 +++++++++++++++
 .../ozone/s3/endpoint/TestS3RequestContext.java    |  86 ++++++++++++++
 5 files changed, 316 insertions(+), 3 deletions(-)

diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketLifecycleHandler.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketLifecycleHandler.java
index c305fe99c95..a375988d660 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketLifecycleHandler.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketLifecycleHandler.java
@@ -93,7 +93,7 @@ public Response 
deleteBucketLifecycleConfiguration(S3RequestContext context, Str
   protected void deleteLifecycleConfiguration(S3RequestContext context, String 
bucketName)
       throws IOException, OS3Exception {
     try {
-      context.getVolume().getBucket(bucketName).deleteLifecycleConfiguration();
+      context.getBucket(bucketName).deleteLifecycleConfiguration();
     } catch (OMException ex) {
       // DeleteBucketLifecycle is idempotent: deleting a missing config
       // must still return 204, not 404 — same as normal key deletion.
@@ -114,7 +114,7 @@ private void verifyBucketOwner(S3RequestContext context, 
String bucketName) thro
     }
 
     try {
-      String actualOwner = 
context.getVolume().getBucket(bucketName).getOwner();
+      String actualOwner = context.getBucket(bucketName).getOwner();
       if (actualOwner != null && !actualOwner.equals(expectedBucketOwner)) {
         LOG.debug("Bucket: {}, ExpectedBucketOwner: {}, ActualBucketOwner: {}",
             bucketName, expectedBucketOwner, actualOwner);
@@ -130,7 +130,7 @@ public Response 
putBucketLifecycleConfiguration(S3RequestContext context, String
       throws IOException, OS3Exception {
     verifyBucketOwner(context, bucketName);
     S3LifecycleConfiguration s3LifecycleConfiguration;
-    OzoneBucket ozoneBucket = context.getVolume().getBucket(bucketName);
+    OzoneBucket ozoneBucket = context.getBucket(bucketName);
     OmLifecycleConfiguration lcc;
     try {
       s3LifecycleConfiguration = new 
PutBucketLifecycleConfigurationUnmarshaller().readFrom(body);
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3RequestContext.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3RequestContext.java
index ebcb773cef4..6fab0a75c23 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3RequestContext.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3RequestContext.java
@@ -21,8 +21,10 @@
 import java.io.IOException;
 import org.apache.hadoop.ozone.audit.AuditLogger.PerformanceStringBuilder;
 import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneBucket;
 import org.apache.hadoop.ozone.client.OzoneVolume;
 import org.apache.hadoop.util.Time;
+import org.apache.ratis.util.Preconditions;
 
 class S3RequestContext {
   private final long startNanos;
@@ -30,6 +32,7 @@ class S3RequestContext {
   private final EndpointBase endpoint;
   private S3GAction action;
   private OzoneVolume volume;
+  private OzoneBucket bucket;
 
   S3RequestContext(EndpointBase endpoint, S3GAction action) {
     this.endpoint = endpoint;
@@ -54,6 +57,15 @@ OzoneVolume getVolume() throws IOException {
     return volume;
   }
 
+  OzoneBucket getBucket(String name) throws IOException {
+    if (bucket == null) {
+      bucket = getVolume().getBucket(name);
+    } else {
+      Preconditions.assertEquals(bucket.getName(), name, "Multiple buckets in 
one request are not supported");
+    }
+    return bucket;
+  }
+
   S3GAction getAction() {
     return action;
   }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3LifecycleConfigurationDelete.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3LifecycleConfigurationDelete.java
index 5080f192b9c..0775af8fa8e 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3LifecycleConfigurationDelete.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3LifecycleConfigurationDelete.java
@@ -19,21 +19,42 @@
 
 import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
 import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
+import static 
org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertErrorResponse;
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_LIFECYCLE_CONFIGURATION;
+import static 
org.apache.hadoop.ozone.s3.util.S3Consts.EXPECTED_BUCKET_OWNER_HEADER;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.ozone.client.ObjectStore;
+import org.apache.hadoop.ozone.client.OzoneBucket;
 import org.apache.hadoop.ozone.client.OzoneClient;
 import org.apache.hadoop.ozone.client.OzoneClientStub;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
 import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
 import org.apache.hadoop.ozone.s3.util.S3Consts;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 /**
  * Testing for DeleteBucketLifecycleConfiguration.
@@ -93,4 +114,108 @@ private static InputStream getBody() {
 
     return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
   }
+
+  @ParameterizedTest
+  @NullAndEmptySource
+  @ValueSource(strings = "owner")
+  public void testDeleteLifecycleLookupCount(String expectedOwner) throws 
Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders headers = mock(HttpHeaders.class);
+    
when(headers.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn(expectedOwner);
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, headers);
+
+    try (Response response = endpoint.delete("bucket1")) {
+      assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT);
+    }
+
+    verify(proxy).getBucketDetails("volume1", "bucket1");
+    verify(proxy).deleteLifecycleConfiguration("volume1", "bucket1");
+  }
+
+  @Test
+  public void testDeleteLifecycleWithoutHeaders() throws Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, null);
+    try (Response response = endpoint.delete("bucket1")) {
+      assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT);
+    }
+    verify(proxy).deleteLifecycleConfiguration("volume1", "bucket1");
+  }
+
+  @ParameterizedTest
+  @NullAndEmptySource
+  @ValueSource(strings = "owner")
+  public void testDeleteLifecycleMissingConfigurationIsIdempotent(String 
expectedOwner) throws Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders headers = mock(HttpHeaders.class);
+    
when(headers.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn(expectedOwner);
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, headers);
+    doThrow(new OMException("Missing configuration", 
ResultCodes.LIFECYCLE_CONFIGURATION_NOT_FOUND))
+        .when(proxy).deleteLifecycleConfiguration("volume1", "bucket1");
+    try (Response response = endpoint.delete("bucket1")) {
+      assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT);
+    }
+  }
+
+  @ParameterizedTest
+  @CsvSource({", BUCKET_NOT_FOUND, NO_SUCH_BUCKET", "owner, BUCKET_NOT_FOUND, 
NO_SUCH_BUCKET",
+      ", VOLUME_NOT_FOUND, NO_SUCH_BUCKET", "owner, VOLUME_NOT_FOUND, 
NO_SUCH_BUCKET",
+      ", PERMISSION_DENIED, ACCESS_DENIED", "owner, PERMISSION_DENIED, 
ACCESS_DENIED",
+      ", INTERNAL_ERROR, INTERNAL_ERROR", "owner, INTERNAL_ERROR, 
INTERNAL_ERROR"})
+  public void testDeleteLifecycleProtocolErrors(
+      String expectedOwner, ResultCodes code, S3ErrorTable expected) throws 
Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders headers = mock(HttpHeaders.class);
+    
when(headers.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn(expectedOwner);
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, headers);
+    doThrow(new OMException("Delete failed", 
code)).when(proxy).deleteLifecycleConfiguration("volume1", "bucket1");
+    assertErrorResponse(expected, () -> endpoint.delete("bucket1"));
+  }
+
+  @Test
+  public void testDeleteLifecycleRejectsOwnerBeforeDeleting() throws Exception 
{
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders headers = mock(HttpHeaders.class);
+    
when(headers.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("other-owner");
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, headers);
+    assertErrorResponse(S3ErrorTable.ACCESS_DENIED, () -> 
endpoint.delete("bucket1"));
+    verify(proxy, never()).deleteLifecycleConfiguration("volume1", "bucket1");
+  }
+
+  @ParameterizedTest
+  @CsvSource({", BUCKET_NOT_FOUND, NO_SUCH_BUCKET", "owner, BUCKET_NOT_FOUND, 
ACCESS_DENIED",
+      ", VOLUME_NOT_FOUND, NO_SUCH_BUCKET", "owner, VOLUME_NOT_FOUND, 
ACCESS_DENIED",
+      ", PERMISSION_DENIED, ACCESS_DENIED", "owner, PERMISSION_DENIED, 
ACCESS_DENIED",
+      ", INTERNAL_ERROR, INTERNAL_ERROR", "owner, INTERNAL_ERROR, 
ACCESS_DENIED",
+      "'', PERMISSION_DENIED, ACCESS_DENIED"})
+  public void testDeleteLifecycleLookupFailurePreventsDeletion(
+      String expectedOwner, ResultCodes code, S3ErrorTable expected) throws 
Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders headers = mock(HttpHeaders.class);
+    
when(headers.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn(expectedOwner);
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, headers);
+    when(proxy.getBucketDetails("volume1", "bucket1")).thenThrow(new 
OMException("Lookup failed", code));
+    assertErrorResponse(expected, () -> endpoint.delete("bucket1"));
+    verify(proxy, never()).deleteLifecycleConfiguration("volume1", "bucket1");
+  }
+
+  private BucketEndpoint newProtocolEndpoint(ClientProtocol proxy, HttpHeaders 
requestHeaders) throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    OzoneVolume volume = OzoneVolume.newBuilder(conf, proxy)
+        .setName("volume1").setAcls(Collections.emptyList()).build();
+    OzoneBucket bucket = OzoneBucket.newBuilder(conf, proxy)
+        .setVolumeName("volume1").setName("bucket1").setOwner("owner").build();
+    when(proxy.getBucketDetails("volume1", "bucket1")).thenReturn(bucket);
+    ObjectStore store = mock(ObjectStore.class);
+    when(store.getS3Volume()).thenReturn(volume);
+    when(store.getClientProxy()).thenReturn(proxy);
+    OzoneClient client = mock(OzoneClient.class);
+    when(client.getObjectStore()).thenReturn(store);
+    when(client.getProxy()).thenReturn(proxy);
+    BucketEndpoint endpoint = EndpointBuilder.newBucketEndpointBuilder()
+        .setClient(client).setHeaders(requestHeaders).build();
+    endpoint.queryParamsForTest().set(S3Consts.QueryParams.LIFECYCLE, "");
+    return endpoint;
+  }
+
 }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3LifecycleConfigurationPut.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3LifecycleConfigurationPut.java
index 0891b1dd8b9..f38575bf925 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3LifecycleConfigurationPut.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3LifecycleConfigurationPut.java
@@ -22,6 +22,7 @@
 import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;
 import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
 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.ACCESS_DENIED;
 import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INTERNAL_ERROR;
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_ARGUMENT;
@@ -30,6 +31,7 @@
 import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_BUCKET;
 import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.QUOTA_EXCEEDED;
 import static 
org.apache.hadoop.ozone.s3.util.S3Consts.EXPECTED_BUCKET_OWNER_HEADER;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -37,14 +39,18 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.Collections;
 import java.util.function.Supplier;
 import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.ozone.client.BucketArgs;
 import org.apache.hadoop.ozone.client.ObjectStore;
 import org.apache.hadoop.ozone.client.OzoneBucket;
@@ -53,14 +59,19 @@
 import org.apache.hadoop.ozone.client.OzoneVolume;
 import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.OmLCExpiration;
+import org.apache.hadoop.ozone.om.helpers.OmLifecycleConfiguration;
 import org.apache.hadoop.ozone.s3.exception.OS3Exception;
+import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
 import org.apache.hadoop.ozone.s3.util.S3Consts;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
 import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
 /**
@@ -820,4 +831,83 @@ private static InputStream 
withAbortDaysAfterInitiationOverflow() {
 
     return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
   }
+
+  @ParameterizedTest
+  @CsvSource({", OBJECT_STORE", "'', OBJECT_STORE", "owner, OBJECT_STORE",
+      ", FILE_SYSTEM_OPTIMIZED", "owner, FILE_SYSTEM_OPTIMIZED"})
+  public void testPutLifecycleReusesBucket(String expectedOwner, BucketLayout 
layout) throws Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders requestHeaders = mock(HttpHeaders.class);
+    
when(requestHeaders.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn(expectedOwner);
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, requestHeaders, 
layout);
+
+    try (Response response = endpoint.put("bucket1", onePrefix())) {
+      assertThat(response.getStatus()).isEqualTo(HTTP_OK);
+    }
+
+    verify(proxy).getBucketDetails("volume1", "bucket1");
+    ArgumentCaptor<OmLifecycleConfiguration> config = 
ArgumentCaptor.forClass(OmLifecycleConfiguration.class);
+    verify(proxy).setLifecycleConfiguration(config.capture());
+    assertThat(config.getValue().getVolume()).isEqualTo("volume1");
+    assertThat(config.getValue().getBucket()).isEqualTo("bucket1");
+    assertThat(config.getValue().getBucketLayout()).isEqualTo(layout);
+  }
+
+  @Test
+  public void testPutLifecycleOwnerMismatchPrecedesXmlValidation() throws 
Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders requestHeaders = mock(HttpHeaders.class);
+    
when(requestHeaders.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("other-owner");
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, requestHeaders, 
BucketLayout.OBJECT_STORE);
+    assertErrorResponse(ACCESS_DENIED, () -> endpoint.put("bucket1", null));
+    verify(proxy, never()).setLifecycleConfiguration(any());
+  }
+
+  @ParameterizedTest
+  @CsvSource({", BUCKET_NOT_FOUND, NO_SUCH_BUCKET", "owner, BUCKET_NOT_FOUND, 
ACCESS_DENIED",
+      ", INTERNAL_ERROR, INTERNAL_ERROR", "owner, INTERNAL_ERROR, 
ACCESS_DENIED",
+      ", PERMISSION_DENIED, ACCESS_DENIED", "owner, PERMISSION_DENIED, 
ACCESS_DENIED",
+      "'', PERMISSION_DENIED, ACCESS_DENIED", ", VOLUME_NOT_FOUND, 
NO_SUCH_BUCKET",
+      "owner, VOLUME_NOT_FOUND, ACCESS_DENIED"})
+  public void testPutLifecycleLookupFailurePrecedesXmlValidation(
+      String expectedOwner, ResultCodes code, S3ErrorTable expected) throws 
Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders requestHeaders = mock(HttpHeaders.class);
+    
when(requestHeaders.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn(expectedOwner);
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, requestHeaders, 
BucketLayout.OBJECT_STORE);
+    when(proxy.getBucketDetails("volume1", "bucket1")).thenThrow(new 
OMException("Lookup failed", code));
+    assertErrorResponse(expected, () -> endpoint.put("bucket1", null));
+    verify(proxy, never()).setLifecycleConfiguration(any());
+  }
+
+  @Test
+  public void testPutLifecycleMatchingOwnerStillValidatesXml() throws 
Exception {
+    ClientProtocol proxy = mock(ClientProtocol.class);
+    HttpHeaders requestHeaders = mock(HttpHeaders.class);
+    
when(requestHeaders.getHeaderString(EXPECTED_BUCKET_OWNER_HEADER)).thenReturn("owner");
+    BucketEndpoint endpoint = newProtocolEndpoint(proxy, requestHeaders, 
BucketLayout.OBJECT_STORE);
+    assertErrorResponse(MALFORMED_XML, () -> endpoint.put("bucket1", null));
+    verify(proxy, never()).setLifecycleConfiguration(any());
+  }
+
+  private BucketEndpoint newProtocolEndpoint(ClientProtocol proxy, HttpHeaders 
requestHeaders, BucketLayout layout)
+      throws Exception {
+    OzoneConfiguration conf = new OzoneConfiguration();
+    OzoneVolume volume = OzoneVolume.newBuilder(conf, proxy)
+        .setName("volume1").setAcls(Collections.emptyList()).build();
+    OzoneBucket bucket = OzoneBucket.newBuilder(conf, proxy)
+        
.setVolumeName("volume1").setName("bucket1").setOwner("owner").setBucketLayout(layout).build();
+    when(proxy.getBucketDetails("volume1", "bucket1")).thenReturn(bucket);
+    ObjectStore store = mock(ObjectStore.class);
+    when(store.getS3Volume()).thenReturn(volume);
+    when(store.getClientProxy()).thenReturn(proxy);
+    OzoneClient client = mock(OzoneClient.class);
+    when(client.getObjectStore()).thenReturn(store);
+    when(client.getProxy()).thenReturn(proxy);
+    BucketEndpoint endpoint = EndpointBuilder.newBucketEndpointBuilder()
+        .setClient(client).setHeaders(requestHeaders).build();
+    endpoint.queryParamsForTest().set(S3Consts.QueryParams.LIFECYCLE, "");
+    return endpoint;
+  }
+
 }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3RequestContext.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3RequestContext.java
new file mode 100644
index 00000000000..dc96fc0cde2
--- /dev/null
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestS3RequestContext.java
@@ -0,0 +1,86 @@
+/*
+ * 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.s3.endpoint;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import org.apache.hadoop.ozone.audit.S3GAction;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Tests bucket reuse within a request. */
+class TestS3RequestContext {
+  private EndpointBase endpoint;
+  private OzoneVolume volume;
+  private OzoneBucket bucket;
+  private S3RequestContext context;
+
+  @BeforeEach
+  void setup() throws IOException {
+    endpoint = mock(EndpointBase.class);
+    volume = mock(OzoneVolume.class);
+    bucket = mock(OzoneBucket.class);
+    when(bucket.getName()).thenReturn("bucket1");
+    when(endpoint.getVolume()).thenReturn(volume);
+    when(volume.getBucket("bucket1")).thenReturn(bucket);
+    context = new S3RequestContext(endpoint, S3GAction.PUT_BUCKET_LIFECYCLE);
+  }
+
+  @Test
+  void reusesBucketWithinRequest() throws IOException {
+    assertThat(context.getBucket("bucket1")).isSameAs(bucket);
+    assertThat(context.getBucket("bucket1")).isSameAs(bucket);
+    verify(volume).getBucket("bucket1");
+    verify(endpoint).getVolume();
+  }
+
+  @Test
+  void rejectsDifferentBucket() throws IOException {
+    context.getBucket("bucket1");
+    assertThrows(IllegalStateException.class, () -> 
context.getBucket("bucket2"));
+    verify(volume, never()).getBucket("bucket2");
+    assertThat(context.getBucket("bucket1")).isSameAs(bucket);
+    verify(volume).getBucket("bucket1");
+  }
+
+  @Test
+  void retriesFailedLookup() throws IOException {
+    when(volume.getBucket("bucket1")).thenThrow(new IOException("Lookup 
failed")).thenReturn(bucket);
+    assertThrows(IOException.class, () -> context.getBucket("bucket1"));
+    assertThat(context.getBucket("bucket1")).isSameAs(bucket);
+    assertThat(context.getBucket("bucket1")).isSameAs(bucket);
+    verify(volume, times(2)).getBucket("bucket1");
+  }
+
+  @Test
+  void doesNotShareBucketBetweenRequests() throws IOException {
+    S3RequestContext second = new S3RequestContext(endpoint, 
S3GAction.PUT_BUCKET_LIFECYCLE);
+    assertThat(context.getBucket("bucket1")).isSameAs(bucket);
+    assertThat(second.getBucket("bucket1")).isSameAs(bucket);
+    verify(volume, times(2)).getBucket("bucket1");
+  }
+}


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

Reply via email to