merge main

This commit is contained in:
weisd
2025-06-09 15:31:11 +08:00
84 changed files with 10192 additions and 4976 deletions
+9 -3
View File
@@ -16,6 +16,7 @@ use std::sync::Arc;
use time::OffsetDateTime;
use tracing::error;
use crate::bucket::target::BucketTarget;
use crate::config::com::{read_config, save_config};
use crate::error::{Error, Result};
use crate::new_object_layer_fn;
@@ -278,8 +279,11 @@ impl BucketMetadata {
self.replication_config_updated_at = updated;
}
BUCKET_TARGETS_FILE => {
self.tagging_config_xml = data;
self.tagging_config_updated_at = updated;
// let x = data.clone();
// let str = std::str::from_utf8(&x).expect("Invalid UTF-8");
// println!("update config:{}", str);
self.bucket_targets_config_json = data.clone();
self.bucket_targets_config_updated_at = updated;
}
_ => return Err(Error::other(format!("config file not found : {}", config_file))),
}
@@ -342,8 +346,10 @@ impl BucketMetadata {
if !self.replication_config_xml.is_empty() {
self.replication_config = Some(deserialize::<ReplicationConfiguration>(&self.replication_config_xml)?);
}
//let temp = self.bucket_targets_config_json.clone();
if !self.bucket_targets_config_json.is_empty() {
self.bucket_target_config = Some(BucketTargets::unmarshal(&self.bucket_targets_config_json)?);
let arr: Vec<BucketTarget> = serde_json::from_slice(&self.bucket_targets_config_json)?;
self.bucket_target_config = Some(BucketTargets { targets: arr });
} else {
self.bucket_target_config = Some(BucketTargets::default())
}
+15 -3
View File
@@ -7,6 +7,7 @@ use crate::StorageAPI;
use crate::bucket::error::BucketMetadataError;
use crate::bucket::metadata::{BUCKET_LIFECYCLE_CONFIG, load_bucket_metadata_parse};
use crate::bucket::utils::is_meta_bucketname;
use crate::cmd::bucket_targets;
use crate::error::{Error, Result, is_err_bucket_not_found};
use crate::global::{GLOBAL_Endpoints, is_dist_erasure, is_erasure, new_object_layer_fn};
use crate::heal::heal_commands::HealOpts;
@@ -226,7 +227,9 @@ impl BucketMetadataSys {
match res {
Ok(res) => {
if let Some(bucket) = buckets.get(idx) {
mp.insert(bucket.clone(), Arc::new(res));
let x = Arc::new(res);
mp.insert(bucket.clone(), x.clone());
bucket_targets::init_bucket_targets(bucket, x.clone()).await;
}
}
Err(e) => {
@@ -340,6 +343,7 @@ impl BucketMetadataSys {
}
pub async fn get_config_from_disk(&self, bucket: &str) -> Result<BucketMetadata> {
println!("load data from disk");
if is_meta_bucketname(bucket) {
return Err(Error::other("errInvalidArgument"));
}
@@ -549,7 +553,12 @@ impl BucketMetadataSys {
pub async fn get_replication_config(&self, bucket: &str) -> Result<(ReplicationConfiguration, OffsetDateTime)> {
let (bm, reload) = match self.get_config(bucket).await {
Ok(res) => res,
Ok(res) => {
if res.0.replication_config.is_none() {
return Err(BucketMetadataError::BucketReplicationConfigNotFound.into());
}
res
}
Err(err) => {
warn!("get_replication_config err {:?}", &err);
return if err == Error::ConfigNotFound {
@@ -564,7 +573,7 @@ impl BucketMetadataSys {
if reload {
// TODO: globalBucketTargetSys
}
//println!("549 {:?}", config.clone());
Ok((config.clone(), bm.replication_config_updated_at))
} else {
Err(BucketMetadataError::BucketReplicationConfigNotFound.into())
@@ -584,9 +593,12 @@ impl BucketMetadataSys {
}
};
println!("573");
if let Some(config) = &bm.bucket_target_config {
if reload {
// TODO: globalBucketTargetSys
//config.
}
Ok(config.clone())
+2 -1
View File
@@ -4,8 +4,9 @@ pub mod metadata_sys;
pub mod object_lock;
pub mod policy_sys;
mod quota;
pub mod replication;
pub mod tagging;
mod target;
pub mod target;
pub mod utils;
pub mod versioning;
pub mod versioning_sys;
@@ -0,0 +1,27 @@
// Replication status type for x-amz-replication-status header
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StatusType {
Pending,
Completed,
CompletedLegacy,
Failed,
Replica,
}
impl StatusType {
// Converts the enum variant to its string representation
pub fn as_str(&self) -> &'static str {
match self {
StatusType::Pending => "PENDING",
StatusType::Completed => "COMPLETED",
StatusType::CompletedLegacy => "COMPLETE",
StatusType::Failed => "FAILED",
StatusType::Replica => "REPLICA",
}
}
// Checks if the status is empty (not set)
pub fn is_empty(&self) -> bool {
matches!(self, StatusType::Pending) // Adjust this as needed
}
}
+1
View File
@@ -0,0 +1 @@
pub mod datatypes;
+1
View File
@@ -25,6 +25,7 @@ pub fn encode_tags(tags: Vec<Tag>) -> String {
for tag in tags.iter() {
if let (Some(k), Some(v)) = (tag.key.as_ref(), tag.value.as_ref()) {
//encoded.append_pair(k.as_ref().unwrap().as_str(), v.as_ref().unwrap().as_str());
encoded.append_pair(k.as_str(), v.as_str());
}
}
+52 -27
View File
@@ -1,15 +1,16 @@
use crate::error::Result;
use rmp_serde::Serializer as rmpSerializer;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use time::OffsetDateTime;
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct Credentials {
access_key: String,
secret_key: String,
session_token: Option<String>,
expiration: Option<OffsetDateTime>,
#[serde(rename = "accessKey")]
pub access_key: String,
#[serde(rename = "secretKey")]
pub secret_key: String,
pub session_token: Option<String>,
pub expiration: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
@@ -20,52 +21,53 @@ pub enum ServiceType {
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct LatencyStat {
curr: Duration, // 当前延迟
avg: Duration, // 平均延迟
max: Duration, // 最大延迟
curr: u64, // 当前延迟
avg: u64, // 平均延迟
max: u64, // 最大延迟
}
// 定义 BucketTarget 结构体
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
pub struct BucketTarget {
source_bucket: String,
#[serde(rename = "sourcebucket")]
pub source_bucket: String,
endpoint: String,
pub endpoint: String,
credentials: Option<Credentials>,
target_bucket: String,
pub credentials: Option<Credentials>,
#[serde(rename = "targetbucket")]
pub target_bucket: String,
secure: bool,
path: Option<String>,
pub path: Option<String>,
api: Option<String>,
arn: Option<String>,
pub arn: Option<String>,
#[serde(rename = "type")]
pub type_: Option<String>,
type_: ServiceType,
region: Option<String>,
pub region: Option<String>,
bandwidth_limit: Option<i64>,
#[serde(rename = "replicationSync")]
replication_sync: bool,
storage_class: Option<String>,
health_check_duration: Option<Duration>,
#[serde(rename = "healthCheckDuration")]
health_check_duration: u64,
#[serde(rename = "disableProxy")]
disable_proxy: bool,
reset_before_date: Option<OffsetDateTime>,
#[serde(rename = "resetBeforeDate")]
reset_before_date: String,
reset_id: Option<String>,
total_downtime: Duration,
#[serde(rename = "totalDowntime")]
total_downtime: u64,
last_online: Option<OffsetDateTime>,
#[serde(rename = "isOnline")]
online: bool,
latency: LatencyStat,
@@ -73,6 +75,15 @@ pub struct BucketTarget {
deployment_id: Option<String>,
edge: bool,
#[serde(rename = "edgeSyncBeforeExpiry")]
edge_sync_before_expiry: bool,
}
impl BucketTarget {
pub fn is_empty(self) -> bool {
//self.target_bucket.is_empty() && self.endpoint.is_empty() && self.arn.is_empty()
self.target_bucket.is_empty() && self.endpoint.is_empty() && self.arn.is_none()
}
}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
@@ -93,4 +104,18 @@ impl BucketTargets {
let t: BucketTargets = rmp_serde::from_slice(buf)?;
Ok(t)
}
pub fn is_empty(&self) -> bool {
if self.targets.is_empty() {
return true;
}
for target in &self.targets {
if !target.clone().is_empty() {
return false;
}
}
true
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
use std::collections::HashMap;
use chrono::{DateTime, Utc};
// Representation of the replication status
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StatusType {
Pending,
Completed,
CompletedLegacy,
Failed,
Replica,
}
// Representation of version purge status type (customize as needed)
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VersionPurgeStatusType {
Pending,
Completed,
Failed,
}
// ReplicationState struct definition
#[derive(Debug, Clone)]
pub struct ReplicationState {
// Timestamp when the last replica update was received
pub replica_time_stamp: DateTime<Utc>,
// Replica status
pub replica_status: StatusType,
// Represents DeleteMarker replication state
pub delete_marker: bool,
// Timestamp when the last replication activity happened
pub replication_time_stamp: DateTime<Utc>,
// Stringified representation of all replication activity
pub replication_status_internal: String,
// Stringified representation of all version purge statuses
// Example format: "arn1=PENDING;arn2=COMPLETED;"
pub version_purge_status_internal: String,
// Stringified representation of replication decision for each target
pub replicate_decision_str: String,
// Map of ARN -> replication status for ongoing replication activity
pub targets: HashMap<String, StatusType>,
// Map of ARN -> VersionPurgeStatus for all the targets
pub purge_targets: HashMap<String, VersionPurgeStatusType>,
// Map of ARN -> stringified reset id and timestamp for all the targets
pub reset_statuses_map: HashMap<String, String>,
}
+867
View File
@@ -0,0 +1,867 @@
#![allow(unused_variables)]
#![allow(dead_code)]
use crate::{
StorageAPI,
bucket::{metadata_sys, target::BucketTarget},
endpoints::Node,
peer::{PeerS3Client, RemotePeerS3Client},
};
use crate::{
bucket::{self, target::BucketTargets},
new_object_layer_fn, peer, store_api,
};
//use tokio::sync::RwLock;
use aws_sdk_s3::Client as S3Client;
use chrono::Utc;
use lazy_static::lazy_static;
use std::sync::Arc;
use std::{
collections::HashMap,
time::{Duration, SystemTime},
};
use thiserror::Error;
use tokio::sync::RwLock;
pub struct TClient {
pub s3cli: S3Client,
pub remote_peer_client: peer::RemotePeerS3Client,
pub arn: String,
}
impl TClient {
pub fn new(s3cli: S3Client, remote_peer_client: RemotePeerS3Client, arn: String) -> Self {
TClient {
s3cli,
remote_peer_client,
arn,
}
}
}
pub struct EpHealth {
pub endpoint: String,
pub scheme: String,
pub online: bool,
pub last_online: SystemTime,
pub last_hc_at: SystemTime,
pub offline_duration: Duration,
pub latency: LatencyStat, // Assuming LatencyStat is a custom struct
}
impl EpHealth {
pub fn new(
endpoint: String,
scheme: String,
online: bool,
last_online: SystemTime,
last_hc_at: SystemTime,
offline_duration: Duration,
latency: LatencyStat,
) -> Self {
EpHealth {
endpoint,
scheme,
online,
last_online,
last_hc_at,
offline_duration,
latency,
}
}
}
pub struct LatencyStat {
// Define the fields of LatencyStat as per your requirements
}
pub struct ArnTarget {
client: TargetClient,
last_refresh: chrono::DateTime<Utc>,
}
impl ArnTarget {
pub fn new(bucket: String, endpoint: String, ak: String, sk: String) -> Self {
Self {
client: TargetClient {
bucket,
storage_class: "STANDRD".to_string(),
disable_proxy: false,
health_check_duration: Duration::from_secs(100),
endpoint,
reset_id: "0".to_string(),
replicate_sync: false,
secure: false,
arn: "".to_string(),
client: reqwest::Client::new(),
ak,
sk,
},
last_refresh: Utc::now(),
}
}
}
// pub fn get_s3client_from_para(
// ak: &str,
// sk: &str,
// url: &str,
// _region: &str,
// ) -> Result<S3Client, Box<dyn Error>> {
// let credentials = Credentials::new(ak, sk, None, None, "");
// let region = Region::new("us-east-1".to_string());
// let config = Config::builder()
// .region(region)
// .endpoint_url(url.to_string())
// .credentials_provider(credentials)
// .behavior_version(BehaviorVersion::latest()) // Adjust as necessary
// .build();
// Ok(S3Client::from_conf(config))
// }
pub struct BucketTargetSys {
arn_remote_map: Arc<RwLock<HashMap<String, ArnTarget>>>,
targets_map: Arc<RwLock<HashMap<String, Vec<bucket::target::BucketTarget>>>>,
hc: HashMap<String, EpHealth>,
//store:Option<Arc<ecstore::store::ECStore>>,
}
lazy_static! {
pub static ref GLOBAL_Bucket_Target_Sys: std::sync::OnceLock<BucketTargetSys> = BucketTargetSys::new().into();
}
//#[derive(Debug)]
// pub enum SetTargetError {
// NotFound,
// }
pub async fn get_bucket_target_client(bucket: &str, arn: &str) -> Result<TargetClient, SetTargetError> {
if let Some(sys) = GLOBAL_Bucket_Target_Sys.get() {
sys.get_remote_target_client2(arn).await
} else {
Err(SetTargetError::TargetNotFound(bucket.to_string()))
}
}
#[derive(Debug)]
pub struct BucketRemoteTargetNotFound {
pub bucket: String,
}
pub async fn init_bucket_targets(bucket: &str, meta: Arc<bucket::metadata::BucketMetadata>) {
println!("140 {}", bucket);
if let Some(sys) = GLOBAL_Bucket_Target_Sys.get() {
if let Some(tgts) = meta.bucket_target_config.clone() {
for tgt in tgts.targets {
warn!("ak and sk is:{:?}", tgt.credentials);
let _ = sys.set_target(bucket, &tgt, false, true).await;
//sys.targets_map.
}
}
}
}
pub async fn remove_bucket_target(bucket: &str, arn_str: &str) {
if let Some(sys) = GLOBAL_Bucket_Target_Sys.get() {
let _ = sys.remove_target(bucket, arn_str).await;
}
}
impl Default for BucketTargetSys {
fn default() -> Self {
Self::new()
}
}
impl BucketTargetSys {
pub fn new() -> Self {
BucketTargetSys {
arn_remote_map: Arc::new(RwLock::new(HashMap::new())),
targets_map: Arc::new(RwLock::new(HashMap::new())),
hc: HashMap::new(),
}
}
pub async fn list_bucket_targets(&self, bucket: &str) -> Result<BucketTargets, BucketRemoteTargetNotFound> {
let targets_map = self.targets_map.read().await;
if let Some(targets) = targets_map.get(bucket) {
Ok(BucketTargets {
targets: targets.clone(),
})
} else {
Err(BucketRemoteTargetNotFound {
bucket: bucket.to_string(),
})
}
}
pub async fn list_targets(&self, bucket: Option<&str>, _arn_type: Option<&str>) -> Vec<BucketTarget> {
let _ = _arn_type;
//let health_stats = self.health_stats();
let mut targets = Vec::new();
if let Some(bucket_name) = bucket {
if let Ok(ts) = self.list_bucket_targets(bucket_name).await {
for t in ts.targets {
//if arn_type.map_or(true, |arn| t.target_type == arn) {
//if let Some(hs) = health_stats.get(&t.url().host) {
// t.total_downtime = hs.offline_duration;
// t.online = hs.online;
// t.last_online = hs.last_online;
// t.latency = LatencyStat {
// curr: hs.latency.curr,
// avg: hs.latency.avg,
// max: hs.latency.peak,
// };
//}
targets.push(t.clone());
//}
}
}
return targets;
}
// Locking and iterating over all targets in the system
let targets_map = self.targets_map.read().await;
for tgts in targets_map.values() {
for t in tgts {
//if arn_type.map_or(true, |arn| t.target_type == arn) {
// if let Some(hs) = health_stats.get(&t.url().host) {
// t.total_downtime = hs.offline_duration;
// t.online = hs.online;
// t.last_online = hs.last_online;
// t.latency = LatencyStat {
// curr: hs.latency.curr,
// avg: hs.latency.avg,
// max: hs.latency.peak,
// };
// }
targets.push(t.clone());
//}
}
}
targets
}
pub async fn remove_target(&self, bucket: &str, arn_str: &str) -> Result<(), SetTargetError> {
//to do need lock;
let mut targets_map = self.targets_map.write().await;
let tgts = targets_map.get(bucket);
let mut arn_remotes_map = self.arn_remote_map.write().await;
if tgts.is_none() {
//Err(SetTargetError::TargetNotFound(bucket.to_string()));
return Ok(());
}
let tgts = tgts.unwrap(); // 安全解引用
let mut targets = Vec::with_capacity(tgts.len());
let mut found = false;
// 遍历 targets,找出不匹配的 ARN
for tgt in tgts {
if tgt.arn != Some(arn_str.to_string()) {
targets.push(tgt.clone()); // 克隆符合条件的项
} else {
found = true; // 找到匹配的 ARN
}
}
// 如果没有找到匹配的 ARN,则返回错误
if !found {
return Ok(());
}
// 更新 targets_map
targets_map.insert(bucket.to_string(), targets);
arn_remotes_map.remove(arn_str);
let targets = self.list_targets(Some(bucket), None).await;
println!("targets is {}", targets.len());
match serde_json::to_vec(&targets) {
Ok(json) => {
let _ = metadata_sys::update(bucket, "bucket-targets.json", json).await;
}
Err(e) => {
println!("序列化失败{}", e);
}
}
Ok(())
}
pub async fn get_remote_arn(&self, bucket: &str, target: Option<&BucketTarget>, depl_id: &str) -> (Option<String>, bool) {
if target.is_none() {
return (None, false);
}
let target = target.unwrap();
let targets_map = self.targets_map.read().await;
// 获取锁以访问 arn_remote_map
let mut _arn_remotes_map = self.arn_remote_map.read().await;
if let Some(tgts) = targets_map.get(bucket) {
for tgt in tgts {
if tgt.type_ == target.type_
&& tgt.target_bucket == target.target_bucket
&& tgt.endpoint == target.endpoint
&& tgt.credentials.as_ref().unwrap().access_key == target.credentials.as_ref().unwrap().access_key
{
return (tgt.arn.clone(), true);
}
}
}
// if !target.type_.is_valid() {
// return (None, false);
// }
println!("generate_arn");
(Some(generate_arn(target.clone(), depl_id.to_string())), false)
}
pub async fn get_remote_target_client2(&self, arn: &str) -> Result<TargetClient, SetTargetError> {
let map = self.arn_remote_map.read().await;
info!("get remote target client and arn is: {}", arn);
if let Some(value) = map.get(arn) {
let mut x = value.client.clone();
x.arn = arn.to_string();
Ok(x)
} else {
error!("not find target");
Err(SetTargetError::TargetNotFound(arn.to_string()))
}
}
// pub async fn get_remote_target_client(&self, _tgt: &BucketTarget) -> Result<TargetClient, SetTargetError> {
// // Mocked implementation for obtaining a remote client
// let tcli = TargetClient {
// bucket: _tgt.target_bucket.clone(),
// storage_class: "STANDRD".to_string(),
// disable_proxy: false,
// health_check_duration: Duration::from_secs(100),
// endpoint: _tgt.endpoint.clone(),
// reset_id: "0".to_string(),
// replicate_sync: false,
// secure: false,
// arn: "".to_string(),
// client: reqwest::Client::new(),
// ak: _tgt.
// };
// Ok(tcli)
// }
// pub async fn get_remote_target_client_with_bucket(&self, _bucket: String) -> Result<TargetClient, SetTargetError> {
// // Mocked implementation for obtaining a remote client
// let tcli = TargetClient {
// bucket: _tgt.target_bucket.clone(),
// storage_class: "STANDRD".to_string(),
// disable_proxy: false,
// health_check_duration: Duration::from_secs(100),
// endpoint: _tgt.endpoint.clone(),
// reset_id: "0".to_string(),
// replicate_sync: false,
// secure: false,
// arn: "".to_string(),
// client: reqwest::Client::new(),
// };
// Ok(tcli)
// }
async fn local_is_bucket_versioned(&self, _bucket: &str) -> bool {
let Some(store) = new_object_layer_fn() else {
return false;
};
//store.get_bucket_info(bucket, opts)
// let binfo:BucketInfo = store
// .get_bucket_info(bucket, &ecstore::store_api::BucketOptions::default()).await;
match store.get_bucket_info(_bucket, &store_api::BucketOptions::default()).await {
Ok(info) => {
println!("Bucket Info: {:?}", info);
info.versionning
}
Err(err) => {
eprintln!("Error: {:?}", err);
false
}
}
}
async fn is_bucket_versioned(&self, _bucket: &str) -> bool {
true
// let url_str = "http://127.0.0.1:9001";
// // 转换为 Url 类型
// let parsed_url = url::Url::parse(url_str).unwrap();
// let node = Node {
// url: parsed_url,
// pools: vec![],
// is_local: false,
// grid_host: "".to_string(),
// };
// let cli = ecstore::peer::RemotePeerS3Client::new(Some(node), None);
// match cli.get_bucket_info(_bucket, &ecstore::store_api::BucketOptions::default()).await
// {
// Ok(info) => {
// println!("Bucket Info: {:?}", info);
// info.versionning
// }
// Err(err) => {
// eprintln!("Error: {:?}", err);
// return false;
// }
// }
}
pub async fn set_target(&self, bucket: &str, tgt: &BucketTarget, update: bool, fromdisk: bool) -> Result<(), SetTargetError> {
// if !tgt.type_.is_valid() && !update {
// return Err(SetTargetError::InvalidTargetType(bucket.to_string()));
// }
//let client = self.get_remote_target_client(tgt).await?;
if tgt.type_ == Some("replication".to_string()) && !fromdisk {
let versioning_config = self.local_is_bucket_versioned(bucket).await;
if !versioning_config {
// println!("111111111");
return Err(SetTargetError::TargetNotVersioned(bucket.to_string()));
}
}
let url_str = format!("http://{}", tgt.endpoint.clone());
println!("url str is {}", url_str);
// 转换为 Url 类型
let parsed_url = url::Url::parse(&url_str).unwrap();
let node = Node {
url: parsed_url,
pools: vec![],
is_local: false,
grid_host: "".to_string(),
};
let cli = peer::RemotePeerS3Client::new(Some(node), None);
match cli
.get_bucket_info(&tgt.target_bucket, &store_api::BucketOptions::default())
.await
{
Ok(info) => {
println!("Bucket Info: {:?}", info);
if !info.versionning {
println!("2222222222 {}", info.versionning);
return Err(SetTargetError::TargetNotVersioned(tgt.target_bucket.to_string()));
}
}
Err(err) => {
println!("remote bucket 369 is:{}", tgt.target_bucket);
eprintln!("Error: {:?}", err);
return Err(SetTargetError::SourceNotVersioned(tgt.target_bucket.to_string()));
}
}
//if tgt.target_type == BucketTargetType::ReplicationService {
// Check if target is a RustFS server and alive
// let hc_result = tokio::time::timeout(Duration::from_secs(3), client.health_check(&tgt.endpoint)).await;
// match hc_result {
// Ok(Ok(true)) => {} // Server is alive
// Ok(Ok(false)) | Ok(Err(_)) | Err(_) => {
// return Err(SetTargetError::HealthCheckFailed(tgt.target_bucket.clone()));
// }
// }
//Lock and update target maps
let mut targets_map = self.targets_map.write().await;
let mut arn_remotes_map = self.arn_remote_map.write().await;
let targets = targets_map.entry(bucket.to_string()).or_default();
let mut found = false;
for existing_target in targets.iter_mut() {
println!("418 exist:{}", existing_target.source_bucket.clone());
if existing_target.type_ == tgt.type_ {
if existing_target.arn == tgt.arn {
if !update {
return Err(SetTargetError::TargetAlreadyExists(existing_target.target_bucket.clone()));
}
*existing_target = tgt.clone();
found = true;
break;
}
if existing_target.endpoint == tgt.endpoint {
println!("endpoint is same:{}", tgt.endpoint.clone());
return Err(SetTargetError::TargetAlreadyExists(existing_target.target_bucket.clone()));
}
}
}
if !found && !update {
println!("437 exist:{}", tgt.arn.clone().unwrap());
targets.push(tgt.clone());
}
let arntgt: ArnTarget = ArnTarget::new(
tgt.target_bucket.clone(),
tgt.endpoint.clone(),
tgt.credentials.clone().unwrap().access_key.clone(),
tgt.credentials.clone().unwrap().secret_key,
);
arn_remotes_map.insert(tgt.arn.clone().unwrap().clone(), arntgt);
//self.update_bandwidth_limit(bucket, &tgt.arn, tgt.bandwidth_limit).await;
Ok(())
}
}
#[derive(Clone)]
pub struct TargetClient {
pub client: reqwest::Client, // Using reqwest HTTP client
pub health_check_duration: Duration,
pub bucket: String, // Remote bucket target
pub replicate_sync: bool,
pub storage_class: String, // Storage class on remote
pub disable_proxy: bool,
pub arn: String, // ARN to uniquely identify remote target
pub reset_id: String,
pub endpoint: String,
pub secure: bool,
pub ak: String,
pub sk: String,
}
impl TargetClient {
#[allow(clippy::too_many_arguments)]
pub fn new(
client: reqwest::Client,
health_check_duration: Duration,
bucket: String,
replicate_sync: bool,
storage_class: String,
disable_proxy: bool,
arn: String,
reset_id: String,
endpoint: String,
secure: bool,
ak: String,
sk: String,
) -> Self {
TargetClient {
client,
health_check_duration,
bucket,
replicate_sync,
storage_class,
disable_proxy,
arn,
reset_id,
endpoint,
secure,
ak,
sk,
}
}
pub async fn bucket_exists(&self, _bucket: &str) -> Result<bool, SetTargetError> {
Ok(true) // Mocked implementation
}
}
use tracing::{error, info, warn};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct VersioningConfig {
pub enabled: bool,
}
impl VersioningConfig {
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
#[derive(Debug)]
pub struct Client;
impl Client {
pub async fn bucket_exists(&self, _bucket: &str) -> Result<bool, SetTargetError> {
Ok(true) // Mocked implementation
}
pub async fn get_bucket_versioning(&self, _bucket: &str) -> Result<VersioningConfig, SetTargetError> {
Ok(VersioningConfig { enabled: true })
}
pub async fn health_check(&self, _endpoint: &str) -> Result<bool, SetTargetError> {
Ok(true) // Mocked health check
}
}
#[derive(Debug, PartialEq)]
pub struct ServiceType(String);
impl ServiceType {
pub fn is_valid(&self) -> bool {
!self.0.is_empty() // 根据需求添加具体的验证逻辑
}
}
#[derive(Debug, PartialEq)]
pub struct ARN {
pub arn_type: String,
pub id: String,
pub region: String,
pub bucket: String,
}
impl ARN {
/// 检查 ARN 是否为空
pub fn is_empty(&self) -> bool {
//!self.arn_type.is_valid()
false
}
/// 从字符串解析 ARN
pub fn parse(s: &str) -> Result<Self, String> {
// ARN 必须是格式 arn:rustfs:<Type>:<REGION>:<ID>:<remote-bucket>
if !s.starts_with("arn:rustfs:") {
return Err(format!("Invalid ARN {}", s));
}
let tokens: Vec<&str> = s.split(':').collect();
if tokens.len() != 6 || tokens[4].is_empty() || tokens[5].is_empty() {
return Err(format!("Invalid ARN {}", s));
}
Ok(ARN {
arn_type: tokens[2].to_string(),
region: tokens[3].to_string(),
id: tokens[4].to_string(),
bucket: tokens[5].to_string(),
})
}
}
// 实现 `Display` trait,使得可以直接使用 `format!` 或 `{}` 输出 ARN
impl std::fmt::Display for ARN {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "arn:rustfs:{}:{}:{}:{}", self.arn_type, self.region, self.id, self.bucket)
}
}
fn must_get_uuid() -> String {
Uuid::new_v4().to_string()
// match Uuid::new_v4() {
// Ok(uuid) => uuid.to_string(),
// Err(err) => {
// error!("Critical error: {}", err);
// panic!("Failed to generate UUID: {}", err); // Ensures similar behavior as Go's logger.CriticalIf
// }
// }
}
fn generate_arn(target: BucketTarget, depl_id: String) -> String {
let mut uuid: String = depl_id;
if uuid.is_empty() {
uuid = must_get_uuid();
}
let arn: ARN = ARN {
arn_type: target.type_.unwrap(),
id: (uuid),
region: "us-east-1".to_string(),
bucket: (target.target_bucket),
};
arn.to_string()
}
// use std::collections::HashMap;
// use std::sync::{Arc, Mutex, RwLock};
// use std::time::Duration;
// use tokio::time::timeout;
// use tokio::sync::RwLock as AsyncRwLock;
// use serde::Deserialize;
// use thiserror::Error;
// #[derive(Debug, Clone, PartialEq)]
// pub enum BucketTargetType {
// ReplicationService,
// // Add other service types as needed
// }
// impl BucketTargetType {
// pub fn is_valid(&self) -> bool {
// matches!(self, BucketTargetType::ReplicationService)
// }
// }
// #[derive(Debug, Clone)]
// pub struct BucketTarget {
// pub arn: String,
// pub target_bucket: String,
// pub endpoint: String,
// pub credentials: Credentials,
// pub secure: bool,
// pub bandwidth_limit: Option<u64>,
// pub target_type: BucketTargetType,
// }
// #[derive(Debug, Clone)]
// pub struct Credentials {
// pub access_key: String,
// pub secret_key: String,
// }
// #[derive(Debug)]
// pub struct BucketTargetSys {
// targets_map: Arc<RwLock<HashMap<String, Vec<BucketTarget>>>>,
// arn_remotes_map: Arc<Mutex<HashMap<String, ArnTarget>>>,
// }
// impl BucketTargetSys {
// pub fn new() -> Self {
// Self {
// targets_map: Arc::new(RwLock::new(HashMap::new())),
// arn_remotes_map: Arc::new(Mutex::new(HashMap::new())),
// }
// }
// pub async fn set_target(
// &self,
// bucket: &str,
// tgt: &BucketTarget,
// update: bool,
// ) -> Result<(), SetTargetError> {
// if !tgt.target_type.is_valid() && !update {
// return Err(SetTargetError::InvalidTargetType(bucket.to_string()));
// }
// let client = self.get_remote_target_client(tgt).await?;
// // Validate if target credentials are OK
// let exists = client.bucket_exists(&tgt.target_bucket).await?;
// if !exists {
// return Err(SetTargetError::TargetNotFound(tgt.target_bucket.clone()));
// }
// if tgt.target_type == BucketTargetType::ReplicationService {
// if !self.is_bucket_versioned(bucket).await {
// return Err(SetTargetError::SourceNotVersioned(bucket.to_string()));
// }
// let versioning_config = client.get_bucket_versioning(&tgt.target_bucket).await?;
// if !versioning_config.is_enabled() {
// return Err(SetTargetError::TargetNotVersioned(tgt.target_bucket.clone()));
// }
// }
// // Check if target is a RustFS server and alive
// let hc_result = timeout(Duration::from_secs(3), client.health_check(&tgt.endpoint)).await;
// match hc_result {
// Ok(Ok(true)) => {} // Server is alive
// Ok(Ok(false)) | Ok(Err(_)) | Err(_) => {
// return Err(SetTargetError::HealthCheckFailed(tgt.target_bucket.clone()));
// }
// }
// // Lock and update target maps
// let mut targets_map = self.targets_map.write().await;
// let mut arn_remotes_map = self.arn_remotes_map.lock().unwrap();
// let targets = targets_map.entry(bucket.to_string()).or_default();
// let mut found = false;
// for existing_target in targets.iter_mut() {
// if existing_target.target_type == tgt.target_type {
// if existing_target.arn == tgt.arn {
// if !update {
// return Err(SetTargetError::TargetAlreadyExists(existing_target.target_bucket.clone()));
// }
// *existing_target = tgt.clone();
// found = true;
// break;
// }
// if existing_target.endpoint == tgt.endpoint {
// return Err(SetTargetError::TargetAlreadyExists(existing_target.target_bucket.clone()));
// }
// }
// }
// if !found && !update {
// targets.push(tgt.clone());
// }
// arn_remotes_map.insert(tgt.arn.clone(), ArnTarget { client });
// self.update_bandwidth_limit(bucket, &tgt.arn, tgt.bandwidth_limit).await;
// Ok(())
// }
// async fn get_remote_target_client(&self, tgt: &BucketTarget) -> Result<Client, SetTargetError> {
// // Mocked implementation for obtaining a remote client
// Ok(Client {})
// }
// async fn is_bucket_versioned(&self, bucket: &str) -> bool {
// // Mocked implementation for checking if a bucket is versioned
// true
// }
// async fn update_bandwidth_limit(
// &self,
// bucket: &str,
// arn: &str,
// limit: Option<u64>,
// ) {
// // Mocked implementation for updating bandwidth limits
// }
// }
// #[derive(Debug)]
// pub struct Client;
// impl Client {
// pub async fn bucket_exists(&self, _bucket: &str) -> Result<bool, SetTargetError> {
// Ok(true) // Mocked implementation
// }
// pub async fn get_bucket_versioning(
// &self,
// _bucket: &str,
// ) -> Result<VersioningConfig, SetTargetError> {
// Ok(VersioningConfig { enabled: true })
// }
// pub async fn health_check(&self, _endpoint: &str) -> Result<bool, SetTargetError> {
// Ok(true) // Mocked health check
// }
// }
// #[derive(Debug, Clone)]
// pub struct ArnTarget {
// pub client: Client,
// }
#[derive(Debug, Error)]
pub enum SetTargetError {
#[error("Invalid target type for bucket {0}")]
InvalidTargetType(String),
#[error("Target bucket {0} not found")]
TargetNotFound(String),
#[error("Source bucket {0} is not versioned")]
SourceNotVersioned(String),
#[error("Target bucket {0} is not versioned")]
TargetNotVersioned(String),
#[error("Health check failed for bucket {0}")]
HealthCheckFailed(String),
#[error("Target bucket {0} already exists")]
TargetAlreadyExists(String),
}
+2
View File
@@ -0,0 +1,2 @@
pub mod bucket_replication;
pub mod bucket_targets;
+1 -1
View File
@@ -2207,7 +2207,7 @@ impl DiskAPI for LocalDisk {
for info in obj_infos.iter() {
let done = ScannerMetrics::time(ScannerMetric::ApplyVersion);
let sz: usize;
(obj_deleted, sz) = item.apply_actions(info, &size_s).await;
(obj_deleted, sz) = item.apply_actions(info, &mut size_s).await;
done();
if obj_deleted {
+3329 -3349
View File
File diff suppressed because it is too large Load Diff
+107 -4
View File
@@ -18,8 +18,10 @@ use super::{
data_usage_cache::{DataUsageCache, DataUsageEntry, DataUsageHash},
heal_commands::{HEAL_DEEP_SCAN, HEAL_NORMAL_SCAN, HealScanMode},
};
use crate::{bucket::metadata_sys, cmd::bucket_replication::queue_replication_heal};
use crate::{
bucket::{versioning::VersioningApi, versioning_sys::BucketVersioningSys},
cmd::bucket_replication::ReplicationStatusType,
disk,
heal::data_usage::DATA_USAGE_ROOT,
};
@@ -170,9 +172,9 @@ pub async fn init_data_scanner() {
// Calculate randomized sleep duration
// Use random factor (0.0 to 1.0) multiplied by the scanner cycle duration
let random_factor: f64 = {
let mut rng = rand::thread_rng();
rng.gen_range(1.0..10.0)
let random_factor = {
let mut rng = rand::rng();
rng.random_range(1.0..10.0)
};
let base_cycle_duration = SCANNER_CYCLE.load(Ordering::SeqCst) as f64;
let sleep_duration_secs = random_factor * base_cycle_duration;
@@ -556,13 +558,114 @@ impl ScannerItem {
Ok(object_infos)
}
pub async fn apply_actions(&self, oi: &ObjectInfo, _size_s: &SizeSummary) -> (bool, usize) {
pub async fn apply_actions(&mut self, oi: &ObjectInfo, _size_s: &mut SizeSummary) -> (bool, usize) {
let done = ScannerMetrics::time(ScannerMetric::Ilm);
//todo: lifecycle
info!(
"apply_actions {} {} {:?} {:?}",
oi.bucket.clone(),
oi.name.clone(),
oi.version_id.clone(),
oi.user_defined.clone()
);
// Create a mutable clone if you need to modify fields
let mut oi = oi.clone();
oi.replication_status = ReplicationStatusType::from(
oi.user_defined
.as_ref()
.and_then(|map| map.get("x-amz-bucket-replication-status"))
.unwrap_or(&"PENDING".to_string()),
);
info!("apply status is: {:?}", oi.replication_status);
self.heal_replication(&oi, _size_s).await;
done();
(false, oi.size)
}
pub async fn heal_replication(&mut self, oi: &ObjectInfo, size_s: &mut SizeSummary) {
if oi.version_id.is_none() {
error!(
"heal_replication: no version_id or replication config {} {} {}",
oi.bucket,
oi.name,
oi.version_id.is_none()
);
return;
}
//let config = s3s::dto::ReplicationConfiguration{ role: todo!(), rules: todo!() };
// Use the provided variable instead of borrowing self mutably.
let replication = match metadata_sys::get_replication_config(&oi.bucket).await {
Ok((replication, _)) => replication,
Err(_) => {
error!("heal_replication: failed to get replication config for bucket {} {}", oi.bucket, oi.name);
return;
}
};
if replication.rules.is_empty() {
error!("heal_replication: no replication rules for bucket {} {}", oi.bucket, oi.name);
return;
}
if replication.role.is_empty() {
// error!("heal_replication: no replication role for bucket {} {}", oi.bucket, oi.name);
// return;
}
//if oi.delete_marker || !oi.version_purge_status.is_empty() {
if oi.delete_marker {
error!(
"heal_replication: delete marker or version purge status {} {} {:?} {} {:?}",
oi.bucket, oi.name, oi.version_id, oi.delete_marker, oi.version_purge_status
);
return;
}
if oi.replication_status == ReplicationStatusType::Completed {
return;
}
info!("replication status is: {:?} and user define {:?}", oi.replication_status, oi.user_defined);
let roi = queue_replication_heal(&oi.bucket, oi, &replication, 3).await;
if roi.is_none() {
info!("not need heal {} {} {:?}", oi.bucket, oi.name, oi.version_id);
return;
}
for (arn, tgt_status) in &roi.unwrap().target_statuses {
let tgt_size_s = size_s.repl_target_stats.entry(arn.clone()).or_default();
match tgt_status {
ReplicationStatusType::Pending => {
tgt_size_s.pending_count += 1;
tgt_size_s.pending_size += oi.size;
size_s.pending_count += 1;
size_s.pending_size += oi.size;
}
ReplicationStatusType::Failed => {
tgt_size_s.failed_count += 1;
tgt_size_s.failed_size += oi.size;
size_s.failed_count += 1;
size_s.failed_size += oi.size;
}
ReplicationStatusType::Completed | ReplicationStatusType::CompletedLegacy => {
tgt_size_s.replicated_count += 1;
tgt_size_s.replicated_size += oi.size;
size_s.replicated_count += 1;
size_s.replicated_size += oi.size;
}
_ => {}
}
}
if matches!(oi.replication_status, ReplicationStatusType::Replica) {
size_s.replica_count += 1;
size_s.replica_size += oi.size;
}
}
}
#[derive(Debug, Default)]
+2 -2
View File
@@ -438,8 +438,8 @@ impl DataUsageCache {
}
retries += 1;
let dur = {
let mut rng = rand::thread_rng();
rng.gen_range(0..1_000)
let mut rng = rand::rng();
rng.random_range(0..1_000)
};
sleep(Duration::from_millis(dur)).await;
}
+1
View File
@@ -3,6 +3,7 @@ pub mod admin_server_info;
pub mod bucket;
pub mod cache_value;
mod chunk_stream;
pub mod cmd;
pub mod config;
pub mod disk;
pub mod disks_layout;
+10 -3
View File
@@ -1,3 +1,4 @@
use crate::bucket::metadata_sys;
use crate::disk::error::{Error, Result};
use crate::disk::error_reduce::{BUCKET_OP_IGNORED_ERRS, is_all_buckets_not_found, reduce_write_quorum_errs};
use crate::disk::{DiskAPI, DiskStore};
@@ -425,14 +426,17 @@ impl PeerS3Client for LocalPeerS3Client {
}
// TODO: reduceWriteQuorumErrs
// debug!("get_bucket_info errs:{:?}", errs);
let mut versioned = false;
if let Ok(sys) = metadata_sys::get(bucket).await {
versioned = sys.versioning();
}
ress.iter()
.find_map(|op| {
op.as_ref().map(|v| BucketInfo {
name: v.name.clone(),
created: v.created,
versionning: versioned,
..Default::default()
})
})
@@ -491,10 +495,13 @@ pub struct RemotePeerS3Client {
}
impl RemotePeerS3Client {
fn new(node: Option<Node>, pools: Option<Vec<usize>>) -> Self {
pub fn new(node: Option<Node>, pools: Option<Vec<usize>>) -> Self {
let addr = node.as_ref().map(|v| v.url.to_string()).unwrap_or_default().to_string();
Self { node, pools, addr }
}
pub fn get_addr(&self) -> String {
self.addr.clone()
}
}
#[async_trait]
+10 -13
View File
@@ -59,10 +59,7 @@ use http::HeaderMap;
use lock::{LockApi, namespace_lock::NsLockMap};
use madmin::heal_commands::{HealDriveInfo, HealResultItem};
use md5::{Digest as Md5Digest, Md5};
use rand::{
thread_rng,
{Rng, seq::SliceRandom},
};
use rand::{Rng, seq::SliceRandom};
use rustfs_filemeta::{
FileInfo, FileMeta, FileMetaShallowVersion, MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams, ObjectPartInfo,
RawFileInfo, file_info_from_raw, merge_file_meta_versions,
@@ -133,7 +130,7 @@ impl SetDisks {
}
}
let mut rng = thread_rng();
let mut rng = rand::rng();
disks.shuffle(&mut rng);
@@ -142,7 +139,7 @@ impl SetDisks {
async fn get_online_local_disks(&self) -> Vec<Option<DiskStore>> {
let mut disks = self.get_online_disks().await;
let mut rng = thread_rng();
let mut rng = rand::rng();
disks.shuffle(&mut rng);
@@ -165,10 +162,10 @@ impl SetDisks {
let mut futures = Vec::with_capacity(disks.len());
let mut numbers: Vec<usize> = (0..disks.len()).collect();
{
let mut rng = thread_rng();
let mut rng = rand::rng();
disks.shuffle(&mut rng);
numbers.shuffle(&mut thread_rng());
numbers.shuffle(&mut rng);
}
for &i in numbers.iter() {
@@ -242,7 +239,7 @@ impl SetDisks {
async fn _get_local_disks(&self) -> Vec<Option<DiskStore>> {
let mut disks = self.get_disks_internal().await;
let mut rng = thread_rng();
let mut rng = rand::rng();
disks.shuffle(&mut rng);
@@ -3021,7 +3018,7 @@ impl SetDisks {
// in different order per erasure set, this wider spread is needed when
// there are lots of buckets with different order of objects in them.
let permutes = {
let mut rng = thread_rng();
let mut rng = rand::rng();
let mut permutes: Vec<usize> = (0..buckets.len()).collect();
permutes.shuffle(&mut rng);
permutes
@@ -3043,8 +3040,8 @@ impl SetDisks {
let (buckets_results_tx, mut buckets_results_rx) = mpsc::channel::<DataUsageEntryInfo>(disks.len());
let update_time = {
let mut rng = thread_rng();
Duration::from_secs(30) + Duration::from_secs_f64(10.0 * rng.gen_range(0.0..1.0))
let mut rng = rand::rng();
Duration::from_secs(30) + Duration::from_secs_f64(10.0 * rng.random_range(0.0..1.0))
};
let mut ticker = interval(update_time);
@@ -3389,7 +3386,7 @@ impl SetDisks {
}
{
let mut rng = thread_rng();
let mut rng = rand::rng();
// 随机洗牌
disks.shuffle(&mut rng);
+3 -1
View File
@@ -49,6 +49,7 @@ use glob::Pattern;
use http::HeaderMap;
use lazy_static::lazy_static;
use madmin::heal_commands::HealResultItem;
use rand::Rng as _;
use rustfs_filemeta::MetaCacheEntry;
use s3s::dto::{BucketVersioningStatus, ObjectLockConfiguration, ObjectLockEnabled, VersioningConfiguration};
use std::cmp::Ordering;
@@ -501,7 +502,8 @@ impl ECStore {
return None;
}
let random_u64: u64 = rand::random();
let mut rng = rand::rng();
let random_u64: u64 = rng.random_range(0..total);
let choose = random_u64 % total;
let mut at_total = 0;
+11
View File
@@ -1,5 +1,6 @@
use crate::bucket::metadata_sys::get_versioning_config;
use crate::bucket::versioning::VersioningApi as _;
use crate::cmd::bucket_replication::{ReplicationStatusType, VersionPurgeStatusType};
use crate::error::{Error, Result};
use crate::heal::heal_ops::HealSequence;
use crate::store_utils::clean_metadata;
@@ -317,6 +318,11 @@ pub struct ObjectInfo {
pub inlined: bool,
pub metadata_only: bool,
pub version_only: bool,
pub replication_status_internal: String,
pub replication_status: ReplicationStatusType,
pub version_purge_status_internal: String,
pub version_purge_status: VersionPurgeStatusType,
pub checksum: Vec<u8>,
}
impl Clone for ObjectInfo {
@@ -345,6 +351,11 @@ impl Clone for ObjectInfo {
inlined: self.inlined,
metadata_only: self.metadata_only,
version_only: self.version_only,
replication_status_internal: self.replication_status_internal.clone(),
replication_status: self.replication_status.clone(),
version_purge_status_internal: self.version_purge_status_internal.clone(),
version_purge_status: self.version_purge_status.clone(),
checksum: Default::default(),
}
}
}
+11 -9
View File
@@ -15,7 +15,6 @@ use crate::utils::path::{self, SLASH_SEPARATOR, base_dir_from_prefix};
use crate::{store::ECStore, store_api::ListObjectsV2Info};
use futures::future::join_all;
use rand::seq::SliceRandom;
use rand::thread_rng;
use rustfs_filemeta::{
FileInfo, MetaCacheEntries, MetaCacheEntriesSorted, MetaCacheEntriesSortedResult, MetaCacheEntry, MetadataResolutionParams,
merge_file_meta_versions,
@@ -24,7 +23,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::broadcast::{self, Receiver as B_Receiver};
use tokio::sync::mpsc::{self, Receiver, Sender};
use tracing::error;
use tracing::{error, warn};
use uuid::Uuid;
const MAX_OBJECT_LIST: i32 = 1000;
@@ -539,6 +538,9 @@ impl ECStore {
error!("gather_results err {:?}", err);
let _ = err_tx2.send(Arc::new(err));
}
// cancel call exit spawns
let _ = cancel_tx.send(true);
});
let mut result = {
@@ -564,9 +566,6 @@ impl ECStore {
}
};
// cancel call exit spawns
cancel_tx.send(true).map_err(Error::other)?;
// wait spawns exit
join_all(vec![job1, job2]).await;
@@ -621,7 +620,7 @@ impl ECStore {
tokio::spawn(async move {
if let Err(err) = merge_entry_channels(rx, inputs, sender.clone(), 1).await {
println!("merge_entry_channels err {:?}", err)
error!("merge_entry_channels err {:?}", err)
}
});
@@ -715,7 +714,7 @@ impl ECStore {
let fallback_disks = {
if ask_disks > 0 && disks.len() > ask_disks as usize {
let mut rand = thread_rng();
let mut rand = rand::rng();
disks.shuffle(&mut rand);
disks.split_off(ask_disks as usize)
} else {
@@ -1067,7 +1066,10 @@ async fn merge_entry_channels(
return Ok(())
}
},
_ = rx.recv()=>return Err(Error::other("cancel")),
_ = rx.recv()=>{
warn!("merge_entry_channels rx.recv() cancel");
return Ok(())
},
}
}
}
@@ -1230,7 +1232,7 @@ impl SetDisks {
let mut fallback_disks = Vec::new();
if ask_disks > 0 && disks.len() > ask_disks as usize {
let mut rand = thread_rng();
let mut rand = rand::rng();
disks.shuffle(&mut rand);
fallback_disks = disks.split_off(ask_disks as usize);
+1
View File
@@ -1,3 +1,4 @@
pub const AMZ_OBJECT_TAGGING: &str = "X-Amz-Tagging";
pub const AMZ_BUCKET_REPLICATION_STATUS: &str = "X-Amz-Replication-Status";
pub const AMZ_STORAGE_CLASS: &str = "x-amz-storage-class";
pub const AMZ_DECODED_CONTENT_LENGTH: &str = "X-Amz-Decoded-Content-Length";