mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-13 16:46:55 +00:00
merge main
This commit is contained in:
@@ -174,9 +174,9 @@ async fn license_handler() -> impl IntoResponse {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn _is_private_ip(ip: std::net::IpAddr) -> bool {
|
||||
fn _is_private_ip(ip: IpAddr) -> bool {
|
||||
match ip {
|
||||
std::net::IpAddr::V4(ip) => {
|
||||
IpAddr::V4(ip) => {
|
||||
let octets = ip.octets();
|
||||
// 10.0.0.0/8
|
||||
octets[0] == 10 ||
|
||||
@@ -185,7 +185,7 @@ fn _is_private_ip(ip: std::net::IpAddr) -> bool {
|
||||
// 192.168.0.0/16
|
||||
(octets[0] == 192 && octets[1] == 168)
|
||||
}
|
||||
std::net::IpAddr::V6(_) => false,
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ async fn start_server(server_addr: SocketAddr, tls_path: Option<String>, app: Ro
|
||||
.handle(handle.clone())
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.map_err(|e| io::Error::other(e))?;
|
||||
.map_err(io::Error::other)?;
|
||||
|
||||
info!("HTTPS server running on https://{}", server_addr);
|
||||
|
||||
|
||||
@@ -2420,6 +2420,7 @@ impl Node for NodeService {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(unused_imports)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use protos::proto_gen::node_service::{
|
||||
|
||||
@@ -34,7 +34,7 @@ pub fn get_license() -> Option<Token> {
|
||||
#[allow(unreachable_code)]
|
||||
pub fn license_check() -> Result<()> {
|
||||
return Ok(());
|
||||
let inval_license = LICENSE.get().map(|token| {
|
||||
let invalid_license = LICENSE.get().map(|token| {
|
||||
if token.expired < SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() {
|
||||
error!("License expired");
|
||||
return Err(Error::from_string("Incorrect license, please contact RustFS.".to_string()));
|
||||
@@ -43,7 +43,7 @@ pub fn license_check() -> Result<()> {
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// let inval_license = config::get_config().license.as_ref().map(|license| {
|
||||
// let invalid_license = config::get_config().license.as_ref().map(|license| {
|
||||
// if license.is_empty() {
|
||||
// error!("License is empty");
|
||||
// return Err(Error::from_string("Incorrect license, please contact RustFS.".to_string()));
|
||||
@@ -58,7 +58,7 @@ pub fn license_check() -> Result<()> {
|
||||
// Ok(())
|
||||
// });
|
||||
|
||||
if inval_license.is_none() || inval_license.is_some_and(|v| v.is_err()) {
|
||||
if invalid_license.is_none() || invalid_license.is_some_and(|v| v.is_err()) {
|
||||
return Err(Error::from_string("Incorrect license, please contact RustFS.".to_string()));
|
||||
}
|
||||
|
||||
|
||||
+28
-2
@@ -72,6 +72,7 @@ const MI_B: usize = 1024 * 1024;
|
||||
#[global_allocator]
|
||||
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn check_auth(req: Request<()>) -> Result<Request<()>, Status> {
|
||||
let token: MetadataValue<_> = "rustfs rpc".parse().unwrap();
|
||||
|
||||
@@ -80,6 +81,7 @@ fn check_auth(req: Request<()>) -> Result<Request<()>, Status> {
|
||||
_ => Err(Status::unauthenticated("No valid auth token")),
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
fn print_server_info() {
|
||||
let cfg = CONSOLE_CONFIG.get().unwrap();
|
||||
@@ -452,7 +454,8 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
let conn = http_server_clone.serve_connection(TokioIo::new(tls_socket), value_clone);
|
||||
let conn = graceful_clone.watch(conn);
|
||||
if let Err(err) = conn.await {
|
||||
error!("Https Connection error: {}", err);
|
||||
// Handle hyper::Error and low-level IO errors at a more granular level
|
||||
handle_connection_error(&*err);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -467,7 +470,8 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
let conn = http_server_clone.serve_connection(TokioIo::new(socket), value_clone);
|
||||
let conn = graceful_clone.watch(conn);
|
||||
if let Err(err) = conn.await {
|
||||
error!("Http Connection error: {}", err);
|
||||
// Handle hyper::Error and low-level IO errors at a more granular level
|
||||
handle_connection_error(&*err);
|
||||
}
|
||||
});
|
||||
debug!("Http handshake success");
|
||||
@@ -591,3 +595,25 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
info!("server is stopped state: {:?}", state_manager.current_state());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connection_error(err: &(dyn std::error::Error + 'static)) {
|
||||
if let Some(hyper_err) = err.downcast_ref::<hyper::Error>() {
|
||||
if hyper_err.is_incomplete_message() {
|
||||
warn!("The HTTP connection is closed prematurely and the message is not completed:{}", hyper_err);
|
||||
} else if hyper_err.is_closed() {
|
||||
warn!("The HTTP connection is closed:{}", hyper_err);
|
||||
} else if hyper_err.is_parse() {
|
||||
error!("HTTP message parsing failed:{}", hyper_err);
|
||||
} else if hyper_err.is_user() {
|
||||
error!("HTTP user-custom error:{}", hyper_err);
|
||||
} else if hyper_err.is_canceled() {
|
||||
warn!("The HTTP connection is canceled:{}", hyper_err);
|
||||
} else {
|
||||
error!("Unknown hyper error:{:?}", hyper_err);
|
||||
}
|
||||
} else if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
|
||||
error!("Unknown connection IO error:{}", io_err);
|
||||
} else {
|
||||
error!("Unknown connection error type:{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,3 +87,403 @@ pub fn to_s3_error(err: Error) -> S3Error {
|
||||
|
||||
S3Error::with_message(S3ErrorCode::InternalError, format!(" ec err {}", err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use s3s::S3ErrorCode;
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_not_implemented() {
|
||||
let storage_err = StorageError::NotImplemented;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NotImplemented);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_argument() {
|
||||
let storage_err =
|
||||
StorageError::InvalidArgument("test-bucket".to_string(), "test-object".to_string(), "test-version".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Invalid arguments provided"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("test-version"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_method_not_allowed() {
|
||||
let storage_err = StorageError::MethodNotAllowed;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::MethodNotAllowed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_bucket_not_found() {
|
||||
let storage_err = StorageError::BucketNotFound("test-bucket".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_bucket_not_empty() {
|
||||
let storage_err = StorageError::BucketNotEmpty("test-bucket".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::BucketNotEmpty);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not empty"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_bucket_name_invalid() {
|
||||
let storage_err = StorageError::BucketNameInvalid("invalid-bucket-name".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidBucketName);
|
||||
assert!(s3_err.message().unwrap().contains("invalid bucket name"));
|
||||
assert!(s3_err.message().unwrap().contains("invalid-bucket-name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_name_invalid() {
|
||||
let storage_err = StorageError::ObjectNameInvalid("test-bucket".to_string(), "invalid-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("invalid object name"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("invalid-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_bucket_exists() {
|
||||
let storage_err = StorageError::BucketExists("existing-bucket".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::BucketAlreadyExists);
|
||||
assert!(s3_err.message().unwrap().contains("existing-bucket"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_storage_full() {
|
||||
let storage_err = StorageError::StorageFull;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::ServiceUnavailable);
|
||||
assert!(s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage reached its minimum free drive threshold"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_slow_down() {
|
||||
let storage_err = StorageError::SlowDown;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
assert!(s3_err.message().unwrap().contains("Please reduce your request rate"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_prefix_access_denied() {
|
||||
let storage_err = StorageError::PrefixAccessDenied("test-bucket".to_string(), "test-prefix".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::AccessDenied);
|
||||
assert!(s3_err.message().unwrap().contains("PrefixAccessDenied"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-prefix"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_upload_id_key_combination() {
|
||||
let storage_err = StorageError::InvalidUploadIDKeyCombination("test-bucket".to_string(), "test-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Invalid UploadID KeyCombination"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_malformed_upload_id() {
|
||||
let storage_err = StorageError::MalformedUploadID("malformed-id".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Malformed UploadID"));
|
||||
assert!(s3_err.message().unwrap().contains("malformed-id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_name_too_long() {
|
||||
let storage_err = StorageError::ObjectNameTooLong("test-bucket".to_string(), "very-long-object-name".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Object name too long"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("very-long-object-name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_name_prefix_as_slash() {
|
||||
let storage_err = StorageError::ObjectNamePrefixAsSlash("test-bucket".to_string(), "/invalid-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Object name contains forward slash as prefix"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("/invalid-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_not_found() {
|
||||
let storage_err = StorageError::ObjectNotFound("test-bucket".to_string(), "missing-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchKey);
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("missing-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_version_not_found() {
|
||||
let storage_err =
|
||||
StorageError::VersionNotFound("test-bucket".to_string(), "test-object".to_string(), "missing-version".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchVersion);
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("missing-version"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_upload_id() {
|
||||
let storage_err =
|
||||
StorageError::InvalidUploadID("test-bucket".to_string(), "test-object".to_string(), "invalid-upload-id".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidPart);
|
||||
assert!(s3_err.message().unwrap().contains("Invalid upload id"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("invalid-upload-id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_version_id() {
|
||||
let storage_err = StorageError::InvalidVersionID(
|
||||
"test-bucket".to_string(),
|
||||
"test-object".to_string(),
|
||||
"invalid-version-id".to_string(),
|
||||
);
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Invalid version id"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("invalid-version-id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_data_movement_overwrite_err() {
|
||||
let storage_err = StorageError::DataMovementOverwriteErr(
|
||||
"test-bucket".to_string(),
|
||||
"test-object".to_string(),
|
||||
"test-version".to_string(),
|
||||
);
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("invalid data movement operation"));
|
||||
assert!(s3_err.message().unwrap().contains("source and destination pool are the same"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("test-version"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_exists_as_directory() {
|
||||
let storage_err = StorageError::ObjectExistsAsDirectory("test-bucket".to_string(), "directory-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Object exists on"));
|
||||
assert!(s3_err.message().unwrap().contains("as directory"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("directory-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_insufficient_read_quorum() {
|
||||
let storage_err = StorageError::InsufficientReadQuorum;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
assert!(s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage resources are insufficient for the read operation"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_insufficient_write_quorum() {
|
||||
let storage_err = StorageError::InsufficientWriteQuorum;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
assert!(s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage resources are insufficient for the write operation"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_decommission_not_started() {
|
||||
let storage_err = StorageError::DecommissionNotStarted;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Decommission Not Started"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_decommission_already_running() {
|
||||
let storage_err = StorageError::DecommissionAlreadyRunning;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
assert!(s3_err.message().unwrap().contains("Decommission already running"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_volume_not_found() {
|
||||
let storage_err = StorageError::VolumeNotFound("test-volume".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
assert!(s3_err.message().unwrap().contains("test-volume"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_part() {
|
||||
let storage_err = StorageError::InvalidPart(1, "expected-part".to_string(), "got-part".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidPart);
|
||||
assert!(s3_err.message().unwrap().contains("Specified part could not be found"));
|
||||
assert!(s3_err.message().unwrap().contains("PartNumber"));
|
||||
assert!(s3_err.message().unwrap().contains("expected-part"));
|
||||
assert!(s3_err.message().unwrap().contains("got-part"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_done_for_now() {
|
||||
let storage_err = StorageError::DoneForNow;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
assert!(s3_err.message().unwrap().contains("DoneForNow"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_non_storage_error() {
|
||||
// Test with a non-StorageError
|
||||
let err = Error::from_string("Generic error message".to_string());
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
assert!(s3_err.message().unwrap().contains("ec err"));
|
||||
assert!(s3_err.message().unwrap().contains("Generic error message"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_with_unicode_strings() {
|
||||
let storage_err = StorageError::BucketNotFound("测试桶".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
assert!(s3_err.message().unwrap().contains("测试桶"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_with_special_characters() {
|
||||
let storage_err = StorageError::ObjectNameInvalid("bucket-with-@#$%".to_string(), "object-with-!@#$%^&*()".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("invalid object name"));
|
||||
assert!(s3_err.message().unwrap().contains("bucket-with-@#$%"));
|
||||
assert!(s3_err.message().unwrap().contains("object-with-!@#$%^&*()"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_with_empty_strings() {
|
||||
let storage_err = StorageError::BucketNotFound("".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_with_very_long_strings() {
|
||||
let long_bucket_name = "a".repeat(1000);
|
||||
let storage_err = StorageError::BucketNotFound(long_bucket_name.clone());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
assert!(s3_err.message().unwrap().contains(&long_bucket_name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,3 +241,428 @@ lazy_static! {
|
||||
"x-amz-replication-status"
|
||||
];
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn create_test_headers() -> HeaderMap<HeaderValue> {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("content-type", HeaderValue::from_static("application/json"));
|
||||
headers.insert("x-amz-meta-custom", HeaderValue::from_static("custom-value"));
|
||||
headers.insert("x-rustfs-meta-internal", HeaderValue::from_static("internal-value"));
|
||||
headers.insert("cache-control", HeaderValue::from_static("no-cache"));
|
||||
headers
|
||||
}
|
||||
|
||||
fn create_test_metadata() -> HashMap<String, String> {
|
||||
let mut metadata = HashMap::new();
|
||||
metadata.insert("key1".to_string(), "value1".to_string());
|
||||
metadata.insert("key2".to_string(), "value2".to_string());
|
||||
metadata
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_del_opts_basic() {
|
||||
let headers = create_test_headers();
|
||||
let metadata = Some(create_test_metadata());
|
||||
|
||||
let result = del_opts("test-bucket", "test-object", None, &headers, metadata).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert!(opts.user_defined.is_some());
|
||||
assert_eq!(opts.version_id, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_del_opts_with_directory_object() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
let result = del_opts("test-bucket", "test-dir/", None, &headers, None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert_eq!(opts.version_id, Some(Uuid::nil().to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_del_opts_with_valid_version_id() {
|
||||
let headers = create_test_headers();
|
||||
let valid_uuid = Uuid::new_v4().to_string();
|
||||
|
||||
let result = del_opts("test-bucket", "test-object", Some(valid_uuid.clone()), &headers, None).await;
|
||||
|
||||
// This test may fail if versioning is not enabled for the bucket
|
||||
// In a real test environment, you would mock BucketVersioningSys
|
||||
match result {
|
||||
Ok(opts) => {
|
||||
assert_eq!(opts.version_id, Some(valid_uuid));
|
||||
}
|
||||
Err(_) => {
|
||||
// Expected if versioning is not enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_del_opts_with_invalid_version_id() {
|
||||
let headers = create_test_headers();
|
||||
let invalid_uuid = "invalid-uuid".to_string();
|
||||
|
||||
let result = del_opts("test-bucket", "test-object", Some(invalid_uuid), &headers, None).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(err) = result {
|
||||
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
match storage_err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_opts_basic() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
let result = get_opts("test-bucket", "test-object", None, None, &headers).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert_eq!(opts.part_number, None);
|
||||
assert_eq!(opts.version_id, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_opts_with_part_number() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
let result = get_opts("test-bucket", "test-object", None, Some(5), &headers).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert_eq!(opts.part_number, Some(5));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_opts_with_directory_object() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
let result = get_opts("test-bucket", "test-dir/", None, None, &headers).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert_eq!(opts.version_id, Some(Uuid::nil().to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_opts_with_invalid_version_id() {
|
||||
let headers = create_test_headers();
|
||||
let invalid_uuid = "invalid-uuid".to_string();
|
||||
|
||||
let result = get_opts("test-bucket", "test-object", Some(invalid_uuid), None, &headers).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(err) = result {
|
||||
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
match storage_err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put_opts_basic() {
|
||||
let headers = create_test_headers();
|
||||
let metadata = Some(create_test_metadata());
|
||||
|
||||
let result = put_opts("test-bucket", "test-object", None, &headers, metadata).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert!(opts.user_defined.is_some());
|
||||
assert_eq!(opts.version_id, None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put_opts_with_directory_object() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
let result = put_opts("test-bucket", "test-dir/", None, &headers, None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert_eq!(opts.version_id, Some(Uuid::nil().to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put_opts_with_invalid_version_id() {
|
||||
let headers = create_test_headers();
|
||||
let invalid_uuid = "invalid-uuid".to_string();
|
||||
|
||||
let result = put_opts("test-bucket", "test-object", Some(invalid_uuid), &headers, None).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(err) = result {
|
||||
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
match storage_err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_copy_dst_opts() {
|
||||
let headers = create_test_headers();
|
||||
let metadata = Some(create_test_metadata());
|
||||
|
||||
let result = copy_dst_opts("test-bucket", "test-object", None, &headers, metadata).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert!(opts.user_defined.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_src_opts() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
let result = copy_src_opts("test-bucket", "test-object", &headers);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert!(opts.user_defined.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_put_opts_from_headers() {
|
||||
let headers = create_test_headers();
|
||||
let metadata = Some(create_test_metadata());
|
||||
|
||||
let result = put_opts_from_headers(&headers, metadata);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert!(opts.user_defined.is_some());
|
||||
let user_defined = opts.user_defined.unwrap();
|
||||
assert_eq!(user_defined.get("key1"), Some(&"value1".to_string()));
|
||||
assert_eq!(user_defined.get("key2"), Some(&"value2".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_opts_with_metadata() {
|
||||
let headers = create_test_headers();
|
||||
let metadata = Some(create_test_metadata());
|
||||
|
||||
let result = get_default_opts(&headers, metadata, false);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert!(opts.user_defined.is_some());
|
||||
let user_defined = opts.user_defined.unwrap();
|
||||
assert_eq!(user_defined.get("key1"), Some(&"value1".to_string()));
|
||||
assert_eq!(user_defined.get("key2"), Some(&"value2".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_opts_without_metadata() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
let result = get_default_opts(&headers, None, false);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let opts = result.unwrap();
|
||||
assert!(opts.user_defined.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_basic() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
let metadata = extract_metadata(&headers);
|
||||
|
||||
assert!(metadata.contains_key("content-type"));
|
||||
assert_eq!(metadata.get("content-type"), Some(&"application/json".to_string()));
|
||||
assert!(metadata.contains_key("cache-control"));
|
||||
assert_eq!(metadata.get("cache-control"), Some(&"no-cache".to_string()));
|
||||
assert!(metadata.contains_key("custom"));
|
||||
assert_eq!(metadata.get("custom"), Some(&"custom-value".to_string()));
|
||||
assert!(metadata.contains_key("internal"));
|
||||
assert_eq!(metadata.get("internal"), Some(&"internal-value".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_from_mime_amz_meta() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("x-amz-meta-user-id", HeaderValue::from_static("12345"));
|
||||
headers.insert("x-amz-meta-project", HeaderValue::from_static("test-project"));
|
||||
headers.insert("x-amz-meta-", HeaderValue::from_static("empty-key")); // Should be ignored
|
||||
|
||||
let mut metadata = HashMap::new();
|
||||
extract_metadata_from_mime(&headers, &mut metadata);
|
||||
|
||||
assert_eq!(metadata.get("user-id"), Some(&"12345".to_string()));
|
||||
assert_eq!(metadata.get("project"), Some(&"test-project".to_string()));
|
||||
assert!(!metadata.contains_key(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_from_mime_rustfs_meta() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("x-rustfs-meta-internal-id", HeaderValue::from_static("67890"));
|
||||
headers.insert("x-rustfs-meta-category", HeaderValue::from_static("documents"));
|
||||
|
||||
let mut metadata = HashMap::new();
|
||||
extract_metadata_from_mime(&headers, &mut metadata);
|
||||
|
||||
assert_eq!(metadata.get("internal-id"), Some(&"67890".to_string()));
|
||||
assert_eq!(metadata.get("category"), Some(&"documents".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_from_mime_supported_headers() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("content-type", HeaderValue::from_static("text/plain"));
|
||||
headers.insert("cache-control", HeaderValue::from_static("max-age=3600"));
|
||||
headers.insert("content-language", HeaderValue::from_static("en-US"));
|
||||
headers.insert("content-encoding", HeaderValue::from_static("gzip"));
|
||||
headers.insert("content-disposition", HeaderValue::from_static("attachment"));
|
||||
headers.insert("x-amz-storage-class", HeaderValue::from_static("STANDARD"));
|
||||
headers.insert("x-amz-tagging", HeaderValue::from_static("key1=value1&key2=value2"));
|
||||
headers.insert("expires", HeaderValue::from_static("Wed, 21 Oct 2015 07:28:00 GMT"));
|
||||
headers.insert("x-amz-replication-status", HeaderValue::from_static("COMPLETED"));
|
||||
|
||||
let mut metadata = HashMap::new();
|
||||
extract_metadata_from_mime(&headers, &mut metadata);
|
||||
|
||||
assert_eq!(metadata.get("content-type"), Some(&"text/plain".to_string()));
|
||||
assert_eq!(metadata.get("cache-control"), Some(&"max-age=3600".to_string()));
|
||||
assert_eq!(metadata.get("content-language"), Some(&"en-US".to_string()));
|
||||
assert_eq!(metadata.get("content-encoding"), Some(&"gzip".to_string()));
|
||||
assert_eq!(metadata.get("content-disposition"), Some(&"attachment".to_string()));
|
||||
assert_eq!(metadata.get("x-amz-storage-class"), Some(&"STANDARD".to_string()));
|
||||
assert_eq!(metadata.get("x-amz-tagging"), Some(&"key1=value1&key2=value2".to_string()));
|
||||
assert_eq!(metadata.get("expires"), Some(&"Wed, 21 Oct 2015 07:28:00 GMT".to_string()));
|
||||
assert_eq!(metadata.get("x-amz-replication-status"), Some(&"COMPLETED".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_from_mime_default_content_type() {
|
||||
let headers = HeaderMap::new();
|
||||
|
||||
let mut metadata = HashMap::new();
|
||||
extract_metadata_from_mime(&headers, &mut metadata);
|
||||
|
||||
assert_eq!(metadata.get("content-type"), Some(&"binary/octet-stream".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_from_mime_existing_content_type() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("content-type", HeaderValue::from_static("application/json"));
|
||||
|
||||
let mut metadata = HashMap::new();
|
||||
extract_metadata_from_mime(&headers, &mut metadata);
|
||||
|
||||
assert_eq!(metadata.get("content-type"), Some(&"application/json".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_from_mime_unicode_values() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("x-amz-meta-chinese", HeaderValue::from_bytes("测试值".as_bytes()).unwrap());
|
||||
headers.insert("x-rustfs-meta-emoji", HeaderValue::from_bytes("🚀".as_bytes()).unwrap());
|
||||
|
||||
let mut metadata = HashMap::new();
|
||||
extract_metadata_from_mime(&headers, &mut metadata);
|
||||
|
||||
assert_eq!(metadata.get("chinese"), Some(&"测试值".to_string()));
|
||||
assert_eq!(metadata.get("emoji"), Some(&"🚀".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_from_mime_unsupported_headers() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("authorization", HeaderValue::from_static("Bearer token"));
|
||||
headers.insert("host", HeaderValue::from_static("example.com"));
|
||||
headers.insert("user-agent", HeaderValue::from_static("test-agent"));
|
||||
|
||||
let mut metadata = HashMap::new();
|
||||
extract_metadata_from_mime(&headers, &mut metadata);
|
||||
|
||||
// These headers should not be included in metadata
|
||||
assert!(!metadata.contains_key("authorization"));
|
||||
assert!(!metadata.contains_key("host"));
|
||||
assert!(!metadata.contains_key("user-agent"));
|
||||
// But default content-type should be added
|
||||
assert_eq!(metadata.get("content-type"), Some(&"binary/octet-stream".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_supported_headers_constant() {
|
||||
let expected_headers = vec![
|
||||
"content-type",
|
||||
"cache-control",
|
||||
"content-language",
|
||||
"content-encoding",
|
||||
"content-disposition",
|
||||
"x-amz-storage-class",
|
||||
"x-amz-tagging",
|
||||
"expires",
|
||||
"x-amz-replication-status",
|
||||
];
|
||||
|
||||
assert_eq!(*SUPPORTED_HEADERS, expected_headers);
|
||||
assert_eq!(SUPPORTED_HEADERS.len(), 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_empty_headers() {
|
||||
let headers = HeaderMap::new();
|
||||
|
||||
let metadata = extract_metadata(&headers);
|
||||
|
||||
// Should only contain default content-type
|
||||
assert_eq!(metadata.len(), 1);
|
||||
assert_eq!(metadata.get("content-type"), Some(&"binary/octet-stream".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_metadata_mixed_headers() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert("content-type", HeaderValue::from_static("application/xml"));
|
||||
headers.insert("x-amz-meta-version", HeaderValue::from_static("1.0"));
|
||||
headers.insert("x-rustfs-meta-source", HeaderValue::from_static("upload"));
|
||||
headers.insert("cache-control", HeaderValue::from_static("public"));
|
||||
headers.insert("authorization", HeaderValue::from_static("Bearer xyz")); // Should be ignored
|
||||
|
||||
let metadata = extract_metadata(&headers);
|
||||
|
||||
assert_eq!(metadata.get("content-type"), Some(&"application/xml".to_string()));
|
||||
assert_eq!(metadata.get("version"), Some(&"1.0".to_string()));
|
||||
assert_eq!(metadata.get("source"), Some(&"upload".to_string()));
|
||||
assert_eq!(metadata.get("cache-control"), Some(&"public".to_string()));
|
||||
assert!(!metadata.contains_key("authorization"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user