mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-27 15:37:02 +00:00
fix(ecstore): require write quorum for metadata early stop (#4300)
This commit is contained in:
@@ -170,10 +170,10 @@ impl SetDisks {
|
||||
object: &str,
|
||||
opts: &ObjectOptions,
|
||||
read_data: bool,
|
||||
caller_allows_early_stop: bool,
|
||||
) -> Result<(FileInfo, Vec<FileInfo>, Vec<Option<DiskStore>>)> {
|
||||
// Read-only callers (GET/HEAD/tag read) may use the metadata early-stop
|
||||
// fast path.
|
||||
self.get_object_fileinfo_gated(bucket, object, opts, read_data, true).await
|
||||
self.get_object_fileinfo_gated(bucket, object, opts, read_data, caller_allows_early_stop)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Like `get_object_fileinfo`, but `allow_early_stop=false` forces the full
|
||||
@@ -329,7 +329,7 @@ impl SetDisks {
|
||||
object: &str,
|
||||
opts: &ObjectOptions,
|
||||
) -> (ObjectInfo, usize, Option<StorageError>) {
|
||||
let fi = match self.get_object_fileinfo(bucket, object, opts, false).await {
|
||||
let fi = match self.get_object_fileinfo(bucket, object, opts, false, false).await {
|
||||
Ok((fi, _, _)) => fi,
|
||||
Err(e) => return (ObjectInfo::default(), 0, Some(e)),
|
||||
};
|
||||
@@ -2783,6 +2783,8 @@ mod tests {
|
||||
accumulator.observe_file_info(&metadata_early_stop_candidate("object", 1));
|
||||
assert!(accumulator.early_stop_decision().is_none());
|
||||
accumulator.observe_file_info(&metadata_early_stop_candidate("object", 2));
|
||||
assert!(accumulator.early_stop_decision().is_none());
|
||||
accumulator.observe_file_info(&metadata_early_stop_candidate("object", 3));
|
||||
|
||||
assert_eq!(
|
||||
accumulator.early_stop_decision(),
|
||||
@@ -2790,7 +2792,7 @@ mod tests {
|
||||
reason: GET_METADATA_EARLY_STOP_REASON_VALID_QUORUM
|
||||
})
|
||||
);
|
||||
assert_eq!(accumulator.valid_responses, 2);
|
||||
assert_eq!(accumulator.valid_responses, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2799,25 +2801,31 @@ mod tests {
|
||||
|
||||
accumulator.observe_file_info(&metadata_early_stop_candidate("object", 1));
|
||||
accumulator.observe_file_info(&metadata_early_stop_candidate("object", 2));
|
||||
assert!(accumulator.early_stop_decision().is_none());
|
||||
accumulator.observe_file_info(&metadata_early_stop_candidate("object", 3));
|
||||
|
||||
assert!(accumulator.early_stop_decision().is_some());
|
||||
assert_eq!(accumulator.candidate_votes, 2);
|
||||
assert_eq!(accumulator.candidate_votes, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_quorum_accumulator_partial_result_remains_quorum_compatible() {
|
||||
let first = metadata_early_stop_candidate("object", 1);
|
||||
let second = metadata_early_stop_candidate("object", 2);
|
||||
let parts_metadata = vec![first.clone(), second, FileInfo::default(), FileInfo::default()];
|
||||
let third = metadata_early_stop_candidate("object", 3);
|
||||
let parts_metadata = vec![first.clone(), second, third, FileInfo::default()];
|
||||
let errs = vec![None, None, None, None];
|
||||
|
||||
let (read_quorum, _) = SetDisks::object_quorum_from_meta(&parts_metadata, &errs, 2)
|
||||
let (read_quorum, write_quorum) = SetDisks::object_quorum_from_meta(&parts_metadata, &errs, 2)
|
||||
.expect("partial early-stop metadata should preserve read quorum");
|
||||
let read_quorum = usize::try_from(read_quorum).expect("read quorum should be non-negative");
|
||||
let write_quorum = usize::try_from(write_quorum).expect("write quorum should be non-negative");
|
||||
let selection_quorum = SetDisks::latest_fileinfo_selection_quorum("", &parts_metadata, &errs, read_quorum, write_quorum);
|
||||
let selected = SetDisks::pick_valid_fileinfo(&parts_metadata, None, Some("etag-1".to_string()), read_quorum)
|
||||
.expect("partial early-stop metadata should preserve selected FileInfo");
|
||||
|
||||
assert_eq!(read_quorum, 2);
|
||||
assert_eq!(selection_quorum, 3);
|
||||
assert_eq!(selected.name, first.name);
|
||||
assert_eq!(selected.get_etag(), first.get_etag());
|
||||
}
|
||||
@@ -2870,6 +2878,8 @@ mod tests {
|
||||
|
||||
accumulator.observe_file_info(&deleted);
|
||||
accumulator.observe_file_info(&deleted);
|
||||
assert!(accumulator.early_stop_decision().is_none());
|
||||
accumulator.observe_file_info(&deleted);
|
||||
|
||||
assert_eq!(
|
||||
accumulator.early_stop_decision(),
|
||||
@@ -3008,6 +3018,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_early_stop_rejects_healing_and_free_version_requests() {
|
||||
temp_env::with_vars(
|
||||
[
|
||||
(ENV_RUSTFS_GET_METADATA_EARLY_STOP_ENABLE, Some("true")),
|
||||
(ENV_RUSTFS_GET_METADATA_VERSION_EARLY_STOP_ENABLE, Some("true")),
|
||||
],
|
||||
|| {
|
||||
assert!(!should_allow_metadata_early_stop(false, "", true, false));
|
||||
assert!(!should_allow_metadata_early_stop(false, "", false, true));
|
||||
assert!(!should_allow_metadata_early_stop(false, "version-id", true, false));
|
||||
assert!(!should_allow_metadata_early_stop(false, "version-id", false, true));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn version_early_stop_gate_defaults_to_disabled() {
|
||||
temp_env::with_var(ENV_RUSTFS_GET_METADATA_VERSION_EARLY_STOP_ENABLE, None::<&str>, || {
|
||||
|
||||
Reference in New Issue
Block a user