mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-08 14:23:13 +00:00
fix(site-replication): site replication flag issue and resolved the replication storm (#4120)
* fix(site-replication): Add Docker Compose setup for site replication testing - Fixed the site replication flag issue and resolved the replication storm. - Introduced a new directory for site replication tests with Docker Compose. - Created `docker-compose.yml` to define three RustFS sites and a setup container. - Added `README.md` to document the purpose, usage, and test flow of the replication setup. - Implemented `run-object-flow-check.sh` script to verify object replication across sites. - Configured health checks and volume permissions for the RustFS containers. - Enabled customization of access keys, bucket names, and other parameters via environment variables. * fix * fix
This commit is contained in:
@@ -63,7 +63,7 @@ use crate::table_catalog;
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use http::{HeaderMap, Uri};
|
||||
use rustfs_filemeta::{ReplicationStatusType, ReplicationType};
|
||||
use rustfs_filemeta::ReplicationType;
|
||||
use rustfs_s3_ops::S3Operation;
|
||||
use rustfs_targets::EventName;
|
||||
use rustfs_utils::CompressionAlgorithm;
|
||||
@@ -560,8 +560,13 @@ impl DefaultMultipartUsecase {
|
||||
..Default::default()
|
||||
};
|
||||
let mt2 = obj_info.user_defined.clone();
|
||||
let replicate_options =
|
||||
get_must_replicate_options(&mt2, "".to_string(), ReplicationStatusType::Empty, ReplicationType::Object, opts.clone());
|
||||
let replicate_options = get_must_replicate_options(
|
||||
&mt2,
|
||||
"".to_string(),
|
||||
opts.delete_marker_replication_status(),
|
||||
ReplicationType::Object,
|
||||
opts.clone(),
|
||||
);
|
||||
let dsc = must_replicate(&bucket, &key, replicate_options).await;
|
||||
|
||||
if dsc.replicate_any() {
|
||||
@@ -694,8 +699,13 @@ impl DefaultMultipartUsecase {
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let replicate_options =
|
||||
get_must_replicate_options(&mt2, "".to_string(), ReplicationStatusType::Empty, ReplicationType::Object, opts.clone());
|
||||
let replicate_options = get_must_replicate_options(
|
||||
&mt2,
|
||||
"".to_string(),
|
||||
opts.delete_marker_replication_status(),
|
||||
ReplicationType::Object,
|
||||
opts.clone(),
|
||||
);
|
||||
let dsc = must_replicate(&bucket, &key, replicate_options).await;
|
||||
if dsc.replicate_any() {
|
||||
insert_str(&mut opts.user_defined, SUFFIX_REPLICATION_TIMESTAMP, jiff::Zoned::now().to_string());
|
||||
|
||||
@@ -2994,8 +2994,13 @@ impl DefaultObjectUsecase {
|
||||
.map(|ctx| ctx.request_id.clone())
|
||||
.unwrap_or_else(|| request_context::RequestContext::fallback().request_id);
|
||||
|
||||
let repoptions =
|
||||
get_must_replicate_options(&mt2, "".to_string(), ReplicationStatusType::Empty, ReplicationType::Object, opts.clone());
|
||||
let repoptions = get_must_replicate_options(
|
||||
&mt2,
|
||||
"".to_string(),
|
||||
opts.delete_marker_replication_status(),
|
||||
ReplicationType::Object,
|
||||
opts.clone(),
|
||||
);
|
||||
let dsc = must_replicate(&bucket, &key, repoptions).await;
|
||||
|
||||
if dsc.replicate_any() {
|
||||
@@ -3105,8 +3110,13 @@ impl DefaultObjectUsecase {
|
||||
|
||||
let e_tag = obj_info.etag.clone().map(|etag| to_s3s_etag(&etag));
|
||||
|
||||
let repoptions =
|
||||
get_must_replicate_options(&mt2, "".to_string(), ReplicationStatusType::Empty, ReplicationType::Object, opts);
|
||||
let repoptions = get_must_replicate_options(
|
||||
&mt2,
|
||||
"".to_string(),
|
||||
opts.delete_marker_replication_status(),
|
||||
ReplicationType::Object,
|
||||
opts,
|
||||
);
|
||||
|
||||
let dsc = must_replicate(&bucket, &key, repoptions).await;
|
||||
let expiration = resolve_put_object_expiration(&bucket, &obj_info).await;
|
||||
|
||||
@@ -16,15 +16,16 @@ use super::{BucketVersioningSys, Result, StorageError};
|
||||
use crate::storage::storage_api::options_consumer::contract::{object::HTTPPreconditions, range::HTTPRangeSpec};
|
||||
use http::header::{IF_MATCH, IF_NONE_MATCH};
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use rustfs_filemeta::ReplicationStatusType;
|
||||
use rustfs_utils::http::{
|
||||
AMZ_BUCKET_REPLICATION_STATUS, SUFFIX_FORCE_DELETE, SUFFIX_REPLICATION_ACTUAL_OBJECT_SIZE, SUFFIX_REPLICATION_SSEC_CRC,
|
||||
SUFFIX_SOURCE_DELETEMARKER, SUFFIX_SOURCE_MTIME, SUFFIX_SOURCE_REPLICATION_REQUEST, SUFFIX_SOURCE_VERSION_ID, get_header,
|
||||
insert_header_map, is_encryption_metadata_key, is_internal_key,
|
||||
};
|
||||
use rustfs_utils::http::{
|
||||
AMZ_META_UNENCRYPTED_CONTENT_LENGTH, AMZ_META_UNENCRYPTED_CONTENT_MD5, AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER,
|
||||
AMZ_OBJECT_LOCK_MODE_LOWER, AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE_LOWER,
|
||||
};
|
||||
use rustfs_utils::http::{
|
||||
SUFFIX_FORCE_DELETE, SUFFIX_REPLICATION_ACTUAL_OBJECT_SIZE, SUFFIX_REPLICATION_SSEC_CRC, SUFFIX_SOURCE_DELETEMARKER,
|
||||
SUFFIX_SOURCE_MTIME, SUFFIX_SOURCE_REPLICATION_REQUEST, SUFFIX_SOURCE_VERSION_ID, get_header, insert_header_map,
|
||||
is_encryption_metadata_key, is_internal_key,
|
||||
};
|
||||
use s3s::header::X_AMZ_OBJECT_LOCK_MODE;
|
||||
use s3s::header::X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE;
|
||||
|
||||
@@ -275,6 +276,7 @@ pub fn get_complete_multipart_upload_opts(headers: &HeaderMap<HeaderValue>) -> s
|
||||
replication_request,
|
||||
..Default::default()
|
||||
};
|
||||
apply_replica_status_from_headers(headers, &mut opts);
|
||||
|
||||
fill_conditional_writes_opts_from_header(headers, &mut opts)?;
|
||||
Ok(opts)
|
||||
@@ -297,6 +299,7 @@ pub fn copy_src_opts(_bucket: &str, _object: &str, headers: &HeaderMap<HeaderVal
|
||||
|
||||
pub fn put_opts_from_headers(headers: &HeaderMap<HeaderValue>, metadata: HashMap<String, String>) -> Result<ObjectOptions> {
|
||||
let mut opts = get_default_opts(headers, metadata, false)?;
|
||||
apply_replica_status_from_headers(headers, &mut opts);
|
||||
if get_header(headers, SUFFIX_SOURCE_REPLICATION_REQUEST).as_deref() == Some("true") {
|
||||
opts.replication_request = true;
|
||||
if let Some(v) = get_header(headers, SUFFIX_SOURCE_MTIME) {
|
||||
@@ -313,6 +316,16 @@ pub fn put_opts_from_headers(headers: &HeaderMap<HeaderValue>, metadata: HashMap
|
||||
Ok(opts)
|
||||
}
|
||||
|
||||
fn apply_replica_status_from_headers(headers: &HeaderMap<HeaderValue>, opts: &mut ObjectOptions) {
|
||||
if headers
|
||||
.get(AMZ_BUCKET_REPLICATION_STATUS)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.is_some_and(|status| status.eq_ignore_ascii_case(ReplicationStatusType::Replica.as_str()))
|
||||
{
|
||||
opts.set_replica_status(ReplicationStatusType::Replica);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates default options for getting an object from a bucket.
|
||||
pub fn get_default_opts(
|
||||
_headers: &HeaderMap<HeaderValue>,
|
||||
@@ -788,13 +801,15 @@ mod tests {
|
||||
use super::{
|
||||
ENV_REJECT_ARCHIVE_CONTENT_ENCODING, SUPPORTED_HEADERS, copy_dst_opts, copy_src_opts, del_opts,
|
||||
detect_content_type_from_object_name, extract_metadata, extract_metadata_from_mime,
|
||||
extract_metadata_from_mime_with_object_name, filter_object_metadata, get_default_opts, get_opts, parse_copy_source_range,
|
||||
put_opts, put_opts_from_headers, validate_archive_content_encoding,
|
||||
extract_metadata_from_mime_with_object_name, filter_object_metadata, get_complete_multipart_upload_opts,
|
||||
get_default_opts, get_opts, parse_copy_source_range, put_opts, put_opts_from_headers, validate_archive_content_encoding,
|
||||
};
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use rustfs_filemeta::ReplicationStatusType;
|
||||
use rustfs_utils::http::{
|
||||
AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER, AMZ_OBJECT_LOCK_MODE_LOWER, AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE_LOWER,
|
||||
SUFFIX_FORCE_DELETE, SUFFIX_SOURCE_MTIME, SUFFIX_SOURCE_REPLICATION_REQUEST, insert_header,
|
||||
AMZ_BUCKET_REPLICATION_STATUS, AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER, AMZ_OBJECT_LOCK_MODE_LOWER,
|
||||
AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE_LOWER, SUFFIX_FORCE_DELETE, SUFFIX_SOURCE_MTIME, SUFFIX_SOURCE_REPLICATION_REQUEST,
|
||||
insert_header,
|
||||
};
|
||||
use s3s::S3ErrorCode;
|
||||
use std::collections::HashMap;
|
||||
@@ -1126,6 +1141,32 @@ mod tests {
|
||||
assert!(opts_invalid.mod_time.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_put_opts_from_headers_with_replica_status() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
AMZ_BUCKET_REPLICATION_STATUS,
|
||||
HeaderValue::from_static(ReplicationStatusType::Replica.as_str()),
|
||||
);
|
||||
|
||||
let opts = put_opts_from_headers(&headers, HashMap::new()).expect("replica status header should parse");
|
||||
|
||||
assert_eq!(opts.delete_marker_replication_status(), ReplicationStatusType::Replica);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_multipart_opts_with_replica_status() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
AMZ_BUCKET_REPLICATION_STATUS,
|
||||
HeaderValue::from_static(ReplicationStatusType::Replica.as_str()),
|
||||
);
|
||||
|
||||
let opts = get_complete_multipart_upload_opts(&headers).expect("replica status header should parse");
|
||||
|
||||
assert_eq!(opts.delete_marker_replication_status(), ReplicationStatusType::Replica);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_default_opts_with_metadata() {
|
||||
let headers = create_test_headers();
|
||||
|
||||
Reference in New Issue
Block a user