mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-29 16:37:07 +00:00
fix(ecstore): handle metadata-less bucket residue (#6591)
* fix(ecstore): handle metadata-less bucket residue Diagnose metadata-less on-disk residue before non-force DeleteBucket reaches physical deletion, and keep scanner-discovered metadata-missing objects on a non-destructive heal path. Add explicit heal --remove cleanup for unversioned metadata-less data directories, using the existing data-dir delete primitive and fail-closed shape checks so pre-commit or unknown residue is preserved. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(connect): adapt offline array validator Wrap the filesystem summary validator in a closure so Option::is_some_and can pass the concrete array reference accepted by serde_json::Value::as_array. Co-Authored-By: heihutu <heihutu@gmail.com> * fix(connect): remove redundant offline test clones Move the temporary path into the swap closure after deriving the output path, keeping clippy's redundant-clone lint clean for offline bundle tests. Co-Authored-By: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -335,7 +335,7 @@ fn valid_payload(collector: OfflineCollector, value: &serde_json::Value) -> bool
|
||||
&& object.get("totalBytes").and_then(serde_json::Value::as_u64).is_some()
|
||||
&& object.get("underPressure").and_then(serde_json::Value::as_bool).is_some()
|
||||
}),
|
||||
OfflineCollector::FilesystemSummary => value.as_array().is_some_and(ordered_strings),
|
||||
OfflineCollector::FilesystemSummary => value.as_array().is_some_and(|values| ordered_strings(values)),
|
||||
OfflineCollector::NetworkSummary => value.as_object().is_some_and(|object| {
|
||||
object.len() == 2
|
||||
&& object.get("bondCount").and_then(serde_json::Value::as_u64).is_some()
|
||||
@@ -902,7 +902,7 @@ mod tests {
|
||||
let moved = temp.path().join("moved");
|
||||
fs::create_dir(&original).expect("original output directory");
|
||||
let swapped_output = original.join("bundle.zip");
|
||||
let swap_original = original.clone();
|
||||
let swap_original = original;
|
||||
let swap_moved = moved.clone();
|
||||
test_support::set(
|
||||
Stage::BeforePublish,
|
||||
@@ -933,7 +933,7 @@ mod tests {
|
||||
let moved = temp.path().join("publish-moved");
|
||||
fs::create_dir(&original).expect("original publish directory");
|
||||
let swapped_output = original.join("bundle.zip");
|
||||
let swap_original = original.clone();
|
||||
let swap_original = original;
|
||||
let swap_moved = moved.clone();
|
||||
test_support::set(
|
||||
Stage::Rename,
|
||||
|
||||
+8
-1
@@ -307,7 +307,7 @@ impl From<StorageError> for ApiError {
|
||||
StorageError::InvalidArgument(_, _, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::MethodNotAllowed => S3ErrorCode::MethodNotAllowed,
|
||||
StorageError::BucketNotFound(_) => S3ErrorCode::NoSuchBucket,
|
||||
StorageError::BucketNotEmpty(_) => S3ErrorCode::BucketNotEmpty,
|
||||
StorageError::BucketNotEmpty(_) | StorageError::BucketNotEmptyWithDetails { .. } => S3ErrorCode::BucketNotEmpty,
|
||||
StorageError::BucketNameInvalid(_) => S3ErrorCode::InvalidBucketName,
|
||||
StorageError::ObjectNameInvalid(_, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::BucketExists(_) => S3ErrorCode::BucketAlreadyOwnedByYou,
|
||||
@@ -706,6 +706,13 @@ mod tests {
|
||||
(StorageError::MethodNotAllowed, S3ErrorCode::MethodNotAllowed),
|
||||
(StorageError::BucketNotFound("test".into()), S3ErrorCode::NoSuchBucket),
|
||||
(StorageError::BucketNotEmpty("test".into()), S3ErrorCode::BucketNotEmpty),
|
||||
(
|
||||
StorageError::BucketNotEmptyWithDetails {
|
||||
bucket: "test".into(),
|
||||
details: "metadata-less residue".into(),
|
||||
},
|
||||
S3ErrorCode::BucketNotEmpty,
|
||||
),
|
||||
(StorageError::BucketNameInvalid("test".into()), S3ErrorCode::InvalidBucketName),
|
||||
(
|
||||
StorageError::ObjectNameInvalid("test".into(), "test".into()),
|
||||
|
||||
Reference in New Issue
Block a user