mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-29 00:17:11 +00:00
refactor(storage): inline object tagging entrypoints (#2486)
This commit is contained in:
@@ -46,7 +46,6 @@ use datafusion::arrow::{
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use http::{HeaderMap, HeaderValue, StatusCode};
|
use http::{HeaderMap, HeaderValue, StatusCode};
|
||||||
use md5::Context as Md5Context;
|
use md5::Context as Md5Context;
|
||||||
use metrics::{counter, histogram};
|
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
use rustfs_ecstore::bucket::quota::checker::QuotaChecker;
|
use rustfs_ecstore::bucket::quota::checker::QuotaChecker;
|
||||||
use rustfs_ecstore::bucket::{
|
use rustfs_ecstore::bucket::{
|
||||||
@@ -68,7 +67,7 @@ use rustfs_ecstore::bucket::{
|
|||||||
DeletedObjectReplicationInfo, check_replicate_delete, get_must_replicate_options, must_replicate, schedule_replication,
|
DeletedObjectReplicationInfo, check_replicate_delete, get_must_replicate_options, must_replicate, schedule_replication,
|
||||||
schedule_replication_delete,
|
schedule_replication_delete,
|
||||||
},
|
},
|
||||||
tagging::{decode_tags, encode_tags},
|
tagging::decode_tags,
|
||||||
utils::serialize,
|
utils::serialize,
|
||||||
versioning::VersioningApi,
|
versioning::VersioningApi,
|
||||||
versioning_sys::BucketVersioningSys,
|
versioning_sys::BucketVersioningSys,
|
||||||
@@ -1005,83 +1004,6 @@ impl DefaultObjectUsecase {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self, req))]
|
|
||||||
pub async fn execute_put_object_tagging(
|
|
||||||
&self,
|
|
||||||
req: S3Request<PutObjectTaggingInput>,
|
|
||||||
) -> S3Result<S3Response<PutObjectTaggingOutput>> {
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
let mut helper = OperationHelper::new(&req, EventName::ObjectTaggingPut, S3Operation::PutObjectTagging);
|
|
||||||
let PutObjectTaggingInput {
|
|
||||||
bucket,
|
|
||||||
key: object,
|
|
||||||
tagging,
|
|
||||||
..
|
|
||||||
} = req.input.clone();
|
|
||||||
|
|
||||||
crate::storage::s3_api::tagging::validate_object_tag_set(&tagging.tag_set)?;
|
|
||||||
|
|
||||||
let Some(store) = new_object_layer_fn() else {
|
|
||||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
|
||||||
};
|
|
||||||
|
|
||||||
let tags = encode_tags(tagging.tag_set);
|
|
||||||
debug!("Encoded tags: {}", tags);
|
|
||||||
|
|
||||||
let version_id = req.input.version_id.clone();
|
|
||||||
let opts = ObjectOptions {
|
|
||||||
version_id: parse_object_version_id(version_id)?.map(Into::into),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
store.put_object_tags(&bucket, &object, &tags, &opts).await.map_err(|e| {
|
|
||||||
error!("Failed to put object tags: {}", e);
|
|
||||||
counter!("rustfs.put_object_tagging.failure").increment(1);
|
|
||||||
ApiError::from(e)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let event_object_info = match store.get_object_info(&bucket, &object, &opts).await {
|
|
||||||
Ok(info) => Some(info),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(
|
|
||||||
bucket = %bucket,
|
|
||||||
object = %object,
|
|
||||||
version_id = ?req.input.version_id,
|
|
||||||
error = %err,
|
|
||||||
"failed to load object info for put-object-tagging notification; falling back to request context"
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
counter!("rustfs.put_object_tagging.success").increment(1);
|
|
||||||
|
|
||||||
let event_version_id = req
|
|
||||||
.input
|
|
||||||
.version_id
|
|
||||||
.as_deref()
|
|
||||||
.filter(|version_id| !version_id.is_empty())
|
|
||||||
.map(str::to_string)
|
|
||||||
.or_else(|| {
|
|
||||||
event_object_info
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|info| info.version_id.map(|version_id| version_id.to_string()))
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
if let Some(event_object_info) = event_object_info {
|
|
||||||
helper = helper.object(event_object_info);
|
|
||||||
}
|
|
||||||
helper = helper.version_id(event_version_id);
|
|
||||||
|
|
||||||
let result = Ok(S3Response::new(PutObjectTaggingOutput {
|
|
||||||
version_id: req.input.version_id.clone(),
|
|
||||||
}));
|
|
||||||
let _ = helper.complete(&result);
|
|
||||||
let duration = start_time.elapsed();
|
|
||||||
histogram!("rustfs.object_tagging.operation.duration.seconds", "operation" => "put").record(duration.as_secs_f64());
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(
|
#[instrument(
|
||||||
level = "debug",
|
level = "debug",
|
||||||
skip(self, req),
|
skip(self, req),
|
||||||
@@ -2229,74 +2151,6 @@ impl DefaultObjectUsecase {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self, req))]
|
|
||||||
pub async fn execute_delete_object_tagging(
|
|
||||||
&self,
|
|
||||||
req: S3Request<DeleteObjectTaggingInput>,
|
|
||||||
) -> S3Result<S3Response<DeleteObjectTaggingOutput>> {
|
|
||||||
let start_time = std::time::Instant::now();
|
|
||||||
let mut helper = OperationHelper::new(&req, EventName::ObjectTaggingDelete, S3Operation::DeleteObjectTagging);
|
|
||||||
let DeleteObjectTaggingInput {
|
|
||||||
bucket,
|
|
||||||
key: object,
|
|
||||||
version_id,
|
|
||||||
..
|
|
||||||
} = req.input.clone();
|
|
||||||
|
|
||||||
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_for_parse = version_id.clone();
|
|
||||||
let opts = ObjectOptions {
|
|
||||||
version_id: parse_object_version_id(version_id_for_parse)?.map(Into::into),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
store.delete_object_tags(&bucket, &object, &opts).await.map_err(|e| {
|
|
||||||
error!("Failed to delete object tags: {}", e);
|
|
||||||
ApiError::from(e)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let event_object_info = match store.get_object_info(&bucket, &object, &opts).await {
|
|
||||||
Ok(info) => Some(info),
|
|
||||||
Err(err) => {
|
|
||||||
warn!(
|
|
||||||
bucket = %bucket,
|
|
||||||
object = %object,
|
|
||||||
version_id = ?version_id,
|
|
||||||
error = %err,
|
|
||||||
"failed to load object info for delete-object-tagging notification; falling back to request context"
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
counter!("rustfs.delete_object_tagging.success").increment(1);
|
|
||||||
|
|
||||||
let event_version_id = version_id
|
|
||||||
.as_deref()
|
|
||||||
.filter(|value| !value.is_empty())
|
|
||||||
.map(str::to_string)
|
|
||||||
.or_else(|| {
|
|
||||||
event_object_info
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|info| info.version_id.map(|version_id| version_id.to_string()))
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
if let Some(event_object_info) = event_object_info {
|
|
||||||
helper = helper.object(event_object_info);
|
|
||||||
}
|
|
||||||
helper = helper.version_id(event_version_id);
|
|
||||||
|
|
||||||
let result = Ok(S3Response::new(DeleteObjectTaggingOutput { version_id }));
|
|
||||||
let _ = helper.complete(&result);
|
|
||||||
let duration = start_time.elapsed();
|
|
||||||
histogram!("rustfs.object_tagging.operation.duration.seconds", "operation" => "delete").record(duration.as_secs_f64());
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(level = "debug", skip(self, req))]
|
#[instrument(level = "debug", skip(self, req))]
|
||||||
pub async fn execute_head_object(&self, req: S3Request<HeadObjectInput>) -> S3Result<S3Response<HeadObjectOutput>> {
|
pub async fn execute_head_object(&self, req: S3Request<HeadObjectInput>) -> S3Result<S3Response<HeadObjectOutput>> {
|
||||||
let mut helper = OperationHelper::new(&req, EventName::ObjectAccessedHead, S3Operation::HeadObject).suppress_event();
|
let mut helper = OperationHelper::new(&req, EventName::ObjectAccessedHead, S3Operation::HeadObject).suppress_event();
|
||||||
@@ -2953,51 +2807,6 @@ mod tests {
|
|||||||
assert!(uuid.is_none());
|
assert!(uuid.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn execute_put_object_tagging_rejects_too_many_tags() {
|
|
||||||
let tag_set = (0..11)
|
|
||||||
.map(|index| Tag {
|
|
||||||
key: Some(format!("k{index}")),
|
|
||||||
value: Some(format!("v{index}")),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let input = PutObjectTaggingInput::builder()
|
|
||||||
.bucket("test-bucket".to_string())
|
|
||||||
.key("test-key".to_string())
|
|
||||||
.tagging(Tagging { tag_set })
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = build_request(input, Method::PUT);
|
|
||||||
let usecase = DefaultObjectUsecase::without_context();
|
|
||||||
|
|
||||||
let err = usecase.execute_put_object_tagging(req).await.unwrap_err();
|
|
||||||
assert_eq!(err.code(), &S3ErrorCode::InvalidTag);
|
|
||||||
assert!(err.to_string().contains("Cannot have more than 10 tags per object"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn execute_put_object_tagging_rejects_empty_tag_key_before_store_lookup() {
|
|
||||||
let input = PutObjectTaggingInput::builder()
|
|
||||||
.bucket("test-bucket".to_string())
|
|
||||||
.key("test-key".to_string())
|
|
||||||
.tagging(Tagging {
|
|
||||||
tag_set: vec![Tag {
|
|
||||||
key: Some(String::new()),
|
|
||||||
value: Some("v1".to_string()),
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = build_request(input, Method::PUT);
|
|
||||||
let usecase = DefaultObjectUsecase::without_context();
|
|
||||||
|
|
||||||
let err = usecase.execute_put_object_tagging(req).await.unwrap_err();
|
|
||||||
assert_eq!(err.code(), &S3ErrorCode::InvalidTag);
|
|
||||||
assert!(err.to_string().contains("Tag key cannot be empty"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn execute_copy_object_rejects_self_copy_without_replace_directive() {
|
async fn execute_copy_object_rejects_self_copy_without_replace_directive() {
|
||||||
let input = CopyObjectInput::builder()
|
let input = CopyObjectInput::builder()
|
||||||
@@ -3073,21 +2882,6 @@ mod tests {
|
|||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn execute_delete_object_tagging_returns_internal_error_when_store_uninitialized() {
|
|
||||||
let input = DeleteObjectTaggingInput::builder()
|
|
||||||
.bucket("test-bucket".to_string())
|
|
||||||
.key("test-key".to_string())
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = build_request(input, Method::DELETE);
|
|
||||||
let usecase = DefaultObjectUsecase::without_context();
|
|
||||||
|
|
||||||
let err = usecase.execute_delete_object_tagging(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()
|
||||||
@@ -3348,27 +3142,6 @@ mod tests {
|
|||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn execute_put_object_tagging_returns_internal_error_when_store_uninitialized() {
|
|
||||||
let input = PutObjectTaggingInput::builder()
|
|
||||||
.bucket("test-bucket".to_string())
|
|
||||||
.key("test-key".to_string())
|
|
||||||
.tagging(Tagging {
|
|
||||||
tag_set: vec![Tag {
|
|
||||||
key: Some("k".to_string()),
|
|
||||||
value: Some("v".to_string()),
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let req = build_request(input, Method::PUT);
|
|
||||||
let usecase = DefaultObjectUsecase::without_context();
|
|
||||||
|
|
||||||
let err = usecase.execute_put_object_tagging(req).await.unwrap_err();
|
|
||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn execute_head_object_rejects_range_with_part_number() {
|
async fn execute_head_object_rejects_range_with_part_number() {
|
||||||
let input = HeadObjectInput::builder()
|
let input = HeadObjectInput::builder()
|
||||||
|
|||||||
+134
-5
@@ -25,7 +25,7 @@ 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, decode_tags_to_map},
|
tagging::{decode_tags, decode_tags_to_map, encode_tags},
|
||||||
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},
|
||||||
@@ -268,8 +268,68 @@ impl S3 for FS {
|
|||||||
&self,
|
&self,
|
||||||
req: S3Request<DeleteObjectTaggingInput>,
|
req: S3Request<DeleteObjectTaggingInput>,
|
||||||
) -> S3Result<S3Response<DeleteObjectTaggingOutput>> {
|
) -> S3Result<S3Response<DeleteObjectTaggingOutput>> {
|
||||||
let usecase = DefaultObjectUsecase::from_global();
|
record_s3_op(S3Operation::DeleteObjectTagging, &req.input.bucket);
|
||||||
usecase.execute_delete_object_tagging(req).await
|
let start_time = std::time::Instant::now();
|
||||||
|
let mut helper = OperationHelper::new(&req, EventName::ObjectTaggingDelete, S3Operation::DeleteObjectTagging);
|
||||||
|
let DeleteObjectTaggingInput {
|
||||||
|
bucket,
|
||||||
|
key: object,
|
||||||
|
version_id,
|
||||||
|
..
|
||||||
|
} = req.input.clone();
|
||||||
|
|
||||||
|
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_for_parse = version_id.clone();
|
||||||
|
let opts = ObjectOptions {
|
||||||
|
version_id: parse_object_version_id(version_id_for_parse)?.map(Into::into),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
store.delete_object_tags(&bucket, &object, &opts).await.map_err(|e| {
|
||||||
|
error!("Failed to delete object tags: {}", e);
|
||||||
|
ApiError::from(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let event_object_info = match store.get_object_info(&bucket, &object, &opts).await {
|
||||||
|
Ok(info) => Some(info),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
bucket = %bucket,
|
||||||
|
object = %object,
|
||||||
|
version_id = ?version_id,
|
||||||
|
error = %err,
|
||||||
|
"failed to load object info for delete-object-tagging notification; falling back to request context"
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
counter!("rustfs.delete_object_tagging.success").increment(1);
|
||||||
|
|
||||||
|
let event_version_id = version_id
|
||||||
|
.as_deref()
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
.map(str::to_string)
|
||||||
|
.or_else(|| {
|
||||||
|
event_object_info
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|info| info.version_id.map(|version_id| version_id.to_string()))
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
if let Some(event_object_info) = event_object_info {
|
||||||
|
helper = helper.object(event_object_info);
|
||||||
|
}
|
||||||
|
helper = helper.version_id(event_version_id);
|
||||||
|
|
||||||
|
let result = Ok(S3Response::new(DeleteObjectTaggingOutput { version_id }));
|
||||||
|
let _ = helper.complete(&result);
|
||||||
|
let duration = start_time.elapsed();
|
||||||
|
histogram!("rustfs.object_tagging.operation.duration.seconds", "operation" => "delete").record(duration.as_secs_f64());
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete multiple objects
|
/// Delete multiple objects
|
||||||
@@ -932,8 +992,77 @@ impl S3 for FS {
|
|||||||
|
|
||||||
#[instrument(level = "debug", skip(self, req))]
|
#[instrument(level = "debug", skip(self, req))]
|
||||||
async fn put_object_tagging(&self, req: S3Request<PutObjectTaggingInput>) -> S3Result<S3Response<PutObjectTaggingOutput>> {
|
async fn put_object_tagging(&self, req: S3Request<PutObjectTaggingInput>) -> S3Result<S3Response<PutObjectTaggingOutput>> {
|
||||||
let usecase = DefaultObjectUsecase::from_global();
|
record_s3_op(S3Operation::PutObjectTagging, &req.input.bucket);
|
||||||
usecase.execute_put_object_tagging(req).await
|
let start_time = std::time::Instant::now();
|
||||||
|
let mut helper = OperationHelper::new(&req, EventName::ObjectTaggingPut, S3Operation::PutObjectTagging);
|
||||||
|
let PutObjectTaggingInput {
|
||||||
|
bucket,
|
||||||
|
key: object,
|
||||||
|
tagging,
|
||||||
|
..
|
||||||
|
} = req.input.clone();
|
||||||
|
|
||||||
|
crate::storage::s3_api::tagging::validate_object_tag_set(&tagging.tag_set)?;
|
||||||
|
|
||||||
|
let Some(store) = new_object_layer_fn() else {
|
||||||
|
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
let tags = encode_tags(tagging.tag_set);
|
||||||
|
debug!("Encoded tags: {}", tags);
|
||||||
|
|
||||||
|
let version_id = req.input.version_id.clone();
|
||||||
|
let opts = ObjectOptions {
|
||||||
|
version_id: parse_object_version_id(version_id)?.map(Into::into),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
store.put_object_tags(&bucket, &object, &tags, &opts).await.map_err(|e| {
|
||||||
|
error!("Failed to put object tags: {}", e);
|
||||||
|
counter!("rustfs.put_object_tagging.failure").increment(1);
|
||||||
|
ApiError::from(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let event_object_info = match store.get_object_info(&bucket, &object, &opts).await {
|
||||||
|
Ok(info) => Some(info),
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
bucket = %bucket,
|
||||||
|
object = %object,
|
||||||
|
version_id = ?req.input.version_id,
|
||||||
|
error = %err,
|
||||||
|
"failed to load object info for put-object-tagging notification; falling back to request context"
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
counter!("rustfs.put_object_tagging.success").increment(1);
|
||||||
|
|
||||||
|
let event_version_id = req
|
||||||
|
.input
|
||||||
|
.version_id
|
||||||
|
.as_deref()
|
||||||
|
.filter(|version_id| !version_id.is_empty())
|
||||||
|
.map(str::to_string)
|
||||||
|
.or_else(|| {
|
||||||
|
event_object_info
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|info| info.version_id.map(|version_id| version_id.to_string()))
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
if let Some(event_object_info) = event_object_info {
|
||||||
|
helper = helper.object(event_object_info);
|
||||||
|
}
|
||||||
|
helper = helper.version_id(event_version_id);
|
||||||
|
|
||||||
|
let result = Ok(S3Response::new(PutObjectTaggingOutput {
|
||||||
|
version_id: req.input.version_id.clone(),
|
||||||
|
}));
|
||||||
|
let _ = helper.complete(&result);
|
||||||
|
let duration = start_time.elapsed();
|
||||||
|
histogram!("rustfs.object_tagging.operation.duration.seconds", "operation" => "put").record(duration.as_secs_f64());
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn restore_object(&self, req: S3Request<RestoreObjectInput>) -> S3Result<S3Response<RestoreObjectOutput>> {
|
async fn restore_object(&self, req: S3Request<RestoreObjectInput>) -> S3Result<S3Response<RestoreObjectOutput>> {
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use rustfs_zip::CompressionFormat;
|
use rustfs_zip::CompressionFormat;
|
||||||
use s3s::dto::{
|
use s3s::dto::{
|
||||||
CORSConfiguration, CORSRule, Delimiter, GetObjectAclInput, GetObjectLegalHoldInput, GetObjectRetentionInput,
|
CORSConfiguration, CORSRule, DeleteObjectTaggingInput, Delimiter, GetObjectAclInput, GetObjectLegalHoldInput,
|
||||||
GetObjectTaggingInput, LambdaFunctionConfiguration, ObjectLockLegalHold, ObjectLockLegalHoldStatus, ObjectLockRetention,
|
GetObjectRetentionInput, GetObjectTaggingInput, LambdaFunctionConfiguration, ObjectLockLegalHold,
|
||||||
ObjectLockRetentionMode, QueueConfiguration, TopicConfiguration,
|
ObjectLockLegalHoldStatus, ObjectLockRetention, ObjectLockRetentionMode, PutObjectTaggingInput, QueueConfiguration, Tag,
|
||||||
|
Tagging, TopicConfiguration,
|
||||||
};
|
};
|
||||||
use s3s::{S3, S3Error, S3ErrorCode, S3Request, s3_error};
|
use s3s::{S3, S3Error, S3ErrorCode, S3Request, s3_error};
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
@@ -237,6 +238,82 @@ mod tests {
|
|||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_put_object_tagging_rejects_too_many_tags() {
|
||||||
|
let tag_set = (0..11)
|
||||||
|
.map(|index| Tag {
|
||||||
|
key: Some(format!("k{index}")),
|
||||||
|
value: Some(format!("v{index}")),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let input = PutObjectTaggingInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.tagging(Tagging { tag_set })
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fs = FS::new();
|
||||||
|
let err = fs.put_object_tagging(build_request(input, Method::PUT)).await.unwrap_err();
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InvalidTag);
|
||||||
|
assert!(err.to_string().contains("Cannot have more than 10 tags per object"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_put_object_tagging_rejects_empty_tag_key_before_store_lookup() {
|
||||||
|
let input = PutObjectTaggingInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.tagging(Tagging {
|
||||||
|
tag_set: vec![Tag {
|
||||||
|
key: Some(String::new()),
|
||||||
|
value: Some("v1".to_string()),
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fs = FS::new();
|
||||||
|
let err = fs.put_object_tagging(build_request(input, Method::PUT)).await.unwrap_err();
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InvalidTag);
|
||||||
|
assert!(err.to_string().contains("Tag key cannot be empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_put_object_tagging_returns_internal_error_when_store_uninitialized() {
|
||||||
|
let input = PutObjectTaggingInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.tagging(Tagging {
|
||||||
|
tag_set: vec![Tag {
|
||||||
|
key: Some("k".to_string()),
|
||||||
|
value: Some("v".to_string()),
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fs = FS::new();
|
||||||
|
let err = fs.put_object_tagging(build_request(input, Method::PUT)).await.unwrap_err();
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_object_tagging_returns_internal_error_when_store_uninitialized() {
|
||||||
|
let input = DeleteObjectTaggingInput::builder()
|
||||||
|
.bucket("test-bucket".to_string())
|
||||||
|
.key("test-key".to_string())
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let fs = FS::new();
|
||||||
|
let err = fs
|
||||||
|
.delete_object_tagging(build_request(input, Method::DELETE))
|
||||||
|
.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