mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-01 09:48:20 +00:00
fix(s3): round-trip null-version delete-marker identity (#6765)
* fix(s3): round-trip null-version delete-marker identity through listing and delete responses On a versioning-suspended bucket, a null delete marker's identity was lost on the way back to the client at three points (issue #6745): ListObjectVersions advertised the marker's VersionId as the literal nil UUID instead of null; deleting by that id succeeded but the DeleteObjects/DeleteObject response reported the identity as null with no way to correlate it to the request; and the response lacked DeleteMarker/DeleteMarkerVersionId because the marker-ness comparison mixed the client-facing identity (Some(nil)) with the storage identity (None), so the removal also mis-recorded accounting and fired DeleteMarkerCreated semantics on later paths. - Listing (bucket_usecase, s3_api/bucket, build_list_versions_next_marker) now maps the synthesized nil UUID to the literal null everywhere it reaches the wire, and VersionMarker::parse folds a nil-UUID marker from older listings into VersionMarker::Null so pagination resumes correctly. - delete_objects normalizes both sides of the marker-ness comparison via delete_file_info_version_id (matching the adjacent explicit_delete_marker admission check) and reports DeleteMarkerVersionId as null for an explicit null-marker removal. - resolve_delete_version_state reports delete_marker for an explicit-version delete whose target is a delete marker even when the bucket is versioning-suspended, fixing x-amz-delete-marker on the single-object path. - The DeleteObjects response entry echoes the version identity the request addressed for marker removals, marker-removal accounting no longer records a marker creation, and notification events fire DeleteMarkerCreated only for actual marker creation. Fixes #6745 * fix(s3): keep null-marker removal write shape undeleted and report marker semantics response-side The first cut marked the storage delete request deleted for a null-marker removal, which FileMeta::delete_version interprets as the suspended-bucket delete-mints-a-marker write and re-creates the marker just removed. Carry marker-ness to responses via explicit_delete_removed_marker (single path) and a response-only branch flag (batch path) instead, keeping every storage write shape byte-identical to the pre-fix behavior. Adds an embedded end-to-end regression test covering the full issue #6745 round trip.
This commit is contained in:
@@ -167,7 +167,15 @@ impl VersionMarker {
|
||||
if marker == NULL_VERSION_MARKER {
|
||||
Ok(Self::Null)
|
||||
} else {
|
||||
Ok(Self::Version(Uuid::parse_str(marker)?))
|
||||
let version = Uuid::parse_str(marker)?;
|
||||
// Older releases advertised the null version as a nil UUID
|
||||
// (issue #6745); a stored null version has no UUID, so resuming
|
||||
// by `Version(nil)` could never match. Fold it into `Null`.
|
||||
if version.is_nil() {
|
||||
Ok(Self::Null)
|
||||
} else {
|
||||
Ok(Self::Version(version))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,6 +722,19 @@ fn is_modified_since(mod_time: &OffsetDateTime, given_time: &OffsetDateTime) ->
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn version_marker_parse_folds_null_and_nil_uuid_into_null() {
|
||||
assert_eq!(VersionMarker::parse("null"), Ok(VersionMarker::Null));
|
||||
// Older releases advertised the null version as a nil UUID
|
||||
// (issue #6745); it must resume as the null marker, not a UUID no
|
||||
// stored version carries.
|
||||
assert_eq!(VersionMarker::parse(Uuid::nil().to_string()), Ok(VersionMarker::Null));
|
||||
|
||||
let version = Uuid::from_u128(7);
|
||||
assert_eq!(VersionMarker::parse(version.to_string()), Ok(VersionMarker::Version(version)));
|
||||
assert!(VersionMarker::parse("not-a-version").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_preconditions_ignore_empty_etag_headers() {
|
||||
let opts = HTTPPreconditions {
|
||||
|
||||
Reference in New Issue
Block a user