mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 16:07:05 +00:00
fix(ecstore): migrate tier free versions during decommission
This commit is contained in:
@@ -4642,6 +4642,63 @@ fn resolve_delete_version_state(opts: &ObjectOptions, goi: &ObjectInfo, version_
|
||||
}
|
||||
|
||||
impl SetDisks {
|
||||
/// Publish an internal tier free-version record without changing its
|
||||
/// delete-marker shape or remote-tier identity. The caller holds the
|
||||
/// source and target object locks; a write quorum is required before the
|
||||
/// source cleanup may remove the original record.
|
||||
#[tracing::instrument(skip(self, fi, opts))]
|
||||
pub(crate) async fn decommission_tier_free_version(
|
||||
&self,
|
||||
bucket: &str,
|
||||
object: &str,
|
||||
fi: &FileInfo,
|
||||
opts: &ObjectOptions,
|
||||
) -> Result<()> {
|
||||
if !fi.deleted || !fi.tier_free_version() {
|
||||
return Err(Error::other("decommission tier free-version write requires a free version record"));
|
||||
}
|
||||
if opts
|
||||
.namespace_lock_fence
|
||||
.as_ref()
|
||||
.is_some_and(NamespaceLockFence::is_lock_lost)
|
||||
|| opts
|
||||
.bucket_lifecycle_lock_fence
|
||||
.as_ref()
|
||||
.is_some_and(NamespaceLockFence::is_lock_lost)
|
||||
{
|
||||
return Err(StorageError::NamespaceLockQuorumUnavailable {
|
||||
mode: "decommission_tier_free_version_commit",
|
||||
bucket: bucket.to_string(),
|
||||
object: object.to_string(),
|
||||
required: 1,
|
||||
achieved: 0,
|
||||
});
|
||||
}
|
||||
|
||||
let disks = self.disks.read().await.clone();
|
||||
let write_quorum = self.default_write_quorum();
|
||||
let futures = disks.into_iter().map(|disk| {
|
||||
let file_info = fi.clone();
|
||||
async move {
|
||||
if let Some(disk) = disk {
|
||||
disk.write_metadata("", bucket, object, file_info).await
|
||||
} else {
|
||||
Err(DiskError::DiskNotFound)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut errs = Vec::new();
|
||||
for result in join_all(futures).await {
|
||||
match result {
|
||||
Ok(_) => errs.push(None),
|
||||
Err(err) => errs.push(Some(err)),
|
||||
}
|
||||
}
|
||||
|
||||
resolve_tiered_decommission_write_quorum_result(&errs, write_quorum, bucket, object)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, fi, opts))]
|
||||
pub(crate) async fn decommission_tiered_object(
|
||||
&self,
|
||||
@@ -9864,6 +9921,57 @@ mod tests {
|
||||
assert_ne!(updated.erasure.distribution, original.erasure.distribution);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn decommission_tier_free_version_preserves_remote_identity() {
|
||||
let set_disks = make_local_bucket_test_set_disks().await;
|
||||
let bucket = "free-version-decommission";
|
||||
let object = "object.txt";
|
||||
let version_id = Uuid::new_v4();
|
||||
let mut free_version = FileInfo {
|
||||
name: object.to_string(),
|
||||
volume: bucket.to_string(),
|
||||
version_id: Some(version_id),
|
||||
mod_time: Some(time::OffsetDateTime::now_utc()),
|
||||
deleted: true,
|
||||
transition_tier: "WARM-TIER".to_string(),
|
||||
transitioned_objname: "remote/object".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
free_version.set_tier_free_version();
|
||||
|
||||
set_disks
|
||||
.decommission_tier_free_version(bucket, object, &free_version, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("free-version metadata should reach the target quorum");
|
||||
set_disks
|
||||
.decommission_tier_free_version(bucket, object, &free_version, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("replaying the same free-version metadata should be idempotent");
|
||||
|
||||
let versions = set_disks
|
||||
.load_file_info_versions_exact(bucket, object)
|
||||
.await
|
||||
.expect("migrated free-version metadata should decode")
|
||||
.expect("migrated free-version metadata should exist");
|
||||
let migrated = versions
|
||||
.versions
|
||||
.iter()
|
||||
.find(|version| version.version_id == Some(version_id))
|
||||
.expect("free version should be present on the target");
|
||||
|
||||
assert_eq!(
|
||||
versions
|
||||
.versions
|
||||
.iter()
|
||||
.filter(|version| version.version_id == Some(version_id))
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert!(migrated.tier_free_version());
|
||||
assert_eq!(migrated.transition_tier, "WARM-TIER");
|
||||
assert_eq!(migrated.transitioned_objname, "remote/object");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_tiered_decommission_write_quorum_result_allows_successful_quorum() {
|
||||
let errs = vec![None, None, Some(DiskError::DiskNotFound), None];
|
||||
|
||||
Reference in New Issue
Block a user