list_object v2 use channel, todo other params

This commit is contained in:
weisd
2024-12-22 04:57:15 +08:00
parent 03b84cb0f1
commit 13302c3586
11 changed files with 408 additions and 51 deletions
+4
View File
@@ -452,6 +452,10 @@ pub fn is_all_not_found(errs: &[Option<Error>]) -> bool {
!errs.is_empty()
}
pub fn is_all_volume_not_found(errs: &[Option<Error>]) -> bool {
DiskError::VolumeNotFound.count_errs(errs) == errs.len()
}
pub fn is_all_buckets_not_found(errs: &[Option<Error>]) -> bool {
if errs.is_empty() {
return false;
+8 -3
View File
@@ -439,7 +439,11 @@ impl LocalDisk {
}
}
Err(err)
if let Some(os_err) = err.downcast_ref::<std::io::Error>() {
Err(os_err_to_file_err(std::io::Error::new(os_err.kind(), os_err.to_string())))
} else {
Err(err)
}
}
}
}
@@ -531,7 +535,7 @@ impl LocalDisk {
return Err(Error::new(DiskError::UnsupportedDisk));
}
return Err(Error::new(e));
return Err(os_err_to_file_err(e));
}
};
@@ -848,7 +852,7 @@ impl LocalDisk {
continue;
}
let name = path::path_join_buf(&[current, &entry]);
let name = path::path_join_buf(&[current, entry]);
if !dir_stack.is_empty() {
if let Some(pop) = dir_stack.pop() {
@@ -1514,6 +1518,7 @@ impl DiskAPI for LocalDisk {
return Err(e);
}
};
Ok(entries)
}
+87 -1
View File
@@ -15,6 +15,10 @@ pub const STORAGE_FORMAT_FILE: &str = "xl.meta";
pub const STORAGE_FORMAT_FILE_BACKUP: &str = "xl.meta.bkp";
use crate::{
bucket::{
metadata_sys::get_versioning_config,
versioning::{self, VersioningApi},
},
erasure::Writer,
error::{Error, Result},
file_meta::{merge_file_meta_versions, FileMeta, FileMetaShallowVersion},
@@ -24,7 +28,7 @@ use crate::{
heal_commands::{HealScanMode, HealingTracker},
},
io,
store_api::{FileInfo, RawFileInfo},
store_api::{FileInfo, ObjectInfo, RawFileInfo},
};
use endpoint::Endpoint;
use error::DiskError;
@@ -41,6 +45,7 @@ use std::{
cmp::Ordering,
fmt::Debug,
io::{Cursor, SeekFrom},
ops::Index,
path::PathBuf,
sync::Arc,
};
@@ -795,6 +800,10 @@ impl MetaCacheEntry {
pub struct MetaCacheEntries(pub Vec<Option<MetaCacheEntry>>);
impl MetaCacheEntries {
#[allow(clippy::should_implement_trait)]
pub fn as_ref(&self) -> &[Option<MetaCacheEntry>] {
&self.0
}
pub fn resolve(&self, mut params: MetadataResolutionParams) -> Result<Option<MetaCacheEntry>> {
if self.0.is_empty() {
return Ok(None);
@@ -885,6 +894,83 @@ impl MetaCacheEntries {
}
}
#[derive(Debug)]
pub struct MetaCacheEntriesSorted {
pub o: MetaCacheEntries,
// pub list_id: String,
// pub reuse: bool,
// pub lastSkippedEntry: String,
}
impl MetaCacheEntriesSorted {
pub fn entries(&self) -> Vec<MetaCacheEntry> {
let entries: Vec<MetaCacheEntry> = self.o.0.iter().flatten().cloned().collect();
entries
}
pub async fn file_infos(&self, bucket: &str, prefix: &str, delimiter: &str) -> Vec<ObjectInfo> {
let vcfg = get_versioning_config(bucket).await.ok();
let mut objects = Vec::with_capacity(self.o.as_ref().len());
let mut prev_prefix = "";
for entry in self.o.as_ref().iter().flatten() {
if entry.is_object() {
if !delimiter.is_empty() {
if let Some(idx) = entry.name.trim_start_matches(prefix).find(delimiter) {
let idx = prefix.len() + idx + delimiter.len();
if let Some(curr_prefix) = entry.name.get(0..idx) {
if curr_prefix == prev_prefix {
continue;
}
prev_prefix = curr_prefix.clone();
objects.push(ObjectInfo {
is_dir: true,
bucket: bucket.to_owned(),
name: curr_prefix.to_owned(),
..Default::default()
});
}
continue;
}
}
if let Ok(Some(fi)) = entry.to_fileinfo(bucket) {
// TODO:VersionPurgeStatus
let versioned = vcfg.clone().map(|v| v.0.versioned(&entry.name)).unwrap_or_default();
objects.push(fi.to_object_info(bucket, &entry.name, versioned));
}
continue;
}
if entry.is_dir() {
if delimiter.is_empty() {
continue;
}
if let Some(idx) = entry.name.trim_start_matches(prefix).find(delimiter) {
let idx = prefix.len() + idx + delimiter.len();
if let Some(curr_prefix) = entry.name.get(0..idx) {
if curr_prefix == prev_prefix {
continue;
}
prev_prefix = curr_prefix.clone();
objects.push(ObjectInfo {
is_dir: true,
bucket: bucket.to_owned(),
name: curr_prefix.to_owned(),
..Default::default()
});
}
}
}
}
objects
}
}
#[derive(Clone, Debug, Default)]
pub struct DiskOption {
pub cleanup: bool,
+1
View File
@@ -7,6 +7,7 @@ use tokio::io::AsyncRead;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWrite;
use tokio::io::AsyncWriteExt;
use tracing::warn;
// use std::sync::Arc;
// use tokio::sync::mpsc;
// use tokio::sync::mpsc::Sender;
+12 -9
View File
@@ -164,12 +164,13 @@ impl SetDisks {
let mut infos = Vec::with_capacity(disks.len());
let mut futures = Vec::with_capacity(disks.len());
let mut rng = thread_rng();
disks.shuffle(&mut rng);
let mut numbers: Vec<usize> = (0..disks.len()).collect();
numbers.shuffle(&mut rand::thread_rng());
{
let mut rng = thread_rng();
disks.shuffle(&mut rng);
numbers.shuffle(&mut rand::thread_rng());
}
for &i in numbers.iter() {
let disk = disks[i].clone();
@@ -818,6 +819,7 @@ impl SetDisks {
};
if let Some(err) = reduce_read_quorum_errs(errs, object_op_ignored_errs().as_ref(), expected_rquorum) {
// warn!("object_quorum_from_meta err {:?}", &err);
return Err(err);
}
@@ -1687,11 +1689,12 @@ impl SetDisks {
// TODO: 优化并发 可用数量中断
let (parts_metadata, errs) = Self::read_all_fileinfo(&disks, "", bucket, object, vid.as_str(), read_data, false).await;
// warn!("get_object_fileinfo parts_metadata {:?}", &parts_metadata);
warn!("get_object_fileinfo {}/{} errs {:?}", bucket, object, &errs);
// warn!("get_object_fileinfo {}/{} errs {:?}", bucket, object, &errs);
let _min_disks = self.set_drive_count - self.default_parity_count;
let (read_quorum, _) = Self::object_quorum_from_meta(&parts_metadata, &errs, self.default_parity_count)?;
let (read_quorum, _) = Self::object_quorum_from_meta(&parts_metadata, &errs, self.default_parity_count)
.map_err(|err| to_object_err(err, vec![bucket, object]))?;
if let Some(err) = reduce_read_quorum_errs(&errs, object_op_ignored_errs().as_ref(), read_quorum as usize) {
error!(
@@ -1700,7 +1703,7 @@ impl SetDisks {
read_quorum,
&errs
);
return Err(err);
return Err(to_object_err(err, vec![bucket, object]));
}
let (op_online_disks, mot_time, etag) = Self::list_online_disks(&disks, &parts_metadata, &errs, read_quorum as usize);
@@ -3832,7 +3835,7 @@ impl StorageAPI for SetDisks {
}
async fn list_objects_v2(
&self,
self: Arc<Self>,
_bucket: &str,
_prefix: &str,
_continuation_token: &str,
+1 -1
View File
@@ -451,7 +451,7 @@ impl StorageAPI for Sets {
self.get_disks_by_key(object).delete_object(bucket, object, opts).await
}
async fn list_objects_v2(
&self,
self: Arc<Self>,
_bucket: &str,
_prefix: &str,
_continuation_token: &str,
+5 -3
View File
@@ -1512,7 +1512,7 @@ impl StorageAPI for ECStore {
// TODO: review
async fn list_objects_v2(
&self,
self: Arc<Self>,
bucket: &str,
prefix: &str,
continuation_token: &str,
@@ -2170,9 +2170,11 @@ async fn init_local_peer(endpoint_pools: &EndpointServerPools, host: &String, po
*GLOBAL_Local_Node_Name.write().await = peer_set[0].clone();
}
pub fn is_valid_object_prefix(object: &str) -> bool {
pub fn is_valid_object_prefix(_object: &str) -> bool {
// Implement object prefix validation
!object.is_empty() // Placeholder
// !object.is_empty() // Placeholder
// FIXME: TODO:
true
}
fn is_valid_object_name(object: &str) -> bool {
+1 -1
View File
@@ -843,7 +843,7 @@ pub trait StorageAPI: ObjectIO {
async fn delete_bucket(&self, bucket: &str, opts: &DeleteBucketOptions) -> Result<()>;
// ListObjects TODO: FIXME:
async fn list_objects_v2(
&self,
self: Arc<Self>,
bucket: &str,
prefix: &str,
continuation_token: &str,
+264 -17
View File
@@ -1,5 +1,8 @@
use crate::cache_value::metacache_set::{list_path_raw, ListPathRawOptions};
use crate::disk::{DiskInfo, DiskStore, MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams, WalkDirOptions};
use crate::disk::error::{is_all_not_found, is_all_volume_not_found, is_err_eof, DiskError};
use crate::disk::{
DiskInfo, DiskStore, MetaCacheEntries, MetaCacheEntriesSorted, MetaCacheEntry, MetadataResolutionParams, WalkDirOptions,
};
use crate::error::{Error, Result};
use crate::file_meta::merge_file_meta_versions;
use crate::peer::is_reserved_or_invalid_bucket;
@@ -14,9 +17,10 @@ use rand::seq::SliceRandom;
use rand::thread_rng;
use std::collections::{HashMap, HashSet};
use std::io::ErrorKind;
use tokio::sync::broadcast::Receiver as B_Receiver;
use std::sync::Arc;
use tokio::sync::broadcast::{self, Receiver as B_Receiver};
use tokio::sync::mpsc::{self, Receiver, Sender};
use tracing::error;
use tracing::{error, warn};
const MAX_OBJECT_LIST: i32 = 1000;
const MAX_DELETE_LIST: i32 = 1000;
@@ -114,7 +118,7 @@ impl ListPathOptions {
impl ECStore {
#[allow(clippy::too_many_arguments)]
pub async fn inner_list_objects_v2(
&self,
self: Arc<Self>,
bucket: &str,
prefix: &str,
continuation_token: &str,
@@ -131,13 +135,18 @@ impl ECStore {
}
};
self.list_objects_generic(bucket, prefix, marker, delimiter, max_keys).await?;
// FIXME:TODO:
unimplemented!()
let loi = self.list_objects_generic(bucket, prefix, marker, delimiter, max_keys).await?;
Ok(ListObjectsV2Info {
is_truncated: loi.is_truncated,
continuation_token: continuation_token.to_owned(),
next_continuation_token: loi.next_marker,
objects: loi.objects,
prefixes: loi.prefixes,
})
}
pub async fn list_objects_generic(
&self,
self: Arc<Self>,
bucket: &str,
prefix: &str,
marker: &str,
@@ -155,14 +164,73 @@ impl ECStore {
..Default::default()
};
let merged = self.list_path(&opts).await?;
let mut err_eof = false;
let has_merged = match self.list_path(&opts).await {
Ok(res) => Some(res),
Err(err) => {
if !is_err_eof(&err) {
return Err(err);
}
// FIXME:TODO:
err_eof = true;
None
}
};
todo!()
let mut get_objects = if let Some(merged) = has_merged {
merged.file_infos(bucket, prefix, delimiter).await
} else {
Vec::new()
};
let is_truncated = {
if max_keys > 0 && get_objects.len() > max_keys as usize {
get_objects.truncate(max_keys as usize);
true
} else {
!err_eof && get_objects.len() > 0
}
};
let mut prefixes: Vec<String> = Vec::new();
let mut objects = Vec::with_capacity(get_objects.len());
for obj in get_objects.into_iter() {
if obj.is_dir && obj.mod_time.is_none() && !delimiter.is_empty() {
let mut found = false;
if delimiter != SLASH_SEPARATOR {
for p in prefixes.iter() {
if found {
break;
}
found = p == &obj.name;
}
}
if !found {
prefixes.push(obj.name.clone());
}
} else {
objects.push(obj);
}
}
let next_marker = {
if is_truncated {
objects.last().map(|last| last.name.clone())
} else {
None
}
};
Ok(ListObjectsInfo {
is_truncated,
next_marker: next_marker.unwrap_or_default(),
objects,
prefixes,
})
}
pub async fn list_path(&self, o: &ListPathOptions) -> Result<ListObjectsInfo> {
pub async fn list_path(self: Arc<Self>, o: &ListPathOptions) -> Result<MetaCacheEntriesSorted> {
check_list_objs_args(&o.bucket, &o.prefix, &o.marker)?;
// if opts.prefix.ends_with(SLASH_SEPARATOR) {
// return Err(Error::msg("eof"));
@@ -181,7 +249,7 @@ impl ECStore {
return Err(Error::new(std::io::Error::from(ErrorKind::UnexpectedEof)));
}
if o.prefix.ends_with(SLASH_SEPARATOR) {
if o.prefix.starts_with(SLASH_SEPARATOR) {
return Err(Error::new(std::io::Error::from(ErrorKind::UnexpectedEof)));
}
@@ -207,7 +275,29 @@ impl ECStore {
}
// FIXME:TODO:
todo!()
let (tx, rx) = broadcast::channel(1);
let (sender, mut recv) = mpsc::channel(o.limit as usize);
let store = self.clone();
let opts = o.clone();
let rx1 = rx.resubscribe();
tokio::spawn(async move {
if let Err(err) = store.list_merged(rx1, opts, sender).await {
error!("list_merged err {:?}", err);
}
});
let rx2 = rx.resubscribe();
let res = gather_results(rx2, &o, &mut recv).await?;
tx.send(true)?;
// TODO: recv list_merged err
Ok(MetaCacheEntriesSorted {
o: MetaCacheEntries(res.into_iter().map(Some).collect()),
})
// let mut opts = opts.clone();
@@ -247,20 +337,57 @@ impl ECStore {
}
}
let merge_res = merge_entry_channels(rx, inputs, sender.clone(), 1).await;
tokio::spawn(async move {
if let Err(err) = merge_entry_channels(rx, inputs, sender.clone(), 1).await {
println!("merge_entry_channels err {:?}", err)
}
});
// let merge_res = merge_entry_channels(rx, inputs, sender.clone(), 1).await;
let results = join_all(futures).await;
let mut all_at_eof = true;
let mut errs = Vec::new();
for result in results {
if let Err(err) = result {
all_at_eof = false;
errs.push(Some(err));
} else {
errs.push(None);
}
}
unimplemented!()
if is_all_not_found(&errs) {
if is_all_volume_not_found(&errs) {
return Err(Error::new(DiskError::VolumeNotFound));
}
return Ok(Vec::new());
}
// merge_res?;
// TODO check cancel
for err in errs.iter() {
if let Some(err) = err {
if is_err_eof(err) {
continue;
}
return Err(err.clone());
} else {
all_at_eof = false;
continue;
}
}
// check all_at_eof
_ = all_at_eof;
Ok(Vec::new())
// // let mut errs = Vec::new();
// let mut ress = Vec::new();
@@ -320,6 +447,42 @@ impl ECStore {
}
}
// TODO: FIXME: 异步
async fn gather_results(
_rx: B_Receiver<bool>,
opts: &ListPathOptions,
recv: &mut Receiver<MetaCacheEntry>,
) -> Result<Vec<MetaCacheEntry>> {
let mut returned = false;
let mut results = Vec::new();
while let Some(entry) = recv.recv().await {
if returned {
continue;
}
// TODO: rx.recv()
// TODO: isLatestDeletemarker
if !opts.include_directories && (entry.is_dir() || (!opts.versioned && entry.is_object())) {
continue;
}
if !opts.marker.is_empty() && entry.name < opts.marker {
continue;
}
// TODO: other
if opts.limit > 0 && results.len() >= opts.limit as usize {
returned = true;
continue;
}
results.push(entry);
}
Ok(results)
}
async fn select_from(
in_channels: &mut [Receiver<MetaCacheEntry>],
idx: usize,
@@ -390,6 +553,8 @@ async fn merge_entry_channels(
if let Some(best_entry) = &best {
let other_idx = i;
// println!("get other_entry {:?}", other_entry.name);
if path::clean(&best_entry.name) == path::clean(&other_entry.name) {
let dir_matches = best_entry.is_dir() && other_entry.is_dir();
let suffix_matche =
@@ -426,6 +591,8 @@ async fn merge_entry_channels(
}
}
// println!("get best_entry {} {:?}", &best_idx, &best.clone().unwrap_or_default().name);
// TODO:
if !to_merge.is_empty() {
if let Some(entry) = &best {
@@ -667,10 +834,13 @@ mod test {
use crate::disk::MetaCacheEntries;
use crate::disk::MetaCacheEntry;
use crate::disk::WalkDirOptions;
use crate::endpoints::EndpointServerPools;
use crate::error::Error;
use crate::metacache::writer::MetacacheReader;
use crate::set_disk::SetDisks;
use crate::store::ECStore;
use crate::store_list_objects::ListPathOptions;
use crate::StorageAPI;
use futures::future::join_all;
use lock::namespace_lock::NsLockMap;
use tokio::sync::broadcast;
@@ -788,7 +958,7 @@ mod test {
}
#[tokio::test]
async fn test_list_path() {
async fn test_set_list_path() {
let mut ep = Endpoint::try_from("/Users/weisd/project/weisd/s3-rustfs/target/volume/test").unwrap();
ep.pool_idx = 0;
ep.set_idx = 0;
@@ -829,4 +999,81 @@ mod test {
println!("get entry {:?}", entry.name)
}
}
#[tokio::test]
async fn test_list_merged() {
let server_address = "localhost:9000";
let (endpoint_pools, _setup_type) = EndpointServerPools::from_volumes(
server_address,
vec!["/Users/weisd/project/weisd/s3-rustfs/target/volume/test".to_string()],
)
.unwrap();
let store = ECStore::new(server_address.to_string(), endpoint_pools.clone())
.await
.unwrap();
let (_tx, rx) = broadcast::channel(1);
let bucket = "dada".to_owned();
let opts = ListPathOptions {
bucket,
recursive: true,
..Default::default()
};
let (sender, mut recv) = mpsc::channel(10);
store.list_merged(rx, opts, sender).await.unwrap();
while let Some(entry) = recv.recv().await {
println!("get entry {:?}", entry.name)
}
}
#[tokio::test]
async fn test_list_path() {
let server_address = "localhost:9000";
let (endpoint_pools, _setup_type) = EndpointServerPools::from_volumes(
server_address,
vec!["/Users/weisd/project/weisd/s3-rustfs/target/volume/test".to_string()],
)
.unwrap();
let store = ECStore::new(server_address.to_string(), endpoint_pools.clone())
.await
.unwrap();
let bucket = "dada".to_owned();
let opts = ListPathOptions {
bucket,
recursive: true,
limit: 100,
..Default::default()
};
let ret = store.list_path(&opts).await.unwrap();
println!("ret {:?}", ret);
}
// #[tokio::test]
// async fn test_list_objects_v2() {
// let server_address = "localhost:9000";
// let (endpoint_pools, _setup_type) = EndpointServerPools::from_volumes(
// server_address,
// vec!["/Users/weisd/project/weisd/s3-rustfs/target/volume/test".to_string()],
// )
// .unwrap();
// let store = ECStore::new(server_address.to_string(), endpoint_pools.clone())
// .await
// .unwrap();
// let ret = store.list_objects_v2("data", "", "", "", 100, false, "").await.unwrap();
// println!("ret {:?}", ret);
// }
}
+16 -13
View File
@@ -6,6 +6,7 @@ use ecstore::{
store_api::{HTTPRangeSpec, ObjectIO, ObjectInfo, ObjectOptions, PutObjReader},
store_list_objects::ListPathOptions,
utils::path::dir,
StorageAPI,
};
use futures::future::try_join_all;
use log::{debug, warn};
@@ -41,29 +42,31 @@ impl ObjectStore {
for item in items {
let prefix = format!("{}{}", prefix, item);
futures.push(async move {
// let items = self
// .object_api
// .clone()
// .list_path(&ListPathOptions {
// bucket: Self::BUCKET_NAME.into(),
// prefix: prefix.clone(),
// ..Default::default()
// })
// .await;
let items = self
.object_api
.list_path(&ListPathOptions {
bucket: Self::BUCKET_NAME.into(),
prefix: prefix.clone(),
..Default::default()
})
.clone()
.list_objects_v2(Self::BUCKET_NAME.into(), &prefix.clone(), "", "", 0, false, "")
.await;
match items {
Ok(items) => Result::<_, crate::Error>::Ok(items.objects),
Ok(items) => Result::<_, crate::Error>::Ok(items.prefixes),
Err(e) if is_not_found(&e) => Result::<_, crate::Error>::Ok(vec![]),
Err(e) => Err(Error::StringError(format!("list {prefix} failed, err: {e:?}"))),
}
});
}
Ok(try_join_all(futures)
.await?
.into_iter()
.flat_map(|x| x.into_iter())
.map(|x| x.name)
.collect())
// TODO: FIXME:
Ok(try_join_all(futures).await?.into_iter().flat_map(|x| x.into_iter()).collect())
}
async fn load_policy(&self, name: &str) -> crate::Result<PolicyDoc> {
+9 -3
View File
@@ -460,8 +460,6 @@ impl S3 for FS {
#[tracing::instrument(level = "debug", skip(self, req))]
async fn list_objects_v2(&self, req: S3Request<ListObjectsV2Input>) -> S3Result<S3Response<ListObjectsV2Output>> {
// warn!("list_objects_v2 input {:?}", &req.input);
let ListObjectsV2Input {
bucket,
continuation_token,
@@ -475,6 +473,7 @@ impl S3 for FS {
let prefix = prefix.unwrap_or_default();
let delimiter = delimiter.unwrap_or_default();
let max_keys = max_keys.unwrap_or(1000);
let Some(store) = new_object_layer_fn() else {
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
@@ -486,7 +485,7 @@ impl S3 for FS {
&prefix,
&continuation_token.unwrap_or_default(),
&delimiter,
max_keys.unwrap_or_default(),
max_keys,
fetch_owner.unwrap_or_default(),
&start_after.unwrap_or_default(),
)
@@ -519,6 +518,12 @@ impl S3 for FS {
let key_count = objects.len() as i32;
let common_prefixes = object_infos
.prefixes
.into_iter()
.map(|v| CommonPrefix { prefix: Some(v) })
.collect();
let output = ListObjectsV2Output {
key_count: Some(key_count),
max_keys: Some(key_count),
@@ -526,6 +531,7 @@ impl S3 for FS {
delimiter: Some(delimiter),
name: Some(bucket),
prefix: Some(prefix),
common_prefixes: Some(common_prefixes),
..Default::default()
};