mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-30 16:59:52 +00:00
refactor: Reimplement bucket replication system with enhanced architecture (#590)
* feat:refactor replication * use aws sdk for replication client * refactor/replication * merge main * fix lifecycle test
This commit is contained in:
@@ -35,7 +35,7 @@ uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] }
|
||||
tokio = { workspace = true, features = ["io-util", "macros", "sync"] }
|
||||
xxhash-rust = { workspace = true, features = ["xxh64"] }
|
||||
bytes.workspace = true
|
||||
rustfs-utils = { workspace = true, features = ["hash"] }
|
||||
rustfs-utils = { workspace = true, features = ["hash","http"] }
|
||||
byteorder = { workspace = true }
|
||||
tracing.workspace = true
|
||||
thiserror.workspace = true
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::headers::RESERVED_METADATA_PREFIX_LOWER;
|
||||
use crate::headers::RUSTFS_HEALING;
|
||||
use crate::{ReplicationState, ReplicationStatusType, VersionPurgeStatusType};
|
||||
use bytes::Bytes;
|
||||
use rmp_serde::Serializer;
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
use rustfs_utils::http::headers::{RESERVED_METADATA_PREFIX_LOWER, RUSTFS_HEALING};
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
@@ -202,7 +202,7 @@ pub struct FileInfo {
|
||||
// MarkDeleted marks this version as deleted
|
||||
pub mark_deleted: bool,
|
||||
// ReplicationState - Internal replication state to be passed back in ObjectInfo
|
||||
// pub replication_state: Option<ReplicationState>, // TODO: implement ReplicationState
|
||||
pub replication_state_internal: Option<ReplicationState>,
|
||||
pub data: Option<Bytes>,
|
||||
pub num_versions: usize,
|
||||
pub successor_mod_time: Option<OffsetDateTime>,
|
||||
@@ -471,6 +471,29 @@ impl FileInfo {
|
||||
// TODO: Add replication_state comparison when implemented
|
||||
// && self.replication_state == other.replication_state
|
||||
}
|
||||
|
||||
pub fn version_purge_status(&self) -> VersionPurgeStatusType {
|
||||
self.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.composite_version_purge_status())
|
||||
.unwrap_or(VersionPurgeStatusType::Empty)
|
||||
}
|
||||
pub fn replication_status(&self) -> ReplicationStatusType {
|
||||
self.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.composite_replication_status())
|
||||
.unwrap_or(ReplicationStatusType::Empty)
|
||||
}
|
||||
pub fn delete_marker_replication_status(&self) -> ReplicationStatusType {
|
||||
if self.deleted {
|
||||
self.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.composite_replication_status())
|
||||
.unwrap_or(ReplicationStatusType::Empty)
|
||||
} else {
|
||||
ReplicationStatusType::Empty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
|
||||
+297
-103
@@ -15,12 +15,13 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::fileinfo::{ErasureAlgo, ErasureInfo, FileInfo, FileInfoVersions, ObjectPartInfo, RawFileInfo};
|
||||
use crate::filemeta_inline::InlineData;
|
||||
use crate::headers::{
|
||||
use crate::{ReplicationStatusType, VersionPurgeStatusType};
|
||||
use byteorder::ByteOrder;
|
||||
use bytes::Bytes;
|
||||
use rustfs_utils::http::headers::{
|
||||
self, AMZ_META_UNENCRYPTED_CONTENT_LENGTH, AMZ_META_UNENCRYPTED_CONTENT_MD5, AMZ_STORAGE_CLASS, RESERVED_METADATA_PREFIX,
|
||||
RESERVED_METADATA_PREFIX_LOWER, VERSION_PURGE_STATUS_KEY,
|
||||
};
|
||||
use byteorder::ByteOrder;
|
||||
use bytes::Bytes;
|
||||
use s3s::header::X_AMZ_RESTORE;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
@@ -30,6 +31,7 @@ use std::io::{Read, Write};
|
||||
use std::{collections::HashMap, io::Cursor};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::io::AsyncRead;
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
use xxhash_rust::xxh64;
|
||||
|
||||
@@ -159,39 +161,57 @@ impl FileMeta {
|
||||
let i = buf.len() as u64;
|
||||
|
||||
// check version, buf = buf[8..]
|
||||
let (buf, _, _) = Self::check_xl2_v1(buf)?;
|
||||
let (buf, _, _) = Self::check_xl2_v1(buf).map_err(|e| {
|
||||
error!("failed to check XL2 v1 format: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
let (mut size_buf, buf) = buf.split_at(5);
|
||||
|
||||
// Get meta data, buf = crc + data
|
||||
let bin_len = rmp::decode::read_bin_len(&mut size_buf)?;
|
||||
let bin_len = rmp::decode::read_bin_len(&mut size_buf).map_err(|e| {
|
||||
error!("failed to read binary length for metadata: {}", e);
|
||||
Error::other(format!("failed to read binary length for metadata: {e}"))
|
||||
})?;
|
||||
|
||||
if buf.len() < bin_len as usize {
|
||||
error!("insufficient data for metadata: expected {} bytes, got {} bytes", bin_len, buf.len());
|
||||
return Err(Error::other("insufficient data for metadata"));
|
||||
}
|
||||
let (meta, buf) = buf.split_at(bin_len as usize);
|
||||
|
||||
if buf.len() < 5 {
|
||||
error!("insufficient data for CRC: expected 5 bytes, got {} bytes", buf.len());
|
||||
return Err(Error::other("insufficient data for CRC"));
|
||||
}
|
||||
let (mut crc_buf, buf) = buf.split_at(5);
|
||||
|
||||
// crc check
|
||||
let crc = rmp::decode::read_u32(&mut crc_buf)?;
|
||||
let crc = rmp::decode::read_u32(&mut crc_buf).map_err(|e| {
|
||||
error!("failed to read CRC value: {}", e);
|
||||
Error::other(format!("failed to read CRC value: {e}"))
|
||||
})?;
|
||||
let meta_crc = xxh64::xxh64(meta, XXHASH_SEED) as u32;
|
||||
|
||||
if crc != meta_crc {
|
||||
error!("xl file crc check failed: expected CRC {:#x}, got {:#x}", meta_crc, crc);
|
||||
return Err(Error::other("xl file crc check failed"));
|
||||
}
|
||||
|
||||
if !buf.is_empty() {
|
||||
self.data.update(buf);
|
||||
self.data.validate()?;
|
||||
self.data.validate().map_err(|e| {
|
||||
error!("data validation failed: {}", e);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
|
||||
// Parse meta
|
||||
if !meta.is_empty() {
|
||||
let (versions_len, _, meta_ver, meta) = Self::decode_xl_headers(meta)?;
|
||||
let (versions_len, _, meta_ver, meta) = Self::decode_xl_headers(meta).map_err(|e| {
|
||||
error!("failed to decode XL headers: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
// let (_, meta) = meta.split_at(read_size as usize);
|
||||
|
||||
@@ -201,24 +221,30 @@ impl FileMeta {
|
||||
|
||||
let mut cur: Cursor<&[u8]> = Cursor::new(meta);
|
||||
for _ in 0..versions_len {
|
||||
let bin_len = rmp::decode::read_bin_len(&mut cur)? as usize;
|
||||
let start = cur.position() as usize;
|
||||
let end = start + bin_len;
|
||||
let header_buf = &meta[start..end];
|
||||
let bin_len = rmp::decode::read_bin_len(&mut cur).map_err(|e| {
|
||||
error!("failed to read binary length for version header: {}", e);
|
||||
Error::other(format!("failed to read binary length for version header: {e}"))
|
||||
})? as usize;
|
||||
|
||||
let mut header_buf = vec![0u8; bin_len];
|
||||
|
||||
cur.read_exact(&mut header_buf)?;
|
||||
|
||||
let mut ver = FileMetaShallowVersion::default();
|
||||
ver.header.unmarshal_msg(header_buf)?;
|
||||
ver.header.unmarshal_msg(&header_buf).map_err(|e| {
|
||||
error!("failed to unmarshal version header: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
cur.set_position(end as u64);
|
||||
let bin_len = rmp::decode::read_bin_len(&mut cur).map_err(|e| {
|
||||
error!("failed to read binary length for version metadata: {}", e);
|
||||
Error::other(format!("failed to read binary length for version metadata: {e}"))
|
||||
})? as usize;
|
||||
|
||||
let bin_len = rmp::decode::read_bin_len(&mut cur)? as usize;
|
||||
let start = cur.position() as usize;
|
||||
let end = start + bin_len;
|
||||
let ver_meta_buf = &meta[start..end];
|
||||
let mut ver_meta_buf = vec![0u8; bin_len];
|
||||
cur.read_exact(&mut ver_meta_buf)?;
|
||||
|
||||
ver.meta.extend_from_slice(ver_meta_buf);
|
||||
|
||||
cur.set_position(end as u64);
|
||||
ver.meta.extend_from_slice(&ver_meta_buf);
|
||||
|
||||
self.versions.push(ver);
|
||||
}
|
||||
@@ -487,39 +513,39 @@ impl FileMeta {
|
||||
|
||||
let version = FileMetaVersion::from(fi);
|
||||
|
||||
self.add_version_filemata(version)
|
||||
}
|
||||
|
||||
pub fn add_version_filemata(&mut self, version: FileMetaVersion) -> Result<()> {
|
||||
if !version.valid() {
|
||||
return Err(Error::other("file meta version invalid"));
|
||||
}
|
||||
|
||||
// should replace
|
||||
for (idx, ver) in self.versions.iter().enumerate() {
|
||||
if ver.header.version_id != vid {
|
||||
continue;
|
||||
}
|
||||
|
||||
return self.set_idx(idx, version);
|
||||
// 1000 is the limit of versions TODO: make it configurable
|
||||
if self.versions.len() + 1 > 1000 {
|
||||
return Err(Error::other(
|
||||
"You've exceeded the limit on the number of versions you can create on this object",
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: version count limit !
|
||||
if self.versions.is_empty() {
|
||||
self.versions.push(FileMetaShallowVersion::try_from(version)?);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let vid = version.get_version_id();
|
||||
|
||||
if let Some(fidx) = self.versions.iter().position(|v| v.header.version_id == vid) {
|
||||
return self.set_idx(fidx, version);
|
||||
}
|
||||
|
||||
let mod_time = version.get_mod_time();
|
||||
|
||||
// puth a -1 mod time value , so we can relplace this
|
||||
self.versions.push(FileMetaShallowVersion {
|
||||
header: FileMetaVersionHeader {
|
||||
mod_time: Some(OffsetDateTime::from_unix_timestamp(-1)?),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
for (idx, exist) in self.versions.iter().enumerate() {
|
||||
if let Some(ref ex_mt) = exist.header.mod_time {
|
||||
if let Some(ref in_md) = mod_time {
|
||||
if ex_mt <= in_md {
|
||||
// insert
|
||||
self.versions.insert(idx, FileMetaShallowVersion::try_from(version)?);
|
||||
self.versions.pop();
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@@ -527,35 +553,33 @@ impl FileMeta {
|
||||
}
|
||||
|
||||
Err(Error::other("add_version failed"))
|
||||
}
|
||||
|
||||
pub fn add_version_filemata(&mut self, ver: FileMetaVersion) -> Result<()> {
|
||||
if !ver.valid() {
|
||||
return Err(Error::other("attempted to add invalid version"));
|
||||
}
|
||||
// if !ver.valid() {
|
||||
// return Err(Error::other("attempted to add invalid version"));
|
||||
// }
|
||||
|
||||
if self.versions.len() + 1 >= 100 {
|
||||
return Err(Error::other(
|
||||
"You've exceeded the limit on the number of versions you can create on this object",
|
||||
));
|
||||
}
|
||||
// if self.versions.len() + 1 >= 100 {
|
||||
// return Err(Error::other(
|
||||
// "You've exceeded the limit on the number of versions you can create on this object",
|
||||
// ));
|
||||
// }
|
||||
|
||||
let mod_time = ver.get_mod_time();
|
||||
let encoded = ver.marshal_msg()?;
|
||||
let new_version = FileMetaShallowVersion {
|
||||
header: ver.header(),
|
||||
meta: encoded,
|
||||
};
|
||||
// let mod_time = ver.get_mod_time();
|
||||
// let encoded = ver.marshal_msg()?;
|
||||
// let new_version = FileMetaShallowVersion {
|
||||
// header: ver.header(),
|
||||
// meta: encoded,
|
||||
// };
|
||||
|
||||
// Find the insertion position: insert before the first element with mod_time >= new mod_time
|
||||
// This maintains descending order by mod_time (newest first)
|
||||
let insert_pos = self
|
||||
.versions
|
||||
.iter()
|
||||
.position(|existing| existing.header.mod_time <= mod_time)
|
||||
.unwrap_or(self.versions.len());
|
||||
self.versions.insert(insert_pos, new_version);
|
||||
Ok(())
|
||||
// // Find the insertion position: insert before the first element with mod_time >= new mod_time
|
||||
// // This maintains descending order by mod_time (newest first)
|
||||
// let insert_pos = self
|
||||
// .versions
|
||||
// .iter()
|
||||
// .position(|existing| existing.header.mod_time <= mod_time)
|
||||
// .unwrap_or(self.versions.len());
|
||||
// self.versions.insert(insert_pos, new_version);
|
||||
// Ok(())
|
||||
}
|
||||
|
||||
// delete_version deletes version, returns data_dir
|
||||
@@ -575,10 +599,97 @@ impl FileMeta {
|
||||
}
|
||||
|
||||
let mut update_version = fi.mark_deleted;
|
||||
/*if fi.version_purge_status().is_empty()
|
||||
if fi.version_purge_status().is_empty()
|
||||
&& (fi.delete_marker_replication_status() == ReplicationStatusType::Replica
|
||||
|| fi.delete_marker_replication_status() == ReplicationStatusType::Empty)
|
||||
{
|
||||
update_version = fi.mark_deleted;
|
||||
}*/
|
||||
} else {
|
||||
if fi.deleted
|
||||
&& fi.version_purge_status() != VersionPurgeStatusType::Complete
|
||||
&& (!fi.version_purge_status().is_empty() || fi.delete_marker_replication_status().is_empty())
|
||||
{
|
||||
update_version = true;
|
||||
}
|
||||
|
||||
if !fi.version_purge_status().is_empty() && fi.version_purge_status() != VersionPurgeStatusType::Complete {
|
||||
update_version = true;
|
||||
}
|
||||
}
|
||||
|
||||
if fi.deleted {
|
||||
if !fi.delete_marker_replication_status().is_empty() {
|
||||
if let Some(delete_marker) = ventry.delete_marker.as_mut() {
|
||||
if fi.delete_marker_replication_status() == ReplicationStatusType::Replica {
|
||||
delete_marker.meta_sys.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "replica-status"),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.replica_status.clone())
|
||||
.unwrap_or_default()
|
||||
.as_str()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
delete_marker.meta_sys.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "replica-timestamp"),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.replica_timestamp.unwrap_or(OffsetDateTime::UNIX_EPOCH).to_string())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
} else {
|
||||
delete_marker.meta_sys.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "replication-status"),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.replication_status_internal.clone().unwrap_or_default())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
delete_marker.meta_sys.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "replication-timestamp"),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.replication_timestamp.unwrap_or(OffsetDateTime::UNIX_EPOCH).to_string())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !fi.version_purge_status().is_empty() {
|
||||
if let Some(delete_marker) = ventry.delete_marker.as_mut() {
|
||||
delete_marker.meta_sys.insert(
|
||||
VERSION_PURGE_STATUS_KEY.to_string(),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.version_purge_status_internal.clone().unwrap_or_default())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(delete_marker) = ventry.delete_marker.as_mut() {
|
||||
for (k, v) in fi
|
||||
.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.reset_statuses_map.clone())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
delete_marker.meta_sys.insert(k.clone(), v.clone().as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ???
|
||||
if fi.transition_status == TRANSITION_COMPLETE {
|
||||
update_version = false;
|
||||
}
|
||||
@@ -591,22 +702,111 @@ impl FileMeta {
|
||||
match ver.header.version_type {
|
||||
VersionType::Invalid | VersionType::Legacy => return Err(Error::other("invalid file meta version")),
|
||||
VersionType::Delete => {
|
||||
self.versions.remove(i);
|
||||
if fi.deleted && fi.version_id.is_none() {
|
||||
self.add_version_filemata(ventry)?;
|
||||
if update_version {
|
||||
let mut v = self.get_idx(i)?;
|
||||
if v.delete_marker.is_none() {
|
||||
v.delete_marker = Some(MetaDeleteMarker {
|
||||
version_id: fi.version_id,
|
||||
mod_time: fi.mod_time,
|
||||
meta_sys: HashMap::new(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(delete_marker) = v.delete_marker.as_mut() {
|
||||
if !fi.delete_marker_replication_status().is_empty() {
|
||||
if fi.delete_marker_replication_status() == ReplicationStatusType::Replica {
|
||||
delete_marker.meta_sys.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "replica-status"),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.replica_status.clone())
|
||||
.unwrap_or_default()
|
||||
.as_str()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
delete_marker.meta_sys.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "replica-timestamp"),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.replica_timestamp.unwrap_or(OffsetDateTime::UNIX_EPOCH).to_string())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
} else {
|
||||
delete_marker.meta_sys.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "replication-status"),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.replication_status_internal.clone().unwrap_or_default())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
delete_marker.meta_sys.insert(
|
||||
format!("{}{}", RESERVED_METADATA_PREFIX_LOWER, "replication-timestamp"),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.replication_timestamp.unwrap_or(OffsetDateTime::UNIX_EPOCH).to_string())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (k, v) in fi
|
||||
.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.reset_statuses_map.clone())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
delete_marker.meta_sys.insert(k.clone(), v.clone().as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
self.set_idx(i, v)?;
|
||||
return Ok(None);
|
||||
}
|
||||
self.versions.remove(i);
|
||||
|
||||
if (fi.mark_deleted && fi.version_purge_status() != VersionPurgeStatusType::Complete)
|
||||
|| (fi.deleted && fi.version_id.is_none())
|
||||
{
|
||||
self.add_version_filemata(ventry)?;
|
||||
}
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
VersionType::Object => {
|
||||
if update_version && !fi.deleted {
|
||||
let v = self.get_idx(i)?;
|
||||
let mut v = self.get_idx(i)?;
|
||||
|
||||
self.versions.remove(i);
|
||||
if let Some(obj) = v.object.as_mut() {
|
||||
obj.meta_sys.insert(
|
||||
VERSION_PURGE_STATUS_KEY.to_string(),
|
||||
fi.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.version_purge_status_internal.clone().unwrap_or_default())
|
||||
.unwrap_or_default()
|
||||
.as_bytes()
|
||||
.to_vec(),
|
||||
);
|
||||
for (k, v) in fi
|
||||
.replication_state_internal
|
||||
.as_ref()
|
||||
.map(|v| v.reset_statuses_map.clone())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
obj.meta_sys.insert(k.clone(), v.clone().as_bytes().to_vec());
|
||||
}
|
||||
}
|
||||
|
||||
let a = v.object.map(|v| v.data_dir).unwrap_or_default();
|
||||
return Ok(a);
|
||||
let old_dir = v.object.as_ref().map(|v| v.data_dir).unwrap_or_default();
|
||||
self.set_idx(i, v)?;
|
||||
|
||||
return Ok(old_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -641,31 +841,37 @@ impl FileMeta {
|
||||
let obj_version_id = obj.version_id;
|
||||
let obj_data_dir = obj.data_dir;
|
||||
|
||||
if fi.expire_restored {
|
||||
let mut err = if fi.expire_restored {
|
||||
obj.remove_restore_hdrs();
|
||||
self.set_idx(i, ver)?;
|
||||
self.set_idx(i, ver).err()
|
||||
} else if fi.transition_status == TRANSITION_COMPLETE {
|
||||
obj.set_transition(fi);
|
||||
obj.reset_inline_data();
|
||||
self.set_idx(i, ver)?;
|
||||
self.set_idx(i, ver).err()
|
||||
} else {
|
||||
self.versions.remove(i);
|
||||
|
||||
let (free_version, to_free) = obj.init_free_version(fi);
|
||||
|
||||
if to_free {
|
||||
self.add_version_filemata(free_version)?;
|
||||
self.add_version_filemata(free_version).err()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if fi.deleted {
|
||||
self.add_version_filemata(ventry)?;
|
||||
err = self.add_version_filemata(ventry).err();
|
||||
}
|
||||
|
||||
if self.shared_data_dir_count(obj_version_id, obj_data_dir) > 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(e) = err {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(obj_data_dir)
|
||||
}
|
||||
|
||||
@@ -1642,17 +1848,15 @@ impl MetaObject {
|
||||
free_entry.delete_marker = Some(MetaDeleteMarker {
|
||||
version_id: Some(vid),
|
||||
mod_time: self.mod_time,
|
||||
meta_sys: Some(HashMap::<String, Vec<u8>>::new()),
|
||||
meta_sys: HashMap::<String, Vec<u8>>::new(),
|
||||
});
|
||||
|
||||
free_entry
|
||||
.delete_marker
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
let delete_marker = free_entry.delete_marker.as_mut().unwrap();
|
||||
|
||||
delete_marker
|
||||
.meta_sys
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(format!("{RESERVED_METADATA_PREFIX_LOWER}{FREE_VERSION}"), vec![]);
|
||||
|
||||
let tier_key = format!("{RESERVED_METADATA_PREFIX_LOWER}{TRANSITION_TIER}");
|
||||
let tier_obj_key = format!("{RESERVED_METADATA_PREFIX_LOWER}{TRANSITIONED_OBJECTNAME}");
|
||||
let tier_obj_vid_key = format!("{RESERVED_METADATA_PREFIX_LOWER}{TRANSITIONED_VERSION_ID}");
|
||||
@@ -1660,14 +1864,7 @@ impl MetaObject {
|
||||
let aa = [tier_key, tier_obj_key, tier_obj_vid_key];
|
||||
for (k, v) in &self.meta_sys {
|
||||
if aa.contains(k) {
|
||||
free_entry
|
||||
.delete_marker
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.meta_sys
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(k.clone(), v.clone());
|
||||
delete_marker.meta_sys.insert(k.clone(), v.clone());
|
||||
}
|
||||
}
|
||||
return (free_entry, true);
|
||||
@@ -1737,19 +1934,16 @@ pub struct MetaDeleteMarker {
|
||||
#[serde(rename = "MTime")]
|
||||
pub mod_time: Option<OffsetDateTime>, // Object delete marker modified time
|
||||
#[serde(rename = "MetaSys")]
|
||||
pub meta_sys: Option<HashMap<String, Vec<u8>>>, // Delete marker internal metadata
|
||||
pub meta_sys: HashMap<String, Vec<u8>>, // Delete marker internal metadata
|
||||
}
|
||||
|
||||
impl MetaDeleteMarker {
|
||||
pub fn free_version(&self) -> bool {
|
||||
self.meta_sys
|
||||
.as_ref()
|
||||
.map(|v| v.get(FREE_VERSION_META_HEADER).is_some())
|
||||
.unwrap_or_default()
|
||||
self.meta_sys.contains_key(FREE_VERSION_META_HEADER)
|
||||
}
|
||||
|
||||
pub fn into_fileinfo(&self, volume: &str, path: &str, _all_parts: bool) -> FileInfo {
|
||||
let metadata = self.meta_sys.clone().unwrap_or_default();
|
||||
let metadata = self.meta_sys.clone();
|
||||
|
||||
FileInfo {
|
||||
version_id: self.version_id.filter(|&vid| !vid.is_nil()),
|
||||
@@ -1895,7 +2089,7 @@ impl From<FileInfo> for MetaDeleteMarker {
|
||||
Self {
|
||||
version_id: value.version_id,
|
||||
mod_time: value.mod_time,
|
||||
meta_sys: None,
|
||||
meta_sys: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2794,7 +2988,7 @@ mod test {
|
||||
let delete_marker = MetaDeleteMarker {
|
||||
version_id: Some(Uuid::new_v4()),
|
||||
mod_time: Some(OffsetDateTime::now_utc()),
|
||||
meta_sys: None,
|
||||
meta_sys: HashMap::new(),
|
||||
};
|
||||
|
||||
let delete_version = FileMetaVersion {
|
||||
|
||||
@@ -16,8 +16,9 @@ mod error;
|
||||
pub mod fileinfo;
|
||||
mod filemeta;
|
||||
mod filemeta_inline;
|
||||
pub mod headers;
|
||||
pub mod metacache;
|
||||
// pub mod headers;
|
||||
mod metacache;
|
||||
mod replication;
|
||||
|
||||
pub mod test_data;
|
||||
|
||||
@@ -26,3 +27,4 @@ pub use fileinfo::*;
|
||||
pub use filemeta::*;
|
||||
pub use filemeta_inline::*;
|
||||
pub use metacache::*;
|
||||
pub use replication::*;
|
||||
|
||||
@@ -0,0 +1,494 @@
|
||||
use core::fmt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
/// StatusType of Replication for x-amz-replication-status header
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)]
|
||||
pub enum ReplicationStatusType {
|
||||
/// Pending - replication is pending.
|
||||
Pending,
|
||||
/// Completed - replication completed ok.
|
||||
Completed,
|
||||
/// CompletedLegacy was called "COMPLETE" incorrectly.
|
||||
CompletedLegacy,
|
||||
/// Failed - replication failed.
|
||||
Failed,
|
||||
/// Replica - this is a replica.
|
||||
Replica,
|
||||
#[default]
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl ReplicationStatusType {
|
||||
/// Returns string representation of status
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ReplicationStatusType::Pending => "PENDING",
|
||||
ReplicationStatusType::Completed => "COMPLETED",
|
||||
ReplicationStatusType::CompletedLegacy => "COMPLETE",
|
||||
ReplicationStatusType::Failed => "FAILED",
|
||||
ReplicationStatusType::Replica => "REPLICA",
|
||||
ReplicationStatusType::Empty => "",
|
||||
}
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
matches!(self, ReplicationStatusType::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ReplicationStatusType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ReplicationStatusType {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"PENDING" => ReplicationStatusType::Pending,
|
||||
"COMPLETED" => ReplicationStatusType::Completed,
|
||||
"COMPLETE" => ReplicationStatusType::CompletedLegacy,
|
||||
"FAILED" => ReplicationStatusType::Failed,
|
||||
"REPLICA" => ReplicationStatusType::Replica,
|
||||
_ => ReplicationStatusType::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VersionPurgeStatusType> for ReplicationStatusType {
|
||||
fn from(status: VersionPurgeStatusType) -> Self {
|
||||
match status {
|
||||
VersionPurgeStatusType::Pending => ReplicationStatusType::Pending,
|
||||
VersionPurgeStatusType::Complete => ReplicationStatusType::Completed,
|
||||
VersionPurgeStatusType::Failed => ReplicationStatusType::Failed,
|
||||
VersionPurgeStatusType::Empty => ReplicationStatusType::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum VersionPurgeStatusType {
|
||||
Pending,
|
||||
Complete,
|
||||
Failed,
|
||||
#[default]
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl VersionPurgeStatusType {
|
||||
/// Returns string representation of version purge status
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
VersionPurgeStatusType::Pending => "PENDING",
|
||||
VersionPurgeStatusType::Complete => "COMPLETE",
|
||||
VersionPurgeStatusType::Failed => "FAILED",
|
||||
VersionPurgeStatusType::Empty => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the version is pending purge.
|
||||
pub fn is_pending(&self) -> bool {
|
||||
matches!(self, VersionPurgeStatusType::Pending | VersionPurgeStatusType::Failed)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
matches!(self, VersionPurgeStatusType::Empty)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for VersionPurgeStatusType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for VersionPurgeStatusType {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"PENDING" => VersionPurgeStatusType::Pending,
|
||||
"COMPLETE" => VersionPurgeStatusType::Complete,
|
||||
"FAILED" => VersionPurgeStatusType::Failed,
|
||||
_ => VersionPurgeStatusType::Empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Type - replication type enum
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum ReplicationType {
|
||||
#[default]
|
||||
Unset,
|
||||
Object,
|
||||
Delete,
|
||||
Metadata,
|
||||
Heal,
|
||||
ExistingObject,
|
||||
Resync,
|
||||
All,
|
||||
}
|
||||
|
||||
impl ReplicationType {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ReplicationType::Unset => "",
|
||||
ReplicationType::Object => "OBJECT",
|
||||
ReplicationType::Delete => "DELETE",
|
||||
ReplicationType::Metadata => "METADATA",
|
||||
ReplicationType::Heal => "HEAL",
|
||||
ReplicationType::ExistingObject => "EXISTING_OBJECT",
|
||||
ReplicationType::Resync => "RESYNC",
|
||||
ReplicationType::All => "ALL",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
ReplicationType::Object
|
||||
| ReplicationType::Delete
|
||||
| ReplicationType::Metadata
|
||||
| ReplicationType::Heal
|
||||
| ReplicationType::ExistingObject
|
||||
| ReplicationType::Resync
|
||||
| ReplicationType::All
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_data_replication(&self) -> bool {
|
||||
matches!(self, ReplicationType::Object | ReplicationType::Delete | ReplicationType::Heal)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ReplicationType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ReplicationType {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"UNSET" => ReplicationType::Unset,
|
||||
"OBJECT" => ReplicationType::Object,
|
||||
"DELETE" => ReplicationType::Delete,
|
||||
"METADATA" => ReplicationType::Metadata,
|
||||
"HEAL" => ReplicationType::Heal,
|
||||
"EXISTING_OBJECT" => ReplicationType::ExistingObject,
|
||||
"RESYNC" => ReplicationType::Resync,
|
||||
"ALL" => ReplicationType::All,
|
||||
_ => ReplicationType::Unset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ReplicationState represents internal replication state
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
|
||||
pub struct ReplicationState {
|
||||
pub replica_timestamp: Option<OffsetDateTime>,
|
||||
pub replica_status: ReplicationStatusType,
|
||||
pub delete_marker: bool,
|
||||
pub replication_timestamp: Option<OffsetDateTime>,
|
||||
pub replication_status_internal: Option<String>,
|
||||
pub version_purge_status_internal: Option<String>,
|
||||
pub replicate_decision_str: String,
|
||||
pub targets: HashMap<String, ReplicationStatusType>,
|
||||
pub purge_targets: HashMap<String, VersionPurgeStatusType>,
|
||||
pub reset_statuses_map: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ReplicationState {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Returns true if replication state is identical for version purge statuses and replication statuses
|
||||
pub fn equal(&self, other: &ReplicationState) -> bool {
|
||||
self.replica_status == other.replica_status
|
||||
&& self.replication_status_internal == other.replication_status_internal
|
||||
&& self.version_purge_status_internal == other.version_purge_status_internal
|
||||
}
|
||||
|
||||
/// Returns overall replication status for the object version being replicated
|
||||
pub fn composite_replication_status(&self) -> ReplicationStatusType {
|
||||
if let Some(replication_status_internal) = &self.replication_status_internal {
|
||||
match ReplicationStatusType::from(replication_status_internal.as_str()) {
|
||||
ReplicationStatusType::Pending
|
||||
| ReplicationStatusType::Completed
|
||||
| ReplicationStatusType::Failed
|
||||
| ReplicationStatusType::Replica => {
|
||||
return ReplicationStatusType::from(replication_status_internal.as_str());
|
||||
}
|
||||
_ => {
|
||||
let repl_status = get_composite_replication_status(&self.targets);
|
||||
|
||||
if self.replica_timestamp.is_none() {
|
||||
return repl_status;
|
||||
}
|
||||
|
||||
if repl_status == ReplicationStatusType::Completed {
|
||||
if let (Some(replica_timestamp), Some(replication_timestamp)) =
|
||||
(self.replica_timestamp, self.replication_timestamp)
|
||||
{
|
||||
if replica_timestamp > replication_timestamp {
|
||||
return self.replica_status.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return repl_status;
|
||||
}
|
||||
}
|
||||
} else if self.replica_status != ReplicationStatusType::default() {
|
||||
return self.replica_status.clone();
|
||||
}
|
||||
|
||||
ReplicationStatusType::default()
|
||||
}
|
||||
|
||||
/// Returns overall replication purge status for the permanent delete being replicated
|
||||
pub fn composite_version_purge_status(&self) -> VersionPurgeStatusType {
|
||||
match VersionPurgeStatusType::from(self.version_purge_status_internal.clone().unwrap_or_default().as_str()) {
|
||||
VersionPurgeStatusType::Pending | VersionPurgeStatusType::Complete | VersionPurgeStatusType::Failed => {
|
||||
VersionPurgeStatusType::from(self.version_purge_status_internal.clone().unwrap_or_default().as_str())
|
||||
}
|
||||
_ => get_composite_version_purge_status(&self.purge_targets),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns replicatedInfos struct initialized with the previous state of replication
|
||||
pub fn target_state(&self, arn: &str) -> ReplicatedTargetInfo {
|
||||
ReplicatedTargetInfo {
|
||||
arn: arn.to_string(),
|
||||
prev_replication_status: self.targets.get(arn).cloned().unwrap_or_default(),
|
||||
version_purge_status: self.purge_targets.get(arn).cloned().unwrap_or_default(),
|
||||
resync_timestamp: self.reset_statuses_map.get(arn).cloned().unwrap_or_default(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_composite_replication_status(targets: &HashMap<String, ReplicationStatusType>) -> ReplicationStatusType {
|
||||
if targets.is_empty() {
|
||||
return ReplicationStatusType::Empty;
|
||||
}
|
||||
|
||||
let mut completed = 0;
|
||||
for status in targets.values() {
|
||||
match status {
|
||||
ReplicationStatusType::Failed => return ReplicationStatusType::Failed,
|
||||
ReplicationStatusType::Completed => completed += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if completed == targets.len() {
|
||||
ReplicationStatusType::Completed
|
||||
} else {
|
||||
ReplicationStatusType::Pending
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_composite_version_purge_status(targets: &HashMap<String, VersionPurgeStatusType>) -> VersionPurgeStatusType {
|
||||
if targets.is_empty() {
|
||||
return VersionPurgeStatusType::default();
|
||||
}
|
||||
|
||||
let mut completed = 0;
|
||||
for status in targets.values() {
|
||||
match status {
|
||||
VersionPurgeStatusType::Failed => return VersionPurgeStatusType::Failed,
|
||||
VersionPurgeStatusType::Complete => completed += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if completed == targets.len() {
|
||||
VersionPurgeStatusType::Complete
|
||||
} else {
|
||||
VersionPurgeStatusType::Pending
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
pub enum ReplicationAction {
|
||||
/// Replicate all data
|
||||
All,
|
||||
/// Replicate only metadata
|
||||
Metadata,
|
||||
/// Do not replicate
|
||||
#[default]
|
||||
None,
|
||||
}
|
||||
|
||||
impl ReplicationAction {
|
||||
/// Returns string representation of replication action
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
ReplicationAction::All => "all",
|
||||
ReplicationAction::Metadata => "metadata",
|
||||
ReplicationAction::None => "none",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ReplicationAction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ReplicationAction {
|
||||
fn from(s: &str) -> Self {
|
||||
match s {
|
||||
"all" => ReplicationAction::All,
|
||||
"metadata" => ReplicationAction::Metadata,
|
||||
"none" => ReplicationAction::None,
|
||||
_ => ReplicationAction::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ReplicatedTargetInfo struct represents replication info on a target
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct ReplicatedTargetInfo {
|
||||
pub arn: String,
|
||||
pub size: i64,
|
||||
pub duration: Duration,
|
||||
pub replication_action: ReplicationAction,
|
||||
pub op_type: ReplicationType,
|
||||
pub replication_status: ReplicationStatusType,
|
||||
pub prev_replication_status: ReplicationStatusType,
|
||||
pub version_purge_status: VersionPurgeStatusType,
|
||||
pub resync_timestamp: String,
|
||||
pub replication_resynced: bool,
|
||||
pub endpoint: String,
|
||||
pub secure: bool,
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
impl ReplicatedTargetInfo {
|
||||
/// Returns true for a target if arn is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.arn.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// ReplicatedInfos struct contains replication information for multiple targets
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReplicatedInfos {
|
||||
pub replication_timestamp: Option<OffsetDateTime>,
|
||||
pub targets: Vec<ReplicatedTargetInfo>,
|
||||
}
|
||||
|
||||
impl ReplicatedInfos {
|
||||
/// Returns the total size of completed replications
|
||||
pub fn completed_size(&self) -> i64 {
|
||||
let mut sz = 0i64;
|
||||
for target in &self.targets {
|
||||
if target.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if target.replication_status == ReplicationStatusType::Completed
|
||||
&& target.prev_replication_status != ReplicationStatusType::Completed
|
||||
{
|
||||
sz += target.size;
|
||||
}
|
||||
}
|
||||
sz
|
||||
}
|
||||
|
||||
/// Returns true if replication was attempted on any of the targets for the object version queued
|
||||
pub fn replication_resynced(&self) -> bool {
|
||||
for target in &self.targets {
|
||||
if target.is_empty() || !target.replication_resynced {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns internal representation of replication status for all targets
|
||||
pub fn replication_status_internal(&self) -> Option<String> {
|
||||
let mut result = String::new();
|
||||
for target in &self.targets {
|
||||
if target.is_empty() {
|
||||
continue;
|
||||
}
|
||||
result.push_str(&format!("{}={};", target.arn, target.replication_status));
|
||||
}
|
||||
if result.is_empty() { None } else { Some(result) }
|
||||
}
|
||||
|
||||
/// Returns overall replication status across all targets
|
||||
pub fn replication_status(&self) -> ReplicationStatusType {
|
||||
if self.targets.is_empty() {
|
||||
return ReplicationStatusType::Empty;
|
||||
}
|
||||
|
||||
let mut completed = 0;
|
||||
for target in &self.targets {
|
||||
match target.replication_status {
|
||||
ReplicationStatusType::Failed => return ReplicationStatusType::Failed,
|
||||
ReplicationStatusType::Completed => completed += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if completed == self.targets.len() {
|
||||
ReplicationStatusType::Completed
|
||||
} else {
|
||||
ReplicationStatusType::Pending
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns overall version purge status across all targets
|
||||
pub fn version_purge_status(&self) -> VersionPurgeStatusType {
|
||||
if self.targets.is_empty() {
|
||||
return VersionPurgeStatusType::Empty;
|
||||
}
|
||||
|
||||
let mut completed = 0;
|
||||
for target in &self.targets {
|
||||
match target.version_purge_status {
|
||||
VersionPurgeStatusType::Failed => return VersionPurgeStatusType::Failed,
|
||||
VersionPurgeStatusType::Complete => completed += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if completed == self.targets.len() {
|
||||
VersionPurgeStatusType::Complete
|
||||
} else {
|
||||
VersionPurgeStatusType::Pending
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns internal representation of version purge status for all targets
|
||||
pub fn version_purge_status_internal(&self) -> Option<String> {
|
||||
let mut result = String::new();
|
||||
for target in &self.targets {
|
||||
if target.is_empty() || target.version_purge_status.is_empty() {
|
||||
continue;
|
||||
}
|
||||
result.push_str(&format!("{}={};", target.arn, target.version_purge_status));
|
||||
}
|
||||
if result.is_empty() { None } else { Some(result) }
|
||||
}
|
||||
|
||||
/// Returns replication action based on target that actually performed replication
|
||||
pub fn action(&self) -> ReplicationAction {
|
||||
for target in &self.targets {
|
||||
if target.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// rely on replication action from target that actually performed replication now.
|
||||
if target.prev_replication_status != ReplicationStatusType::Completed {
|
||||
return target.replication_action;
|
||||
}
|
||||
}
|
||||
ReplicationAction::None
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ pub fn create_real_xlmeta() -> Result<Vec<u8>> {
|
||||
let delete_marker = MetaDeleteMarker {
|
||||
version_id: Some(delete_version_id),
|
||||
mod_time: Some(OffsetDateTime::from_unix_timestamp(1705312260)?), // 1 minute later
|
||||
meta_sys: None,
|
||||
meta_sys: HashMap::new(),
|
||||
};
|
||||
|
||||
let delete_file_version = FileMetaVersion {
|
||||
@@ -151,7 +151,7 @@ pub fn create_complex_xlmeta() -> Result<Vec<u8>> {
|
||||
let delete_marker = MetaDeleteMarker {
|
||||
version_id: Some(delete_version_id),
|
||||
mod_time: Some(OffsetDateTime::from_unix_timestamp(1705312200 + i * 60 + 30)?),
|
||||
meta_sys: None,
|
||||
meta_sys: HashMap::new(),
|
||||
};
|
||||
|
||||
let delete_file_version = FileMetaVersion {
|
||||
|
||||
Reference in New Issue
Block a user