diff --git a/.config/e2e-full-selection.txt b/.config/e2e-full-selection.txt index eb0c8f9db..e09c9add1 100644 --- a/.config/e2e-full-selection.txt +++ b/.config/e2e-full-selection.txt @@ -1,2 +1,2 @@ -sha256-darwin=9e9687ded961aa4e619a15def6e1ee0a5153be05423d009ee7a58126624c83e7 -sha256-linux=364666693c2bf258997643c9d87e07e7b7501e58175f190d201d53c81946eb20 +sha256-darwin=a6aee967387226b6e6937a0e625db53048c8b2f502636820a09c6ad791341b44 +sha256-linux=6b8b8f3dbdd63414fc969716ae3d7c2ec71712395630bae8bd419de2f32195b3 diff --git a/crates/ecstore/src/core/pools_test.rs b/crates/ecstore/src/core/pools_test.rs index 296f253e2..3b4e10059 100644 --- a/crates/ecstore/src/core/pools_test.rs +++ b/crates/ecstore/src/core/pools_test.rs @@ -2321,6 +2321,18 @@ mod decommission_lock_order_tests { .expect("the multipart target must publish before capacity progress save"); assert!(published.is_multipart(), "published target must retain multipart identity"); assert_eq!(published.version_id.map(|version| version.to_string()), Some(source_version.clone())); + for prefix in [ + rustfs_utils::http::RUSTFS_INTERNAL_PREFIX, + rustfs_utils::http::MINIO_INTERNAL_PREFIX, + ] { + assert_eq!( + published + .user_defined + .get(&format!("{prefix}{}", rustfs_utils::http::SUFFIX_MULTIPART_UPLOAD_ID)), + Some(&source_upload.upload_id), + "migration must preserve the source completion identity under both internal prefixes" + ); + } let mut published_reader = lossy_store.pools[2] .get_object_reader( &bucket, diff --git a/crates/ecstore/src/set_disk/ops/multipart.rs b/crates/ecstore/src/set_disk/ops/multipart.rs index 1d3a4ee50..f41bdf20b 100644 --- a/crates/ecstore/src/set_disk/ops/multipart.rs +++ b/crates/ecstore/src/set_disk/ops/multipart.rs @@ -3108,7 +3108,9 @@ impl crate::storage_api_contracts::multipart::MultipartOperations for SetDisks { // on the object is what lets a retried CompleteMultipartUpload return this // version instead of NoSuchUpload. insert_str writes both internal prefixes; // a later conflicting pair fails closed in the replay matcher. - if !upload_id.is_empty() { + // Internal migration must preserve the source completion identity rather + // than replace it (or invent one) with the temporary transfer upload id. + if !opts.data_movement && !upload_id.is_empty() { insert_str(&mut fi.metadata, rustfs_utils::http::SUFFIX_MULTIPART_UPLOAD_ID, upload_id.to_owned()); } @@ -5115,6 +5117,10 @@ mod tests { ) .await .expect("completed data movement object should be readable"); + assert!( + !rustfs_utils::http::contains_key_str(&completed.user_defined, rustfs_utils::http::SUFFIX_MULTIPART_UPLOAD_ID), + "migration of a source without a completion identity must not invent one" + ); assert!(!rustfs_utils::http::contains_key_str( &completed.user_defined, rustfs_utils::http::SUFFIX_DATA_MOVEMENT_UPLOAD @@ -8822,10 +8828,9 @@ mod tests { ..Default::default() }]; - assert_eq!( + assert!( completed_multipart_upload_matches(&metadata, &parts, false, upload_id, &requested, "bucket", "object") - .expect("quoted ETag must match the stored part"), - true + .expect("quoted ETag must match the stored part") ); let wrong_etag = vec![CompletePart { part_num: 1, @@ -8836,15 +8841,13 @@ mod tests { completed_multipart_upload_matches(&metadata, &parts, false, upload_id, &wrong_etag, "bucket", "object"), Err(StorageError::InvalidPart(1, _, _)) )); - assert_eq!( - completed_multipart_upload_matches(&metadata, &parts, false, "other-upload", &requested, "bucket", "object") - .expect("a different upload id is not this completion"), - false + assert!( + !completed_multipart_upload_matches(&metadata, &parts, false, "other-upload", &requested, "bucket", "object") + .expect("a different upload id is not this completion") ); - assert_eq!( - completed_multipart_upload_matches(&metadata, &parts, true, upload_id, &requested, "bucket", "object") - .expect("a delete marker is not the completed object"), - false + assert!( + !completed_multipart_upload_matches(&metadata, &parts, true, upload_id, &requested, "bucket", "object") + .expect("a delete marker is not the completed object") ); metadata.insert( diff --git a/rustfs/src/app/multipart_usecase.rs b/rustfs/src/app/multipart_usecase.rs index a6754bfe5..e92a3be6e 100644 --- a/rustfs/src/app/multipart_usecase.rs +++ b/rustfs/src/app/multipart_usecase.rs @@ -891,8 +891,8 @@ impl DefaultMultipartUsecase { key: Some(key.clone()), e_tag: obj_info.etag.clone().map(|etag| to_s3s_etag(&etag)), location: Some(location), - server_side_encryption: server_side_encryption.clone(), - ssekms_key_id: ssekms_key_id.clone(), + server_side_encryption, + ssekms_key_id, checksum_crc32, checksum_crc32c, checksum_sha1,