fix: s3 api compatibility (#1370)

Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
安正超
2026-01-05 16:54:16 +08:00
committed by GitHub
parent ab752458ce
commit 60103f0f72
6 changed files with 281 additions and 35 deletions
+68 -6
View File
@@ -34,8 +34,8 @@ use crate::disk::endpoint::{Endpoint, EndpointType};
use crate::disk::{DiskAPI, DiskInfo, DiskInfoOptions};
use crate::error::{Error, Result};
use crate::error::{
StorageError, is_err_bucket_exists, is_err_invalid_upload_id, is_err_object_not_found, is_err_read_quorum,
is_err_version_not_found, to_object_err,
StorageError, is_err_bucket_exists, is_err_bucket_not_found, is_err_invalid_upload_id, is_err_object_not_found,
is_err_read_quorum, is_err_version_not_found, to_object_err,
};
use crate::global::{
DISK_ASSUME_UNKNOWN_SIZE, DISK_FILL_FRACTION, DISK_MIN_INODES, DISK_RESERVE_FRACTION, GLOBAL_BOOT_TIME,
@@ -86,6 +86,46 @@ use tokio_util::sync::CancellationToken;
use tracing::{debug, error, info, instrument, warn};
use uuid::Uuid;
/// Check if a directory contains any xl.meta files (indicating actual S3 objects)
/// This is used to determine if a bucket is empty for deletion purposes.
async fn has_xlmeta_files(path: &std::path::Path) -> bool {
use crate::disk::STORAGE_FORMAT_FILE;
use tokio::fs;
let mut stack = vec![path.to_path_buf()];
while let Some(current_path) = stack.pop() {
let mut entries = match fs::read_dir(&current_path).await {
Ok(entries) => entries,
Err(_) => continue,
};
while let Ok(Some(entry)) = entries.next_entry().await {
let file_name = entry.file_name();
let file_name_str = file_name.to_string_lossy();
// Skip hidden files/directories (like .rustfs.sys)
if file_name_str.starts_with('.') {
continue;
}
// Check if this is an xl.meta file
if file_name_str == STORAGE_FORMAT_FILE {
return true;
}
// If it's a directory, add to stack for further exploration
if let Ok(file_type) = entry.file_type().await
&& file_type.is_dir()
{
stack.push(entry.path());
}
}
}
false
}
const MAX_UPLOADS_LIST: usize = 10000;
#[derive(Debug)]
@@ -1323,14 +1363,36 @@ impl StorageAPI for ECStore {
// TODO: nslock
let mut opts = opts.clone();
// Check bucket exists before deletion (per S3 API spec)
// If bucket doesn't exist, return NoSuchBucket error
if let Err(err) = self.peer_sys.get_bucket_info(bucket, &BucketOptions::default()).await {
// Convert DiskError to StorageError for comparison
let storage_err: StorageError = err.into();
if is_err_bucket_not_found(&storage_err) {
return Err(StorageError::BucketNotFound(bucket.to_string()));
}
return Err(to_object_err(storage_err, vec![bucket]));
}
// Check bucket is empty before deletion (per S3 API spec)
// If bucket is not empty (contains actual objects with xl.meta files) and force
// is not set, return BucketNotEmpty error.
// Note: Empty directories (left after object deletion) should NOT count as objects.
if !opts.force {
// FIXME: check bucket exists
opts.force = true
let local_disks = all_local_disk().await;
for disk in local_disks.iter() {
// Check if bucket directory contains any xl.meta files (actual objects)
// We recursively scan for xl.meta files to determine if bucket has objects
// Use the disk's root path to construct bucket path
let bucket_path = disk.path().join(bucket);
if has_xlmeta_files(&bucket_path).await {
return Err(StorageError::BucketNotEmpty(bucket.to_string()));
}
}
}
self.peer_sys
.delete_bucket(bucket, &opts)
.delete_bucket(bucket, opts)
.await
.map_err(|e| to_object_err(e.into(), vec![bucket]))?;
+16 -1
View File
@@ -741,7 +741,21 @@ impl ObjectInfo {
let inlined = fi.inline_data();
// TODO:expires
// Parse expires from metadata (HTTP date format RFC 7231 or ISO 8601)
let expires = fi.metadata.get("expires").and_then(|s| {
// Try parsing as ISO 8601 first
time::OffsetDateTime::parse(s, &time::format_description::well_known::Iso8601::DEFAULT)
.or_else(|_| {
// Try RFC 2822 format
time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc2822)
})
.or_else(|_| {
// Try RFC 3339 format
time::OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339)
})
.ok()
});
// TODO:ReplicationState
let transitioned_object = TransitionedObject {
@@ -799,6 +813,7 @@ impl ObjectInfo {
user_tags,
content_type,
content_encoding,
expires,
num_versions: fi.num_versions,
successor_mod_time: fi.successor_mod_time,
etag,