diff --git a/crates/ecstore/src/set_disk.rs b/crates/ecstore/src/set_disk.rs index c9ec2d0c8..5ee212b2f 100644 --- a/crates/ecstore/src/set_disk.rs +++ b/crates/ecstore/src/set_disk.rs @@ -3995,10 +3995,7 @@ impl HealOperations for SetDisks { let disks = disks.clone(); let (_, errs) = Self::read_all_fileinfo(&disks, "", bucket, object, version_id, false, false, false).await?; if DiskError::is_all_not_found(&errs) { - warn!( - "heal_object failed, all obj part not found, bucket: {}, obj: {}, version_id: {}", - bucket, object, version_id - ); + debug!(bucket, object, version_id, "heal_object skipped missing object"); let err = if !version_id.is_empty() { Error::FileVersionNotFound } else { diff --git a/crates/ecstore/src/set_disk/heal.rs b/crates/ecstore/src/set_disk/heal.rs index 785c61313..28f594660 100644 --- a/crates/ecstore/src/set_disk/heal.rs +++ b/crates/ecstore/src/set_disk/heal.rs @@ -69,10 +69,7 @@ impl SetDisks { "File info read complete" ); if DiskError::is_all_not_found(&errs) { - warn!( - "heal_object failed, all obj part not found, bucket: {}, obj: {}, version_id: {}", - bucket, object, version_id - ); + debug!(bucket, object, version_id, "heal_object skipped missing object"); let err = if !version_id.is_empty() { DiskError::FileVersionNotFound } else { diff --git a/crates/scanner/src/scanner_folder.rs b/crates/scanner/src/scanner_folder.rs index 8752415e9..bd53a0b2d 100644 --- a/crates/scanner/src/scanner_folder.rs +++ b/crates/scanner/src/scanner_folder.rs @@ -28,7 +28,7 @@ use crate::runtime_config::{ scanner_alert_excess_folders, scanner_alert_excess_version_size, scanner_alert_excess_versions, scanner_yield_every_n_objects, }; use crate::scanner_budget::{ScannerCycleBudget, ScannerCycleBudgetReason}; -use crate::scanner_io::ScannerIODisk as _; +use crate::scanner_io::{SCANNER_SKIP_FILE_ERROR, ScannerIODisk as _}; use crate::sleeper::DynamicSleeper; use metrics::{counter, describe_counter}; use rustfs_common::heal_channel::{ @@ -1286,7 +1286,7 @@ impl FolderScanner { let sz = match self.local_disk.get_size(item.clone()).await { Ok(sz) => sz, Err(e) => { - let is_skip_file = matches!(e, StorageError::Io(ref io) if io.to_string() == "skip file"); + let is_skip_file = matches!(e, StorageError::Io(ref io) if io.to_string() == SCANNER_SKIP_FILE_ERROR); if !is_skip_file { // Track failed objects to prevent infinite retry loops diff --git a/crates/scanner/src/scanner_io.rs b/crates/scanner/src/scanner_io.rs index 9f5142fb6..1693d0fa8 100644 --- a/crates/scanner/src/scanner_io.rs +++ b/crates/scanner/src/scanner_io.rs @@ -35,6 +35,7 @@ use rustfs_ecstore::bucket::versioning::VersioningApi as _; use rustfs_ecstore::bucket::versioning_sys::BucketVersioningSys; use rustfs_ecstore::config::storageclass; use rustfs_ecstore::disk::STORAGE_FORMAT_FILE; +use rustfs_ecstore::disk::error::DiskError; use rustfs_ecstore::disk::{Disk, DiskAPI}; use rustfs_ecstore::error::{Error, StorageError}; use rustfs_ecstore::global::GLOBAL_TierConfigMgr; @@ -56,6 +57,8 @@ use tokio::time::Duration; use tokio_util::sync::CancellationToken; use tracing::{debug, error, warn}; +pub(crate) const SCANNER_SKIP_FILE_ERROR: &str = "skip file"; + const METRIC_SCANNER_SET_SCAN_CONCURRENCY_LIMIT: &str = "rustfs_scanner_set_scan_concurrency_limit"; const METRIC_SCANNER_DISK_SCAN_CONCURRENCY_LIMIT: &str = "rustfs_scanner_disk_scan_concurrency_limit"; const METRIC_SCANNER_SET_SCAN_WAIT_SECONDS: &str = "rustfs_scanner_set_scan_wait_seconds"; @@ -939,11 +942,14 @@ impl ScannerIODisk for Disk { let done_object = Metrics::time(Metric::ScanObject); if !is_xl_meta_path(&item.path) { - return Err(StorageError::other("skip file".to_string())); + return Err(StorageError::other(SCANNER_SKIP_FILE_ERROR.to_string())); } let data = match self.read_metadata(&item.bucket, &item.object_path()).await { Ok(data) => data, + Err(e) if DiskError::is_err_object_not_found(&e) || DiskError::is_err_version_not_found(&e) => { + return Err(StorageError::other(SCANNER_SKIP_FILE_ERROR.to_string())); + } Err(e) => { return Err(StorageError::other(format!( "failed to read metadata: {e}, bucket={}, object_path={}", @@ -960,7 +966,7 @@ impl ScannerIODisk for Disk { Ok(versions) => versions, Err(e) => { error!("Failed to get file info versions: {}/{}, err: {e}", item.bucket, item.object_path()); - return Err(StorageError::other("skip file".to_string())); + return Err(StorageError::other(SCANNER_SKIP_FILE_ERROR.to_string())); } }; @@ -1127,8 +1133,12 @@ impl ScannerIODisk for Disk { #[cfg(test)] mod tests { use super::*; + use crate::scanner_folder::ScannerItem; + use rustfs_ecstore::disk::{DiskOption, STORAGE_FORMAT_FILE, endpoint::Endpoint, new_disk}; + use rustfs_ecstore::pools::path2_bucket_object_with_base_path; use serial_test::serial; use temp_env::with_var; + use uuid::Uuid; #[test] fn record_set_scan_failure_preserves_first_error() { @@ -1216,6 +1226,65 @@ mod tests { assert!(is_xl_meta_path("/data/bucket/object/xl.meta")); } + #[tokio::test] + async fn get_size_treats_missing_metadata_as_skip_file() { + let temp_dir = std::env::temp_dir().join(format!("rustfs-scanner-missing-meta-{}", Uuid::new_v4())); + let bucket = "bucket"; + let object = "object"; + let object_dir = temp_dir.join(bucket).join(object); + let metadata_path = object_dir.join(STORAGE_FORMAT_FILE); + + tokio::fs::create_dir_all(&object_dir) + .await + .expect("failed to create object directory"); + tokio::fs::write(&metadata_path, []) + .await + .expect("failed to create metadata placeholder"); + + let endpoint = Endpoint::try_from(temp_dir.to_string_lossy().as_ref()).expect("failed to create endpoint"); + let disk = new_disk( + &endpoint, + &DiskOption { + cleanup: false, + health_check: false, + }, + ) + .await + .expect("failed to open local disk"); + + let relative_path = metadata_path.to_string_lossy().to_string(); + let (_, scanner_path) = path2_bucket_object_with_base_path(temp_dir.to_string_lossy().as_ref(), relative_path.as_str()); + let file_type = tokio::fs::metadata(&metadata_path) + .await + .expect("failed to stat metadata placeholder") + .file_type(); + + tokio::fs::remove_dir_all(&object_dir) + .await + .expect("failed to remove object directory"); + + let item = ScannerItem { + path: scanner_path, + bucket: bucket.to_string(), + prefix: object.to_string(), + object_name: STORAGE_FORMAT_FILE.to_string(), + file_type, + lifecycle: None, + replication: None, + heal_enabled: false, + heal_bitrot: false, + debug: false, + }; + + let err = disk + .get_size(item) + .await + .expect_err("missing metadata should be skipped instead of reported as a scanner failure"); + assert!(matches!(err, StorageError::Io(ref io) if io.to_string() == SCANNER_SKIP_FILE_ERROR)); + + let _ = tokio::fs::remove_dir_all(&temp_dir).await; + } + #[test] fn cache_root_entry_info_flattens_bucket_children() { let mut cache = DataUsageCache {