mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-18 02:33:15 +00:00
fix(replication): snapshot existing object admission targets (#5634)
This commit is contained in:
@@ -552,10 +552,26 @@ pub enum MrfOpKind {
|
||||
Object,
|
||||
#[serde(rename = "metadata")]
|
||||
Metadata,
|
||||
#[serde(rename = "heal")]
|
||||
Heal,
|
||||
#[serde(rename = "existingObject")]
|
||||
ExistingObject,
|
||||
#[serde(rename = "delete")]
|
||||
Delete,
|
||||
}
|
||||
|
||||
impl MrfOpKind {
|
||||
pub fn replication_type(self) -> ReplicationType {
|
||||
match self {
|
||||
Self::Object => ReplicationType::Object,
|
||||
Self::Metadata => ReplicationType::Metadata,
|
||||
Self::Heal => ReplicationType::Heal,
|
||||
Self::ExistingObject => ReplicationType::ExistingObject,
|
||||
Self::Delete => ReplicationType::Delete,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct MrfReplicateEntry {
|
||||
#[serde(rename = "bucket")]
|
||||
@@ -838,16 +854,17 @@ impl ReplicationWorkerOperation for ReplicateObjectInfo {
|
||||
version_id: self.version_id,
|
||||
retry_count: retry_count_to_mrf(self.retry_count),
|
||||
size: self.size,
|
||||
op: if self.op_type == ReplicationType::Metadata {
|
||||
MrfOpKind::Metadata
|
||||
} else {
|
||||
MrfOpKind::Object
|
||||
op: match self.op_type {
|
||||
ReplicationType::Metadata => MrfOpKind::Metadata,
|
||||
ReplicationType::Heal => MrfOpKind::Heal,
|
||||
ReplicationType::ExistingObject => MrfOpKind::ExistingObject,
|
||||
_ => MrfOpKind::Object,
|
||||
},
|
||||
force_delete: false,
|
||||
delete_marker_version_id: None,
|
||||
delete_marker: false,
|
||||
delete_marker_mtime: None,
|
||||
target_arns: self.dsc.replicate_target_arns(),
|
||||
target_arns: self.admitted_target_arns(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,6 +895,25 @@ static REPL_STATUS_REGEX: LazyLock<Regex> = LazyLock::new(|| match Regex::new(r"
|
||||
});
|
||||
|
||||
impl ReplicateObjectInfo {
|
||||
/// Returns the target set captured when this queued operation was admitted.
|
||||
/// Resync decisions are more specific than the general heal decision and must
|
||||
/// win for ExistingObject work.
|
||||
pub fn admitted_target_arns(&self) -> Vec<String> {
|
||||
if self.op_type == ReplicationType::ExistingObject && !self.existing_obj_resync.is_empty() {
|
||||
let mut arns = self
|
||||
.existing_obj_resync
|
||||
.targets
|
||||
.iter()
|
||||
.filter(|(_, decision)| decision.replicate)
|
||||
.map(|(arn, _)| arn.clone())
|
||||
.collect::<Vec<_>>();
|
||||
arns.sort();
|
||||
arns.dedup();
|
||||
return arns;
|
||||
}
|
||||
self.dsc.replicate_target_arns()
|
||||
}
|
||||
|
||||
/// Returns replication status of a target
|
||||
pub fn target_replication_status(&self, arn: &str) -> ReplicationStatusType {
|
||||
let binding = self.replication_status_internal.clone().unwrap_or_default();
|
||||
@@ -898,20 +934,31 @@ impl ReplicateObjectInfo {
|
||||
version_id: self.version_id,
|
||||
retry_count: retry_count_to_mrf(self.retry_count),
|
||||
size: self.size,
|
||||
op: if self.op_type == ReplicationType::Metadata {
|
||||
MrfOpKind::Metadata
|
||||
} else {
|
||||
MrfOpKind::Object
|
||||
op: match self.op_type {
|
||||
ReplicationType::Metadata => MrfOpKind::Metadata,
|
||||
ReplicationType::Heal => MrfOpKind::Heal,
|
||||
ReplicationType::ExistingObject => MrfOpKind::ExistingObject,
|
||||
_ => MrfOpKind::Object,
|
||||
},
|
||||
force_delete: false,
|
||||
delete_marker_version_id: None,
|
||||
delete_marker: false,
|
||||
delete_marker_mtime: None,
|
||||
target_arns: self.dsc.replicate_target_arns(),
|
||||
target_arns: self.admitted_target_arns(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replicate_decision_for_admitted_targets(target_arns: &[String]) -> ReplicateDecision {
|
||||
let mut decision = ReplicateDecision::new();
|
||||
for arn in target_arns {
|
||||
if !arn.is_empty() {
|
||||
decision.set(ReplicateTargetDecision::new(arn.clone(), true, false));
|
||||
}
|
||||
}
|
||||
decision
|
||||
}
|
||||
|
||||
// constructs a replication status map from string representation
|
||||
pub fn replication_statuses_map(s: &str) -> HashMap<String, ReplicationStatusType> {
|
||||
let mut targets = HashMap::new();
|
||||
@@ -1143,6 +1190,36 @@ mod tests {
|
||||
assert_eq!(info.to_mrf_entry().op, MrfOpKind::Metadata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admission_snapshot_prefers_resync_targets_for_existing_objects() {
|
||||
let mut decision = ReplicateDecision::new();
|
||||
decision.set(ReplicateTargetDecision::new("arn:live".to_string(), true, false));
|
||||
let mut resync = ResyncDecision::new();
|
||||
resync.targets.insert(
|
||||
"arn:admitted".to_string(),
|
||||
ResyncTargetDecision {
|
||||
replicate: true,
|
||||
reset_id: "reset-1".to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let info = ReplicateObjectInfo {
|
||||
op_type: ReplicationType::ExistingObject,
|
||||
dsc: decision,
|
||||
existing_obj_resync: resync,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(info.admitted_target_arns(), vec!["arn:admitted".to_string()]);
|
||||
assert_eq!(info.to_mrf_entry().op, MrfOpKind::ExistingObject);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mrf_operation_kind_round_trips_heal_and_existing_object_intent() {
|
||||
assert_eq!(MrfOpKind::Heal.replication_type(), ReplicationType::Heal);
|
||||
assert_eq!(MrfOpKind::ExistingObject.replication_type(), ReplicationType::ExistingObject);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_state_reads_resync_timestamp_from_target_reset_header_key() {
|
||||
let arn = "arn:rustfs:replication:us-east-1:target:bucket";
|
||||
|
||||
@@ -44,8 +44,8 @@ pub use filemeta::{
|
||||
REPLICATE_INCOMING_DELETE, REPLICATE_MRF, REPLICATE_QUEUED, REPLICATION_RESET, REPLICATION_STATUS, ReplicateDecision,
|
||||
ReplicateObjectInfo, ReplicateTargetDecision, ReplicatedInfos, ReplicatedTargetInfo, ReplicationAction, ReplicationState,
|
||||
ReplicationStatusType, ReplicationType, ReplicationWorkerOperation, ResyncDecision, ResyncTargetDecision,
|
||||
VersionPurgeStatusType, get_replication_state, parse_replicate_decision, replication_statuses_map, target_reset_header,
|
||||
version_purge_statuses_map,
|
||||
VersionPurgeStatusType, get_replication_state, parse_replicate_decision, replicate_decision_for_admitted_targets,
|
||||
replication_statuses_map, target_reset_header, version_purge_statuses_map,
|
||||
};
|
||||
pub use mrf::{MrfOpKind, MrfReplicateEntry, decode_mrf_file, encode_mrf_file};
|
||||
pub use multipart::{
|
||||
|
||||
Reference in New Issue
Block a user