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

ChenSammi 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 a9b67b11ccb HDDS-16150. Fix error code for invalid S3 Lifecycle 
Expiration Days from InvalidRequest to InvalidArgument (#11003)
a9b67b11ccb is described below

commit a9b67b11ccb7c0b8efc075d3123d23d1f4c93133
Author: Henry Chen <[email protected]>
AuthorDate: Tue Aug 18 14:06:37 2026 +0800

    HDDS-16150. Fix error code for invalid S3 Lifecycle Expiration Days from 
InvalidRequest to InvalidArgument (#11003)
---
 .../ozone/s3/endpoint/BucketLifecycleHandler.java  | 19 ++++-
 .../s3/endpoint/S3LifecycleConfiguration.java      |  4 +-
 .../endpoint/TestS3LifecycleConfigurationPut.java  | 82 +++++++++++++++++++---
 3 files changed, 89 insertions(+), 16 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 61191196c0d..cf4270f925b 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
@@ -131,15 +131,28 @@ public Response 
putBucketLifecycleConfiguration(S3RequestContext context, String
     verifyBucketOwner(context, bucketName);
     S3LifecycleConfiguration s3LifecycleConfiguration;
     OzoneBucket ozoneBucket = context.getVolume().getBucket(bucketName);
+    OmLifecycleConfiguration lcc;
     try {
       s3LifecycleConfiguration = new 
PutBucketLifecycleConfigurationUnmarshaller().readFrom(null,
           null, null, null, null, body);
-      OmLifecycleConfiguration lcc =
-          s3LifecycleConfiguration.toOmLifecycleConfiguration(ozoneBucket);
-      ozoneBucket.setLifecycleConfiguration(lcc);
+      lcc = s3LifecycleConfiguration.toOmLifecycleConfiguration(ozoneBucket);
     } catch (WebApplicationException ex) {
       throw S3ErrorTable.newError(S3ErrorTable.MALFORMED_XML, bucketName);
     } catch (OMException ex) {
+      // Rule validation rejects client-supplied values with INVALID_REQUEST, 
which the shared
+      // translation maps to InvalidRequest. AWS S3 uses InvalidArgument for a 
rejected lifecycle
+      // configuration, so only this validation step is remapped.
+      if (ex.getResult() == OMException.ResultCodes.INVALID_REQUEST) {
+        throw S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, bucketName, 
ex);
+      }
+      throw S3ErrorTable.newError(bucketName, ex);
+    }
+
+    try {
+      ozoneBucket.setLifecycleConfiguration(lcc);
+    } catch (OMException ex) {
+      // OM raises INVALID_REQUEST for server-side conditions as well, such as 
a bucket layout
+      // mismatch, so its result codes keep the shared translation instead of 
being remapped.
       throw S3ErrorTable.newError(bucketName, ex);
     }
     return Response.ok().build();
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3LifecycleConfiguration.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3LifecycleConfiguration.java
index ec9ada89a9f..f7d72421fe1 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3LifecycleConfiguration.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/S3LifecycleConfiguration.java
@@ -303,9 +303,9 @@ public OmLifecycleConfiguration 
toOmLifecycleConfiguration(OzoneBucket ozoneBuck
       if (ex.getCause() instanceof OMException) {
         throw (OMException) ex.getCause();
       }
-      throw S3ErrorTable.newError(S3ErrorTable.INVALID_REQUEST, 
ozoneBucket.getName(), ex);
+      throw S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, 
ozoneBucket.getName(), ex);
     } catch (IllegalStateException ex) {
-      throw S3ErrorTable.newError(S3ErrorTable.INVALID_REQUEST, 
ozoneBucket.getName(), ex);
+      throw S3ErrorTable.newError(S3ErrorTable.INVALID_ARGUMENT, 
ozoneBucket.getName(), ex);
     }
   }
 
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 d76ff51b66b..e923526a0f5 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
@@ -24,6 +24,7 @@
 import static java.net.HttpURLConnection.HTTP_OK;
 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;
 import static 
org.apache.hadoop.ozone.s3.exception.S3ErrorTable.INVALID_REQUEST;
 import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.MALFORMED_XML;
 import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NO_SUCH_BUCKET;
@@ -109,22 +110,39 @@ public void 
testLifecycleConfigurationFailWithNonExistentBucket()
   @Test
   public void testPutInvalidLifecycleConfiguration() throws Exception {
     
testInvalidLifecycleConfiguration(TestS3LifecycleConfigurationPut::withoutAction,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
     
testInvalidLifecycleConfiguration(TestS3LifecycleConfigurationPut::withoutFilter,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
     testInvalidLifecycleConfiguration(this::useDuplicateTagInAndOperator,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
     
testInvalidLifecycleConfiguration(this::usePrefixTagWithoutAndOperatorInFilter,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
     
testInvalidLifecycleConfiguration(this::usePrefixAndOperatorCoExistInFilter,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
     testInvalidLifecycleConfiguration(this::usePrefixFilterCoExist,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
     testInvalidLifecycleConfiguration(this::useAndOperatorOnlyOnePrefix,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
     testInvalidLifecycleConfiguration(this::useAndOperatorOnlyOneTag,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
     testInvalidLifecycleConfiguration(this::useEmptyAndOperator,
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
+    
testInvalidLifecycleConfiguration(TestS3LifecycleConfigurationPut::withExpirationZeroDays,
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
+    
testInvalidLifecycleConfiguration(TestS3LifecycleConfigurationPut::withExpirationDaysAndDate,
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
+    
testInvalidLifecycleConfiguration(TestS3LifecycleConfigurationPut::withEmptyFilterAndInvalidDate,
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
+  }
+
+  @Test
+  public void testPutLifecycleConfigurationPropagatesOmInvalidRequest()
+      throws Exception {
+    // OM also raises INVALID_REQUEST for conditions that are not rejected 
rule values, such as the
+    // bucket layout mismatch in OMLifecycleConfigurationSetRequest. Those 
keep reporting
+    // InvalidRequest instead of being remapped to InvalidArgument.
+    assertUnhandledOMExceptionPropagated(
+        new OMException("Bucket layout mismatch", 
OMException.ResultCodes.INVALID_REQUEST),
         HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
   }
 
@@ -176,7 +194,7 @@ public void testPutInvalidExpirationDateLCC() throws 
Exception {
       fail();
     } catch (OS3Exception ex) {
       assertEquals(HTTP_BAD_REQUEST, ex.getHttpCode());
-      assertEquals(INVALID_REQUEST.getCode(), ex.getCode());
+      assertEquals(INVALID_ARGUMENT.getCode(), ex.getCode());
     }
   }
 
@@ -269,12 +287,12 @@ public void 
testPutInvalidAbortIncompleteMultipartUploadConfig() throws Exceptio
     // Test with zero days - should fail
     testInvalidLifecycleConfiguration(
         TestS3LifecycleConfigurationPut::withAbortZeroDays,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
 
     // Test with negative days - should fail
     testInvalidLifecycleConfiguration(
         TestS3LifecycleConfigurationPut::withAbortNegativeDays,
-        HTTP_BAD_REQUEST, INVALID_REQUEST.getCode());
+        HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode());
   }
 
   private static InputStream onePrefix() {
@@ -304,6 +322,48 @@ private static InputStream withoutAction() {
     return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
   }
 
+  private static InputStream withExpirationZeroDays() {
+    String xml = (
+        "<LifecycleConfiguration xmlns=\"http://s3.amazonaws"; +
+        ".com/doc/2006-03-01/\">" +
+        "<Rule>" +
+        "<ID>zero-days</ID>" +
+        "<Prefix>prefix/</Prefix>" +
+        "<Status>Enabled</Status>" +
+        "<Expiration><Days>0</Days></Expiration>" +
+        "</Rule>" +
+        "</LifecycleConfiguration>");
+    return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+  }
+
+  private static InputStream withExpirationDaysAndDate() {
+    String xml = (
+        "<LifecycleConfiguration xmlns=\"http://s3.amazonaws"; +
+        ".com/doc/2006-03-01/\">" +
+        "<Rule>" +
+        "<ID>days-and-date</ID>" +
+        "<Prefix>prefix/</Prefix>" +
+        "<Status>Enabled</Status>" +
+        
"<Expiration><Days>30</Days><Date>2044-01-19T00:00:00+00:00</Date></Expiration>"
 +
+        "</Rule>" +
+        "</LifecycleConfiguration>");
+    return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+  }
+
+  private static InputStream withEmptyFilterAndInvalidDate() {
+    String xml = (
+        "<LifecycleConfiguration xmlns=\"http://s3.amazonaws"; +
+        ".com/doc/2006-03-01/\">" +
+        "<Rule>" +
+        "<ID>empty-filter-invalid-date</ID>" +
+        "<Filter></Filter>" +
+        "<Status>Enabled</Status>" +
+        "<Expiration><Date>2023-03-03</Date></Expiration>" +
+        "</Rule>" +
+        "</LifecycleConfiguration>");
+    return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8));
+  }
+
   private static InputStream withoutStatus() {
     String xml = (
         "<LifecycleConfiguration xmlns=\"http://s3.amazonaws"; +


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

Reply via email to