refactor: move list operations contract (#3550)

This commit is contained in:
安正超
2026-06-18 08:48:18 +08:00
committed by GitHub
parent 36f7ad6936
commit 3fb4cb3d65
23 changed files with 313 additions and 86 deletions
@@ -37,9 +37,7 @@ use crate::global::GLOBAL_LocalNodeName;
use crate::global::{GLOBAL_LifecycleSys, GLOBAL_TierConfigMgr, get_global_deployment_id};
use crate::set_disk::{MAX_PARTS_COUNT, RUSTFS_MULTIPART_BUCKET_KEY, RUSTFS_MULTIPART_OBJECT_KEY, SetDisks};
use crate::store::ECStore;
use crate::store_api::{
GetObjectReader, ListOperations, MultipartOperations, ObjectInfo, ObjectOperations, ObjectOptions, ObjectToDelete,
};
use crate::store_api::{GetObjectReader, MultipartOperations, ObjectInfo, ObjectOperations, ObjectOptions, ObjectToDelete};
use crate::tier::warm_backend::WarmBackendGetOpts;
use async_channel::{Receiver as A_Receiver, Sender as A_Sender, bounded};
use futures::Future;
@@ -60,7 +58,7 @@ use rustfs_filemeta::{
VersionPurgeStatusType, get_file_info, is_restored_object_on_disk,
};
use rustfs_s3_types::EventName;
use rustfs_storage_api::HTTPRangeSpec;
use rustfs_storage_api::{HTTPRangeSpec, ListOperations as _};
use rustfs_utils::{get_env_i64, get_env_usize, path::encode_dir_object, string::strings_has_prefix_fold};
use s3s::dto::{
BucketLifecycleConfiguration, DefaultRetention, ExpirationStatus, ReplicationConfiguration, RestoreRequest,
@@ -24,7 +24,8 @@ use crate::config::com::{delete_config, read_config, save_config};
use crate::disk::RUSTFS_META_BUCKET;
use crate::error::{Error, Result};
use crate::store::ECStore;
use crate::store_api::{ListOperations, ObjectIO, ObjectOperations};
use crate::store_api::{ObjectIO, ObjectOperations};
use rustfs_storage_api::ListOperations as _;
const LOG_COMPONENT_ECSTORE: &str = "ecstore";
const LOG_SUBSYSTEM_LIFECYCLE: &str = "lifecycle";
@@ -20,8 +20,8 @@ use tokio_util::sync::CancellationToken;
use crate::disk::RUSTFS_META_BUCKET;
use crate::error::Result;
use crate::store::ECStore;
use crate::store_api::{ListOperations, ObjectInfo, ObjectInfoOrErr, WalkOptions};
use rustfs_storage_api::{BucketOperations, BucketOptions};
use crate::store_api::{ObjectInfo, ObjectInfoOrErr, WalkOptions};
use rustfs_storage_api::{BucketOperations, BucketOptions, ListOperations as _};
pub const DEFAULT_FREE_VERSION_RECOVERY_LIMIT: usize = 1_000;
const DEFAULT_FREE_VERSION_RECOVERY_SCAN_LIMIT: usize = 10_000;
+1 -1
View File
@@ -20,7 +20,6 @@ use crate::{
disk::DiskAPI,
error::{Error, classify_system_path_failure_reason},
store::ECStore,
store_api::ListOperations,
};
pub use local_snapshot::{
DATA_USAGE_DIR, DATA_USAGE_STATE_DIR, LOCAL_USAGE_SNAPSHOT_VERSION, LocalUsageSnapshot, LocalUsageSnapshotMeta,
@@ -31,6 +30,7 @@ use rustfs_data_usage::{
BucketTargetUsageInfo, BucketUsageInfo, DataUsageCache, DataUsageEntry, DataUsageInfo, DiskUsageStatus, SizeSummary,
};
use rustfs_io_metrics::record_system_path_failure;
use rustfs_storage_api::ListOperations as _;
use rustfs_utils::path::SLASH_SEPARATOR;
use std::{
collections::{HashMap, HashSet, hash_map::Entry},
+11 -3
View File
@@ -52,8 +52,8 @@ use crate::{
event_notification::{EventArgs, send_event},
global::{GLOBAL_LOCAL_DISK_MAP, GLOBAL_LOCAL_DISK_SET_DRIVES, get_global_deployment_id, is_dist_erasure},
store_api::{
DeletedObject, GetObjectReader, HealOperations, ListObjectsV2Info, ListOperations, MultipartOperations, NamespaceLocking,
ObjectIO, ObjectInfo, ObjectOperations, PutObjReader,
DeletedObject, GetObjectReader, HealOperations, ListObjectsV2Info, MultipartOperations, NamespaceLocking, ObjectIO,
ObjectInfo, ObjectOperations, PutObjReader,
},
store_init::load_format_erasure,
};
@@ -2985,7 +2985,15 @@ impl SetDisks {
}
#[async_trait::async_trait]
impl ListOperations for SetDisks {
impl rustfs_storage_api::ListOperations for SetDisks {
type Error = Error;
type ListObjectsV2Info = ListObjectsV2Info;
type ListObjectVersionsInfo = ListObjectVersionsInfo;
type ObjectInfoOrErr = ObjectInfoOrErr;
type WalkOptions = WalkOptions;
type WalkCancellation = CancellationToken;
type WalkResultSender = Sender<ObjectInfoOrErr>;
#[tracing::instrument(skip(self))]
async fn list_objects_v2(
self: Arc<Self>,
+11 -4
View File
@@ -28,9 +28,8 @@ use crate::{
global::{GLOBAL_LOCAL_DISK_SET_DRIVES, get_global_lock_clients, is_dist_erasure},
set_disk::SetDisks,
store_api::{
DeletedObject, GetObjectReader, HealOperations, ListObjectVersionsInfo, ListObjectsV2Info, ListOperations,
MultipartOperations, NamespaceLocking, ObjectIO, ObjectInfo, ObjectOperations, ObjectOptions, ObjectToDelete,
PutObjReader,
DeletedObject, GetObjectReader, HealOperations, ListObjectVersionsInfo, ListObjectsV2Info, MultipartOperations,
NamespaceLocking, ObjectIO, ObjectInfo, ObjectOperations, ObjectOptions, ObjectToDelete, PutObjReader,
},
store_init::{check_format_erasure_values, get_format_erasure_in_quorum, load_format_erasure_all, save_format_file},
};
@@ -647,7 +646,15 @@ impl ObjectOperations for Sets {
}
#[async_trait::async_trait]
impl ListOperations for Sets {
impl rustfs_storage_api::ListOperations for Sets {
type Error = Error;
type ListObjectsV2Info = ListObjectsV2Info;
type ListObjectVersionsInfo = ListObjectVersionsInfo;
type ObjectInfoOrErr = ObjectInfoOrErr;
type WalkOptions = WalkOptions;
type WalkCancellation = CancellationToken;
type WalkResultSender = tokio::sync::mpsc::Sender<ObjectInfoOrErr>;
#[tracing::instrument(skip(self))]
async fn list_objects_v2(
self: Arc<Self>,
+11 -3
View File
@@ -63,8 +63,8 @@ use crate::{
rpc::S3PeerSys,
sets::Sets,
store_api::{
DeletedObject, GetObjectReader, HealOperations, ListObjectsV2Info, ListOperations, MultipartOperations, NamespaceLocking,
ObjectInfo, ObjectOperations, ObjectOptions, ObjectToDelete, PutObjReader,
DeletedObject, GetObjectReader, HealOperations, ListObjectsV2Info, MultipartOperations, NamespaceLocking, ObjectInfo,
ObjectOperations, ObjectOptions, ObjectToDelete, PutObjReader,
},
store_init,
};
@@ -504,7 +504,15 @@ impl ObjectOperations for ECStore {
}
#[async_trait::async_trait]
impl ListOperations for ECStore {
impl rustfs_storage_api::ListOperations for ECStore {
type Error = Error;
type ListObjectsV2Info = ListObjectsV2Info;
type ListObjectVersionsInfo = ListObjectVersionsInfo;
type ObjectInfoOrErr = ObjectInfoOrErr;
type WalkOptions = WalkOptions;
type WalkCancellation = CancellationToken;
type WalkResultSender = tokio::sync::mpsc::Sender<ObjectInfoOrErr>;
// @continuation_token marker
// @start_after as marker when continuation_token empty
// @delimiter default="/", empty when recursive
+27 -32
View File
@@ -49,39 +49,34 @@ pub trait ObjectOperations: Send + Sync + Debug {
}
/// Listing and walking operations.
#[async_trait::async_trait]
#[allow(clippy::too_many_arguments)]
pub trait ListOperations: Send + Sync + Debug {
async fn list_objects_v2(
self: Arc<Self>,
bucket: &str,
prefix: &str,
continuation_token: Option<String>,
delimiter: Option<String>,
max_keys: i32,
fetch_owner: bool,
start_after: Option<String>,
incl_deleted: bool,
) -> Result<ListObjectsV2Info>;
pub trait ListOperations:
rustfs_storage_api::ListOperations<
Error = Error,
ListObjectsV2Info = ListObjectsV2Info,
ListObjectVersionsInfo = ListObjectVersionsInfo,
ObjectInfoOrErr = ObjectInfoOrErr,
WalkOptions = WalkOptions,
WalkCancellation = CancellationToken,
WalkResultSender = tokio::sync::mpsc::Sender<ObjectInfoOrErr>,
> + Send
+ Sync
+ Debug
{
}
async fn list_object_versions(
self: Arc<Self>,
bucket: &str,
prefix: &str,
marker: Option<String>,
version_marker: Option<String>,
delimiter: Option<String>,
max_keys: i32,
) -> Result<ListObjectVersionsInfo>;
async fn walk(
self: Arc<Self>,
rx: CancellationToken,
bucket: &str,
prefix: &str,
result: tokio::sync::mpsc::Sender<ObjectInfoOrErr>,
opts: WalkOptions,
) -> Result<()>;
impl<T> ListOperations for T where
T: rustfs_storage_api::ListOperations<
Error = Error,
ListObjectsV2Info = ListObjectsV2Info,
ListObjectVersionsInfo = ListObjectVersionsInfo,
ObjectInfoOrErr = ObjectInfoOrErr,
WalkOptions = WalkOptions,
WalkCancellation = CancellationToken,
WalkResultSender = tokio::sync::mpsc::Sender<ObjectInfoOrErr>,
> + Send
+ Sync
+ Debug
{
}
/// Multipart upload operations.
+2 -2
View File
@@ -19,10 +19,10 @@ use rustfs_ecstore::{
disk::{DiskStore, endpoint::Endpoint},
error::StorageError,
store::ECStore,
store_api::{HealOperations, ListOperations, ObjectIO, ObjectOperations, ObjectOptions},
store_api::{HealOperations, ObjectIO, ObjectOperations, ObjectOptions},
};
use rustfs_madmin::heal_commands::HealResultItem;
use rustfs_storage_api::{BucketInfo, BucketOperations, DiskSetSelector, StorageAdminApi};
use rustfs_storage_api::{BucketInfo, BucketOperations, DiskSetSelector, ListOperations as _, StorageAdminApi};
use std::sync::Arc;
use tracing::{debug, error, warn};
+2 -1
View File
@@ -23,7 +23,7 @@ use crate::{
use futures::future::join_all;
use rustfs_credentials::get_global_action_cred;
use rustfs_ecstore::error::{StorageError, classify_system_path_failure_reason};
use rustfs_ecstore::store_api::{ListOperations as _, ObjectInfoOrErr, WalkOptions};
use rustfs_ecstore::store_api::{ObjectInfoOrErr, WalkOptions};
use rustfs_ecstore::{
config::{
RUSTFS_CONFIG_PREFIX,
@@ -35,6 +35,7 @@ use rustfs_ecstore::{
use rustfs_io_metrics::record_system_path_failure;
use rustfs_policy::{auth::UserIdentity, policy::PolicyDoc};
use rustfs_storage_api::HTTPPreconditions;
use rustfs_storage_api::ListOperations as _;
use rustfs_utils::path::{SLASH_SEPARATOR, path_join_buf};
use serde::{Serialize, de::DeserializeOwned};
use std::sync::{LazyLock, Mutex};
+3 -2
View File
@@ -21,8 +21,9 @@ use super::types::Container;
use super::{SwiftError, SwiftResult};
use rustfs_credentials::Credentials;
use rustfs_ecstore::resolve_object_store_handle;
use rustfs_ecstore::store_api::ListOperations;
use rustfs_storage_api::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOptions, MakeBucketOptions};
use rustfs_storage_api::{
BucketInfo, BucketOperations, BucketOptions, DeleteBucketOptions, ListOperations as _, MakeBucketOptions,
};
use s3s::dto::{Tag, Tagging};
use sha2::{Digest, Sha256};
use tracing::{debug, error};
+2 -1
View File
@@ -57,7 +57,8 @@ use super::object::{ObjectKeyMapper, head_object};
use super::{SwiftError, SwiftResult};
use rustfs_credentials::Credentials;
use rustfs_ecstore::resolve_object_store_handle;
use rustfs_ecstore::store_api::{ListOperations, ObjectOperations, ObjectOptions};
use rustfs_ecstore::store_api::{ObjectOperations, ObjectOptions};
use rustfs_storage_api::ListOperations as _;
use std::time::{SystemTime, UNIX_EPOCH};
use tracing::{debug, error};
@@ -28,7 +28,7 @@ use rustfs_ecstore::{
global::GLOBAL_TierConfigMgr,
pools::path2_bucket_object_with_base_path,
store::ECStore,
store_api::{ListOperations, MultipartOperations, ObjectIO, ObjectOperations, ObjectOptions, PutObjReader},
store_api::{MultipartOperations, ObjectIO, ObjectOperations, ObjectOptions, PutObjReader},
tier::{
tier_config::{TierConfig, TierMinIO, TierType},
warm_backend::{WarmBackend, WarmBackendGetOpts, build_transition_put_options},
@@ -38,6 +38,7 @@ use rustfs_filemeta::FileMeta;
use rustfs_scanner::scanner::init_data_scanner;
use rustfs_scanner::scanner_folder::ScannerItem;
use rustfs_scanner::scanner_io::ScannerIODisk;
use rustfs_storage_api::ListOperations as _;
use rustfs_storage_api::{BucketOperations, MakeBucketOptions};
use rustfs_utils::path::path_join_buf;
use s3s::dto::RestoreRequest;
+1 -1
View File
@@ -25,6 +25,6 @@ pub use bucket::{BucketInfo, BucketOperations, BucketOptions, DeleteBucketOption
pub use error::{StorageErrorCode, StorageResult};
pub use multipart::{CompletePart, ListMultipartsInfo, ListPartsInfo, MultipartInfo, MultipartUploadResult, PartInfo};
pub use object::{HTTPPreconditions, HTTPRangeError, HTTPRangeSpec, ObjectLockRetentionOptions};
pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ObjectInfoOrErr};
pub use object::{ListObjectVersionsInfo, ListObjectsInfo, ListObjectsV2Info, ListOperations, ObjectInfoOrErr};
pub use object::{ObjectPreconditionError, ObjectPreconditionPart, ObjectPreconditionState};
pub use object::{VersionMarker, WalkOptions, WalkVersionsSortOrder};
+180
View File
@@ -13,6 +13,7 @@
// limitations under the License.
use std::fmt;
use std::sync::Arc;
use time::OffsetDateTime;
use uuid::Uuid;
@@ -179,6 +180,49 @@ impl<Filter> Default for WalkOptions<Filter> {
}
}
#[async_trait::async_trait]
#[allow(clippy::too_many_arguments)]
pub trait ListOperations: Send + Sync + fmt::Debug {
type Error: std::error::Error + Send + Sync + 'static;
type ListObjectsV2Info: Send + 'static;
type ListObjectVersionsInfo: Send + 'static;
type ObjectInfoOrErr: Send + 'static;
type WalkOptions: Send + 'static;
type WalkCancellation: Send + 'static;
type WalkResultSender: Send + 'static;
async fn list_objects_v2(
self: Arc<Self>,
bucket: &str,
prefix: &str,
continuation_token: Option<String>,
delimiter: Option<String>,
max_keys: i32,
fetch_owner: bool,
start_after: Option<String>,
incl_deleted: bool,
) -> Result<Self::ListObjectsV2Info, Self::Error>;
async fn list_object_versions(
self: Arc<Self>,
bucket: &str,
prefix: &str,
marker: Option<String>,
version_marker: Option<String>,
delimiter: Option<String>,
max_keys: i32,
) -> Result<Self::ListObjectVersionsInfo, Self::Error>;
async fn walk(
self: Arc<Self>,
rx: Self::WalkCancellation,
bucket: &str,
prefix: &str,
result: Self::WalkResultSender,
opts: Self::WalkOptions,
) -> Result<(), Self::Error>;
}
#[derive(Debug, Default)]
pub struct ListObjectsInfo<ObjectItem> {
pub is_truncated: bool,
@@ -458,6 +502,142 @@ mod tests {
assert!(!opts.include_free_versions);
}
#[derive(Debug)]
struct TestListBackend;
#[derive(Debug)]
struct TestListError;
impl fmt::Display for TestListError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("test list error")
}
}
impl std::error::Error for TestListError {}
#[async_trait::async_trait]
impl ListOperations for TestListBackend {
type Error = TestListError;
type ListObjectsV2Info = ListObjectsV2Info<&'static str>;
type ListObjectVersionsInfo = ListObjectVersionsInfo<&'static str>;
type ObjectInfoOrErr = ObjectInfoOrErr<&'static str, TestListError>;
type WalkOptions = WalkOptions<fn(&str) -> bool>;
type WalkCancellation = ();
type WalkResultSender = ();
async fn list_objects_v2(
self: Arc<Self>,
bucket: &str,
prefix: &str,
continuation_token: Option<String>,
delimiter: Option<String>,
max_keys: i32,
fetch_owner: bool,
start_after: Option<String>,
incl_deleted: bool,
) -> Result<Self::ListObjectsV2Info, Self::Error> {
assert_eq!(bucket, "bucket");
assert_eq!(prefix, "photos/");
assert_eq!(continuation_token.as_deref(), Some("token"));
assert_eq!(delimiter.as_deref(), Some("/"));
assert_eq!(max_keys, 10);
assert!(fetch_owner);
assert_eq!(start_after.as_deref(), Some("photos/0001.jpg"));
assert!(!incl_deleted);
Ok(ListObjectsV2Info {
is_truncated: true,
continuation_token,
next_continuation_token: Some("next".to_owned()),
objects: vec!["photos/0002.jpg"],
prefixes: vec!["photos/2024/".to_owned()],
})
}
async fn list_object_versions(
self: Arc<Self>,
bucket: &str,
prefix: &str,
marker: Option<String>,
version_marker: Option<String>,
delimiter: Option<String>,
max_keys: i32,
) -> Result<Self::ListObjectVersionsInfo, Self::Error> {
assert_eq!(bucket, "bucket");
assert_eq!(prefix, "photos/");
assert_eq!(marker.as_deref(), Some("photos/0001.jpg"));
assert_eq!(version_marker.as_deref(), Some("version"));
assert_eq!(delimiter.as_deref(), Some("/"));
assert_eq!(max_keys, 10);
Ok(ListObjectVersionsInfo {
is_truncated: false,
next_marker: None,
next_version_idmarker: None,
objects: vec!["photos/0001.jpg"],
prefixes: Vec::new(),
})
}
async fn walk(
self: Arc<Self>,
_rx: Self::WalkCancellation,
bucket: &str,
prefix: &str,
_result: Self::WalkResultSender,
opts: Self::WalkOptions,
) -> Result<(), Self::Error> {
assert_eq!(bucket, "bucket");
assert_eq!(prefix, "photos/");
assert_eq!(opts.marker.as_deref(), Some("photos/0001.jpg"));
Ok(())
}
}
#[tokio::test]
async fn list_operations_trait_exposes_generic_list_contract() -> Result<(), TestListError> {
let backend = Arc::new(TestListBackend);
let listed = backend
.clone()
.list_objects_v2(
"bucket",
"photos/",
Some("token".to_owned()),
Some("/".to_owned()),
10,
true,
Some("photos/0001.jpg".to_owned()),
false,
)
.await?;
assert!(listed.is_truncated);
assert_eq!(listed.objects, vec!["photos/0002.jpg"]);
assert_eq!(listed.next_continuation_token.as_deref(), Some("next"));
let versions = backend
.clone()
.list_object_versions(
"bucket",
"photos/",
Some("photos/0001.jpg".to_owned()),
Some("version".to_owned()),
Some("/".to_owned()),
10,
)
.await?;
assert_eq!(versions.objects, vec!["photos/0001.jpg"]);
let opts = WalkOptions::<fn(&str) -> bool> {
marker: Some("photos/0001.jpg".to_owned()),
..Default::default()
};
backend.walk((), "bucket", "photos/", (), opts).await?;
Ok(())
}
#[test]
fn object_list_response_contracts_default_to_empty_collections() {
let v1 = ListObjectsInfo::<()>::default();