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]