mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-28 17:18:58 +00:00
Merge branch 'main' of github.com:rustfs/s3-rustfs into feature/observability-metrics
# Conflicts: # .github/workflows/build.yml # .github/workflows/ci.yml # Cargo.lock # Cargo.toml # appauth/src/token.rs # crates/config/src/config.rs # crates/event-notifier/examples/simple.rs # crates/event-notifier/src/global.rs # crates/event-notifier/src/lib.rs # crates/event-notifier/src/notifier.rs # crates/event-notifier/src/store.rs # crates/filemeta/src/filemeta.rs # crates/notify/examples/webhook.rs # crates/utils/Cargo.toml # ecstore/Cargo.toml # ecstore/src/cmd/bucket_replication.rs # ecstore/src/config/com.rs # ecstore/src/disk/error.rs # ecstore/src/disk/mod.rs # ecstore/src/set_disk.rs # ecstore/src/store_api.rs # ecstore/src/store_list_objects.rs # iam/Cargo.toml # iam/src/manager.rs # policy/Cargo.toml # rustfs/src/admin/rpc.rs # rustfs/src/main.rs # rustfs/src/storage/mod.rs
This commit is contained in:
@@ -93,6 +93,11 @@ tower-http = { workspace = true, features = [
|
||||
] }
|
||||
urlencoding = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
rustfs-filemeta.workspace = true
|
||||
rustfs-rio.workspace = true
|
||||
base64 = { workspace = true }
|
||||
hmac = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libsystemd.workspace = true
|
||||
|
||||
@@ -2,45 +2,44 @@ use super::router::Operation;
|
||||
use crate::auth::check_key_valid;
|
||||
use crate::auth::get_condition_values;
|
||||
use crate::auth::get_session_token;
|
||||
//use ecstore::error::Error as ec_Error;
|
||||
use crate::storage::error::to_s3_error;
|
||||
use crate::error::ApiError;
|
||||
use bytes::Bytes;
|
||||
use common::error::Error as ec_Error;
|
||||
use ecstore::admin_server_info::get_server_info;
|
||||
use ecstore::bucket::metadata_sys::{self, get_replication_config};
|
||||
use ecstore::bucket::target::BucketTarget;
|
||||
use ecstore::bucket::versioning_sys::BucketVersioningSys;
|
||||
use ecstore::cmd::bucket_targets::{self, GLOBAL_Bucket_Target_Sys};
|
||||
use ecstore::error::StorageError;
|
||||
use ecstore::global::GLOBAL_ALlHealState;
|
||||
use ecstore::heal::data_usage::load_data_usage_from_backend;
|
||||
use ecstore::heal::heal_commands::HealOpts;
|
||||
use ecstore::heal::heal_ops::new_heal_sequence;
|
||||
use ecstore::metrics_realtime::{collect_local_metrics, CollectMetricsOpts, MetricType};
|
||||
use ecstore::metrics_realtime::{CollectMetricsOpts, MetricType, collect_local_metrics};
|
||||
use ecstore::new_object_layer_fn;
|
||||
use ecstore::peer::is_reserved_or_invalid_bucket;
|
||||
use ecstore::pools::{get_total_usable_capacity, get_total_usable_capacity_free};
|
||||
use ecstore::store::is_valid_object_prefix;
|
||||
use ecstore::store_api::BucketOptions;
|
||||
use ecstore::store_api::StorageAPI;
|
||||
use ecstore::utils::path::path_join;
|
||||
use futures::{Stream, StreamExt};
|
||||
use http::{HeaderMap, Uri};
|
||||
use hyper::StatusCode;
|
||||
use iam::get_global_action_cred;
|
||||
use iam::store::MappedPolicy;
|
||||
use rustfs_utils::path::path_join;
|
||||
// use lazy_static::lazy_static;
|
||||
use madmin::metrics::RealtimeMetrics;
|
||||
use madmin::utils::parse_duration;
|
||||
use matchit::Params;
|
||||
use percent_encoding::{percent_encode, AsciiSet, CONTROLS};
|
||||
use percent_encoding::{AsciiSet, CONTROLS, percent_encode};
|
||||
use policy::policy::Args;
|
||||
use policy::policy::BucketPolicy;
|
||||
use policy::policy::action::Action;
|
||||
use policy::policy::action::S3Action;
|
||||
use policy::policy::default::DEFAULT_POLICIES;
|
||||
use policy::policy::Args;
|
||||
use policy::policy::BucketPolicy;
|
||||
use s3s::header::CONTENT_TYPE;
|
||||
use s3s::stream::{ByteStream, DynByteStream};
|
||||
use s3s::{s3_error, Body, S3Error, S3Request, S3Response, S3Result};
|
||||
use s3s::{Body, S3Error, S3Request, S3Response, S3Result, s3_error};
|
||||
use s3s::{S3ErrorCode, StdError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
// use serde_json::to_vec;
|
||||
@@ -666,7 +665,7 @@ impl Operation for HealHandler {
|
||||
#[derive(Default)]
|
||||
struct HealResp {
|
||||
resp_bytes: Vec<u8>,
|
||||
_api_err: Option<ec_Error>,
|
||||
_api_err: Option<StorageError>,
|
||||
_err_body: String,
|
||||
}
|
||||
|
||||
@@ -836,7 +835,7 @@ impl Operation for SetRemoteTargetHandler {
|
||||
}
|
||||
}
|
||||
|
||||
let mut remote_target: BucketTarget = serde_json::from_slice(&body).map_err(|arg0| to_s3_error(arg0.into()))?; // 错误会被传播
|
||||
let mut remote_target: BucketTarget = serde_json::from_slice(&body).map_err(ApiError::other)?; // 错误会被传播
|
||||
remote_target.source_bucket = bucket.clone();
|
||||
|
||||
info!("remote target {} And arn is:", remote_target.source_bucket.clone());
|
||||
|
||||
@@ -5,7 +5,7 @@ use iam::{
|
||||
};
|
||||
use madmin::GroupAddRemove;
|
||||
use matchit::Params;
|
||||
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
|
||||
use s3s::{Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error};
|
||||
use serde::Deserialize;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use tracing::warn;
|
||||
|
||||
@@ -3,7 +3,7 @@ use http::{HeaderMap, StatusCode};
|
||||
use iam::{error::is_err_no_such_user, get_global_action_cred, store::MappedPolicy};
|
||||
use matchit::Params;
|
||||
use policy::policy::Policy;
|
||||
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
|
||||
use s3s::{Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error};
|
||||
use serde::Deserialize;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use ecstore::{new_object_layer_fn, GLOBAL_Endpoints};
|
||||
use ecstore::{GLOBAL_Endpoints, new_object_layer_fn};
|
||||
use http::{HeaderMap, StatusCode};
|
||||
use matchit::Params;
|
||||
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
|
||||
use s3s::{Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error};
|
||||
use serde::Deserialize;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{admin::router::Operation, storage::error::to_s3_error};
|
||||
use crate::{admin::router::Operation, error::ApiError};
|
||||
|
||||
pub struct ListPools {}
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Operation for ListPools {
|
||||
let mut pools_status = Vec::new();
|
||||
|
||||
for (idx, _) in endpoints.as_ref().iter().enumerate() {
|
||||
let state = store.status(idx).await.map_err(to_s3_error)?;
|
||||
let state = store.status(idx).await.map_err(ApiError::from)?;
|
||||
|
||||
pools_status.push(state);
|
||||
}
|
||||
@@ -88,11 +88,7 @@ impl Operation for StatusPool {
|
||||
let has_idx = {
|
||||
if is_byid {
|
||||
let a = query.pool.parse::<usize>().unwrap_or_default();
|
||||
if a < endpoints.as_ref().len() {
|
||||
Some(a)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if a < endpoints.as_ref().len() { Some(a) } else { None }
|
||||
} else {
|
||||
endpoints.get_pool_idx(&query.pool)
|
||||
}
|
||||
@@ -107,7 +103,7 @@ impl Operation for StatusPool {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
};
|
||||
|
||||
let pools_status = store.status(idx).await.map_err(to_s3_error)?;
|
||||
let pools_status = store.status(idx).await.map_err(ApiError::from)?;
|
||||
|
||||
let data = serde_json::to_vec(&pools_status)
|
||||
.map_err(|_e| S3Error::with_message(S3ErrorCode::InternalError, "parse accountInfo failed"))?;
|
||||
@@ -195,7 +191,7 @@ impl Operation for StartDecommission {
|
||||
}
|
||||
|
||||
if !pools_indices.is_empty() {
|
||||
store.decommission(ctx_rx, pools_indices).await.map_err(to_s3_error)?;
|
||||
store.decommission(ctx_rx, pools_indices).await.map_err(ApiError::from)?;
|
||||
}
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, Body::default())))
|
||||
@@ -234,11 +230,7 @@ impl Operation for CancelDecommission {
|
||||
let has_idx = {
|
||||
if is_byid {
|
||||
let a = query.pool.parse::<usize>().unwrap_or_default();
|
||||
if a < endpoints.as_ref().len() {
|
||||
Some(a)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if a < endpoints.as_ref().len() { Some(a) } else { None }
|
||||
} else {
|
||||
endpoints.get_pool_idx(&query.pool)
|
||||
}
|
||||
@@ -253,7 +245,7 @@ impl Operation for CancelDecommission {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
};
|
||||
|
||||
store.decommission_cancel(idx).await.map_err(to_s3_error)?;
|
||||
store.decommission_cancel(idx).await.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, Body::default())))
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use ecstore::{
|
||||
config::error::is_err_config_not_found,
|
||||
StorageAPI,
|
||||
error::StorageError,
|
||||
new_object_layer_fn,
|
||||
notification_sys::get_global_notification_sys,
|
||||
rebalance::{DiskStat, RebalSaveOpt},
|
||||
store_api::BucketOptions,
|
||||
StorageAPI,
|
||||
};
|
||||
use http::{HeaderMap, StatusCode};
|
||||
use matchit::Params;
|
||||
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Request, S3Response, S3Result};
|
||||
use s3s::{Body, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::admin::router::Operation;
|
||||
@@ -56,8 +57,8 @@ pub struct RebalanceAdminStatus {
|
||||
pub id: String, // Identifies the ongoing rebalance operation by a UUID
|
||||
#[serde(rename = "pools")]
|
||||
pub pools: Vec<RebalancePoolStatus>, // Contains all pools, including inactive
|
||||
#[serde(rename = "stoppedAt")]
|
||||
pub stopped_at: Option<SystemTime>, // Optional timestamp when rebalance was stopped
|
||||
#[serde(rename = "stoppedAt", with = "offsetdatetime_rfc3339")]
|
||||
pub stopped_at: Option<OffsetDateTime>, // Optional timestamp when rebalance was stopped
|
||||
}
|
||||
|
||||
pub struct RebalanceStart {}
|
||||
@@ -101,11 +102,13 @@ impl Operation for RebalanceStart {
|
||||
}
|
||||
};
|
||||
|
||||
store.start_rebalance().await;
|
||||
|
||||
warn!("Rebalance started with id: {}", id);
|
||||
if let Some(notification_sys) = get_global_notification_sys() {
|
||||
warn!("Loading rebalance meta");
|
||||
warn!("RebalanceStart Loading rebalance meta start");
|
||||
notification_sys.load_rebalance_meta(true).await;
|
||||
warn!("Rebalance meta loaded");
|
||||
warn!("RebalanceStart Loading rebalance meta done");
|
||||
}
|
||||
|
||||
let resp = RebalanceResp { id };
|
||||
@@ -133,7 +136,7 @@ impl Operation for RebalanceStatus {
|
||||
|
||||
let mut meta = RebalanceMeta::new();
|
||||
if let Err(err) = meta.load(store.pools[0].clone()).await {
|
||||
if is_err_config_not_found(&err) {
|
||||
if err == StorageError::ConfigNotFound {
|
||||
return Err(s3_error!(NoSuchResource, "Pool rebalance is not started"));
|
||||
}
|
||||
|
||||
@@ -175,15 +178,14 @@ impl Operation for RebalanceStatus {
|
||||
let total_bytes_to_rebal = ps.init_capacity as f64 * meta.percent_free_goal - ps.init_free_space as f64;
|
||||
|
||||
let mut elapsed = if let Some(start_time) = ps.info.start_time {
|
||||
SystemTime::now()
|
||||
.duration_since(start_time)
|
||||
.map_err(|e| s3_error!(InternalError, "Failed to calculate elapsed time: {}", e))?
|
||||
let now = OffsetDateTime::now_utc();
|
||||
now - start_time
|
||||
} else {
|
||||
return Err(s3_error!(InternalError, "Start time is not available"));
|
||||
};
|
||||
|
||||
let mut eta = if ps.bytes > 0 {
|
||||
Duration::from_secs_f64(total_bytes_to_rebal * elapsed.as_secs_f64() / ps.bytes as f64)
|
||||
Duration::from_secs_f64(total_bytes_to_rebal * elapsed.as_seconds_f64() / ps.bytes as f64)
|
||||
} else {
|
||||
Duration::ZERO
|
||||
};
|
||||
@@ -193,10 +195,8 @@ impl Operation for RebalanceStatus {
|
||||
}
|
||||
|
||||
if let Some(stopped_at) = stop_time {
|
||||
if let Ok(du) = stopped_at.duration_since(ps.info.start_time.unwrap_or(stopped_at)) {
|
||||
elapsed = du;
|
||||
} else {
|
||||
return Err(s3_error!(InternalError, "Failed to calculate elapsed time"));
|
||||
if let Some(start_time) = ps.info.start_time {
|
||||
elapsed = stopped_at - start_time;
|
||||
}
|
||||
|
||||
eta = Duration::ZERO;
|
||||
@@ -208,7 +208,7 @@ impl Operation for RebalanceStatus {
|
||||
bytes: ps.bytes,
|
||||
bucket: ps.bucket.clone(),
|
||||
object: ps.object.clone(),
|
||||
elapsed: elapsed.as_secs(),
|
||||
elapsed: elapsed.whole_seconds() as u64,
|
||||
eta: eta.as_secs(),
|
||||
});
|
||||
}
|
||||
@@ -244,10 +244,45 @@ impl Operation for RebalanceStop {
|
||||
.await
|
||||
.map_err(|e| s3_error!(InternalError, "Failed to stop rebalance: {}", e))?;
|
||||
|
||||
warn!("handle RebalanceStop save_rebalance_stats done ");
|
||||
if let Some(notification_sys) = get_global_notification_sys() {
|
||||
notification_sys.load_rebalance_meta(true).await;
|
||||
warn!("handle RebalanceStop notification_sys load_rebalance_meta");
|
||||
notification_sys.load_rebalance_meta(false).await;
|
||||
warn!("handle RebalanceStop notification_sys load_rebalance_meta done");
|
||||
}
|
||||
|
||||
return Err(s3_error!(NotImplemented));
|
||||
Ok(S3Response::new((StatusCode::OK, Body::empty())))
|
||||
}
|
||||
}
|
||||
|
||||
mod offsetdatetime_rfc3339 {
|
||||
use serde::{self, Deserialize, Deserializer, Serializer};
|
||||
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
|
||||
|
||||
pub fn serialize<S>(dt: &Option<OffsetDateTime>, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match dt {
|
||||
Some(dt) => {
|
||||
let s = dt.format(&Rfc3339).map_err(serde::ser::Error::custom)?;
|
||||
serializer.serialize_some(&s)
|
||||
}
|
||||
None => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<OffsetDateTime>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let opt = Option::<String>::deserialize(deserializer)?;
|
||||
match opt {
|
||||
Some(s) => {
|
||||
let dt = OffsetDateTime::parse(&s, &Rfc3339).map_err(serde::de::Error::custom)?;
|
||||
Ok(Some(dt))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use matchit::Params;
|
||||
use policy::policy::action::{Action, AdminAction};
|
||||
use policy::policy::{Args, Policy};
|
||||
use s3s::S3ErrorCode::InvalidRequest;
|
||||
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
|
||||
use s3s::{Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error};
|
||||
use serde::Deserialize;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -2,14 +2,16 @@ use crate::{
|
||||
admin::router::Operation,
|
||||
auth::{check_key_valid, get_session_token},
|
||||
};
|
||||
use ecstore::utils::{crypto::base64_encode, xml};
|
||||
use ecstore::bucket::utils::serialize;
|
||||
use http::StatusCode;
|
||||
use iam::{manager::get_token_signing_key, sys::SESSION_POLICY_NAME};
|
||||
use matchit::Params;
|
||||
use policy::{auth::get_new_credentials_with_metadata, policy::Policy};
|
||||
use rustfs_utils::crypto::base64_encode;
|
||||
use s3s::{
|
||||
Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result,
|
||||
dto::{AssumeRoleOutput, Credentials, Timestamp},
|
||||
s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result,
|
||||
s3_error,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
@@ -136,7 +138,7 @@ impl Operation for AssumeRoleHandle {
|
||||
};
|
||||
|
||||
// getAssumeRoleCredentials
|
||||
let output = xml::serialize::<AssumeRoleOutput>(&resp).unwrap();
|
||||
let output = serialize::<AssumeRoleOutput>(&resp).unwrap();
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, Body::from(output))))
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use ecstore::{peer_rest_client::PeerRestClient, GLOBAL_Endpoints};
|
||||
use ecstore::{GLOBAL_Endpoints, peer_rest_client::PeerRestClient};
|
||||
use http::StatusCode;
|
||||
use hyper::Uri;
|
||||
use madmin::service_commands::ServiceTraceOpts;
|
||||
use matchit::Params;
|
||||
use s3s::{s3_error, Body, S3Request, S3Response, S3Result};
|
||||
use s3s::{Body, S3Request, S3Response, S3Result, s3_error};
|
||||
use tracing::warn;
|
||||
|
||||
use crate::admin::router::Operation;
|
||||
|
||||
@@ -5,10 +5,10 @@ use iam::get_global_action_cred;
|
||||
use madmin::{AccountStatus, AddOrUpdateUserReq};
|
||||
use matchit::Params;
|
||||
use policy::policy::{
|
||||
action::{Action, AdminAction},
|
||||
Args,
|
||||
action::{Action, AdminAction},
|
||||
};
|
||||
use s3s::{header::CONTENT_TYPE, s3_error, Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result};
|
||||
use s3s::{Body, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, header::CONTENT_TYPE, s3_error};
|
||||
use serde::Deserialize;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use tracing::warn;
|
||||
|
||||
@@ -3,7 +3,6 @@ pub mod router;
|
||||
mod rpc;
|
||||
pub mod utils;
|
||||
|
||||
use common::error::Result;
|
||||
// use ecstore::global::{is_dist_erasure, is_erasure};
|
||||
use handlers::{
|
||||
group, policys, pools, rebalance,
|
||||
@@ -20,7 +19,7 @@ use s3s::route::S3Route;
|
||||
const ADMIN_PREFIX: &str = "/rustfs/admin";
|
||||
const RUSTFS_ADMIN_PREFIX: &str = "/rustfs/admin";
|
||||
|
||||
pub fn make_admin_route() -> Result<impl S3Route> {
|
||||
pub fn make_admin_route() -> std::io::Result<impl S3Route> {
|
||||
let mut r: S3Router<AdminOperation> = S3Router::new();
|
||||
|
||||
// 1
|
||||
@@ -126,7 +125,7 @@ pub fn make_admin_route() -> Result<impl S3Route> {
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn register_user_route(r: &mut S3Router<AdminOperation>) -> Result<()> {
|
||||
fn register_user_route(r: &mut S3Router<AdminOperation>) -> std::io::Result<()> {
|
||||
// 1
|
||||
r.insert(
|
||||
Method::GET,
|
||||
|
||||
@@ -1,22 +1,93 @@
|
||||
use common::error::Result;
|
||||
use hyper::http::Extensions;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use hmac::{Hmac, Mac};
|
||||
use hyper::HeaderMap;
|
||||
use hyper::Method;
|
||||
use hyper::StatusCode;
|
||||
use hyper::Uri;
|
||||
use hyper::http::Extensions;
|
||||
use matchit::Params;
|
||||
use matchit::Router;
|
||||
use s3s::header;
|
||||
use s3s::route::S3Route;
|
||||
use s3s::s3_error;
|
||||
use s3s::Body;
|
||||
use s3s::S3Request;
|
||||
use s3s::S3Response;
|
||||
use s3s::S3Result;
|
||||
use s3s::header;
|
||||
use s3s::route::S3Route;
|
||||
use s3s::s3_error;
|
||||
use sha2::Sha256;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use super::rpc::RPC_PREFIX;
|
||||
use super::ADMIN_PREFIX;
|
||||
use super::RUSTFS_ADMIN_PREFIX;
|
||||
use super::rpc::RPC_PREFIX;
|
||||
use iam::get_global_action_cred;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
|
||||
const SIGNATURE_HEADER: &str = "x-rustfs-signature";
|
||||
const TIMESTAMP_HEADER: &str = "x-rustfs-timestamp";
|
||||
const SIGNATURE_VALID_DURATION: u64 = 300; // 5 minutes
|
||||
|
||||
/// Get the shared secret for HMAC signing
|
||||
fn get_shared_secret() -> String {
|
||||
if let Some(cred) = get_global_action_cred() {
|
||||
cred.secret_key
|
||||
} else {
|
||||
// Fallback to environment variable if global credentials are not available
|
||||
std::env::var("RUSTFS_RPC_SECRET").unwrap_or_else(|_| "rustfs-default-secret".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate HMAC-SHA256 signature for the given data
|
||||
fn generate_signature(secret: &str, url: &str, method: &str, timestamp: u64) -> String {
|
||||
let data = format!("{}|{}|{}", url, method, timestamp);
|
||||
let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).expect("HMAC can take key of any size");
|
||||
mac.update(data.as_bytes());
|
||||
let result = mac.finalize();
|
||||
general_purpose::STANDARD.encode(result.into_bytes())
|
||||
}
|
||||
|
||||
/// Verify the request signature for RPC requests
|
||||
fn verify_rpc_signature(req: &S3Request<Body>) -> S3Result<()> {
|
||||
let secret = get_shared_secret();
|
||||
|
||||
// Get signature from header
|
||||
let signature = req
|
||||
.headers
|
||||
.get(SIGNATURE_HEADER)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.ok_or_else(|| s3_error!(InvalidArgument, "Missing signature header"))?;
|
||||
|
||||
// Get timestamp from header
|
||||
let timestamp_str = req
|
||||
.headers
|
||||
.get(TIMESTAMP_HEADER)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.ok_or_else(|| s3_error!(InvalidArgument, "Missing timestamp header"))?;
|
||||
|
||||
let timestamp: u64 = timestamp_str
|
||||
.parse()
|
||||
.map_err(|_| s3_error!(InvalidArgument, "Invalid timestamp format"))?;
|
||||
|
||||
// Check timestamp validity (prevent replay attacks)
|
||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
|
||||
if current_time.saturating_sub(timestamp) > SIGNATURE_VALID_DURATION {
|
||||
return Err(s3_error!(InvalidArgument, "Request timestamp expired"));
|
||||
}
|
||||
|
||||
// Generate expected signature
|
||||
let url = req.uri.to_string();
|
||||
let method = req.method.as_str();
|
||||
let expected_signature = generate_signature(&secret, &url, method, timestamp);
|
||||
|
||||
// Compare signatures
|
||||
if signature != expected_signature {
|
||||
return Err(s3_error!(AccessDenied, "Invalid signature"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct S3Router<T> {
|
||||
router: Router<T>,
|
||||
@@ -29,12 +100,12 @@ impl<T: Operation> S3Router<T> {
|
||||
Self { router }
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, method: Method, path: &str, operation: T) -> Result<()> {
|
||||
pub fn insert(&mut self, method: Method, path: &str, operation: T) -> std::io::Result<()> {
|
||||
let path = Self::make_route_str(method, path);
|
||||
|
||||
// warn!("set uri {}", &path);
|
||||
|
||||
self.router.insert(path, operation)?;
|
||||
self.router.insert(path, operation).map_err(std::io::Error::other)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -85,10 +156,16 @@ where
|
||||
|
||||
// check_access before call
|
||||
async fn check_access(&self, req: &mut S3Request<Body>) -> S3Result<()> {
|
||||
// TODO: check access by req.credentials
|
||||
// Check RPC signature verification
|
||||
if req.uri.path().starts_with(RPC_PREFIX) {
|
||||
// Skip signature verification for HEAD requests (health checks)
|
||||
if req.method != Method::HEAD {
|
||||
verify_rpc_signature(req)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// For non-RPC admin requests, check credentials
|
||||
match req.credentials {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(s3_error!(AccessDenied, "Signature is required")),
|
||||
|
||||
+90
-12
@@ -2,11 +2,11 @@ use super::router::AdminOperation;
|
||||
use super::router::Operation;
|
||||
use super::router::S3Router;
|
||||
use crate::storage::ecfs::bytes_stream;
|
||||
use common::error::Result;
|
||||
use ecstore::disk::DiskAPI;
|
||||
use ecstore::io::READ_BUFFER_SIZE;
|
||||
use ecstore::disk::WalkDirOptions;
|
||||
use ecstore::set_disk::DEFAULT_READ_BUFFER_SIZE;
|
||||
use ecstore::store::find_local_disk;
|
||||
use futures::TryStreamExt;
|
||||
use futures::StreamExt;
|
||||
use http::StatusCode;
|
||||
use hyper::Method;
|
||||
use matchit::Params;
|
||||
@@ -17,24 +17,43 @@ use s3s::S3Request;
|
||||
use s3s::S3Response;
|
||||
use s3s::S3Result;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio_util::io::ReaderStream;
|
||||
use tokio_util::io::StreamReader;
|
||||
use tracing::warn;
|
||||
|
||||
pub const RPC_PREFIX: &str = "/rustfs/rpc";
|
||||
|
||||
pub fn register_rpc_route(r: &mut S3Router<AdminOperation>) -> Result<()> {
|
||||
pub fn regist_rpc_route(r: &mut S3Router<AdminOperation>) -> std::io::Result<()> {
|
||||
r.insert(
|
||||
Method::GET,
|
||||
format!("{}{}", RPC_PREFIX, "/read_file_stream").as_str(),
|
||||
AdminOperation(&ReadFile {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::HEAD,
|
||||
format!("{}{}", RPC_PREFIX, "/read_file_stream").as_str(),
|
||||
AdminOperation(&ReadFile {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::PUT,
|
||||
format!("{}{}", RPC_PREFIX, "/put_file_stream").as_str(),
|
||||
AdminOperation(&PutFile {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::GET,
|
||||
format!("{}{}", RPC_PREFIX, "/walk_dir").as_str(),
|
||||
AdminOperation(&WalkDir {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::HEAD,
|
||||
format!("{}{}", RPC_PREFIX, "/walk_dir").as_str(),
|
||||
AdminOperation(&WalkDir {}),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -51,6 +70,9 @@ pub struct ReadFile {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for ReadFile {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
if req.method == Method::HEAD {
|
||||
return Ok(S3Response::new((StatusCode::OK, Body::empty())));
|
||||
}
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: ReadFileQuery =
|
||||
@@ -73,13 +95,68 @@ impl Operation for ReadFile {
|
||||
Ok(S3Response::new((
|
||||
StatusCode::OK,
|
||||
Body::from(StreamingBlob::wrap(bytes_stream(
|
||||
ReaderStream::with_capacity(file, READ_BUFFER_SIZE),
|
||||
ReaderStream::with_capacity(file, DEFAULT_READ_BUFFER_SIZE),
|
||||
query.length,
|
||||
))),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
pub struct WalkDirQuery {
|
||||
disk: String,
|
||||
}
|
||||
|
||||
pub struct WalkDir {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for WalkDir {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
if req.method == Method::HEAD {
|
||||
return Ok(S3Response::new((StatusCode::OK, Body::empty())));
|
||||
}
|
||||
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: WalkDirQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|e| s3_error!(InvalidArgument, "get query failed1 {:?}", e))?;
|
||||
input
|
||||
} else {
|
||||
WalkDirQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
let mut input = req.input;
|
||||
let body = match input.store_all_unlimited().await {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
warn!("get body failed, e: {:?}", e);
|
||||
return Err(s3_error!(InvalidRequest, "get body failed"));
|
||||
}
|
||||
};
|
||||
|
||||
// let body_bytes = decrypt_data(input_cred.secret_key.expose().as_bytes(), &body)
|
||||
// .map_err(|e| S3Error::with_message(S3ErrorCode::InvalidArgument, format!("decrypt_data err {}", e)))?;
|
||||
|
||||
let args: WalkDirOptions =
|
||||
serde_json::from_slice(&body).map_err(|e| s3_error!(InternalError, "unmarshal body err {}", e))?;
|
||||
let Some(disk) = find_local_disk(&query.disk).await else {
|
||||
return Err(s3_error!(InvalidArgument, "disk not found"));
|
||||
};
|
||||
|
||||
let (rd, mut wd) = tokio::io::duplex(DEFAULT_READ_BUFFER_SIZE);
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = disk.walk_dir(args, &mut wd).await {
|
||||
warn!("walk dir err {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
let body = Body::from(StreamingBlob::wrap(ReaderStream::with_capacity(rd, DEFAULT_READ_BUFFER_SIZE)));
|
||||
Ok(S3Response::new((StatusCode::OK, body)))
|
||||
}
|
||||
}
|
||||
|
||||
// /rustfs/rpc/read_file_stream?disk={}&volume={}&path={}&offset={}&length={}"
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
pub struct PutFileQuery {
|
||||
@@ -87,7 +164,7 @@ pub struct PutFileQuery {
|
||||
volume: String,
|
||||
path: String,
|
||||
append: bool,
|
||||
size: usize,
|
||||
size: i64,
|
||||
}
|
||||
pub struct PutFile {}
|
||||
#[async_trait::async_trait]
|
||||
@@ -117,11 +194,12 @@ impl Operation for PutFile {
|
||||
.map_err(|e| s3_error!(InternalError, "read file err {}", e))?
|
||||
};
|
||||
|
||||
let mut body = StreamReader::new(req.input.into_stream().map_err(std::io::Error::other));
|
||||
|
||||
tokio::io::copy(&mut body, &mut file)
|
||||
.await
|
||||
.map_err(|e| s3_error!(InternalError, "copy err {}", e))?;
|
||||
let mut body = req.input;
|
||||
while let Some(item) = body.next().await {
|
||||
let bytes = item.map_err(|e| s3_error!(InternalError, "body stream err {}", e))?;
|
||||
let result = file.write_all(&bytes).await;
|
||||
result.map_err(|e| s3_error!(InternalError, "write file err {}", e))?;
|
||||
}
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, Body::empty())))
|
||||
}
|
||||
|
||||
+3
-3
@@ -7,13 +7,13 @@ use iam::get_global_action_cred;
|
||||
use iam::sys::SESSION_POLICY_NAME;
|
||||
use policy::auth;
|
||||
use policy::auth::get_claims_from_token_with_secret;
|
||||
use s3s::S3Error;
|
||||
use s3s::S3ErrorCode;
|
||||
use s3s::S3Result;
|
||||
use s3s::auth::S3Auth;
|
||||
use s3s::auth::SecretKey;
|
||||
use s3s::auth::SimpleAuth;
|
||||
use s3s::s3_error;
|
||||
use s3s::S3Error;
|
||||
use s3s::S3ErrorCode;
|
||||
use s3s::S3Result;
|
||||
use serde_json::Value;
|
||||
|
||||
pub struct IAMAuth {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use crate::license::get_license;
|
||||
use axum::{
|
||||
Router,
|
||||
body::Body,
|
||||
http::{Response, StatusCode},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use axum_extra::extract::Host;
|
||||
use rustfs_config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY};
|
||||
use rustfs_utils::net::parse_and_resolve_address;
|
||||
use std::io;
|
||||
|
||||
use axum::response::Redirect;
|
||||
use axum_server::tls_rustls::RustlsConfig;
|
||||
use http::{header, Uri};
|
||||
use http::{Uri, header};
|
||||
use mime_guess::from_path;
|
||||
use rust_embed::RustEmbed;
|
||||
use serde::Serialize;
|
||||
@@ -239,8 +240,7 @@ pub async fn start_static_file_server(
|
||||
.layer(tower_http::compression::CompressionLayer::new().gzip(true).deflate(true))
|
||||
.layer(TraceLayer::new_for_http());
|
||||
|
||||
use ecstore::utils::net;
|
||||
let server_addr = net::parse_and_resolve_address(addrs).expect("Failed to parse socket address");
|
||||
let server_addr = parse_and_resolve_address(addrs).expect("Failed to parse socket address");
|
||||
let server_port = server_addr.port();
|
||||
let server_address = server_addr.to_string();
|
||||
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
use ecstore::error::StorageError;
|
||||
use s3s::{S3Error, S3ErrorCode};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ApiError {
|
||||
pub code: S3ErrorCode,
|
||||
pub message: String,
|
||||
pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ApiError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ApiError {}
|
||||
|
||||
impl ApiError {
|
||||
pub fn other<E>(error: E) -> Self
|
||||
where
|
||||
E: std::fmt::Display + Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
ApiError {
|
||||
code: S3ErrorCode::InternalError,
|
||||
message: error.to_string(),
|
||||
source: Some(error.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiError> for S3Error {
|
||||
fn from(err: ApiError) -> Self {
|
||||
let mut s3e = S3Error::with_message(err.code, err.message);
|
||||
if let Some(source) = err.source {
|
||||
s3e.set_source(source);
|
||||
}
|
||||
s3e
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StorageError> for ApiError {
|
||||
fn from(err: StorageError) -> Self {
|
||||
let code = match &err {
|
||||
StorageError::NotImplemented => S3ErrorCode::NotImplemented,
|
||||
StorageError::InvalidArgument(_, _, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::MethodNotAllowed => S3ErrorCode::MethodNotAllowed,
|
||||
StorageError::BucketNotFound(_) => S3ErrorCode::NoSuchBucket,
|
||||
StorageError::BucketNotEmpty(_) => S3ErrorCode::BucketNotEmpty,
|
||||
StorageError::BucketNameInvalid(_) => S3ErrorCode::InvalidBucketName,
|
||||
StorageError::ObjectNameInvalid(_, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::BucketExists(_) => S3ErrorCode::BucketAlreadyExists,
|
||||
StorageError::StorageFull => S3ErrorCode::ServiceUnavailable,
|
||||
StorageError::SlowDown => S3ErrorCode::SlowDown,
|
||||
StorageError::PrefixAccessDenied(_, _) => S3ErrorCode::AccessDenied,
|
||||
StorageError::InvalidUploadIDKeyCombination(_, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::ObjectNameTooLong(_, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::ObjectNamePrefixAsSlash(_, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::ObjectNotFound(_, _) => S3ErrorCode::NoSuchKey,
|
||||
StorageError::ConfigNotFound => S3ErrorCode::NoSuchKey,
|
||||
StorageError::VolumeNotFound => S3ErrorCode::NoSuchBucket,
|
||||
StorageError::FileNotFound => S3ErrorCode::NoSuchKey,
|
||||
StorageError::FileVersionNotFound => S3ErrorCode::NoSuchVersion,
|
||||
StorageError::VersionNotFound(_, _, _) => S3ErrorCode::NoSuchVersion,
|
||||
StorageError::InvalidUploadID(_, _, _) => S3ErrorCode::InvalidPart,
|
||||
StorageError::InvalidVersionID(_, _, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::DataMovementOverwriteErr(_, _, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::ObjectExistsAsDirectory(_, _) => S3ErrorCode::InvalidArgument,
|
||||
StorageError::InvalidPart(_, _, _) => S3ErrorCode::InvalidPart,
|
||||
_ => S3ErrorCode::InternalError,
|
||||
};
|
||||
|
||||
ApiError {
|
||||
code,
|
||||
message: err.to_string(),
|
||||
source: Some(Box::new(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ApiError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
ApiError {
|
||||
code: S3ErrorCode::InternalError,
|
||||
message: err.to_string(),
|
||||
source: Some(Box::new(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<iam::error::Error> for ApiError {
|
||||
fn from(err: iam::error::Error) -> Self {
|
||||
let serr: StorageError = err.into();
|
||||
serr.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use s3s::{S3Error, S3ErrorCode};
|
||||
use std::io::{Error as IoError, ErrorKind};
|
||||
|
||||
#[test]
|
||||
fn test_api_error_from_io_error() {
|
||||
let io_error = IoError::new(ErrorKind::PermissionDenied, "permission denied");
|
||||
let api_error: ApiError = io_error.into();
|
||||
|
||||
assert_eq!(api_error.code, S3ErrorCode::InternalError);
|
||||
assert!(api_error.message.contains("permission denied"));
|
||||
assert!(api_error.source.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_from_io_error_different_kinds() {
|
||||
let test_cases = vec![
|
||||
(ErrorKind::NotFound, "not found"),
|
||||
(ErrorKind::InvalidInput, "invalid input"),
|
||||
(ErrorKind::TimedOut, "timed out"),
|
||||
(ErrorKind::WriteZero, "write zero"),
|
||||
(ErrorKind::Other, "other error"),
|
||||
];
|
||||
|
||||
for (kind, message) in test_cases {
|
||||
let io_error = IoError::new(kind, message);
|
||||
let api_error: ApiError = io_error.into();
|
||||
|
||||
assert_eq!(api_error.code, S3ErrorCode::InternalError);
|
||||
assert!(api_error.message.contains(message));
|
||||
assert!(api_error.source.is_some());
|
||||
|
||||
// Test that source can be downcast back to io::Error
|
||||
let source = api_error.source.as_ref().unwrap();
|
||||
let downcast_io_error = source.downcast_ref::<IoError>();
|
||||
assert!(downcast_io_error.is_some());
|
||||
assert_eq!(downcast_io_error.unwrap().kind(), kind);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_other_function() {
|
||||
let custom_error = "Custom API error";
|
||||
let api_error = ApiError::other(custom_error);
|
||||
|
||||
assert_eq!(api_error.code, S3ErrorCode::InternalError);
|
||||
assert_eq!(api_error.message, custom_error);
|
||||
assert!(api_error.source.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_other_function_with_complex_error() {
|
||||
let io_error = IoError::new(ErrorKind::InvalidData, "complex error");
|
||||
let api_error = ApiError::other(io_error);
|
||||
|
||||
assert_eq!(api_error.code, S3ErrorCode::InternalError);
|
||||
assert!(api_error.message.contains("complex error"));
|
||||
assert!(api_error.source.is_some());
|
||||
|
||||
// Test that source can be downcast back to io::Error
|
||||
let source = api_error.source.as_ref().unwrap();
|
||||
let downcast_io_error = source.downcast_ref::<IoError>();
|
||||
assert!(downcast_io_error.is_some());
|
||||
assert_eq!(downcast_io_error.unwrap().kind(), ErrorKind::InvalidData);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_from_storage_error() {
|
||||
let storage_error = StorageError::BucketNotFound("test-bucket".to_string());
|
||||
let api_error: ApiError = storage_error.into();
|
||||
|
||||
assert_eq!(api_error.code, S3ErrorCode::NoSuchBucket);
|
||||
assert!(api_error.message.contains("test-bucket"));
|
||||
assert!(api_error.source.is_some());
|
||||
|
||||
// Test that source can be downcast back to StorageError
|
||||
let source = api_error.source.as_ref().unwrap();
|
||||
let downcast_storage_error = source.downcast_ref::<StorageError>();
|
||||
assert!(downcast_storage_error.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_from_storage_error_mappings() {
|
||||
let test_cases = vec![
|
||||
(StorageError::NotImplemented, S3ErrorCode::NotImplemented),
|
||||
(
|
||||
StorageError::InvalidArgument("test".into(), "test".into(), "test".into()),
|
||||
S3ErrorCode::InvalidArgument,
|
||||
),
|
||||
(StorageError::MethodNotAllowed, S3ErrorCode::MethodNotAllowed),
|
||||
(StorageError::BucketNotFound("test".into()), S3ErrorCode::NoSuchBucket),
|
||||
(StorageError::BucketNotEmpty("test".into()), S3ErrorCode::BucketNotEmpty),
|
||||
(StorageError::BucketNameInvalid("test".into()), S3ErrorCode::InvalidBucketName),
|
||||
(
|
||||
StorageError::ObjectNameInvalid("test".into(), "test".into()),
|
||||
S3ErrorCode::InvalidArgument,
|
||||
),
|
||||
(StorageError::BucketExists("test".into()), S3ErrorCode::BucketAlreadyExists),
|
||||
(StorageError::StorageFull, S3ErrorCode::ServiceUnavailable),
|
||||
(StorageError::SlowDown, S3ErrorCode::SlowDown),
|
||||
(StorageError::PrefixAccessDenied("test".into(), "test".into()), S3ErrorCode::AccessDenied),
|
||||
(StorageError::ObjectNotFound("test".into(), "test".into()), S3ErrorCode::NoSuchKey),
|
||||
(StorageError::ConfigNotFound, S3ErrorCode::NoSuchKey),
|
||||
(StorageError::VolumeNotFound, S3ErrorCode::NoSuchBucket),
|
||||
(StorageError::FileNotFound, S3ErrorCode::NoSuchKey),
|
||||
(StorageError::FileVersionNotFound, S3ErrorCode::NoSuchVersion),
|
||||
];
|
||||
|
||||
for (storage_error, expected_code) in test_cases {
|
||||
let api_error: ApiError = storage_error.into();
|
||||
assert_eq!(api_error.code, expected_code);
|
||||
assert!(api_error.source.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_from_iam_error() {
|
||||
let iam_error = iam::error::Error::other("IAM test error");
|
||||
let api_error: ApiError = iam_error.into();
|
||||
|
||||
// IAM error is first converted to StorageError, then to ApiError
|
||||
assert!(api_error.source.is_some());
|
||||
assert!(api_error.message.contains("test error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_to_s3_error() {
|
||||
let api_error = ApiError {
|
||||
code: S3ErrorCode::NoSuchBucket,
|
||||
message: "Bucket not found".to_string(),
|
||||
source: Some(Box::new(IoError::new(ErrorKind::NotFound, "not found"))),
|
||||
};
|
||||
|
||||
let s3_error: S3Error = api_error.into();
|
||||
assert_eq!(*s3_error.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_error.message().unwrap_or("").contains("Bucket not found"));
|
||||
assert!(s3_error.source().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_to_s3_error_without_source() {
|
||||
let api_error = ApiError {
|
||||
code: S3ErrorCode::InvalidArgument,
|
||||
message: "Invalid argument".to_string(),
|
||||
source: None,
|
||||
};
|
||||
|
||||
let s3_error: S3Error = api_error.into();
|
||||
assert_eq!(*s3_error.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_error.message().unwrap_or("").contains("Invalid argument"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_display() {
|
||||
let api_error = ApiError {
|
||||
code: S3ErrorCode::InternalError,
|
||||
message: "Test error message".to_string(),
|
||||
source: None,
|
||||
};
|
||||
|
||||
assert_eq!(api_error.to_string(), "Test error message");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_debug() {
|
||||
let api_error = ApiError {
|
||||
code: S3ErrorCode::NoSuchKey,
|
||||
message: "Object not found".to_string(),
|
||||
source: Some(Box::new(IoError::new(ErrorKind::NotFound, "file not found"))),
|
||||
};
|
||||
|
||||
let debug_str = format!("{:?}", api_error);
|
||||
assert!(debug_str.contains("NoSuchKey"));
|
||||
assert!(debug_str.contains("Object not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_roundtrip_through_io_error() {
|
||||
let original_io_error = IoError::new(ErrorKind::PermissionDenied, "original permission error");
|
||||
|
||||
// Convert to ApiError
|
||||
let api_error: ApiError = original_io_error.into();
|
||||
|
||||
// Verify the conversion preserved the information
|
||||
assert_eq!(api_error.code, S3ErrorCode::InternalError);
|
||||
assert!(api_error.message.contains("original permission error"));
|
||||
assert!(api_error.source.is_some());
|
||||
|
||||
// Test that we can downcast back to the original io::Error
|
||||
let source = api_error.source.as_ref().unwrap();
|
||||
let downcast_io_error = source.downcast_ref::<IoError>();
|
||||
assert!(downcast_io_error.is_some());
|
||||
assert_eq!(downcast_io_error.unwrap().kind(), ErrorKind::PermissionDenied);
|
||||
assert!(downcast_io_error.unwrap().to_string().contains("original permission error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_chain_conversion() {
|
||||
// Start with an io::Error
|
||||
let io_error = IoError::new(ErrorKind::InvalidData, "invalid data");
|
||||
|
||||
// Convert to StorageError (simulating what happens in the codebase)
|
||||
let storage_error = StorageError::other(io_error);
|
||||
|
||||
// Convert to ApiError
|
||||
let api_error: ApiError = storage_error.into();
|
||||
|
||||
// Verify the chain is preserved
|
||||
assert!(api_error.source.is_some());
|
||||
|
||||
// Check that we can still access the original error information
|
||||
let source = api_error.source.as_ref().unwrap();
|
||||
let downcast_storage_error = source.downcast_ref::<StorageError>();
|
||||
assert!(downcast_storage_error.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_api_error_error_trait_implementation() {
|
||||
let api_error = ApiError {
|
||||
code: S3ErrorCode::InternalError,
|
||||
message: "Test error".to_string(),
|
||||
source: Some(Box::new(IoError::other("source error"))),
|
||||
};
|
||||
|
||||
// Test that it implements std::error::Error
|
||||
let error: &dyn std::error::Error = &api_error;
|
||||
assert_eq!(error.to_string(), "Test error");
|
||||
// ApiError doesn't implement Error::source() properly, so this would be None
|
||||
// This is expected because ApiError is not a typical Error implementation
|
||||
assert!(error.source().is_none());
|
||||
}
|
||||
}
|
||||
+163
-356
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
use appauth::token::Token;
|
||||
use common::error::{Error, Result};
|
||||
use std::io::{Error, Result};
|
||||
use std::sync::OnceLock;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
@@ -37,7 +37,7 @@ pub fn license_check() -> Result<()> {
|
||||
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()));
|
||||
return Err(Error::other("Incorrect license, please contact RustFS."));
|
||||
}
|
||||
info!("License is valid ! expired at {}", token.expired);
|
||||
Ok(())
|
||||
@@ -46,12 +46,12 @@ pub fn license_check() -> Result<()> {
|
||||
// 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()));
|
||||
// return Err(Error::other("Incorrect license, please contact RustFS.".to_string()));
|
||||
// }
|
||||
// let token = appauth::token::parse_license(license)?;
|
||||
// if token.expired < SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() {
|
||||
// error!("License expired");
|
||||
// return Err(Error::from_string("Incorrect license, please contact RustFS.".to_string()));
|
||||
// return Err(Error::other("Incorrect license, please contact RustFS.".to_string()));
|
||||
// }
|
||||
|
||||
// info!("License is valid ! expired at {}", token.expired);
|
||||
@@ -59,7 +59,7 @@ pub fn license_check() -> Result<()> {
|
||||
// });
|
||||
|
||||
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()));
|
||||
return Err(Error::other("Incorrect license, please contact RustFS."));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+20
-18
@@ -2,6 +2,7 @@ mod admin;
|
||||
mod auth;
|
||||
mod config;
|
||||
mod console;
|
||||
mod error;
|
||||
mod event;
|
||||
mod grpc;
|
||||
pub mod license;
|
||||
@@ -18,7 +19,7 @@ use bytes::Bytes;
|
||||
use chrono::Datelike;
|
||||
use clap::Parser;
|
||||
use common::{
|
||||
error::{Error, Result},
|
||||
// error::{Error, Result},
|
||||
globals::set_global_addr,
|
||||
};
|
||||
use ecstore::bucket::metadata_sys::init_bucket_metadata_sys;
|
||||
@@ -27,7 +28,6 @@ use ecstore::config as ecconfig;
|
||||
use ecstore::config::GLOBAL_ConfigSys;
|
||||
use ecstore::heal::background_heal_ops::init_auto_heal;
|
||||
use ecstore::store_api::BucketOptions;
|
||||
use ecstore::utils::net;
|
||||
use ecstore::StorageAPI;
|
||||
use ecstore::{
|
||||
endpoints::EndpointServerPools,
|
||||
@@ -50,10 +50,12 @@ use license::init_license;
|
||||
use protos::proto_gen::node_service::node_service_server::NodeServiceServer;
|
||||
use rustfs_config::{DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY, RUSTFS_TLS_CERT, RUSTFS_TLS_KEY};
|
||||
use rustfs_obs::{init_obs, set_global_guard, SystemObserver};
|
||||
use rustfs_utils::net::parse_and_resolve_address;
|
||||
use rustls::ServerConfig;
|
||||
use s3s::{host::MultiDomain, service::S3ServiceBuilder};
|
||||
use service::hybrid;
|
||||
use socket2::SockRef;
|
||||
use std::io::{Error, Result};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -73,7 +75,7 @@ const MI_B: usize = 1024 * 1024;
|
||||
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn check_auth(req: Request<()>) -> Result<Request<()>, Status> {
|
||||
fn check_auth(req: Request<()>) -> std::result::Result<Request<()>, Status> {
|
||||
let token: MetadataValue<_> = "rustfs rpc".parse().unwrap();
|
||||
|
||||
match req.metadata().get("authorization") {
|
||||
@@ -107,7 +109,7 @@ async fn main() -> Result<()> {
|
||||
let (_logger, guard) = init_obs(Some(opt.clone().obs_endpoint)).await;
|
||||
|
||||
// Store in global storage
|
||||
set_global_guard(guard)?;
|
||||
set_global_guard(guard).map_err(Error::other)?;
|
||||
|
||||
// Run parameters
|
||||
run(opt).await
|
||||
@@ -120,7 +122,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
// Initialize event notifier
|
||||
// event::init_event_notifier(opt.event_config).await;
|
||||
|
||||
let server_addr = net::parse_and_resolve_address(opt.address.as_str())?;
|
||||
let server_addr = parse_and_resolve_address(opt.address.as_str()).map_err(Error::other)?;
|
||||
let server_port = server_addr.port();
|
||||
let server_address = server_addr.to_string();
|
||||
|
||||
@@ -139,8 +141,8 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
let local_ip = rustfs_utils::get_local_ip().ok_or(local_addr.ip()).unwrap();
|
||||
|
||||
// For RPC
|
||||
let (endpoint_pools, setup_type) = EndpointServerPools::from_volumes(server_address.clone().as_str(), opt.volumes.clone())
|
||||
.map_err(|err| Error::from_string(err.to_string()))?;
|
||||
let (endpoint_pools, setup_type) =
|
||||
EndpointServerPools::from_volumes(server_address.clone().as_str(), opt.volumes.clone()).map_err(Error::other)?;
|
||||
|
||||
// Print RustFS-style logging for pool formatting
|
||||
for (i, eps) in endpoint_pools.as_ref().iter().enumerate() {
|
||||
@@ -164,7 +166,10 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
info!(" RootUser: {}", opt.access_key.clone());
|
||||
info!(" RootPass: {}", opt.secret_key.clone());
|
||||
if DEFAULT_ACCESS_KEY.eq(&opt.access_key) && DEFAULT_SECRET_KEY.eq(&opt.secret_key) {
|
||||
warn!("Detected default credentials '{}:{}', we recommend that you change these values with 'RUSTFS_ACCESS_KEY' and 'RUSTFS_SECRET_KEY' environment variables", DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY);
|
||||
warn!(
|
||||
"Detected default credentials '{}:{}', we recommend that you change these values with 'RUSTFS_ACCESS_KEY' and 'RUSTFS_SECRET_KEY' environment variables",
|
||||
DEFAULT_ACCESS_KEY, DEFAULT_SECRET_KEY
|
||||
);
|
||||
}
|
||||
|
||||
for (i, eps) in endpoint_pools.as_ref().iter().enumerate() {
|
||||
@@ -184,9 +189,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
update_erasure_type(setup_type).await;
|
||||
|
||||
// Initialize the local disk
|
||||
init_local_disks(endpoint_pools.clone())
|
||||
.await
|
||||
.map_err(|err| Error::from_string(err.to_string()))?;
|
||||
init_local_disks(endpoint_pools.clone()).await.map_err(Error::other)?;
|
||||
|
||||
// Setup S3 service
|
||||
// This project uses the S3S library to implement S3 services
|
||||
@@ -208,7 +211,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
|
||||
if !opt.server_domains.is_empty() {
|
||||
info!("virtual-hosted-style requests are enabled use domain_name {:?}", &opt.server_domains);
|
||||
b.set_host(MultiDomain::new(&opt.server_domains)?);
|
||||
b.set_host(MultiDomain::new(&opt.server_domains).map_err(Error::other)?);
|
||||
}
|
||||
|
||||
// // Enable parsing virtual-hosted-style requests
|
||||
@@ -501,9 +504,8 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
// init store
|
||||
let store = ECStore::new(server_addr.clone(), endpoint_pools.clone())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("ECStore::new {:?}", &err);
|
||||
Error::from_string(err.to_string())
|
||||
.inspect_err(|err| {
|
||||
error!("ECStore::new {:?}", err);
|
||||
})?;
|
||||
|
||||
ecconfig::init();
|
||||
@@ -522,7 +524,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map_err(|err| Error::from_string(err.to_string()))?;
|
||||
.map_err(Error::other)?;
|
||||
|
||||
let buckets = buckets_list.into_iter().map(|v| v.name).collect();
|
||||
|
||||
@@ -532,7 +534,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
|
||||
new_global_notification_sys(endpoint_pools.clone()).await.map_err(|err| {
|
||||
error!("new_global_notification_sys failed {:?}", &err);
|
||||
Error::from_string(err.to_string())
|
||||
Error::other(err)
|
||||
})?;
|
||||
|
||||
// init scanner
|
||||
@@ -558,7 +560,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
|
||||
if console_address.is_empty() {
|
||||
error!("console_address is empty");
|
||||
return Err(Error::from_string("console_address is empty".to_string()));
|
||||
return Err(Error::other("console_address is empty".to_string()));
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod service_state;
|
||||
pub(crate) use service_state::wait_for_shutdown;
|
||||
pub(crate) use service_state::SHUTDOWN_TIMEOUT;
|
||||
pub(crate) use service_state::ServiceState;
|
||||
pub(crate) use service_state::ServiceStateManager;
|
||||
pub(crate) use service_state::ShutdownSignal;
|
||||
pub(crate) use service_state::SHUTDOWN_TIMEOUT;
|
||||
pub(crate) use service_state::wait_for_shutdown;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use atomic_enum::atomic_enum;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
use tracing::info;
|
||||
|
||||
// a configurable shutdown timeout
|
||||
@@ -10,7 +10,7 @@ pub(crate) const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn notify_systemd(state: &str) {
|
||||
use libsystemd::daemon::{notify, NotifyState};
|
||||
use libsystemd::daemon::{NotifyState, notify};
|
||||
use tracing::{debug, error};
|
||||
let notify_state = match state {
|
||||
"ready" => NotifyState::Ready,
|
||||
|
||||
@@ -7,7 +7,7 @@ use policy::auth;
|
||||
use policy::policy::action::{Action, S3Action};
|
||||
use policy::policy::{Args, BucketPolicyArgs};
|
||||
use s3s::access::{S3Access, S3AccessContext};
|
||||
use s3s::{dto::*, s3_error, S3Error, S3ErrorCode, S3Request, S3Result};
|
||||
use s3s::{S3Error, S3ErrorCode, S3Request, S3Result, dto::*, s3_error};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -218,11 +218,9 @@ impl S3Access for FS {
|
||||
let req_info = req.extensions.get_mut::<ReqInfo>().expect("ReqInfo not found");
|
||||
let (src_bucket, src_key, version_id) = match &req.input.copy_source {
|
||||
CopySource::AccessPoint { .. } => return Err(s3_error!(NotImplemented)),
|
||||
CopySource::Bucket {
|
||||
ref bucket,
|
||||
ref key,
|
||||
version_id,
|
||||
} => (bucket.to_string(), key.to_string(), version_id.as_ref().map(|v| v.to_string())),
|
||||
CopySource::Bucket { bucket, key, version_id } => {
|
||||
(bucket.to_string(), key.to_string(), version_id.as_ref().map(|v| v.to_string()))
|
||||
}
|
||||
};
|
||||
|
||||
req_info.bucket = Some(src_bucket);
|
||||
|
||||
+243
-170
File diff suppressed because it is too large
Load Diff
+26
-39
@@ -1,6 +1,6 @@
|
||||
use common::error::Error;
|
||||
use ecstore::{bucket::error::BucketMetadataError, disk::error::is_err_file_not_found, store_err::StorageError};
|
||||
use s3s::{s3_error, S3Error, S3ErrorCode};
|
||||
use ecstore::error::StorageError;
|
||||
use s3s::{S3Error, S3ErrorCode, s3_error};
|
||||
pub fn to_s3_error(err: Error) -> S3Error {
|
||||
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
return match storage_err {
|
||||
@@ -56,18 +56,6 @@ pub fn to_s3_error(err: Error) -> S3Error {
|
||||
StorageError::ObjectExistsAsDirectory(bucket, object) => {
|
||||
s3_error!(InvalidArgument, "Object exists on :{} as directory {}", bucket, object)
|
||||
}
|
||||
StorageError::InsufficientReadQuorum => {
|
||||
s3_error!(SlowDown, "Storage resources are insufficient for the read operation")
|
||||
}
|
||||
StorageError::InsufficientWriteQuorum => {
|
||||
s3_error!(SlowDown, "Storage resources are insufficient for the write operation")
|
||||
}
|
||||
StorageError::DecommissionNotStarted => s3_error!(InvalidArgument, "Decommission Not Started"),
|
||||
StorageError::DecommissionAlreadyRunning => s3_error!(InternalError, "Decommission already running"),
|
||||
|
||||
StorageError::VolumeNotFound(bucket) => {
|
||||
s3_error!(NoSuchBucket, "bucket not found {}", bucket)
|
||||
}
|
||||
StorageError::InvalidPart(bucket, object, version_id) => {
|
||||
s3_error!(
|
||||
InvalidPart,
|
||||
@@ -80,15 +68,6 @@ pub fn to_s3_error(err: Error) -> S3Error {
|
||||
StorageError::DoneForNow => s3_error!(InternalError, "DoneForNow"),
|
||||
};
|
||||
}
|
||||
//需要添加 not found bucket replication config
|
||||
if let Some(meta_err) = err.downcast_ref::<BucketMetadataError>() {
|
||||
return match meta_err {
|
||||
BucketMetadataError::BucketReplicationConfigNotFound => {
|
||||
S3Error::with_message(S3ErrorCode::ReplicationConfigurationNotFoundError, format!("{}", err))
|
||||
}
|
||||
_ => S3Error::with_message(S3ErrorCode::InternalError, format!("{}", err)), // 处理其他情况
|
||||
};
|
||||
}
|
||||
|
||||
if is_err_file_not_found(&err) {
|
||||
return S3Error::with_message(S3ErrorCode::NoSuchKey, format!(" ec err {}", err));
|
||||
@@ -196,10 +175,12 @@ mod tests {
|
||||
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"));
|
||||
assert!(
|
||||
s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage reached its minimum free drive threshold")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -266,10 +247,12 @@ mod tests {
|
||||
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("Object name contains forward slash as prefix")
|
||||
);
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("/invalid-object"));
|
||||
}
|
||||
@@ -367,10 +350,12 @@ mod tests {
|
||||
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"));
|
||||
assert!(
|
||||
s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage resources are insufficient for the read operation")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -380,10 +365,12 @@ mod tests {
|
||||
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"));
|
||||
assert!(
|
||||
s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage resources are insufficient for the write operation")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod access;
|
||||
pub mod ecfs;
|
||||
pub mod error;
|
||||
// pub mod error;
|
||||
mod event;
|
||||
pub mod options;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use common::error::{Error, Result};
|
||||
use ecstore::bucket::versioning_sys::BucketVersioningSys;
|
||||
use ecstore::error::Result;
|
||||
use ecstore::error::StorageError;
|
||||
use ecstore::store_api::ObjectOptions;
|
||||
use ecstore::store_err::StorageError;
|
||||
use ecstore::utils::path::is_dir_object;
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use lazy_static::lazy_static;
|
||||
use rustfs_utils::path::is_dir_object;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -25,24 +25,16 @@ pub async fn del_opts(
|
||||
|
||||
if let Some(ref id) = vid {
|
||||
if let Err(_err) = Uuid::parse_str(id.as_str()) {
|
||||
return Err(Error::new(StorageError::InvalidVersionID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidVersionID(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
|
||||
if !versioned {
|
||||
return Err(Error::new(StorageError::InvalidArgument(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut opts = put_opts_from_headers(headers, metadata)
|
||||
.map_err(|err| Error::new(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string())))?;
|
||||
.map_err(|err| StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string()))?;
|
||||
|
||||
opts.version_id = {
|
||||
if is_dir_object(object) && vid.is_none() {
|
||||
@@ -72,24 +64,16 @@ pub async fn get_opts(
|
||||
|
||||
if let Some(ref id) = vid {
|
||||
if let Err(_err) = Uuid::parse_str(id.as_str()) {
|
||||
return Err(Error::new(StorageError::InvalidVersionID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidVersionID(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
|
||||
if !versioned {
|
||||
return Err(Error::new(StorageError::InvalidArgument(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut opts = get_default_opts(headers, None, false)
|
||||
.map_err(|err| Error::new(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string())))?;
|
||||
.map_err(|err| StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string()))?;
|
||||
|
||||
opts.version_id = {
|
||||
if is_dir_object(object) && vid.is_none() {
|
||||
@@ -122,24 +106,16 @@ pub async fn put_opts(
|
||||
|
||||
if let Some(ref id) = vid {
|
||||
if let Err(_err) = Uuid::parse_str(id.as_str()) {
|
||||
return Err(Error::new(StorageError::InvalidVersionID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidVersionID(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
|
||||
if !versioned {
|
||||
return Err(Error::new(StorageError::InvalidArgument(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut opts = put_opts_from_headers(headers, metadata)
|
||||
.map_err(|err| Error::new(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string())))?;
|
||||
.map_err(|err| StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string()))?;
|
||||
|
||||
opts.version_id = {
|
||||
if is_dir_object(object) && vid.is_none() {
|
||||
@@ -317,15 +293,13 @@ mod tests {
|
||||
|
||||
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"),
|
||||
match 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,15 +347,13 @@ mod tests {
|
||||
|
||||
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"),
|
||||
match 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -419,15 +391,13 @@ mod tests {
|
||||
|
||||
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"),
|
||||
match 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user