From 578b9c68a6b6cc3fcad8e22e1caee541482afa35 Mon Sep 17 00:00:00 2001 From: Priyesh Karatha Date: Wed, 7 Oct 2026 10:43:05 +0530 Subject: [PATCH] HDDS-16657. Reject lifecycle Transition actions in PutBucketLifecycleConfiguration --- .../s3/endpoint/S3LifecycleConfiguration.java | 63 +++++++++++++++++++ .../TestS3LifecycleConfigurationPut.java | 27 ++++++++ 2 files changed, 90 insertions(+) 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 2327f9b759c1..f7b628f23a63 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 @@ -83,6 +83,11 @@ public static class Rule { @XmlElement(name = "Filter") private Filter filter; + // Ozone does not support lifecycle Transition actions. This is bound only so a Transition + // element is detected and rejected instead of being silently dropped by JAXB. + @XmlElement(name = "Transition") + private List transitions; + public String getId() { return id; } @@ -130,6 +135,14 @@ public Filter getFilter() { public void setFilter(Filter filter) { this.filter = filter; } + + public List getTransitions() { + return transitions; + } + + public void setTransitions(List transitions) { + this.transitions = transitions; + } } /** @@ -181,6 +194,48 @@ public void setDaysAfterInitiation(Integer daysAfterInitiation) { } } + /** + * Transition entity for lifecycle rule. Ozone does not support lifecycle transition actions + * for any storage class; this is modeled only so a Transition element in the request is + * detected and rejected during conversion instead of being silently ignored. + */ + @XmlAccessorType(XmlAccessType.FIELD) + @XmlRootElement(name = "Transition") + public static class Transition { + @XmlElement(name = "Days") + private String days; + + @XmlElement(name = "Date") + private String date; + + @XmlElement(name = "StorageClass") + private String storageClass; + + public Integer getDays() { + return parseDays(days); + } + + public void setDays(Integer days) { + this.days = days == null ? null : days.toString(); + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getStorageClass() { + return storageClass; + } + + public void setStorageClass(String storageClass) { + this.storageClass = storageClass; + } + } + /** * Parses a lifecycle day-count element, rejecting a value that does not fit in an int instead * of silently overflowing it the way JAXB's built-in Integer converter would. @@ -343,6 +398,14 @@ private OmLCRule convertToOmRule(Rule rule) throws OMException, OS3Exception { if (!STATUS_ENABLED.equals(status) && !STATUS_DISABLED.equals(status)) { throw S3ErrorTable.newError(S3ErrorTable.MALFORMED_XML); } + if (rule.getTransitions() != null && !rule.getTransitions().isEmpty()) { + String storageClass = rule.getTransitions().get(0).getStorageClass(); + String message = StringUtils.isNotEmpty(storageClass) + ? "Invalid lifecycle configuration: storage class '" + storageClass + + "' is not supported for lifecycle transitions" + : "Invalid lifecycle configuration: Transition actions are not supported"; + throw S3ErrorTable.newError(S3ErrorTable.NOT_IMPLEMENTED, rule.getId()).withMessage(message); + } OmLCRule.Builder builder = new OmLCRule.Builder() .setEnabled(STATUS_ENABLED.equals(status)) 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 f38575bf9250..3b735184d255 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 @@ -21,6 +21,7 @@ import static java.net.HttpURLConnection.HTTP_FORBIDDEN; import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED; 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; @@ -28,6 +29,7 @@ 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.NOT_IMPLEMENTED; 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; @@ -350,6 +352,17 @@ public void testPutLifecycleConfigurationWithAbortDaysAfterInitiationOverflow() HTTP_BAD_REQUEST, INVALID_ARGUMENT.getCode()); } + @Test + public void testPutLifecycleConfigurationRejectsTransition() throws Exception { + // Ozone does not support lifecycle Transition actions. A rule containing one must be + // rejected instead of returning HTTP 200 for a rule Ozone can never enforce. + OS3Exception ex = assertThrows(OS3Exception.class, + () -> bucketEndpoint.put("bucket1", withTransition())); + assertEquals(HTTP_NOT_IMPLEMENTED, ex.getHttpCode()); + assertEquals(NOT_IMPLEMENTED.getCode(), ex.getCode()); + assertTrue(ex.getErrorMessage().contains("EC")); + } + private static InputStream onePrefix() { String xml = ("" + @@ -817,6 +830,20 @@ private static InputStream withExpirationDaysOverflow() { return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); } + private static InputStream withTransition() { + String xml = "" + + "" + + "unsupported-transition" + + "" + + "Enabled" + + "2044-01-19T00:00:00Z" + + "1EC" + + "" + + ""; + + return new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)); + } + private static InputStream withAbortDaysAfterInitiationOverflow() { String xml = "" + "" +