Skip to content

HDDS-16151. Support x-amz-expiration header in HEAD object response for lifecycle expiration rules - #11157

Open
henry3260 wants to merge 3 commits into
apache:masterfrom
henry3260:HDDS-16151
Open

HDDS-16151. Support x-amz-expiration header in HEAD object response for lifecycle expiration rules#11157
henry3260 wants to merge 3 commits into
apache:masterfrom
henry3260:HDDS-16151

Conversation

@henry3260

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

On HeadObject, S3 returns an x-amz-expiration header when a lifecycle expiration rule covers
the object, naming the date it is scheduled for deletion and the rule that schedules it:

x-amz-expiration: expiry-date="Fri, 21 Dec 2012 00:00:00 GMT", rule-id="Rule1"

Ozone supports lifecycle expiration rules and already deletes the keys they cover, but S3 Gateway
never emitted this header, so clients cannot read a key's expiration metadata.

S3G now reads the bucket's lifecycle configuration while serving HEAD and reports the earliest
expiry among the enabled expiration rules that cover the key. A Days rule reports the key's
modification time plus the configured days, rounded up to the next midnight UTC, as S3 does; a
Date rule is reported as is.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-16151

How was this patch tested?

  • Added eight TestObjectHead cases: Days rule, Date rule, tag-filter rule, earliest of several
    matching rules, non-matching prefix, Disabled rule, AbortIncompleteMultipartUpload-only rule,
    and no lifecycle configuration. The Date case pins the exact header format against a fixed date.
  • ozone-s3gateway (754 tests) and ozone-client (118 tests) pass; checkstyle and RAT are clean.
  • Added two bucketlifecycle.robot cases (header present on a matching key, absent on a
    non-matching one). These were not run locally -- they need a compose cluster -- so they rely on CI.

Copilot AI lite review requested due to automatic review settings August 29, 2026 05:13
S3 returns x-amz-expiration on HeadObject when a lifecycle expiration rule
covers the object, naming the date it is scheduled for deletion and the rule
that schedules it. S3G never emitted the header.

Report the earliest expiry among the enabled expiration rules that cover the
key. The header is advisory: any failure to read the bucket's lifecycle
configuration only leaves it out, and the HEAD still succeeds.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds S3-compatible lifecycle expiration reporting for HeadObject by emitting the x-amz-expiration response header when an enabled expiration rule covers the requested key, matching AWS’s expected header format and “earliest matching expiry” behavior.

Changes:

  • Add x-amz-expiration constant and emit the header on HEAD when lifecycle expiration rules cover the key.
  • Implement rule/key matching and expiry-date calculation (Days rounded up to next midnight UTC; Date reported as-is).
  • Add unit and smoketest coverage to validate header presence/absence and selection of earliest applicable rule.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectHead.java Adds unit tests covering Days/Date expirations, tag filters, earliest-rule selection, and non-matching/disabled/no-config cases.
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java Introduces EXPIRATION_HEADER (x-amz-expiration) constant for consistent usage.
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java Computes and injects x-amz-expiration on successful HEAD responses based on lifecycle configuration and key metadata.
hadoop-ozone/dist/src/main/smoketest/s3/bucketlifecycle.robot Adds smoketests verifying the AWS CLI head-object output includes/omits Expiration depending on rule match.
hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/OzoneLifecycleConfiguration.java Adds rule/filter matching helpers and isEnabled() used by S3G to determine applicability of lifecycle rules.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@utafrali utafrali left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core feature is correct and well unit-tested, but there are two real issues: an OM RPC is fired on every HEAD request regardless of whether a lifecycle configuration exists (a per-request cost that worsens under load), and the expiration header uses the wrong modification time when partNumber is non-zero. The robot tests also lack value assertions on the header format.

Comment thread hadoop-ozone/dist/src/main/smoketest/s3/bucketlifecycle.robot
…sert header format

Rule ids are supplied by the client and only validated for length, so a quote or
backslash is escaped before it goes into the quoted-string header parameter. An
id carrying a control character cannot appear in a header value at all, so the
header is dropped instead of being emitted malformed.

Note in OzoneLCRule#matches that OM rejects a rule setting both prefix and
filter, and one setting neither, so the order there selects whichever is present
rather than expressing a precedence.

Assert the header format in the robot case instead of only its presence, so a
compose run catches format regressions the unit tests cannot see.
@rich7420

Copy link
Copy Markdown
Contributor

@henry3260 thanks for the patch!

Most buckets carry no lifecycle configuration, and OM reports that by throwing,
so the expiration header added to HEAD paid for an OM call, a bucket read lock
and an exception on every request against such a bucket.

Remember per bucket, for a configurable TTL, that OM reported no configuration,
and skip the lookup while the entry lives. Only the absence is cached: the
configuration itself is read under a bucket-level ACL check that HEAD does not
repeat, so serving stored rules from the gateway would hand them to callers OM
never authorized. ozone.s3g.lifecycle.missing-configuration.cache.ttl (30s
default) bounds the staleness; zero disables the cache.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants