mirror of
https://github.com/rustfs/rustfs.git
synced 2026-09-09 21:56:03 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba613b3b88 |
@@ -13807,7 +13807,17 @@ mod transition_commit_failure_tests {
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn restore_failure_after_snapshot_cleans_exact_generation_and_returns_primary_error() {
|
||||
let (_temp_dirs, disk_stores, set_disks) = hermetic_set_disks(4).await;
|
||||
assert_restore_failure_cleanup_boundary(true).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial_test::serial]
|
||||
async fn restore_failure_after_snapshot_preserves_corrupt_known_transition_metadata() {
|
||||
assert_restore_failure_cleanup_boundary(false).await;
|
||||
}
|
||||
|
||||
async fn assert_restore_failure_cleanup_boundary(legacy_unknown: bool) {
|
||||
let (temp_dirs, disk_stores, set_disks) = hermetic_set_disks(4).await;
|
||||
let bucket = "restore-post-snapshot-cleanup-bucket";
|
||||
let object = "object.bin";
|
||||
for disk in &disk_stores {
|
||||
@@ -13816,7 +13826,15 @@ mod transition_commit_failure_tests {
|
||||
|
||||
let mut reader = PutObjReader::from_vec(b"post-snapshot cleanup source".repeat(1024));
|
||||
let original = set_disks
|
||||
.put_object(bucket, object, &mut reader, &ObjectOptions::default())
|
||||
.put_object(
|
||||
bucket,
|
||||
object,
|
||||
&mut reader,
|
||||
&ObjectOptions {
|
||||
write_completion: WriteCompletion::TailDrained,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("source object should be written");
|
||||
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
|
||||
@@ -13857,16 +13875,70 @@ mod transition_commit_failure_tests {
|
||||
.await
|
||||
.expect("transitioned metadata should be readable")
|
||||
.into_owned();
|
||||
let known_state = source_fi.transition_version_state;
|
||||
assert_ne!(known_state, rustfs_filemeta::TransitionVersionState::Unknown);
|
||||
source_fi.metadata.extend(restore_metadata(operation_id, true));
|
||||
rustfs_utils::http::insert_str(
|
||||
&mut source_fi.metadata,
|
||||
rustfs_utils::http::SUFFIX_TRANSITION_TIER_DESTINATION_ID,
|
||||
"invalid".to_string(),
|
||||
);
|
||||
set_disks
|
||||
.update_object_meta(bucket, object, source_fi, &online_disks)
|
||||
.await
|
||||
.expect("invalid backend identity fixture should be persisted");
|
||||
.expect("restore markers should be persisted");
|
||||
|
||||
// Normal writes reject damage to a reconciled binding. Model on-disk
|
||||
// corruption directly, with and without the legacy missing-state field.
|
||||
let mut corrupted_metadata = Vec::new();
|
||||
for temp_dir in &temp_dirs {
|
||||
let metadata_path = temp_dir.path().join(bucket).join(object).join(STORAGE_FORMAT_FILE);
|
||||
let encoded = tokio::fs::read(&metadata_path)
|
||||
.await
|
||||
.expect("transition metadata should be readable");
|
||||
let mut metadata = FileMeta::load(&encoded).expect("transition metadata should decode");
|
||||
let (version_index, mut version) = metadata
|
||||
.find_version(original.version_id)
|
||||
.expect("transitioned version should exist");
|
||||
let object_meta = version.object.as_mut().expect("transitioned version should be an object");
|
||||
rustfs_utils::http::insert_bytes(
|
||||
&mut object_meta.meta_sys,
|
||||
rustfs_utils::http::SUFFIX_TRANSITION_TIER_DESTINATION_ID,
|
||||
b"invalid".to_vec(),
|
||||
);
|
||||
if legacy_unknown {
|
||||
rustfs_utils::http::remove_bytes(
|
||||
&mut object_meta.meta_sys,
|
||||
rustfs_utils::http::SUFFIX_TRANSITIONED_VERSION_STATE,
|
||||
);
|
||||
}
|
||||
metadata.versions[version_index] =
|
||||
rustfs_filemeta::FileMetaShallowVersion::try_from(version).expect("corrupt fixture should re-encode");
|
||||
tokio::fs::write(&metadata_path, metadata.marshal_msg().expect("corrupt fixture should encode"))
|
||||
.await
|
||||
.expect("corrupt fixture should be written");
|
||||
let persisted = tokio::fs::read(&metadata_path)
|
||||
.await
|
||||
.expect("corrupt fixture should be readable");
|
||||
let fixture = FileMeta::load(&persisted)
|
||||
.expect("corrupt fixture should decode")
|
||||
.find_version(original.version_id)
|
||||
.expect("corrupt version should exist")
|
||||
.1
|
||||
.into_fileinfo(bucket, object, true)
|
||||
.expect("corrupt version should decode");
|
||||
assert_eq!(
|
||||
fixture.transition_version_state,
|
||||
if legacy_unknown {
|
||||
rustfs_filemeta::TransitionVersionState::Unknown
|
||||
} else {
|
||||
known_state
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
rustfs_utils::http::get_str(&fixture.metadata, rustfs_utils::http::SUFFIX_TRANSITION_TIER_DESTINATION_ID),
|
||||
Some("invalid".to_string())
|
||||
);
|
||||
for (key, value) in restore_metadata(operation_id, true) {
|
||||
assert_eq!(fixture.metadata.get(&key), Some(&value), "fixture must retain restore marker {key}");
|
||||
}
|
||||
corrupted_metadata.push((metadata_path, persisted));
|
||||
}
|
||||
set_disks.invalidate_get_object_metadata_cache(bucket, object).await;
|
||||
|
||||
let mut opts = ObjectOptions::default();
|
||||
@@ -13889,6 +13961,23 @@ mod transition_commit_failure_tests {
|
||||
.await
|
||||
.expect("cleanup should leave the transitioned object readable");
|
||||
assert_eq!(cleaned.transitioned_object.status, TRANSITION_COMPLETE);
|
||||
if !legacy_unknown {
|
||||
// Known bindings with corrupt identities must be repaired before
|
||||
// cleanup; rejection must preserve both the binding and markers.
|
||||
for (key, value) in restore_metadata(operation_id, true) {
|
||||
assert_eq!(cleaned.user_defined.get(&key), Some(&value), "cleanup must preserve restore marker {key}");
|
||||
}
|
||||
for (metadata_path, before) in corrupted_metadata {
|
||||
assert_eq!(
|
||||
tokio::fs::read(metadata_path)
|
||||
.await
|
||||
.expect("rejected cleanup metadata should remain readable"),
|
||||
before,
|
||||
"rejected cleanup must leave corrupt known metadata unchanged"
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
assert!(!cleaned.user_defined.contains_key(s3s::header::X_AMZ_RESTORE.as_str()));
|
||||
assert!(
|
||||
rustfs_utils::http::get_str(cleaned.user_defined.as_ref(), rustfs_utils::http::SUFFIX_RESTORE_OPERATION_ID,)
|
||||
|
||||
@@ -19057,27 +19057,34 @@ mod tests {
|
||||
.await
|
||||
.expect("transition metadata should be readable");
|
||||
let mut metadata = FileMeta::load(&encoded).expect("transition metadata should decode");
|
||||
let mut transitioned = metadata
|
||||
.get_all_file_info_versions(bucket, object, true)
|
||||
.expect("transitioned versions should decode")
|
||||
.versions
|
||||
.into_iter()
|
||||
.find(|version| version.version_id == history.version_id)
|
||||
let (version_index, mut transitioned) = metadata
|
||||
.find_version(history.version_id)
|
||||
.expect("transitioned history should exist");
|
||||
transitioned.transition_version_state = rustfs_filemeta::TransitionVersionState::Unknown;
|
||||
rustfs_utils::http::metadata_compat::remove_str(
|
||||
&mut transitioned.metadata,
|
||||
// Rewrite the serialized record to model legacy metadata;
|
||||
// ordinary writes preserve an already reconciled state.
|
||||
rustfs_utils::http::metadata_compat::remove_bytes(
|
||||
&mut transitioned.object.as_mut().expect("history should be an object").meta_sys,
|
||||
rustfs_utils::http::metadata_compat::SUFFIX_TRANSITIONED_VERSION_STATE,
|
||||
);
|
||||
metadata
|
||||
.add_version(transitioned)
|
||||
.expect("unknown state should replace the transitioned version");
|
||||
metadata.versions[version_index] = rustfs_filemeta::FileMetaShallowVersion::try_from(transitioned)
|
||||
.expect("legacy history should re-encode");
|
||||
tokio::fs::write(
|
||||
&metadata_path,
|
||||
metadata.marshal_msg().expect("unknown transition metadata should encode"),
|
||||
)
|
||||
.await
|
||||
.expect("unknown transition metadata should be written");
|
||||
let encoded = tokio::fs::read(&metadata_path)
|
||||
.await
|
||||
.expect("legacy transition metadata should be readable");
|
||||
let legacy = FileMeta::load(&encoded)
|
||||
.expect("legacy transition metadata should decode")
|
||||
.find_version(history.version_id)
|
||||
.expect("legacy history should exist")
|
||||
.1
|
||||
.into_fileinfo(bucket, object, true)
|
||||
.expect("legacy history should decode");
|
||||
assert_eq!(legacy.transition_version_state, rustfs_filemeta::TransitionVersionState::Unknown);
|
||||
}
|
||||
let lifecycle_event = crate::bucket::lifecycle::lifecycle::Event {
|
||||
action: rustfs_scanner_metrics::metrics::IlmAction::DeleteAllVersionsAction,
|
||||
|
||||
@@ -5939,8 +5939,11 @@ mod tests {
|
||||
})
|
||||
.await
|
||||
.expect_err("mismatched kms context should fail");
|
||||
assert_eq!(err.code, S3ErrorCode::InternalError);
|
||||
assert_eq!(err.message, ApiError::error_code_to_message(&S3ErrorCode::InternalError));
|
||||
assert_eq!(err.code, S3ErrorCode::InvalidRequest);
|
||||
assert_eq!(
|
||||
err.message,
|
||||
"Encryption context mismatch: Context mismatch for key 'tenant': expected 'alpha', got 'beta'"
|
||||
);
|
||||
assert_eq!(super::kms_data_plane_error_class(&err), "context_mismatch");
|
||||
|
||||
manager.stop().await.expect("kms service should stop cleanly");
|
||||
|
||||
Reference in New Issue
Block a user