feat: Add RustFS Scanner Module and Multiple Bug Fixes (#1579)

This commit is contained in:
weisd
2026-01-22 13:39:38 +08:00
committed by GitHub
parent 6c5f8e591a
commit 6631407416
47 changed files with 6635 additions and 580 deletions
+13 -2
View File
@@ -15,7 +15,8 @@
use crate::disk::{
CheckPartsResp, DeleteOptions, DiskAPI, DiskError, DiskInfo, DiskInfoOptions, DiskLocation, Endpoint, Error,
FileInfoVersions, ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp, Result, UpdateMetadataOpts, VolumeInfo,
WalkDirOptions, local::LocalDisk,
WalkDirOptions,
local::{LocalDisk, ScanGuard},
};
use bytes::Bytes;
use rustfs_filemeta::{FileInfo, ObjectPartInfo, RawFileInfo};
@@ -259,6 +260,7 @@ impl LocalDiskWrapper {
let test_obj = format!("health-check-{}", Uuid::new_v4());
if Self::perform_health_check(disk.clone(), &TEST_BUCKET, &test_obj, &TEST_DATA, true, CHECK_TIMEOUT_DURATION).await.is_err() && health.swap_ok_to_faulty() {
// Health check failed, disk is considered faulty
warn!("health check: failed, disk is considered faulty");
health.increment_waiting(); // Balance the increment from failed operation
@@ -429,7 +431,7 @@ impl LocalDiskWrapper {
{
// Check if disk is faulty
if self.health.is_faulty() {
warn!("disk {} health is faulty, returning error", self.to_string());
warn!("local disk {} health is faulty, returning error", self.to_string());
return Err(DiskError::FaultyDisk);
}
@@ -476,6 +478,15 @@ impl LocalDiskWrapper {
#[async_trait::async_trait]
impl DiskAPI for LocalDiskWrapper {
async fn read_metadata(&self, volume: &str, path: &str) -> Result<Bytes> {
self.track_disk_health(|| async { self.disk.read_metadata(volume, path).await }, Duration::ZERO)
.await
}
fn start_scan(&self) -> ScanGuard {
self.disk.start_scan()
}
fn to_string(&self) -> String {
self.disk.to_string()
}
+55 -32
View File
@@ -82,7 +82,7 @@ pub struct LocalDisk {
pub format_info: RwLock<FormatInfo>,
pub endpoint: Endpoint,
pub disk_info_cache: Arc<Cache<DiskInfo>>,
pub scanning: AtomicU32,
pub scanning: Arc<AtomicU32>,
pub rotational: bool,
pub fstype: String,
pub major: u64,
@@ -209,7 +209,7 @@ impl LocalDisk {
format_path,
format_info: RwLock::new(format_info),
disk_info_cache: Arc::new(cache),
scanning: AtomicU32::new(0),
scanning: Arc::new(AtomicU32::new(0)),
rotational: Default::default(),
fstype: Default::default(),
minor: Default::default(),
@@ -664,6 +664,7 @@ impl LocalDisk {
match self.read_metadata_with_dmtime(meta_path).await {
Ok(res) => Ok(res),
Err(err) => {
warn!("read_raw: error: {:?}", err);
if err == Error::FileNotFound
&& !skip_access_checks(volume_dir.as_ref().to_string_lossy().to_string().as_str())
&& let Err(e) = access(volume_dir.as_ref()).await
@@ -687,20 +688,6 @@ impl LocalDisk {
Ok((buf, mtime))
}
async fn read_metadata(&self, file_path: impl AsRef<Path>) -> Result<Vec<u8>> {
// Try to use cached file content reading for better performance, with safe fallback
let path = file_path.as_ref().to_path_buf();
// First, try the cache
if let Ok(bytes) = get_global_file_cache().get_file_content(path.clone()).await {
return Ok(bytes.to_vec());
}
// Fallback to direct read if cache fails
let (data, _) = self.read_metadata_with_dmtime(file_path.as_ref()).await?;
Ok(data)
}
async fn read_metadata_with_dmtime(&self, file_path: impl AsRef<Path>) -> Result<(Vec<u8>, Option<OffsetDateTime>)> {
check_path_length(file_path.as_ref().to_string_lossy().as_ref())?;
@@ -1052,7 +1039,7 @@ impl LocalDisk {
if entry.ends_with(STORAGE_FORMAT_FILE) {
let metadata = self
.read_metadata(self.get_object_path(bucket, format!("{}/{}", &current, &entry).as_str())?)
.read_metadata(bucket, format!("{}/{}", &current, &entry).as_str())
.await?;
let entry = entry.strip_suffix(STORAGE_FORMAT_FILE).unwrap_or_default().to_owned();
@@ -1068,7 +1055,7 @@ impl LocalDisk {
out.write_obj(&MetaCacheEntry {
name: name.clone(),
metadata,
metadata: metadata.to_vec(),
..Default::default()
})
.await?;
@@ -1135,14 +1122,14 @@ impl LocalDisk {
let fname = format!("{}/{}", &meta.name, STORAGE_FORMAT_FILE);
match self.read_metadata(self.get_object_path(&opts.bucket, fname.as_str())?).await {
match self.read_metadata(&opts.bucket, fname.as_str()).await {
Ok(res) => {
if is_dir_obj {
meta.name = meta.name.trim_end_matches(GLOBAL_DIR_SUFFIX_WITH_SLASH).to_owned();
meta.name.push_str(SLASH_SEPARATOR_STR);
}
meta.metadata = res;
meta.metadata = res.to_vec();
out.write_obj(&meta).await?;
@@ -1189,6 +1176,14 @@ impl LocalDisk {
}
}
pub struct ScanGuard(pub Arc<AtomicU32>);
impl Drop for ScanGuard {
fn drop(&mut self) {
self.0.fetch_sub(1, Ordering::Release);
}
}
fn is_root_path(path: impl AsRef<Path>) -> bool {
path.as_ref().components().count() == 1 && path.as_ref().has_root()
}
@@ -1579,6 +1574,8 @@ impl DiskAPI for LocalDisk {
.as_str(),
)?;
info!("check_parts: part_path: {:?}", &part_path);
match lstat(&part_path).await {
Ok(st) => {
if st.is_dir() {
@@ -1593,6 +1590,8 @@ impl DiskAPI for LocalDisk {
resp.results[i] = CHECK_PART_SUCCESS;
}
Err(err) => {
info!("check_parts: failed to stat file: {:?}, error: {:?}", &part_path, &err);
let e: DiskError = to_file_error(err).into();
if e == DiskError::FileNotFound {
@@ -1882,19 +1881,20 @@ impl DiskAPI for LocalDisk {
let mut objs_returned = 0;
if opts.base_dir.ends_with(SLASH_SEPARATOR_STR) {
let fpath = self.get_object_path(
&opts.bucket,
path_join_buf(&[
format!("{}{}", opts.base_dir.trim_end_matches(SLASH_SEPARATOR_STR), GLOBAL_DIR_SUFFIX).as_str(),
STORAGE_FORMAT_FILE,
])
.as_str(),
)?;
if let Ok(data) = self.read_metadata(fpath).await {
if let Ok(data) = self
.read_metadata(
&opts.bucket,
path_join_buf(&[
format!("{}{}", opts.base_dir.trim_end_matches(SLASH_SEPARATOR_STR), GLOBAL_DIR_SUFFIX).as_str(),
STORAGE_FORMAT_FILE,
])
.as_str(),
)
.await
{
let meta = MetaCacheEntry {
name: opts.base_dir.clone(),
metadata: data,
metadata: data.to_vec(),
..Default::default()
};
out.write_obj(&meta).await?;
@@ -2541,7 +2541,7 @@ impl DiskAPI for LocalDisk {
info.rotational = self.rotational;
info.mount_path = self.path().to_str().unwrap().to_string();
info.endpoint = self.endpoint.to_string();
info.scanning = self.scanning.load(Ordering::SeqCst) == 1;
info.scanning = self.scanning.load(Ordering::Acquire) == 1;
if info.id.is_none() {
info.id = self.get_disk_id().await.unwrap_or(None);
@@ -2549,6 +2549,29 @@ impl DiskAPI for LocalDisk {
Ok(info)
}
#[tracing::instrument(skip(self))]
fn start_scan(&self) -> ScanGuard {
self.scanning.fetch_add(1, Ordering::Release);
ScanGuard(Arc::clone(&self.scanning))
}
async fn read_metadata(&self, volume: &str, path: &str) -> Result<Bytes> {
// Try to use cached file content reading for better performance, with safe fallback
let file_path = self.get_object_path(volume, path)?;
// let file_path = file_path.join(Path::new(STORAGE_FORMAT_FILE));
// First, try the cache
if let Ok(bytes) = get_global_file_cache().get_file_content(file_path.clone()).await {
return Ok(bytes);
}
// Fallback to direct read if cache fails
let (data, _) = self.read_metadata_with_dmtime(&file_path).await.map_err(|e| {
error!("read_metadata: error: {:?}, file_path={}", e, file_path.to_string_lossy());
e
})?;
Ok(data.into())
}
}
async fn get_disk_info(drive_path: PathBuf) -> Result<(rustfs_utils::os::DiskInfo, bool)> {
+21
View File
@@ -32,6 +32,7 @@ pub const STORAGE_FORMAT_FILE: &str = "xl.meta";
pub const STORAGE_FORMAT_FILE_BACKUP: &str = "xl.meta.bkp";
use crate::disk::disk_store::LocalDiskWrapper;
use crate::disk::local::ScanGuard;
use crate::rpc::RemoteDisk;
use bytes::Bytes;
use endpoint::Endpoint;
@@ -395,6 +396,20 @@ impl DiskAPI for Disk {
Disk::Remote(remote_disk) => remote_disk.disk_info(opts).await,
}
}
fn start_scan(&self) -> ScanGuard {
match self {
Disk::Local(local_disk) => local_disk.start_scan(),
Disk::Remote(remote_disk) => remote_disk.start_scan(),
}
}
async fn read_metadata(&self, volume: &str, path: &str) -> Result<Bytes> {
match self {
Disk::Local(local_disk) => local_disk.read_metadata(volume, path).await,
Disk::Remote(remote_disk) => remote_disk.read_metadata(volume, path).await,
}
}
}
pub async fn new_disk(ep: &Endpoint, opt: &DiskOption) -> Result<DiskStore> {
@@ -458,6 +473,7 @@ pub trait DiskAPI: Debug + Send + Sync + 'static {
opts: &ReadOptions,
) -> Result<FileInfo>;
async fn read_xl(&self, volume: &str, path: &str, read_data: bool) -> Result<RawFileInfo>;
async fn read_metadata(&self, volume: &str, path: &str) -> Result<Bytes>;
async fn rename_data(
&self,
src_volume: &str,
@@ -489,6 +505,7 @@ pub trait DiskAPI: Debug + Send + Sync + 'static {
async fn write_all(&self, volume: &str, path: &str, data: Bytes) -> Result<()>;
async fn read_all(&self, volume: &str, path: &str) -> Result<Bytes>;
async fn disk_info(&self, opts: &DiskInfoOptions) -> Result<DiskInfo>;
fn start_scan(&self) -> ScanGuard;
}
#[derive(Debug, Default, Serialize, Deserialize)]
@@ -694,6 +711,10 @@ pub fn has_part_err(part_errs: &[usize]) -> bool {
part_errs.iter().any(|err| *err != CHECK_PART_SUCCESS)
}
pub fn count_part_not_success(part_errs: &[usize]) -> usize {
part_errs.iter().filter(|err| **err != CHECK_PART_SUCCESS).count()
}
#[cfg(test)]
mod tests {
use super::*;