feat(scanner): add storage seam (#7105)

This commit is contained in:
houseme
2026-09-03 20:31:59 +08:00
committed by GitHub
parent 53cabe9274
commit 62e66baf89
16 changed files with 850 additions and 560 deletions
+282 -14
View File
@@ -15,7 +15,11 @@
use std::collections::HashMap;
use std::sync::Arc;
#[cfg(test)]
use http::HeaderMap;
use rustfs_lock::NamespaceLockWrapper;
use serde::{Deserialize, Serialize};
use tokio::sync::Notify;
pub(crate) use s3s::dto::{
BucketLifecycleConfiguration as EcstoreBucketLifecycleConfiguration, LifecycleRuleFilter as EcstoreLifecycleRuleFilter,
@@ -29,11 +33,7 @@ pub(crate) use rustfs_ecstore::api::bucket::lifecycle::bucket_lifecycle_audit::L
pub(crate) use rustfs_ecstore::api::bucket::lifecycle::bucket_lifecycle_ops::{
apply_expiry_rule as ecstore_apply_expiry_rule, apply_transition_rule as ecstore_apply_transition_rule,
};
pub(crate) use rustfs_ecstore::api::bucket::lifecycle::evaluator::Evaluator as EcstoreEvaluator;
pub(crate) use rustfs_ecstore::api::bucket::lifecycle::lifecycle::{
Event as EcstoreEvent, Lifecycle as EcstoreLifecycle, ObjectOpts as EcstoreObjectOpts,
TRANSITION_COMPLETE as ECSTORE_TRANSITION_COMPLETE, object_opts_from_object_info as ecstore_object_opts_from_object_info,
};
pub(crate) use rustfs_ecstore::api::bucket::lifecycle::lifecycle::object_opts_from_object_info as ecstore_object_opts_from_object_info;
#[cfg(test)]
pub(crate) use rustfs_ecstore::api::bucket::metadata_sys::init_bucket_metadata_sys as ecstore_init_bucket_metadata_sys;
pub(crate) use rustfs_ecstore::api::bucket::metadata_sys::{
@@ -92,6 +92,7 @@ pub(crate) use rustfs_ecstore::api::event::{EventArgs as EcstoreEventArgs, send_
pub(crate) use rustfs_ecstore::api::layout::{
EndpointServerPools as EcstoreEndpointServerPools, Endpoints as EcstoreEndpoints, PoolEndpoints as EcstorePoolEndpoints,
};
pub(crate) use rustfs_ecstore::api::notification::NotificationSys as EcstoreNotificationSys;
pub(crate) use rustfs_ecstore::api::notification::scanner_peer_transport_error_message_is_retryable;
pub(crate) use rustfs_ecstore::api::object::{
SCANNER_PUBLICATION_LEASE_FENCE_METADATA_KEY, ScannerPublicationCommitScope, ScannerPublicationCommitState,
@@ -101,19 +102,25 @@ pub(crate) use rustfs_ecstore::api::rebalance::{
RebalStatus as EcstoreRebalStatus, RebalanceInfo as EcstoreRebalanceInfo, RebalanceMeta as EcstoreRebalanceMeta,
RebalanceStats as EcstoreRebalanceStats,
};
pub(crate) use rustfs_ecstore::api::rpc::ScannerBucketListing as EcstoreScannerBucketListing;
#[cfg(test)]
pub(crate) use rustfs_ecstore::api::runtime::InstanceContext as EcstoreInstanceContext;
pub(crate) use rustfs_ecstore::api::runtime::{
expiry_state_handle as ecstore_expiry_state_handle, global_tier_config_mgr as ecstore_get_global_tier_config_mgr,
object_store_handle as ecstore_resolve_object_store_handle, setup_is_erasure as ecstore_is_erasure,
setup_is_erasure_sd as ecstore_is_erasure_sd,
};
pub(crate) use rustfs_ecstore::api::set_disk::SetDisks as EcstoreSetDisks;
pub(crate) use rustfs_ecstore::api::storage::ECStore as EcstoreStore;
#[cfg(test)]
pub(crate) use rustfs_ecstore::api::storage::SCANNER_PUBLICATION_LEASE_TTL_MS as ECSTORE_SCANNER_PUBLICATION_LEASE_TTL_MS;
#[cfg(test)]
pub(crate) use rustfs_ecstore::api::storage::init_local_disks_with_instance_ctx as ecstore_init_local_disks_with_instance_ctx;
pub(crate) use rustfs_ecstore::api::storage::{
ECStore as EcstoreStore, ScannerDataMovementPauseStatus as EcstoreScannerDataMovementPauseStatus,
};
pub(crate) use rustfs_lifecycle::{
Evaluator as EcstoreEvaluator, Event as EcstoreEvent, Lifecycle as EcstoreLifecycle, ObjectOpts as EcstoreObjectOpts,
TRANSITION_COMPLETE as ECSTORE_TRANSITION_COMPLETE,
};
use rustfs_storage_api as storage_contracts;
pub(crate) mod owner {
@@ -134,11 +141,11 @@ pub(crate) mod owner {
ScannerReplicationQueueAdmission, ecstore_apply_expiry_rule, ecstore_apply_transition_rule, ecstore_expiry_state_handle,
ecstore_get_global_tier_config_mgr, ecstore_get_lifecycle_config, ecstore_get_object_lock_config,
ecstore_get_replication_config, ecstore_invalidate_admin_data_usage_snapshot_cache,
ecstore_invalidate_data_usage_snapshot_cache, ecstore_is_erasure, ecstore_is_erasure_sd,
ecstore_is_reserved_or_invalid_bucket, ecstore_list_path_raw, ecstore_object_opts_from_object_info,
ecstore_path2_bucket_object, ecstore_path2_bucket_object_with_base_path, ecstore_read_config,
ecstore_replace_bucket_usage_memory_from_info, ecstore_resolve_object_store_handle, ecstore_save_config,
ecstore_send_event, scanner_replication_config_for_lifecycle_eval,
ecstore_invalidate_data_usage_snapshot_cache, ecstore_is_erasure, ecstore_is_reserved_or_invalid_bucket,
ecstore_list_path_raw, ecstore_object_opts_from_object_info, ecstore_path2_bucket_object,
ecstore_path2_bucket_object_with_base_path, ecstore_read_config, ecstore_replace_bucket_usage_memory_from_info,
ecstore_resolve_object_store_handle, ecstore_save_config, ecstore_send_event,
scanner_replication_config_for_lifecycle_eval,
};
#[cfg(test)]
@@ -289,9 +296,10 @@ impl From<EcstoreReplicationHealQueueResult> for ScannerReplicationHealResult {
}
pub(crate) mod scan {
#[cfg(test)]
pub(crate) use super::storage_contracts::BucketOperations;
pub(crate) use super::storage_contracts::{
BucketOperations, BucketOptions, NamespaceLocking, SCANNER_ACTIVITY_LEGACY_PROTOCOL_VERSION,
SCANNER_ACTIVITY_PREVIOUS_PROTOCOL_VERSION,
BucketOptions, NamespaceLocking, SCANNER_ACTIVITY_LEGACY_PROTOCOL_VERSION, SCANNER_ACTIVITY_PREVIOUS_PROTOCOL_VERSION,
};
#[cfg(test)]
pub(crate) use super::storage_contracts::{DeleteBucketOptions, MakeBucketOptions, ObjectIO};
@@ -303,3 +311,263 @@ pub(crate) mod scanner_io {
#[cfg(test)]
pub(crate) use super::storage_contracts::{HTTPRangeSpec, ObjectIO};
}
pub(crate) type ScannerBucketListing = EcstoreScannerBucketListing;
pub(crate) type ScannerDataMovementPauseStatus = EcstoreScannerDataMovementPauseStatus;
pub(crate) type ScannerNotificationSys = EcstoreNotificationSys;
#[async_trait::async_trait]
pub(crate) trait ScannerStorage:
crate::ScannerObjectIO
+ crate::ScannerConfigObjectDelete
+ storage_contracts::BucketOperations<Error = EcstoreErrorType>
+ storage_contracts::NamespaceLocking<Error = EcstoreErrorType, NamespaceLock = NamespaceLockWrapper>
{
fn scanner_topology_digest(&self) -> [u8; 32];
fn scanner_namespace_mutation_generation(&self) -> u64;
async fn scanner_data_movement_activity(&self) -> (bool, bool, u64);
async fn scanner_data_usage_publication_blocked(&self) -> bool;
async fn scanner_data_movement_pause_status(&self) -> ScannerDataMovementPauseStatus;
fn scanner_data_movement_generation(&self) -> u64;
fn scanner_data_movement_changed(&self) -> Arc<Notify>;
fn scanner_notification_system(&self) -> Option<Arc<ScannerNotificationSys>>;
async fn setup_is_erasure(&self) -> bool;
async fn setup_is_dist_erasure(&self) -> bool;
async fn setup_is_erasure_sd(&self) -> bool;
async fn list_bucket_for_scanner(&self, opts: &storage_contracts::BucketOptions) -> EcstoreResultType<ScannerBucketListing>;
fn all_set_disks(&self) -> Vec<Arc<EcstoreSetDisks>>;
async fn scanner_pause_backlog_writable_set_disks(&self) -> Vec<Arc<EcstoreSetDisks>>;
#[cfg(test)]
fn scanner_observed_probe_store_key(&self) -> usize;
}
#[async_trait::async_trait]
impl ScannerStorage for EcstoreStore {
fn scanner_topology_digest(&self) -> [u8; 32] {
crate::scanner::scanner_topology_digest(self)
}
fn scanner_namespace_mutation_generation(&self) -> u64 {
EcstoreStore::scanner_namespace_mutation_generation(self)
}
async fn scanner_data_movement_activity(&self) -> (bool, bool, u64) {
EcstoreStore::scanner_data_movement_activity(self).await
}
async fn scanner_data_usage_publication_blocked(&self) -> bool {
EcstoreStore::scanner_data_usage_publication_blocked(self).await
}
async fn scanner_data_movement_pause_status(&self) -> ScannerDataMovementPauseStatus {
EcstoreStore::scanner_data_movement_pause_status(self).await
}
fn scanner_data_movement_generation(&self) -> u64 {
EcstoreStore::scanner_data_movement_generation(self)
}
fn scanner_data_movement_changed(&self) -> Arc<Notify> {
EcstoreStore::scanner_data_movement_changed(self)
}
fn scanner_notification_system(&self) -> Option<Arc<ScannerNotificationSys>> {
EcstoreStore::notification_system(self)
}
async fn setup_is_erasure(&self) -> bool {
EcstoreStore::setup_is_erasure(self).await
}
async fn setup_is_dist_erasure(&self) -> bool {
EcstoreStore::setup_is_dist_erasure(self).await
}
async fn setup_is_erasure_sd(&self) -> bool {
EcstoreStore::setup_is_erasure_sd(self).await
}
async fn list_bucket_for_scanner(&self, opts: &storage_contracts::BucketOptions) -> EcstoreResultType<ScannerBucketListing> {
EcstoreStore::list_bucket_for_scanner(self, opts).await
}
fn all_set_disks(&self) -> Vec<Arc<EcstoreSetDisks>> {
EcstoreStore::all_set_disks(self)
}
async fn scanner_pause_backlog_writable_set_disks(&self) -> Vec<Arc<EcstoreSetDisks>> {
EcstoreStore::scanner_pause_backlog_writable_set_disks(self).await
}
#[cfg(test)]
fn scanner_observed_probe_store_key(&self) -> usize {
std::ptr::from_ref(self).cast::<()>() as usize
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Default)]
struct FakeScannerStorage;
#[async_trait::async_trait]
impl storage_contracts::ObjectIO for FakeScannerStorage {
type Error = EcstoreErrorType;
type RangeSpec = storage_contracts::HTTPRangeSpec;
type HeaderMap = HeaderMap;
type ObjectOptions = <EcstoreStore as storage_contracts::ObjectIO>::ObjectOptions;
type ObjectInfo = <EcstoreStore as storage_contracts::ObjectIO>::ObjectInfo;
type GetObjectReader = <EcstoreStore as storage_contracts::ObjectIO>::GetObjectReader;
type PutObjectReader = <EcstoreStore as storage_contracts::ObjectIO>::PutObjectReader;
async fn get_object_reader(
&self,
_bucket: &str,
_object: &str,
_range: Option<Self::RangeSpec>,
_h: Self::HeaderMap,
_opts: &Self::ObjectOptions,
) -> Result<Self::GetObjectReader, Self::Error> {
Err(EcstoreErrorType::other("fake scanner storage has no object reader"))
}
async fn put_object(
&self,
_bucket: &str,
_object: &str,
_data: &mut Self::PutObjectReader,
_opts: &Self::ObjectOptions,
) -> Result<Self::ObjectInfo, Self::Error> {
Err(EcstoreErrorType::other("fake scanner storage has no object writer"))
}
}
#[async_trait::async_trait]
impl storage_contracts::BucketOperations for FakeScannerStorage {
type Error = EcstoreErrorType;
async fn make_bucket(&self, _bucket: &str, _opts: &storage_contracts::MakeBucketOptions) -> Result<(), Self::Error> {
Err(EcstoreErrorType::other("fake scanner storage cannot make buckets"))
}
async fn get_bucket_info(
&self,
_bucket: &str,
_opts: &storage_contracts::BucketOptions,
) -> Result<storage_contracts::BucketInfo, Self::Error> {
Err(EcstoreErrorType::other("fake scanner storage cannot read buckets"))
}
async fn list_bucket(
&self,
_opts: &storage_contracts::BucketOptions,
) -> Result<Vec<storage_contracts::BucketInfo>, Self::Error> {
Ok(Vec::new())
}
async fn delete_bucket(&self, _bucket: &str, _opts: &storage_contracts::DeleteBucketOptions) -> Result<(), Self::Error> {
Err(EcstoreErrorType::other("fake scanner storage cannot delete buckets"))
}
}
#[async_trait::async_trait]
impl storage_contracts::NamespaceLocking for FakeScannerStorage {
type Error = EcstoreErrorType;
type NamespaceLock = NamespaceLockWrapper;
async fn new_ns_lock(&self, _bucket: &str, _object: &str) -> Result<Self::NamespaceLock, Self::Error> {
Err(EcstoreErrorType::other("fake scanner storage has no namespace lock"))
}
}
#[async_trait::async_trait]
impl crate::ScannerConfigObjectDelete for FakeScannerStorage {
async fn delete_config_object(
&self,
_bucket: &str,
_object: &str,
_opts: <EcstoreStore as storage_contracts::ObjectIO>::ObjectOptions,
) -> EcstoreResultType<<EcstoreStore as storage_contracts::ObjectIO>::ObjectInfo> {
Err(EcstoreErrorType::other("fake scanner storage cannot delete objects"))
}
async fn scanner_data_usage_publication_admission(&self) -> Option<crate::ScannerDataUsagePublicationAdmission> {
Some(crate::ScannerDataUsagePublicationAdmission::unfenced())
}
}
#[async_trait::async_trait]
impl ScannerStorage for FakeScannerStorage {
fn scanner_topology_digest(&self) -> [u8; 32] {
[0; 32]
}
fn scanner_namespace_mutation_generation(&self) -> u64 {
0
}
async fn scanner_data_movement_activity(&self) -> (bool, bool, u64) {
(false, false, 0)
}
async fn scanner_data_usage_publication_blocked(&self) -> bool {
false
}
async fn scanner_data_movement_pause_status(&self) -> ScannerDataMovementPauseStatus {
ScannerDataMovementPauseStatus::default()
}
fn scanner_data_movement_generation(&self) -> u64 {
0
}
fn scanner_data_movement_changed(&self) -> Arc<Notify> {
Arc::new(Notify::new())
}
fn scanner_notification_system(&self) -> Option<Arc<ScannerNotificationSys>> {
None
}
async fn setup_is_erasure(&self) -> bool {
false
}
async fn setup_is_dist_erasure(&self) -> bool {
false
}
async fn setup_is_erasure_sd(&self) -> bool {
true
}
async fn list_bucket_for_scanner(
&self,
_opts: &storage_contracts::BucketOptions,
) -> EcstoreResultType<ScannerBucketListing> {
Err(EcstoreErrorType::other("fake scanner storage cannot list scanner buckets"))
}
fn all_set_disks(&self) -> Vec<Arc<EcstoreSetDisks>> {
Vec::new()
}
async fn scanner_pause_backlog_writable_set_disks(&self) -> Vec<Arc<EcstoreSetDisks>> {
Vec::new()
}
fn scanner_observed_probe_store_key(&self) -> usize {
0
}
}
fn assert_scanner_storage<S: ScannerStorage>() {}
#[test]
fn scanner_storage_contract_accepts_fake_without_ecstore() {
assert_scanner_storage::<FakeScannerStorage>();
}
}