fix(capacity): harden scope registry, scan symlink guard, and test temp dir cleanup (#2432)

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: houseme <4829346+houseme@users.noreply.github.com>
This commit is contained in:
houseme
2026-04-08 20:58:17 +08:00
committed by GitHub
parent d4ea14c2ba
commit 064e21062d
32 changed files with 2751 additions and 1217 deletions
+47
View File
@@ -67,6 +67,7 @@ use http::HeaderMap;
use md5::{Digest as Md5Digest, Md5};
use rand::{Rng, seq::SliceRandom};
use regex::Regex;
use rustfs_common::capacity_scope::{CapacityScope, CapacityScopeDisk, record_capacity_scope, record_global_dirty_scope};
use rustfs_common::heal_channel::{DriveState, HealChannelPriority, HealItemType, HealOpts, HealScanMode, send_heal_disk};
use rustfs_config::MI_B;
use rustfs_filemeta::{
@@ -133,6 +134,36 @@ fn env_non_negative_usize(name: &str) -> Option<usize> {
rustfs_utils::get_env_opt_usize(name)
}
fn capacity_scope_from_disks(disks: &[Option<DiskStore>]) -> CapacityScope {
let mut unique = HashSet::with_capacity(disks.len());
let mut scoped_disks = Vec::with_capacity(disks.len());
for disk in disks.iter().flatten() {
let scope_disk = CapacityScopeDisk {
endpoint: disk.endpoint().to_string(),
drive_path: disk.to_string(),
};
if unique.insert(scope_disk.clone()) {
scoped_disks.push(scope_disk);
}
}
CapacityScope { disks: scoped_disks }
}
fn record_capacity_scope_if_needed(scope_token: Option<Uuid>, disks: &[Option<DiskStore>]) {
let scope = capacity_scope_from_disks(disks);
if scope.disks.is_empty() {
return;
}
record_global_dirty_scope(scope.clone());
if let Some(token) = scope_token {
record_capacity_scope(token, scope);
}
}
fn resolved_put_inline_buffer_enabled(object_size: i64, inline_by_topology: bool) -> bool {
if !inline_by_topology || object_size < 0 {
return false;
@@ -1087,6 +1118,8 @@ impl ObjectIO for SetDisks {
}
}
record_capacity_scope_if_needed(opts.capacity_scope_token, &online_disks);
fi.replication_state_internal = Some(opts.put_replication_state());
fi.is_latest = true;
@@ -1681,6 +1714,8 @@ impl ObjectOperations for SetDisks {
}
}
record_capacity_scope_if_needed(opts.capacity_scope_token, &disks);
// TODO: add_partial
if dist_erasure {
@@ -1829,6 +1864,10 @@ impl ObjectOperations for SetDisks {
.await
.map_err(|e| to_object_err(e, vec![bucket, object]))?;
if let Ok(disks) = self.get_disks(0, 0).await {
record_capacity_scope_if_needed(opts.capacity_scope_token, &disks);
}
let mut oi = ObjectInfo::from_file_info(&fi, bucket, object, opts.versioned || opts.version_suspended);
oi.replication_decision = goi.replication_decision;
return Ok(oi);
@@ -1855,6 +1894,10 @@ impl ObjectOperations for SetDisks {
.await
.map_err(|e| to_object_err(e, vec![bucket, object]))?;
if let Ok(disks) = self.get_disks(0, 0).await {
record_capacity_scope_if_needed(opts.capacity_scope_token, &disks);
}
let mut obj_info = ObjectInfo::from_file_info(&dfi, bucket, object, opts.versioned || opts.version_suspended);
obj_info.size = goi.size;
Ok(obj_info)
@@ -2157,6 +2200,8 @@ impl ObjectOperations for SetDisks {
error = ?err,
"transition completed on remote tier but source cleanup failed; skipping external lifecycle transition notification"
);
} else {
record_capacity_scope_if_needed(opts.capacity_scope_token, &disks);
}
for disk in disks.iter() {
@@ -3515,6 +3560,8 @@ impl MultipartOperations for SetDisks {
}
}
record_capacity_scope_if_needed(opts.capacity_scope_token, &online_disks);
fi.is_latest = true;
Ok(ObjectInfo::from_file_info(&fi, bucket, object, opts.versioned || opts.version_suspended))
+2
View File
@@ -558,6 +558,8 @@ impl SetDisks {
}
}
record_capacity_scope_if_needed(None, &out_dated_disks);
Ok((result, None))
}
Err(err) => Ok((result, Some(err))),
+1
View File
@@ -73,6 +73,7 @@ pub struct ObjectOptions {
pub resolved_checksum: Option<Bytes>,
pub want_checksum: Option<Checksum>,
pub skip_verify_bitrot: bool,
pub capacity_scope_token: Option<Uuid>,
}
impl ObjectOptions {