diff --git a/crates/ecstore/src/config/storageclass.rs b/crates/ecstore/src/config/storageclass.rs index 3e1814334..8f3a25055 100644 --- a/crates/ecstore/src/config/storageclass.rs +++ b/crates/ecstore/src/config/storageclass.rs @@ -151,7 +151,7 @@ pub struct Config { optimize: Option, inline_block: usize, initialized: bool, - #[serde(skip)] + #[serde(default, skip_serializing_if = "std::ops::Not::not")] inline_block_explicit: bool, #[serde(skip)] standard_parities: Vec, @@ -813,6 +813,25 @@ mod tests { assert!(validate_parity(0, 0).is_err()); } + #[test] + fn explicit_inline_block_survives_config_round_trip() { + let cfg = lookup_config_for_pools_with_env( + &KVS::new(), + &[12], + StorageClassEnvOverrides { + inline_block: Some("128KiB".to_string()), + ..Default::default() + }, + ) + .expect("explicit inline block should resolve"); + assert!(cfg.should_inline(100 * 1024, 8, false)); + + let encoded = serde_json::to_string(&cfg).expect("config should serialize"); + assert!(encoded.contains("\"inline_block_explicit\":true")); + let decoded: Config = serde_json::from_str(&encoded).expect("explicit inline config should deserialize"); + assert!(decoded.should_inline(100 * 1024, 8, false)); + } + #[test] fn lookup_config_reads_rrs_from_class_rrs_key() { // Regression: kvs.get(RRS) used RRS="REDUCED_REDUNDANCY" instead of diff --git a/crates/ecstore/src/set_disk/ops/object.rs b/crates/ecstore/src/set_disk/ops/object.rs index 3a5ae4904..46b25a3c7 100644 --- a/crates/ecstore/src/set_disk/ops/object.rs +++ b/crates/ecstore/src/set_disk/ops/object.rs @@ -6029,6 +6029,51 @@ mod inline_put_commit_path_tests { assert_eq!(restored, payload); } + #[tokio::test] + async fn ec_8_4_versioned_budget_reaches_put_placement_decision() { + let (_temp_dirs, disk_stores, set_disks) = hermetic_set_disks(12).await; + set_disks.set_test_storage_class_config( + lookup_config_for_pools_without_env(&KVS::new(), &[12]).expect("EC8+4 storage class should resolve"), + ); + let bucket = "ec-8-4-versioned-inline-budget"; + let object = "object.bin"; + let payload = vec![0x73; 64 * 1024]; + make_bucket(&disk_stores, bucket).await; + + let options = ObjectOptions { + versioned: true, + ..Default::default() + }; + let mut reader = PutObjReader::from_vec(payload.clone()); + set_disks + .put_object(bucket, object, &mut reader, &options) + .await + .expect("versioned EC8+4 PUT should use the reduced inline budget"); + + for (disk_index, disk) in disk_stores.iter().enumerate() { + let file_info = disk + .read_version("", bucket, object, "", &ReadOptions::default()) + .await + .unwrap_or_else(|err| panic!("disk {disk_index} should persist versioned EC8+4 metadata: {err}")); + assert!( + !file_info.inline_data(), + "disk {disk_index} must keep the versioned shard outside xl.meta" + ); + } + + let mut object_reader = set_disks + .get_object_reader(bucket, object, None, HeaderMap::new(), &options) + .await + .expect("versioned non-inline EC8+4 object should remain readable"); + let mut restored = Vec::new(); + object_reader + .stream + .read_to_end(&mut restored) + .await + .expect("versioned non-inline EC8+4 object should stream"); + assert_eq!(restored, payload); + } + #[tokio::test] async fn inline_put_direct_commit_accepts_exact_quorum_and_rejects_quorum_minus_one() { let (_temp_dirs, disk_stores, set_disks) = hermetic_set_disks(4).await;