mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 05:06:28 +00:00
refactor(storage): inline object metadata read entrypoints (#2485)
Signed-off-by: 安正超 <anzhengchao@gmail.com> Co-authored-by: houseme <housemecn@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -36,7 +36,6 @@ use crate::storage::options::{
|
|||||||
filter_object_metadata, get_content_sha256_with_query, get_opts, normalize_content_encoding_for_storage, put_opts,
|
filter_object_metadata, get_content_sha256_with_query, get_opts, normalize_content_encoding_for_storage, put_opts,
|
||||||
validate_archive_content_encoding,
|
validate_archive_content_encoding,
|
||||||
};
|
};
|
||||||
use crate::storage::s3_api::acl;
|
|
||||||
use crate::storage::s3_api::multipart::parse_list_parts_params;
|
use crate::storage::s3_api::multipart::parse_list_parts_params;
|
||||||
use crate::storage::timeout_wrapper::{RequestTimeoutWrapper, TimeoutConfig};
|
use crate::storage::timeout_wrapper::{RequestTimeoutWrapper, TimeoutConfig};
|
||||||
use crate::storage::*;
|
use crate::storage::*;
|
||||||
@@ -1170,23 +1169,6 @@ impl DefaultObjectUsecase {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_get_object_acl(&self, req: S3Request<GetObjectAclInput>) -> S3Result<S3Response<GetObjectAclOutput>> {
|
|
||||||
let GetObjectAclInput {
|
|
||||||
bucket, key, version_id, ..
|
|
||||||
} = req.input;
|
|
||||||
|
|
||||||
let Some(store) = new_object_layer_fn() else {
|
|
||||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
|
||||||
};
|
|
||||||
|
|
||||||
let opts: ObjectOptions = get_opts(&bucket, &key, version_id.clone(), None, &req.headers)
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::from)?;
|
|
||||||
store.get_object_info(&bucket, &key, &opts).await.map_err(ApiError::from)?;
|
|
||||||
|
|
||||||
Ok(S3Response::new(acl::build_get_object_acl_output()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn execute_get_object_attributes(
|
pub async fn execute_get_object_attributes(
|
||||||
&self,
|
&self,
|
||||||
req: S3Request<GetObjectAttributesInput>,
|
req: S3Request<GetObjectAttributesInput>,
|
||||||
@@ -1416,61 +1398,6 @@ impl DefaultObjectUsecase {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_get_object_legal_hold(
|
|
||||||
&self,
|
|
||||||
req: S3Request<GetObjectLegalHoldInput>,
|
|
||||||
) -> S3Result<S3Response<GetObjectLegalHoldOutput>> {
|
|
||||||
let mut helper =
|
|
||||||
OperationHelper::new(&req, EventName::ObjectAccessedGetLegalHold, S3Operation::GetObjectLegalHold).suppress_event();
|
|
||||||
let GetObjectLegalHoldInput {
|
|
||||||
bucket, key, version_id, ..
|
|
||||||
} = req.input.clone();
|
|
||||||
|
|
||||||
let Some(store) = new_object_layer_fn() else {
|
|
||||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = store
|
|
||||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::from)?;
|
|
||||||
|
|
||||||
validate_bucket_object_lock_enabled(&bucket).await?;
|
|
||||||
|
|
||||||
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, None, &req.headers)
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::from)?;
|
|
||||||
|
|
||||||
let object_info = store.get_object_info(&bucket, &key, &opts).await.map_err(|e| {
|
|
||||||
error!("get_object_info failed, {}", e.to_string());
|
|
||||||
s3_error!(InternalError, "{}", e.to_string())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let legal_hold = object_info
|
|
||||||
.user_defined
|
|
||||||
.get(AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER)
|
|
||||||
.map(|v| v.as_str().to_string());
|
|
||||||
|
|
||||||
let status = if let Some(v) = legal_hold {
|
|
||||||
v
|
|
||||||
} else {
|
|
||||||
ObjectLockLegalHoldStatus::OFF.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let output = GetObjectLegalHoldOutput {
|
|
||||||
legal_hold: Some(ObjectLockLegalHold {
|
|
||||||
status: Some(ObjectLockLegalHoldStatus::from(status)),
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
let version_id = req.input.version_id.clone().unwrap_or_else(|| Uuid::new_v4().to_string());
|
|
||||||
helper = helper.object(object_info).version_id(version_id);
|
|
||||||
|
|
||||||
let result = Ok(S3Response::new(output));
|
|
||||||
let _ = helper.complete(&result);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
pub async fn execute_get_object_lock_configuration(
|
pub async fn execute_get_object_lock_configuration(
|
||||||
&self,
|
&self,
|
||||||
@@ -1500,95 +1427,6 @@ impl DefaultObjectUsecase {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn execute_get_object_retention(
|
|
||||||
&self,
|
|
||||||
req: S3Request<GetObjectRetentionInput>,
|
|
||||||
) -> S3Result<S3Response<GetObjectRetentionOutput>> {
|
|
||||||
let mut helper =
|
|
||||||
OperationHelper::new(&req, EventName::ObjectAccessedGetRetention, S3Operation::GetObjectRetention).suppress_event();
|
|
||||||
let GetObjectRetentionInput {
|
|
||||||
bucket, key, version_id, ..
|
|
||||||
} = req.input.clone();
|
|
||||||
|
|
||||||
let Some(store) = new_object_layer_fn() else {
|
|
||||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
|
||||||
};
|
|
||||||
|
|
||||||
validate_bucket_object_lock_enabled(&bucket).await?;
|
|
||||||
|
|
||||||
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, None, &req.headers)
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::from)?;
|
|
||||||
|
|
||||||
let object_info = store.get_object_info(&bucket, &key, &opts).await.map_err(|e| {
|
|
||||||
error!("get_object_info failed, {}", e.to_string());
|
|
||||||
s3_error!(InternalError, "{}", e.to_string())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mode = object_info
|
|
||||||
.user_defined
|
|
||||||
.get("x-amz-object-lock-mode")
|
|
||||||
.map(|v| ObjectLockRetentionMode::from(v.as_str().to_string()));
|
|
||||||
|
|
||||||
let retain_until_date = object_info
|
|
||||||
.user_defined
|
|
||||||
.get("x-amz-object-lock-retain-until-date")
|
|
||||||
.and_then(|v| OffsetDateTime::parse(v.as_str(), &Rfc3339).ok())
|
|
||||||
.map(Timestamp::from);
|
|
||||||
|
|
||||||
let output = GetObjectRetentionOutput {
|
|
||||||
retention: Some(ObjectLockRetention { mode, retain_until_date }),
|
|
||||||
};
|
|
||||||
let version_id = req.input.version_id.clone().unwrap_or_default();
|
|
||||||
helper = helper.object(object_info).version_id(version_id);
|
|
||||||
|
|
||||||
let result = Ok(S3Response::new(output));
|
|
||||||
let _ = helper.complete(&result);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self, req))]
|
|
||||||
pub async fn execute_get_object_tagging(
|
|
||||||
&self,
|
|
||||||
req: S3Request<GetObjectTaggingInput>,
|
|
||||||
) -> S3Result<S3Response<GetObjectTaggingOutput>> {
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
let GetObjectTaggingInput { bucket, key: object, .. } = req.input;
|
|
||||||
|
|
||||||
info!("Starting get_object_tagging for bucket: {}, object: {}", bucket, object);
|
|
||||||
|
|
||||||
let Some(store) = new_object_layer_fn() else {
|
|
||||||
error!("Store not initialized");
|
|
||||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
|
||||||
};
|
|
||||||
|
|
||||||
let version_id = req.input.version_id.clone();
|
|
||||||
let opts = ObjectOptions {
|
|
||||||
version_id: parse_object_version_id(version_id)?.map(Into::into),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let tags = store.get_object_tags(&bucket, &object, &opts).await.map_err(|e| {
|
|
||||||
if is_err_object_not_found(&e) {
|
|
||||||
error!("Object not found: {}", e);
|
|
||||||
return s3_error!(NoSuchKey);
|
|
||||||
}
|
|
||||||
error!("Failed to get object tags: {}", e);
|
|
||||||
ApiError::from(e).into()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let tag_set = decode_tags(tags.as_str());
|
|
||||||
debug!("Decoded tag set: {:?}", tag_set);
|
|
||||||
|
|
||||||
counter!("rustfs.get_object_tagging.success").increment(1);
|
|
||||||
let duration = start_time.elapsed();
|
|
||||||
histogram!("rustfs.object_tagging.operation.duration.seconds", "operation" => "get").record(duration.as_secs_f64());
|
|
||||||
Ok(S3Response::new(GetObjectTaggingOutput {
|
|
||||||
tag_set,
|
|
||||||
version_id: req.input.version_id.clone(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self, req))]
|
#[instrument(level = "debug", skip(self, req))]
|
||||||
pub async fn execute_copy_object(&self, req: S3Request<CopyObjectInput>) -> S3Result<S3Response<CopyObjectOutput>> {
|
pub async fn execute_copy_object(&self, req: S3Request<CopyObjectInput>) -> S3Result<S3Response<CopyObjectOutput>> {
|
||||||
let mut helper = OperationHelper::new(&req, EventName::ObjectCreatedCopy, S3Operation::CopyObject);
|
let mut helper = OperationHelper::new(&req, EventName::ObjectCreatedCopy, S3Operation::CopyObject);
|
||||||
@@ -3250,21 +3088,6 @@ mod tests {
|
|||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn execute_get_object_acl_returns_internal_error_when_store_uninitialized() {
|
|
||||||
let input = GetObjectAclInput::builder()
|
|
||||||
.bucket("test-bucket".to_string())
|
|
||||||
.key("test-key".to_string())
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = build_request(input, Method::GET);
|
|
||||||
let usecase = DefaultObjectUsecase::without_context();
|
|
||||||
|
|
||||||
let err = usecase.execute_get_object_acl(req).await.unwrap_err();
|
|
||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn execute_get_object_attributes_returns_internal_error_when_store_uninitialized() {
|
async fn execute_get_object_attributes_returns_internal_error_when_store_uninitialized() {
|
||||||
let input = GetObjectAttributesInput::builder()
|
let input = GetObjectAttributesInput::builder()
|
||||||
@@ -3372,51 +3195,6 @@ mod tests {
|
|||||||
assert!(build_put_object_expiration_header(&event).is_none());
|
assert!(build_put_object_expiration_header(&event).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn execute_get_object_legal_hold_returns_internal_error_when_store_uninitialized() {
|
|
||||||
let input = GetObjectLegalHoldInput::builder()
|
|
||||||
.bucket("test-bucket".to_string())
|
|
||||||
.key("test-key".to_string())
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = build_request(input, Method::GET);
|
|
||||||
let usecase = DefaultObjectUsecase::without_context();
|
|
||||||
|
|
||||||
let err = usecase.execute_get_object_legal_hold(req).await.unwrap_err();
|
|
||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn execute_get_object_retention_returns_internal_error_when_store_uninitialized() {
|
|
||||||
let input = GetObjectRetentionInput::builder()
|
|
||||||
.bucket("test-bucket".to_string())
|
|
||||||
.key("test-key".to_string())
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = build_request(input, Method::GET);
|
|
||||||
let usecase = DefaultObjectUsecase::without_context();
|
|
||||||
|
|
||||||
let err = usecase.execute_get_object_retention(req).await.unwrap_err();
|
|
||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn execute_get_object_tagging_returns_internal_error_when_store_uninitialized() {
|
|
||||||
let input = GetObjectTaggingInput::builder()
|
|
||||||
.bucket("test-bucket".to_string())
|
|
||||||
.key("test-key".to_string())
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = build_request(input, Method::GET);
|
|
||||||
let usecase = DefaultObjectUsecase::without_context();
|
|
||||||
|
|
||||||
let err = usecase.execute_get_object_tagging(req).await.unwrap_err();
|
|
||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn execute_put_object_acl_returns_internal_error_when_store_uninitialized() {
|
async fn execute_put_object_acl_returns_internal_error_when_store_uninitialized() {
|
||||||
let input = PutObjectAclInput::builder()
|
let input = PutObjectAclInput::builder()
|
||||||
|
|||||||
+151
-10
@@ -15,11 +15,17 @@
|
|||||||
use crate::app::bucket_usecase::DefaultBucketUsecase;
|
use crate::app::bucket_usecase::DefaultBucketUsecase;
|
||||||
use crate::app::multipart_usecase::DefaultMultipartUsecase;
|
use crate::app::multipart_usecase::DefaultMultipartUsecase;
|
||||||
use crate::app::object_usecase::DefaultObjectUsecase;
|
use crate::app::object_usecase::DefaultObjectUsecase;
|
||||||
|
use crate::error::ApiError;
|
||||||
|
use crate::storage::helper::OperationHelper;
|
||||||
|
use crate::storage::options::get_opts;
|
||||||
|
use crate::storage::s3_api::acl;
|
||||||
|
use crate::storage::validate_bucket_object_lock_enabled;
|
||||||
|
use metrics::{counter, histogram};
|
||||||
use rustfs_ecstore::{
|
use rustfs_ecstore::{
|
||||||
bucket::{
|
bucket::{
|
||||||
metadata::{BUCKET_ACCELERATE_CONFIG, BUCKET_LOGGING_CONFIG, BUCKET_REQUEST_PAYMENT_CONFIG, BUCKET_WEBSITE_CONFIG},
|
metadata::{BUCKET_ACCELERATE_CONFIG, BUCKET_LOGGING_CONFIG, BUCKET_REQUEST_PAYMENT_CONFIG, BUCKET_WEBSITE_CONFIG},
|
||||||
metadata_sys,
|
metadata_sys,
|
||||||
tagging::decode_tags_to_map,
|
tagging::{decode_tags, decode_tags_to_map},
|
||||||
utils::serialize,
|
utils::serialize,
|
||||||
},
|
},
|
||||||
error::{StorageError, is_err_bucket_not_found, is_err_object_not_found, is_err_version_not_found},
|
error::{StorageError, is_err_bucket_not_found, is_err_object_not_found, is_err_version_not_found},
|
||||||
@@ -27,9 +33,12 @@ use rustfs_ecstore::{
|
|||||||
store_api::{BucketOperations, BucketOptions, ObjectOperations, ObjectOptions},
|
store_api::{BucketOperations, BucketOptions, ObjectOperations, ObjectOptions},
|
||||||
};
|
};
|
||||||
use rustfs_s3_common::{S3Operation, record_s3_op};
|
use rustfs_s3_common::{S3Operation, record_s3_op};
|
||||||
|
use rustfs_targets::EventName;
|
||||||
|
use rustfs_utils::http::headers::AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER;
|
||||||
use s3s::{S3, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, dto::*, s3_error};
|
use s3s::{S3, S3Error, S3ErrorCode, S3Request, S3Response, S3Result, dto::*, s3_error};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use tracing::{debug, error, instrument, warn};
|
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
|
||||||
|
use tracing::{debug, error, info, instrument, warn};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -452,8 +461,20 @@ impl S3 for FS {
|
|||||||
|
|
||||||
async fn get_object_acl(&self, req: S3Request<GetObjectAclInput>) -> S3Result<S3Response<GetObjectAclOutput>> {
|
async fn get_object_acl(&self, req: S3Request<GetObjectAclInput>) -> S3Result<S3Response<GetObjectAclOutput>> {
|
||||||
record_s3_op(S3Operation::GetObjectAcl, &req.input.bucket);
|
record_s3_op(S3Operation::GetObjectAcl, &req.input.bucket);
|
||||||
let usecase = DefaultObjectUsecase::from_global();
|
let GetObjectAclInput {
|
||||||
usecase.execute_get_object_acl(req).await
|
bucket, key, version_id, ..
|
||||||
|
} = req.input;
|
||||||
|
|
||||||
|
let Some(store) = new_object_layer_fn() else {
|
||||||
|
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, None, &req.headers)
|
||||||
|
.await
|
||||||
|
.map_err(ApiError::from)?;
|
||||||
|
store.get_object_info(&bucket, &key, &opts).await.map_err(ApiError::from)?;
|
||||||
|
|
||||||
|
Ok(S3Response::new(acl::build_get_object_acl_output()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_object_attributes(
|
async fn get_object_attributes(
|
||||||
@@ -468,8 +489,55 @@ impl S3 for FS {
|
|||||||
&self,
|
&self,
|
||||||
req: S3Request<GetObjectLegalHoldInput>,
|
req: S3Request<GetObjectLegalHoldInput>,
|
||||||
) -> S3Result<S3Response<GetObjectLegalHoldOutput>> {
|
) -> S3Result<S3Response<GetObjectLegalHoldOutput>> {
|
||||||
let usecase = DefaultObjectUsecase::from_global();
|
let mut helper =
|
||||||
usecase.execute_get_object_legal_hold(req).await
|
OperationHelper::new(&req, EventName::ObjectAccessedGetLegalHold, S3Operation::GetObjectLegalHold).suppress_event();
|
||||||
|
let GetObjectLegalHoldInput {
|
||||||
|
bucket, key, version_id, ..
|
||||||
|
} = req.input.clone();
|
||||||
|
|
||||||
|
let Some(store) = new_object_layer_fn() else {
|
||||||
|
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = store
|
||||||
|
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||||
|
.await
|
||||||
|
.map_err(ApiError::from)?;
|
||||||
|
|
||||||
|
validate_bucket_object_lock_enabled(&bucket).await?;
|
||||||
|
|
||||||
|
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, None, &req.headers)
|
||||||
|
.await
|
||||||
|
.map_err(ApiError::from)?;
|
||||||
|
|
||||||
|
let object_info = store.get_object_info(&bucket, &key, &opts).await.map_err(|e| {
|
||||||
|
error!("get_object_info failed, {}", e.to_string());
|
||||||
|
s3_error!(InternalError, "{}", e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let legal_hold = object_info
|
||||||
|
.user_defined
|
||||||
|
.get(AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER)
|
||||||
|
.map(|v| v.as_str().to_string());
|
||||||
|
|
||||||
|
let status = if let Some(v) = legal_hold {
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
ObjectLockLegalHoldStatus::OFF.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let output = GetObjectLegalHoldOutput {
|
||||||
|
legal_hold: Some(ObjectLockLegalHold {
|
||||||
|
status: Some(ObjectLockLegalHoldStatus::from(status)),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let version_id = req.input.version_id.clone().unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||||
|
helper = helper.object(object_info).version_id(version_id);
|
||||||
|
|
||||||
|
let result = Ok(S3Response::new(output));
|
||||||
|
let _ = helper.complete(&result);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
@@ -486,15 +554,88 @@ impl S3 for FS {
|
|||||||
&self,
|
&self,
|
||||||
req: S3Request<GetObjectRetentionInput>,
|
req: S3Request<GetObjectRetentionInput>,
|
||||||
) -> S3Result<S3Response<GetObjectRetentionOutput>> {
|
) -> S3Result<S3Response<GetObjectRetentionOutput>> {
|
||||||
let usecase = DefaultObjectUsecase::from_global();
|
let mut helper =
|
||||||
usecase.execute_get_object_retention(req).await
|
OperationHelper::new(&req, EventName::ObjectAccessedGetRetention, S3Operation::GetObjectRetention).suppress_event();
|
||||||
|
let GetObjectRetentionInput {
|
||||||
|
bucket, key, version_id, ..
|
||||||
|
} = req.input.clone();
|
||||||
|
|
||||||
|
let Some(store) = new_object_layer_fn() else {
|
||||||
|
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
validate_bucket_object_lock_enabled(&bucket).await?;
|
||||||
|
|
||||||
|
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, None, &req.headers)
|
||||||
|
.await
|
||||||
|
.map_err(ApiError::from)?;
|
||||||
|
|
||||||
|
let object_info = store.get_object_info(&bucket, &key, &opts).await.map_err(|e| {
|
||||||
|
error!("get_object_info failed, {}", e.to_string());
|
||||||
|
s3_error!(InternalError, "{}", e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mode = object_info
|
||||||
|
.user_defined
|
||||||
|
.get("x-amz-object-lock-mode")
|
||||||
|
.map(|v| ObjectLockRetentionMode::from(v.as_str().to_string()));
|
||||||
|
|
||||||
|
let retain_until_date = object_info
|
||||||
|
.user_defined
|
||||||
|
.get("x-amz-object-lock-retain-until-date")
|
||||||
|
.and_then(|v| OffsetDateTime::parse(v.as_str(), &Rfc3339).ok())
|
||||||
|
.map(Timestamp::from);
|
||||||
|
|
||||||
|
let output = GetObjectRetentionOutput {
|
||||||
|
retention: Some(ObjectLockRetention { mode, retain_until_date }),
|
||||||
|
};
|
||||||
|
let version_id = req.input.version_id.clone().unwrap_or_default();
|
||||||
|
helper = helper.object(object_info).version_id(version_id);
|
||||||
|
|
||||||
|
let result = Ok(S3Response::new(output));
|
||||||
|
let _ = helper.complete(&result);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self))]
|
#[instrument(level = "debug", skip(self))]
|
||||||
async fn get_object_tagging(&self, req: S3Request<GetObjectTaggingInput>) -> S3Result<S3Response<GetObjectTaggingOutput>> {
|
async fn get_object_tagging(&self, req: S3Request<GetObjectTaggingInput>) -> S3Result<S3Response<GetObjectTaggingOutput>> {
|
||||||
record_s3_op(S3Operation::GetObjectTagging, &req.input.bucket);
|
record_s3_op(S3Operation::GetObjectTagging, &req.input.bucket);
|
||||||
let usecase = DefaultObjectUsecase::from_global();
|
let start_time = std::time::Instant::now();
|
||||||
usecase.execute_get_object_tagging(req).await
|
let bucket = req.input.bucket.as_str();
|
||||||
|
let object = req.input.key.as_str();
|
||||||
|
|
||||||
|
info!("Starting get_object_tagging for bucket: {}, object: {}", bucket, object);
|
||||||
|
|
||||||
|
let Some(store) = new_object_layer_fn() else {
|
||||||
|
error!("Store not initialized");
|
||||||
|
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let version_id = req.input.version_id.clone();
|
||||||
|
let opts = ObjectOptions {
|
||||||
|
version_id: parse_object_version_id(version_id)?.map(Into::into),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let tags = store.get_object_tags(bucket, object, &opts).await.map_err(|e| {
|
||||||
|
if is_err_object_not_found(&e) {
|
||||||
|
error!("Object not found: {}", e);
|
||||||
|
return s3_error!(NoSuchKey);
|
||||||
|
}
|
||||||
|
error!("Failed to get object tags: {}", e);
|
||||||
|
ApiError::from(e).into()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let tag_set = decode_tags(tags.as_str());
|
||||||
|
debug!("Decoded tag set: {:?}", tag_set);
|
||||||
|
|
||||||
|
counter!("rustfs.get_object_tagging.success").increment(1);
|
||||||
|
let duration = start_time.elapsed();
|
||||||
|
histogram!("rustfs.object_tagging.operation.duration.seconds", "operation" => "get").record(duration.as_secs_f64());
|
||||||
|
Ok(S3Response::new(GetObjectTaggingOutput {
|
||||||
|
tag_set,
|
||||||
|
version_id: req.input.version_id.clone(),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self, req))]
|
#[instrument(level = "debug", skip(self, req))]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ mod tests {
|
|||||||
process_lambda_configurations, process_queue_configurations, process_topic_configurations,
|
process_lambda_configurations, process_queue_configurations, process_topic_configurations,
|
||||||
validate_bucket_object_lock_enabled, validate_list_object_unordered_with_delimiter,
|
validate_bucket_object_lock_enabled, validate_list_object_unordered_with_delimiter,
|
||||||
};
|
};
|
||||||
use http::{HeaderMap, HeaderValue, StatusCode};
|
use http::{Extensions, HeaderMap, HeaderValue, Method, StatusCode, Uri};
|
||||||
use rustfs_config::MI_B;
|
use rustfs_config::MI_B;
|
||||||
use rustfs_ecstore::bucket::{metadata::BucketMetadata, metadata_sys};
|
use rustfs_ecstore::bucket::{metadata::BucketMetadata, metadata_sys};
|
||||||
use rustfs_ecstore::set_disk::DEFAULT_READ_BUFFER_SIZE;
|
use rustfs_ecstore::set_disk::DEFAULT_READ_BUFFER_SIZE;
|
||||||
@@ -35,12 +35,27 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use rustfs_zip::CompressionFormat;
|
use rustfs_zip::CompressionFormat;
|
||||||
use s3s::dto::{
|
use s3s::dto::{
|
||||||
CORSConfiguration, CORSRule, Delimiter, LambdaFunctionConfiguration, ObjectLockLegalHold, ObjectLockLegalHoldStatus,
|
CORSConfiguration, CORSRule, Delimiter, GetObjectAclInput, GetObjectLegalHoldInput, GetObjectRetentionInput,
|
||||||
ObjectLockRetention, ObjectLockRetentionMode, QueueConfiguration, TopicConfiguration,
|
GetObjectTaggingInput, LambdaFunctionConfiguration, ObjectLockLegalHold, ObjectLockLegalHoldStatus, ObjectLockRetention,
|
||||||
|
ObjectLockRetentionMode, QueueConfiguration, TopicConfiguration,
|
||||||
};
|
};
|
||||||
use s3s::{S3Error, S3ErrorCode, s3_error};
|
use s3s::{S3, S3Error, S3ErrorCode, S3Request, s3_error};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
|
fn build_request<T>(input: T, method: Method) -> S3Request<T> {
|
||||||
|
S3Request {
|
||||||
|
input,
|
||||||
|
method,
|
||||||
|
uri: Uri::from_static("/"),
|
||||||
|
headers: HeaderMap::new(),
|
||||||
|
extensions: Extensions::new(),
|
||||||
|
credentials: None,
|
||||||
|
region: None,
|
||||||
|
service: None,
|
||||||
|
trailing_headers: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fs_creation() {
|
fn test_fs_creation() {
|
||||||
let _fs = FS::new();
|
let _fs = FS::new();
|
||||||
@@ -170,6 +185,58 @@ mod tests {
|
|||||||
assert_eq!(gz_format.extension(), "gz");
|
assert_eq!(gz_format.extension(), "gz");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_object_acl_returns_internal_error_when_store_uninitialized() {
|
||||||
|
let input = GetObjectAclInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fs = FS::new();
|
||||||
|
let err = fs.get_object_acl(build_request(input, Method::GET)).await.unwrap_err();
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_object_legal_hold_returns_internal_error_when_store_uninitialized() {
|
||||||
|
let input = GetObjectLegalHoldInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fs = FS::new();
|
||||||
|
let err = fs.get_object_legal_hold(build_request(input, Method::GET)).await.unwrap_err();
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_object_retention_returns_internal_error_when_store_uninitialized() {
|
||||||
|
let input = GetObjectRetentionInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fs = FS::new();
|
||||||
|
let err = fs.get_object_retention(build_request(input, Method::GET)).await.unwrap_err();
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_object_tagging_returns_internal_error_when_store_uninitialized() {
|
||||||
|
let input = GetObjectTaggingInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fs = FS::new();
|
||||||
|
let err = fs.get_object_tagging(build_request(input, Method::GET)).await.unwrap_err();
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_adaptive_buffer_size_with_profile() {
|
fn test_adaptive_buffer_size_with_profile() {
|
||||||
const KB: i64 = 1024;
|
const KB: i64 = 1024;
|
||||||
|
|||||||
Reference in New Issue
Block a user