fix(ecstore): reconcile object cleanup receipts (#6077)

* fix(s3): keep multipart completion publication owned

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(s3): keep put publication owned

Co-Authored-By: heihutu <heihutu@gmail.com>

* chore(app): route multipart context through facade

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(ecstore): gate object transaction fencing

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(ecstore): fence object transaction epochs

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(ecstore): reconcile old data cleanup receipts

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-14 02:08:50 +08:00
committed by GitHub
parent 6178083985
commit 5b54c4303d
11 changed files with 1776 additions and 200 deletions
+59 -2
View File
@@ -18,8 +18,9 @@ use rmp_serde::Serializer;
use rustfs_utils::HashAlgorithm;
use rustfs_utils::http::{
AMZ_OBJECT_TAGGING, SUFFIX_COMPRESSION, SUFFIX_DATA_MOVED, SUFFIX_DATA_MOVED_TAGS, SUFFIX_FREE_VERSION, SUFFIX_HEALING,
SUFFIX_INLINE_DATA, SUFFIX_TIER_FV_ID, SUFFIX_TIER_FV_MARKER, SUFFIX_TIER_SKIP_FV_ID, contains_key_str, get_str,
has_internal_suffix, insert_str, is_encryption_metadata_key, starts_with_ignore_ascii_case,
SUFFIX_INLINE_DATA, SUFFIX_OBJECT_TRANSACTION_EPOCH, SUFFIX_TIER_FV_ID, SUFFIX_TIER_FV_MARKER, SUFFIX_TIER_SKIP_FV_ID,
contains_key_str, get_consistent_str, get_str, has_internal_suffix, insert_str, is_encryption_metadata_key,
starts_with_ignore_ascii_case,
};
use s3s::dto::{RestoreStatus, Timestamp};
use s3s::header::X_AMZ_RESTORE;
@@ -1172,6 +1173,22 @@ impl FileInfo {
insert_str(&mut self.metadata, SUFFIX_DATA_MOVED, String::new());
}
pub fn set_object_transaction_epoch(&mut self, epoch: Uuid) {
insert_str(&mut self.metadata, SUFFIX_OBJECT_TRANSACTION_EPOCH, epoch.to_string());
}
pub fn object_transaction_epoch(&self) -> Result<Option<Uuid>> {
if !contains_key_str(&self.metadata, SUFFIX_OBJECT_TRANSACTION_EPOCH) {
return Ok(None);
}
let value = get_consistent_str(&self.metadata, SUFFIX_OBJECT_TRANSACTION_EPOCH).ok_or(Error::FileCorrupt)?;
let epoch = Uuid::parse_str(value).map_err(|_| Error::FileCorrupt)?;
if epoch.is_nil() {
return Err(Error::FileCorrupt);
}
Ok(Some(epoch))
}
pub fn inline_data(&self) -> bool {
contains_key_str(&self.metadata, SUFFIX_INLINE_DATA) && !self.is_remote()
}
@@ -1484,6 +1501,46 @@ mod tests {
assert_eq!(ei.get_checksum_info(99).algorithm, HashAlgorithm::HighwayHash256S);
}
#[test]
fn object_transaction_epoch_uses_consistent_dual_internal_metadata() {
let mut fi = validation_test_fileinfo();
assert_eq!(fi.object_transaction_epoch().expect("absent epoch should decode"), None);
let epoch = Uuid::new_v4();
let epoch_text = epoch.to_string();
fi.set_object_transaction_epoch(epoch);
assert_eq!(fi.object_transaction_epoch().expect("written epoch should decode"), Some(epoch));
assert_eq!(fi.metadata.get("x-rustfs-internal-object-transaction-epoch"), Some(&epoch_text));
assert_eq!(fi.metadata.get("x-minio-internal-object-transaction-epoch"), Some(&epoch_text));
let mut rustfs_only = validation_test_fileinfo();
rustfs_only
.metadata
.insert("x-rustfs-internal-object-transaction-epoch".to_string(), epoch_text);
assert_eq!(
rustfs_only
.object_transaction_epoch()
.expect("single compatibility key should decode"),
Some(epoch)
);
let mut conflicting = fi.clone();
conflicting
.metadata
.insert("x-minio-internal-object-transaction-epoch".to_string(), Uuid::new_v4().to_string());
assert_eq!(conflicting.object_transaction_epoch(), Err(Error::FileCorrupt));
let mut malformed = validation_test_fileinfo();
malformed
.metadata
.insert("x-rustfs-internal-object-transaction-epoch".to_string(), "not-a-uuid".to_string());
assert_eq!(malformed.object_transaction_epoch(), Err(Error::FileCorrupt));
let mut nil = validation_test_fileinfo();
nil.set_object_transaction_epoch(Uuid::nil());
assert_eq!(nil.object_transaction_epoch(), Err(Error::FileCorrupt));
}
// backlog#949: distribution range/permutation validation.
#[test]
fn is_valid_distribution_accepts_permutation() {