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 6d15218437b HDDS-16372. Surface OM's detailed message for rejected
lifecycle configuration (#11189)
6d15218437b is described below
commit 6d15218437ba3c617c6ccda8cea8682cefdbf165
Author: Priyesh Karatha <[email protected]>
AuthorDate: Tue Sep 15 12:57:34 2026 +0530
HDDS-16372. Surface OM's detailed message for rejected lifecycle
configuration (#11189)
---
.../ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java | 21 ++++++++++
.../ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java | 20 +++++++++
.../ozone/s3/endpoint/BucketLifecycleHandler.java | 5 ++-
.../endpoint/TestS3LifecycleConfigurationPut.java | 48 +++++++++++++++++-----
4 files changed, 82 insertions(+), 12 deletions(-)
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 42e8140dcb8..07dae405a12 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
@@ -2257,6 +2257,27 @@ public void testS3LifecycleConfigurationCreationFailed()
{
assertEquals(ErrorType.Client, ase2.getErrorType());
assertEquals(HttpURLConnection.HTTP_NOT_FOUND, ase2.getStatusCode());
assertEquals("NoSuchBucket", ase2.getErrorCode());
+
+ BucketLifecycleConfiguration pastDateConfig = new
BucketLifecycleConfiguration();
+ List<BucketLifecycleConfiguration.Rule> pastDateRules = new ArrayList<>();
+ BucketLifecycleConfiguration.Rule pastDateRule = new
BucketLifecycleConfiguration.Rule()
+ .withId("past-date")
+ .withPrefix("logs/")
+ .withStatus(BucketLifecycleConfiguration.ENABLED)
+ .withExpirationDate(Date.from(Instant.parse("2020-01-01T00:00:00Z")));
+ pastDateRules.add(pastDateRule);
+ pastDateConfig.setRules(pastDateRules);
+ SetBucketLifecycleConfigurationRequest pastDateRequest =
+ new SetBucketLifecycleConfigurationRequest(bucketName, pastDateConfig);
+
+ AmazonServiceException ase3 = assertThrows(AmazonServiceException.class,
+ () -> s3Client.setBucketLifecycleConfiguration(pastDateRequest));
+ assertEquals(ErrorType.Client, ase3.getErrorType());
+ assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, ase3.getStatusCode());
+ assertEquals(S3ErrorTable.INVALID_REQUEST.getCode(), ase3.getErrorCode());
+ assertTrue(ase3.getErrorMessage().contains("must be in the future"),
+ "The client should receive OM's detailed message explaining the date
is in the past, got: "
+ + ase3.getErrorMessage());
}
@Test
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 bfcf5b741bc..28c4efad510 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
@@ -2785,6 +2785,26 @@ public void testS3LifecycleConfigurationCreationFailed()
{
.lifecycleConfiguration(validConfig)));
assertEquals(HttpURLConnection.HTTP_NOT_FOUND, exception2.statusCode());
assertEquals(S3ErrorTable.NO_SUCH_BUCKET.getCode(),
exception2.awsErrorDetails().errorCode());
+
+ LifecycleRule pastDateRule = LifecycleRule.builder()
+ .id("past-date")
+ .prefix("logs/")
+ .status(ExpirationStatus.ENABLED)
+
.expiration(LifecycleExpiration.builder().date(Instant.parse("2020-01-01T00:00:00Z")).build())
+ .build();
+ BucketLifecycleConfiguration pastDateConfig =
BucketLifecycleConfiguration.builder()
+ .rules(pastDateRule)
+ .build();
+
+ S3Exception exception3 = assertThrows(S3Exception.class,
+ () -> s3Client.putBucketLifecycleConfiguration(b -> b
+ .bucket(bucketName)
+ .lifecycleConfiguration(pastDateConfig)));
+ assertEquals(HttpURLConnection.HTTP_BAD_REQUEST, exception3.statusCode());
+ assertEquals(S3ErrorTable.INVALID_REQUEST.getCode(),
exception3.awsErrorDetails().errorCode());
+ assertTrue(exception3.awsErrorDetails().errorMessage().contains("must be
in the future"),
+ "The client should receive OM's detailed message explaining the date
is in the past, got: "
+ + exception3.awsErrorDetails().errorMessage());
}
@Test
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 f25ea9d8b89..ae6efe0f107 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
@@ -142,7 +142,7 @@ public Response
putBucketLifecycleConfiguration(S3RequestContext context, String
// 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(S3ErrorTable.INVALID_ARGUMENT, bucketName,
ex).withMessage(ex.getMessage());
}
throw S3ErrorTable.newError(bucketName, ex);
}
@@ -152,6 +152,9 @@ public Response
putBucketLifecycleConfiguration(S3RequestContext context, String
} 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.
+ if (ex.getResult() == OMException.ResultCodes.INVALID_REQUEST) {
+ throw S3ErrorTable.newError(bucketName,
ex).withMessage(ex.getMessage());
+ }
throw S3ErrorTable.newError(bucketName, ex);
}
return Response.ok().build();
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 e923526a0f5..1abace96ab4 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
@@ -32,6 +32,7 @@
import static
org.apache.hadoop.ozone.s3.util.S3Consts.EXPECTED_BUCKET_OWNER_HEADER;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doThrow;
@@ -53,6 +54,7 @@
import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.OmLCExpiration;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
import org.apache.hadoop.ozone.s3.util.S3Consts;
import org.junit.jupiter.api.BeforeEach;
@@ -140,10 +142,13 @@ 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());
+ // InvalidRequest instead of being remapped to InvalidArgument, and OM's
message is surfaced.
+ OMException omException = new OMException("Bucket layout mismatch",
OMException.ResultCodes.INVALID_REQUEST);
+ OS3Exception ex = putLifecycleConfigurationThrowingOm(omException);
+ assertEquals(HTTP_BAD_REQUEST, ex.getHttpCode());
+ assertEquals(INVALID_REQUEST.getCode(), ex.getCode());
+ assertEquals(omException.getMessage(), ex.getErrorMessage(),
+ "OM's detailed message should be surfaced for server-side
INVALID_REQUEST");
}
@Test
@@ -239,13 +244,21 @@ public void
testPutLifecycleConfigurationPropagatesInternalError()
throws Exception {
// An unexpected internal OM failure must be reported as InternalError
(HTTP 500),
// not swallowed into a false HTTP 200 success.
- assertUnhandledOMExceptionPropagated(
- new OMException("boom", OMException.ResultCodes.INTERNAL_ERROR),
- HTTP_INTERNAL_ERROR, INTERNAL_ERROR.getCode());
+ OMException omException = new OMException("boom",
OMException.ResultCodes.INTERNAL_ERROR);
+ OS3Exception ex = putLifecycleConfigurationThrowingOm(omException);
+ assertEquals(HTTP_INTERNAL_ERROR, ex.getHttpCode());
+ assertEquals(INTERNAL_ERROR.getCode(), ex.getCode());
+ assertEquals(INTERNAL_ERROR.getErrorMessage(), ex.getErrorMessage());
}
private void assertUnhandledOMExceptionPropagated(OMException omException,
int expectedHttpCode, String expectedErrorCode) throws Exception {
+ OS3Exception ex = putLifecycleConfigurationThrowingOm(omException);
+ assertEquals(expectedHttpCode, ex.getHttpCode());
+ assertEquals(expectedErrorCode, ex.getCode());
+ }
+
+ private OS3Exception putLifecycleConfigurationThrowingOm(OMException
omException) throws Exception {
OzoneClient mockClient = mock(OzoneClient.class);
ObjectStore mockObjectStore = mock(ObjectStore.class);
OzoneVolume mockVolume = mock(OzoneVolume.class);
@@ -266,10 +279,23 @@ private void
assertUnhandledOMExceptionPropagated(OMException omException,
.build();
endpoint.queryParamsForTest().set(S3Consts.QueryParams.LIFECYCLE, "");
- OS3Exception ex = assertThrows(OS3Exception.class,
- () -> endpoint.put("bucket1", onePrefix()));
- assertEquals(expectedHttpCode, ex.getHttpCode());
- assertEquals(expectedErrorCode, ex.getCode());
+ return assertThrows(OS3Exception.class, () -> endpoint.put("bucket1",
onePrefix()));
+ }
+
+ @Test
+ public void
testPutLifecycleConfigurationWithPastExpirationDateReturnsDetailedMessage()
throws Exception {
+ OMException omException = assertThrows(OMException.class,
+ () -> new
OmLCExpiration.Builder().setDate("2020-01-01T00:00:00Z").build()
+ .valid(System.currentTimeMillis()));
+ assertTrue(omException.getMessage().contains("must be in the future"),
+ "Precondition: OM's real validation message should explain the date is
in the past, got: "
+ + omException.getMessage());
+
+ OS3Exception ex = putLifecycleConfigurationThrowingOm(omException);
+ assertEquals(HTTP_BAD_REQUEST, ex.getHttpCode());
+ assertEquals(INVALID_REQUEST.getCode(), ex.getCode());
+ assertEquals(omException.getMessage(), ex.getErrorMessage(),
+ "The client should receive OM's detailed message explaining the date
is in the past");
}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]