mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-27 15:37:02 +00:00
feat: Add RustFS Scanner Module and Multiple Bug Fixes (#1579)
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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!("{}/{}", ¤t, &entry).as_str())?)
|
||||
.read_metadata(bucket, format!("{}/{}", ¤t, &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)> {
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user