mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-11 23:56:53 +00:00
fix: restore localized samples in tests (#749)
* fix: restore required localized examples * style: fix formatting issues
This commit is contained in:
@@ -96,21 +96,21 @@ async fn is_server_resolvable(endpoint: &Endpoint) -> Result<()> {
|
||||
let decoded_payload = flatbuffers::root::<PingBody>(finished_data);
|
||||
assert!(decoded_payload.is_ok());
|
||||
|
||||
// 创建客户端
|
||||
// Create the client
|
||||
let mut client = node_service_time_out_client(&addr)
|
||||
.await
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
|
||||
// 构造 PingRequest
|
||||
// Build the PingRequest
|
||||
let request = Request::new(PingRequest {
|
||||
version: 1,
|
||||
body: bytes::Bytes::copy_from_slice(finished_data),
|
||||
});
|
||||
|
||||
// 发送请求并获取响应
|
||||
// Send the request and obtain the response
|
||||
let response: PingResponse = client.ping(request).await?.into_inner();
|
||||
|
||||
// 打印响应
|
||||
// Print the response
|
||||
let ping_response_body = flatbuffers::root::<PingBody>(&response.body);
|
||||
if let Err(e) = ping_response_body {
|
||||
eprintln!("{e}");
|
||||
|
||||
@@ -428,8 +428,8 @@ where
|
||||
let sec = t.unix_timestamp() - 62135596800;
|
||||
let nsec = t.nanosecond();
|
||||
buf[0] = 0xc7; // mext8
|
||||
buf[1] = 0x0c; // 长度
|
||||
buf[2] = 0x05; // 时间扩展类型
|
||||
buf[1] = 0x0c; // Length
|
||||
buf[2] = 0x05; // Time extension type
|
||||
BigEndian::write_u64(&mut buf[3..], sec as u64);
|
||||
BigEndian::write_u32(&mut buf[11..], nsec);
|
||||
s.serialize_bytes(&buf)
|
||||
|
||||
@@ -16,16 +16,16 @@ use crate::error::Result;
|
||||
use rmp_serde::Serializer as rmpSerializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// 定义 QuotaType 枚举类型
|
||||
// Define the QuotaType enum
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum QuotaType {
|
||||
Hard,
|
||||
}
|
||||
|
||||
// 定义 BucketQuota 结构体
|
||||
// Define the BucketQuota structure
|
||||
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
|
||||
pub struct BucketQuota {
|
||||
quota: Option<u64>, // 使用 Option 来表示可能不存在的字段
|
||||
quota: Option<u64>, // Use Option to represent optional fields
|
||||
|
||||
size: u64,
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ pub trait ReplicationConfigurationExt {
|
||||
}
|
||||
|
||||
impl ReplicationConfigurationExt for ReplicationConfiguration {
|
||||
/// 检查是否有现有对象复制规则
|
||||
/// Check whether any object-replication rules exist
|
||||
fn has_existing_object_replication(&self, arn: &str) -> (bool, bool) {
|
||||
let mut has_arn = false;
|
||||
|
||||
@@ -117,7 +117,7 @@ impl ReplicationConfigurationExt for ReplicationConfiguration {
|
||||
rules
|
||||
}
|
||||
|
||||
/// 获取目标配置
|
||||
/// Retrieve the destination configuration
|
||||
fn get_destination(&self) -> Destination {
|
||||
if !self.rules.is_empty() {
|
||||
self.rules[0].destination.clone()
|
||||
@@ -134,7 +134,7 @@ impl ReplicationConfigurationExt for ReplicationConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断对象是否应该被复制
|
||||
/// Determine whether an object should be replicated
|
||||
fn replicate(&self, obj: &ObjectOpts) -> bool {
|
||||
let rules = self.filter_actionable_rules(obj);
|
||||
|
||||
@@ -164,16 +164,16 @@ impl ReplicationConfigurationExt for ReplicationConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
// 常规对象/元数据复制
|
||||
// Regular object/metadata replication
|
||||
return rule.metadata_replicate(obj);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 检查是否有活跃的规则
|
||||
/// 可选择性地提供前缀
|
||||
/// 如果recursive为true,函数还会在前缀下的任何级别有活跃规则时返回true
|
||||
/// 如果没有指定前缀,recursive实际上为true
|
||||
/// Check for an active rule
|
||||
/// Optionally accept a prefix
|
||||
/// When recursive is true, return true if any level under the prefix has an active rule
|
||||
/// Without a prefix, recursive behaves as true
|
||||
fn has_active_rules(&self, prefix: &str, recursive: bool) -> bool {
|
||||
if self.rules.is_empty() {
|
||||
return false;
|
||||
@@ -187,13 +187,13 @@ impl ReplicationConfigurationExt for ReplicationConfiguration {
|
||||
if let Some(filter) = &rule.filter {
|
||||
if let Some(filter_prefix) = &filter.prefix {
|
||||
if !prefix.is_empty() && !filter_prefix.is_empty() {
|
||||
// 传入的前缀必须在规则前缀中
|
||||
// The provided prefix must fall within the rule prefix
|
||||
if !recursive && !prefix.starts_with(filter_prefix) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是递归的,我们可以跳过这个规则,如果它不匹配测试前缀或前缀下的级别不匹配
|
||||
// When recursive, skip this rule if it does not match the test prefix or hierarchy
|
||||
if recursive && !rule.prefix().starts_with(prefix) && !prefix.starts_with(rule.prefix()) {
|
||||
continue;
|
||||
}
|
||||
@@ -204,7 +204,7 @@ impl ReplicationConfigurationExt for ReplicationConfiguration {
|
||||
false
|
||||
}
|
||||
|
||||
/// 过滤目标ARN,返回配置中不同目标ARN的切片
|
||||
/// Filter target ARNs and return a slice of the distinct values in the config
|
||||
fn filter_target_arns(&self, obj: &ObjectOpts) -> Vec<String> {
|
||||
let mut arns = Vec::new();
|
||||
let mut targets_map: HashSet<String> = HashSet::new();
|
||||
@@ -216,7 +216,7 @@ impl ReplicationConfigurationExt for ReplicationConfiguration {
|
||||
}
|
||||
|
||||
if !self.role.is_empty() {
|
||||
arns.push(self.role.clone()); // 如果存在,使用传统的RoleArn
|
||||
arns.push(self.role.clone()); // Use the legacy RoleArn when present
|
||||
return arns;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
// #[allow(clippy::shadow_same)] // necessary for `pin_mut!`
|
||||
// Box::pin(async move {
|
||||
// pin_mut!(body);
|
||||
// // 上一次没用完的数据
|
||||
// // Data left over from the previous call
|
||||
// let mut prev_bytes = Bytes::new();
|
||||
// let mut read_size = 0;
|
||||
|
||||
// loop {
|
||||
// let data: Vec<Bytes> = {
|
||||
// // 读固定大小的数据
|
||||
// // Read a fixed-size chunk
|
||||
// match Self::read_data(body.as_mut(), prev_bytes, chunk_size).await {
|
||||
// None => break,
|
||||
// Some(Err(e)) => return Err(e),
|
||||
@@ -72,13 +72,13 @@
|
||||
|
||||
// if read_size + prev_bytes.len() >= content_length {
|
||||
// // debug!(
|
||||
// // "读完了 read_size:{} + prev_bytes.len({}) == content_length {}",
|
||||
// // "Finished reading: read_size:{} + prev_bytes.len({}) == content_length {}",
|
||||
// // read_size,
|
||||
// // prev_bytes.len(),
|
||||
// // content_length,
|
||||
// // );
|
||||
|
||||
// // 填充 0?
|
||||
// // Pad with zeros?
|
||||
// if !need_padding {
|
||||
// y.yield_ok(prev_bytes).await;
|
||||
// break;
|
||||
@@ -115,7 +115,7 @@
|
||||
// {
|
||||
// let mut bytes_buffer = Vec::new();
|
||||
|
||||
// // 只执行一次
|
||||
// // Run only once
|
||||
// let mut push_data_bytes = |mut bytes: Bytes| {
|
||||
// // debug!("read from body {} split per {}, prev_bytes: {}", bytes.len(), data_size, prev_bytes.len());
|
||||
|
||||
@@ -127,11 +127,11 @@
|
||||
// return Some(bytes);
|
||||
// }
|
||||
|
||||
// // 合并上一次数据
|
||||
// // Merge with the previous data
|
||||
// if !prev_bytes.is_empty() {
|
||||
// let need_size = data_size.wrapping_sub(prev_bytes.len());
|
||||
// // debug!(
|
||||
// // " 上一次有剩余{},从这一次中取{},共:{}",
|
||||
// // "Previous leftover {}, take {} now, total: {}",
|
||||
// // prev_bytes.len(),
|
||||
// // need_size,
|
||||
// // prev_bytes.len() + need_size
|
||||
@@ -143,7 +143,7 @@
|
||||
// combined.extend_from_slice(&data);
|
||||
|
||||
// // debug!(
|
||||
// // "取到的长度大于所需,取出需要的长度:{},与上一次合并得到:{},bytes 剩余:{}",
|
||||
// // "Fetched more bytes than needed: {}, merged result {}, remaining bytes {}",
|
||||
// // need_size,
|
||||
// // combined.len(),
|
||||
// // bytes.len(),
|
||||
@@ -156,7 +156,7 @@
|
||||
// combined.extend_from_slice(&bytes);
|
||||
|
||||
// // debug!(
|
||||
// // "取到的长度小于所需,取出需要的长度:{},与上一次合并得到:{},bytes 剩余:{},直接返回",
|
||||
// // "Fetched fewer bytes than needed: {}, merged result {}, remaining bytes {}, return immediately",
|
||||
// // need_size,
|
||||
// // combined.len(),
|
||||
// // bytes.len(),
|
||||
@@ -166,29 +166,29 @@
|
||||
// }
|
||||
// }
|
||||
|
||||
// // 取到的数据比需要的块大,从 bytes 中截取需要的块大小
|
||||
// // If the fetched data exceeds the chunk, slice the required size
|
||||
// if data_size <= bytes.len() {
|
||||
// let n = bytes.len() / data_size;
|
||||
|
||||
// for _ in 0..n {
|
||||
// let data = bytes.split_to(data_size);
|
||||
|
||||
// // println!("bytes_buffer.push: {},剩余:{}", data.len(), bytes.len());
|
||||
// // println!("bytes_buffer.push: {}, remaining: {}", data.len(), bytes.len());
|
||||
// bytes_buffer.push(data);
|
||||
// }
|
||||
|
||||
// Some(bytes)
|
||||
// } else {
|
||||
// // 不够
|
||||
// // Insufficient data
|
||||
// Some(bytes)
|
||||
// }
|
||||
// };
|
||||
|
||||
// // 剩余数据
|
||||
// // Remaining data
|
||||
// let remaining_bytes = 'outer: {
|
||||
// // // 如果上一次数据足够,跳出
|
||||
// // // Exit if the previous data was sufficient
|
||||
// // if let Some(remaining_bytes) = push_data_bytes(prev_bytes) {
|
||||
// // println!("从剩下的取");
|
||||
// // println!("Consuming leftovers");
|
||||
// // break 'outer remaining_bytes;
|
||||
// // }
|
||||
|
||||
|
||||
@@ -49,12 +49,12 @@ pub fn reduce_quorum_errs(errors: &[Option<Error>], ignored_errs: &[Error], quor
|
||||
pub fn reduce_errs(errors: &[Option<Error>], ignored_errs: &[Error]) -> (usize, Option<Error>) {
|
||||
let nil_error = Error::other("nil".to_string());
|
||||
|
||||
// 首先统计 None 的数量(作为 nil 错误)
|
||||
// First count the number of None values (treated as nil errors)
|
||||
let nil_count = errors.iter().filter(|e| e.is_none()).count();
|
||||
|
||||
let err_counts = errors
|
||||
.iter()
|
||||
.filter_map(|e| e.as_ref()) // 只处理 Some 的错误
|
||||
.filter_map(|e| e.as_ref()) // Only process errors stored in Some
|
||||
.fold(std::collections::HashMap::new(), |mut acc, e| {
|
||||
if is_ignored_err(ignored_errs, e) {
|
||||
return acc;
|
||||
@@ -63,13 +63,13 @@ pub fn reduce_errs(errors: &[Option<Error>], ignored_errs: &[Error]) -> (usize,
|
||||
acc
|
||||
});
|
||||
|
||||
// 找到最高频率的非 nil 错误
|
||||
// Find the most frequent non-nil error
|
||||
let (best_err, best_count) = err_counts
|
||||
.into_iter()
|
||||
.max_by(|(_, c1), (_, c2)| c1.cmp(c2))
|
||||
.unwrap_or((nil_error.clone(), 0));
|
||||
|
||||
// 比较 nil 错误和最高频率的非 nil 错误, 优先选择 nil 错误
|
||||
// Compare nil errors with the top non-nil error and prefer the nil error
|
||||
if nil_count > best_count || (nil_count == best_count && nil_count > 0) {
|
||||
(nil_count, None)
|
||||
} else {
|
||||
|
||||
@@ -319,8 +319,8 @@ impl LocalDisk {
|
||||
}
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
// 在 Windows 上,卷名不应该包含保留字符。
|
||||
// 这个正则表达式匹配了不允许的字符。
|
||||
// Windows volume names must not include reserved characters.
|
||||
// This regular expression matches disallowed characters.
|
||||
if volname.contains('|')
|
||||
|| volname.contains('<')
|
||||
|| volname.contains('>')
|
||||
@@ -333,7 +333,7 @@ impl LocalDisk {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 对于非 Windows 系统,可能需要其他的验证逻辑。
|
||||
// Non-Windows systems may require additional validation rules.
|
||||
}
|
||||
|
||||
true
|
||||
@@ -563,7 +563,7 @@ impl LocalDisk {
|
||||
|
||||
// return Ok(());
|
||||
|
||||
// TODO: 异步通知 检测硬盘空间 清空回收站
|
||||
// TODO: async notifications for disk space checks and trash cleanup
|
||||
|
||||
let trash_path = self.get_object_path(super::RUSTFS_META_TMP_DELETED_BUCKET, Uuid::new_v4().to_string().as_str())?;
|
||||
// if let Some(parent) = trash_path.parent() {
|
||||
@@ -846,13 +846,13 @@ impl LocalDisk {
|
||||
}
|
||||
}
|
||||
|
||||
// 没有版本了,删除 xl.meta
|
||||
// Remove xl.meta when no versions remain
|
||||
if fm.versions.is_empty() {
|
||||
self.delete_file(&volume_dir, &xlpath, true, false).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 更新 xl.meta
|
||||
// Update xl.meta
|
||||
let buf = fm.marshal_msg()?;
|
||||
|
||||
let volume_dir = self.get_bucket_path(volume)?;
|
||||
@@ -1050,7 +1050,7 @@ impl LocalDisk {
|
||||
|
||||
let mut dir_objes = HashSet::new();
|
||||
|
||||
// 第一层过滤
|
||||
// First-level filtering
|
||||
for item in entries.iter_mut() {
|
||||
let entry = item.clone();
|
||||
// check limit
|
||||
@@ -1229,7 +1229,7 @@ fn is_root_path(path: impl AsRef<Path>) -> bool {
|
||||
path.as_ref().components().count() == 1 && path.as_ref().has_root()
|
||||
}
|
||||
|
||||
// 过滤 std::io::ErrorKind::NotFound
|
||||
// Filter std::io::ErrorKind::NotFound
|
||||
pub async fn read_file_exists(path: impl AsRef<Path>) -> Result<(Bytes, Option<Metadata>)> {
|
||||
let p = path.as_ref();
|
||||
let (data, meta) = match read_file_all(&p).await {
|
||||
@@ -1920,11 +1920,11 @@ impl DiskAPI for LocalDisk {
|
||||
}
|
||||
}
|
||||
|
||||
// xl.meta 路径
|
||||
// xl.meta path
|
||||
let src_file_path = src_volume_dir.join(Path::new(format!("{}/{}", &src_path, STORAGE_FORMAT_FILE).as_str()));
|
||||
let dst_file_path = dst_volume_dir.join(Path::new(format!("{}/{}", &dst_path, STORAGE_FORMAT_FILE).as_str()));
|
||||
|
||||
// data_dir 路径
|
||||
// data_dir path
|
||||
let has_data_dir_path = {
|
||||
let has_data_dir = {
|
||||
if !fi.is_remote() {
|
||||
@@ -1952,7 +1952,7 @@ impl DiskAPI for LocalDisk {
|
||||
check_path_length(src_file_path.to_string_lossy().to_string().as_str())?;
|
||||
check_path_length(dst_file_path.to_string_lossy().to_string().as_str())?;
|
||||
|
||||
// 读旧 xl.meta
|
||||
// Read the previous xl.meta
|
||||
|
||||
let has_dst_buf = match super::fs::read_file(&dst_file_path).await {
|
||||
Ok(res) => Some(res),
|
||||
@@ -2437,7 +2437,7 @@ impl DiskAPI for LocalDisk {
|
||||
async fn delete_volume(&self, volume: &str) -> Result<()> {
|
||||
let p = self.get_bucket_path(volume)?;
|
||||
|
||||
// TODO: 不能用递归删除,如果目录下面有文件,返回 errVolumeNotEmpty
|
||||
// TODO: avoid recursive deletion; return errVolumeNotEmpty when files remain
|
||||
|
||||
if let Err(err) = fs::remove_dir_all(&p).await {
|
||||
let e: DiskError = to_volume_error(err).into();
|
||||
@@ -2591,7 +2591,7 @@ mod test {
|
||||
assert!(object_path.to_string_lossy().contains("test-bucket"));
|
||||
assert!(object_path.to_string_lossy().contains("test-object"));
|
||||
|
||||
// 清理测试目录
|
||||
// Clean up the test directory
|
||||
let _ = fs::remove_dir_all(&test_dir).await;
|
||||
}
|
||||
|
||||
@@ -2656,7 +2656,7 @@ mod test {
|
||||
disk.delete_volume(vol).await.unwrap();
|
||||
}
|
||||
|
||||
// 清理测试目录
|
||||
// Clean up the test directory
|
||||
let _ = fs::remove_dir_all(&test_dir).await;
|
||||
}
|
||||
|
||||
@@ -2680,7 +2680,7 @@ mod test {
|
||||
assert!(!disk_info.fs_type.is_empty());
|
||||
assert!(disk_info.total > 0);
|
||||
|
||||
// 清理测试目录
|
||||
// Clean up the test directory
|
||||
let _ = fs::remove_dir_all(&test_dir).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -431,7 +431,7 @@ pub trait DiskAPI: Debug + Send + Sync + 'static {
|
||||
async fn stat_volume(&self, volume: &str) -> Result<VolumeInfo>;
|
||||
async fn delete_volume(&self, volume: &str) -> Result<()>;
|
||||
|
||||
// 并发边读边写 w <- MetaCacheEntry
|
||||
// Concurrent read/write pipeline w <- MetaCacheEntry
|
||||
async fn walk_dir<W: AsyncWrite + Unpin + Send>(&self, opts: WalkDirOptions, wr: &mut W) -> Result<()>;
|
||||
|
||||
// Metadata operations
|
||||
@@ -466,7 +466,7 @@ pub trait DiskAPI: Debug + Send + Sync + 'static {
|
||||
) -> Result<RenameDataResp>;
|
||||
|
||||
// File operations.
|
||||
// 读目录下的所有文件、目录
|
||||
// Read every file and directory within the folder
|
||||
async fn list_dir(&self, origvolume: &str, volume: &str, dir_path: &str, count: i32) -> Result<Vec<String>>;
|
||||
async fn read_file(&self, volume: &str, path: &str) -> Result<FileReader>;
|
||||
async fn read_file_stream(&self, volume: &str, path: &str, offset: usize, length: usize) -> Result<FileReader>;
|
||||
@@ -1000,7 +1000,7 @@ mod tests {
|
||||
// Note: is_online() might return false for local disks without proper initialization
|
||||
// This is expected behavior for test environments
|
||||
|
||||
// 清理测试目录
|
||||
// Clean up the test directory
|
||||
let _ = fs::remove_dir_all(&test_dir).await;
|
||||
}
|
||||
|
||||
@@ -1031,7 +1031,7 @@ mod tests {
|
||||
let location = disk.get_disk_location();
|
||||
assert!(location.valid() || (!location.valid() && endpoint.pool_idx < 0));
|
||||
|
||||
// 清理测试目录
|
||||
// Clean up the test directory
|
||||
let _ = fs::remove_dir_all(&test_dir).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ pub async fn os_mkdir_all(dir_path: impl AsRef<Path>, base_dir: impl AsRef<Path>
|
||||
}
|
||||
|
||||
if let Some(parent) = dir_path.as_ref().parent() {
|
||||
// 不支持递归,直接 create_dir_all 了
|
||||
// Without recursion support, fall back to create_dir_all
|
||||
if let Err(e) = super::fs::make_dir_all(&parent).await {
|
||||
if e.kind() == io::ErrorKind::AlreadyExists {
|
||||
return Ok(());
|
||||
|
||||
@@ -297,24 +297,24 @@ impl Erasure {
|
||||
pub fn encode_data(self: Arc<Self>, data: &[u8]) -> Result<Vec<Bytes>> {
|
||||
let (shard_size, total_size) = self.need_size(data.len());
|
||||
|
||||
// 生成一个新的 所需的所有分片数据长度
|
||||
// Generate the total length required for all shards
|
||||
let mut data_buffer = BytesMut::with_capacity(total_size);
|
||||
|
||||
// 复制源数据
|
||||
// Copy the source data
|
||||
data_buffer.extend_from_slice(data);
|
||||
data_buffer.resize(total_size, 0u8);
|
||||
|
||||
{
|
||||
// ec encode, 结果会写进 data_buffer
|
||||
// Perform EC encoding; the results go into data_buffer
|
||||
let data_slices: SmallVec<[&mut [u8]; 16]> = data_buffer.chunks_exact_mut(shard_size).collect();
|
||||
|
||||
// parity 数量大于 0 才 ec
|
||||
// Only perform EC encoding when parity shards are present
|
||||
if self.parity_shards > 0 {
|
||||
self.encoder.as_ref().unwrap().encode(data_slices).map_err(Error::other)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 零拷贝分片,所有 shard 引用 data_buffer
|
||||
// Zero-copy shards: every shard references data_buffer
|
||||
let mut data_buffer = data_buffer.freeze();
|
||||
let mut shards = Vec::with_capacity(self.total_shard_count());
|
||||
for _ in 0..self.total_shard_count() {
|
||||
@@ -333,13 +333,13 @@ impl Erasure {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 每个分片长度,所需要的总长度
|
||||
// The length per shard and the total required length
|
||||
fn need_size(&self, data_size: usize) -> (usize, usize) {
|
||||
let shard_size = self.shard_size(data_size);
|
||||
(shard_size, shard_size * (self.total_shard_count()))
|
||||
}
|
||||
|
||||
// 算出每个分片大小
|
||||
// Compute each shard size
|
||||
pub fn shard_size(&self, data_size: usize) -> usize {
|
||||
data_size.div_ceil(self.data_shards)
|
||||
}
|
||||
@@ -354,7 +354,7 @@ impl Erasure {
|
||||
let last_shard_size = last_block_size.div_ceil(self.data_shards);
|
||||
num_shards * self.shard_size(self.block_size) + last_shard_size
|
||||
|
||||
// // 因为写入的时候 ec 需要补全,所以最后一个长度应该也是一样的
|
||||
// When writing, EC pads the data so the last shard length should match
|
||||
// if last_block_size != 0 {
|
||||
// num_shards += 1
|
||||
// }
|
||||
@@ -447,12 +447,12 @@ pub trait ReadAt {
|
||||
}
|
||||
|
||||
pub struct ShardReader {
|
||||
readers: Vec<Option<BitrotReader>>, // 磁盘
|
||||
data_block_count: usize, // 总的分片数量
|
||||
readers: Vec<Option<BitrotReader>>, // Disk readers
|
||||
data_block_count: usize, // Total number of shards
|
||||
parity_block_count: usize,
|
||||
shard_size: usize, // 每个分片的块大小 一次读取一块
|
||||
shard_file_size: usize, // 分片文件总长度
|
||||
offset: usize, // 在分片中的 offset
|
||||
shard_size: usize, // Block size per shard (read one block at a time)
|
||||
shard_file_size: usize, // Total size of the shard file
|
||||
offset: usize, // Offset within the shard
|
||||
}
|
||||
|
||||
impl ShardReader {
|
||||
@@ -470,7 +470,7 @@ impl ShardReader {
|
||||
pub async fn read(&mut self) -> Result<Vec<Option<Vec<u8>>>> {
|
||||
// let mut disks = self.readers;
|
||||
let reader_length = self.readers.len();
|
||||
// 需要读取的块长度
|
||||
// Length of the block to read
|
||||
let mut read_length = self.shard_size;
|
||||
if self.offset + read_length > self.shard_file_size {
|
||||
read_length = self.shard_file_size - self.offset
|
||||
|
||||
@@ -387,7 +387,7 @@ mod tests {
|
||||
}
|
||||
assert_eq!(n, data.len());
|
||||
|
||||
// 读
|
||||
// Read
|
||||
let reader = bitrot_writer.into_inner();
|
||||
let reader = Cursor::new(reader.into_inner());
|
||||
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::HighwayHash256);
|
||||
@@ -433,7 +433,7 @@ mod tests {
|
||||
let res = bitrot_reader.read(&mut buf).await;
|
||||
|
||||
if idx == count - 1 {
|
||||
// 最后一个块,应该返回错误
|
||||
// The last chunk should trigger an error
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err().kind(), std::io::ErrorKind::InvalidData);
|
||||
break;
|
||||
|
||||
@@ -58,7 +58,7 @@ impl Clone for ReedSolomonEncoder {
|
||||
Self {
|
||||
data_shards: self.data_shards,
|
||||
parity_shards: self.parity_shards,
|
||||
// 为新实例创建空的缓存,不共享缓存
|
||||
// Create an empty cache for the new instance instead of sharing one
|
||||
encoder_cache: std::sync::RwLock::new(None),
|
||||
decoder_cache: std::sync::RwLock::new(None),
|
||||
}
|
||||
@@ -947,7 +947,7 @@ mod tests {
|
||||
let block_size = 1024 * 1024; // 1MB block size
|
||||
let erasure = Erasure::new(data_shards, parity_shards, block_size);
|
||||
|
||||
// 创建2MB的测试数据,这样可以测试多个1MB块的处理
|
||||
// Build 2 MB of test data so multiple 1 MB chunks are exercised
|
||||
let mut data = Vec::with_capacity(2 * 1024 * 1024);
|
||||
for i in 0..(2 * 1024 * 1024) {
|
||||
data.push((i % 256) as u8);
|
||||
@@ -961,7 +961,7 @@ mod tests {
|
||||
data.len() / 1024
|
||||
);
|
||||
|
||||
// 编码数据
|
||||
// Encode the data
|
||||
let start = std::time::Instant::now();
|
||||
let shards = erasure.encode_data(&data).unwrap();
|
||||
let encode_duration = start.elapsed();
|
||||
|
||||
@@ -384,7 +384,7 @@ impl PoolMeta {
|
||||
|
||||
let mut update = false;
|
||||
|
||||
// 检查指定的池是否需要从已退役的池中移除。
|
||||
// Determine whether the selected pool should be removed from the retired list.
|
||||
for k in specified_pools.keys() {
|
||||
if let Some(pi) = remembered_pools.get(k) {
|
||||
if pi.completed {
|
||||
@@ -400,7 +400,7 @@ impl PoolMeta {
|
||||
// )));
|
||||
}
|
||||
} else {
|
||||
// 如果之前记住的池不再存在,允许更新,因为可能是添加了一个新池。
|
||||
// If the previous pool no longer exists, allow updates because a new pool may have been added.
|
||||
update = true;
|
||||
}
|
||||
}
|
||||
@@ -409,7 +409,7 @@ impl PoolMeta {
|
||||
for (k, pi) in remembered_pools.iter() {
|
||||
if let Some(pos) = specified_pools.get(k) {
|
||||
if *pos != pi.position {
|
||||
update = true; // 池的顺序发生了变化,允许更新。
|
||||
update = true; // Pool order changed, allow the update.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,12 +427,12 @@ impl PoolMeta {
|
||||
for pool in &self.pools {
|
||||
if let Some(decommission) = &pool.decommission {
|
||||
if decommission.complete || decommission.canceled {
|
||||
// 不需要恢复的情况:
|
||||
// - 退役已完成
|
||||
// - 退役已取消
|
||||
// Recovery is not required when:
|
||||
// - Decommissioning completed
|
||||
// - Decommissioning was cancelled
|
||||
continue;
|
||||
}
|
||||
// 其他情况需要恢复
|
||||
// All other scenarios require recovery
|
||||
new_pools.push(pool.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,15 +421,15 @@ impl ECStore {
|
||||
if let Some(pool_stat) = meta.pool_stats.get_mut(pool_index) {
|
||||
info!("bucket_rebalance_done: buckets {:?}", &pool_stat.buckets);
|
||||
|
||||
// 使用 retain 来过滤掉要删除的 bucket
|
||||
// Use retain to filter out buckets slated for removal
|
||||
let mut found = false;
|
||||
pool_stat.buckets.retain(|b| {
|
||||
if b.as_str() == bucket.as_str() {
|
||||
found = true;
|
||||
pool_stat.rebalanced_buckets.push(b.clone());
|
||||
false // 删除这个元素
|
||||
false // Remove this element
|
||||
} else {
|
||||
true // 保留这个元素
|
||||
true // Keep this element
|
||||
}
|
||||
});
|
||||
|
||||
@@ -946,13 +946,13 @@ impl ECStore {
|
||||
let mut reader = rd.stream;
|
||||
|
||||
for (i, part) in object_info.parts.iter().enumerate() {
|
||||
// 每次从 reader 中读取一个 part 上传
|
||||
// Read one part from the reader and upload it each time
|
||||
|
||||
let mut chunk = vec![0u8; part.size];
|
||||
|
||||
reader.read_exact(&mut chunk).await?;
|
||||
|
||||
// 每次从 reader 中读取一个 part 上传
|
||||
// Read one part from the reader and upload it each time
|
||||
let mut data = PutObjReader::from_vec(chunk);
|
||||
|
||||
let pi = match self
|
||||
|
||||
@@ -536,7 +536,7 @@ impl PeerS3Client for LocalPeerS3Client {
|
||||
}
|
||||
}
|
||||
|
||||
// errVolumeNotEmpty 不删除,把已经删除的重新创建
|
||||
// For errVolumeNotEmpty, do not delete; recreate only the entries already removed
|
||||
|
||||
for (idx, err) in errs.into_iter().enumerate() {
|
||||
if err.is_none() && recreate {
|
||||
|
||||
@@ -83,7 +83,7 @@ impl DiskAPI for RemoteDisk {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn is_online(&self) -> bool {
|
||||
// TODO: 连接状态
|
||||
// TODO: connection status tracking
|
||||
if node_service_time_out_client(&self.addr).await.is_ok() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ impl SetDisks {
|
||||
|
||||
let mut futures = Vec::with_capacity(disks.len());
|
||||
if let Some(ret_err) = reduce_write_quorum_errs(&errs, OBJECT_OP_IGNORED_ERRS, write_quorum) {
|
||||
// TODO: 并发
|
||||
// TODO: add concurrency
|
||||
for (i, err) in errs.iter().enumerate() {
|
||||
if err.is_some() {
|
||||
continue;
|
||||
@@ -891,7 +891,7 @@ impl SetDisks {
|
||||
}
|
||||
|
||||
if let Some(err) = reduce_write_quorum_errs(&errs, OBJECT_OP_IGNORED_ERRS, write_quorum) {
|
||||
// TODO: 并发
|
||||
// TODO: add concurrency
|
||||
for (i, err) in errs.iter().enumerate() {
|
||||
if err.is_some() {
|
||||
continue;
|
||||
@@ -1700,7 +1700,7 @@ impl SetDisks {
|
||||
|
||||
let disks = rl.clone();
|
||||
|
||||
// 主动释放锁
|
||||
// Explicitly release the lock
|
||||
drop(rl);
|
||||
|
||||
for (i, opdisk) in disks.iter().enumerate() {
|
||||
@@ -1744,7 +1744,7 @@ impl SetDisks {
|
||||
}
|
||||
};
|
||||
|
||||
// check endpoint 是否一致
|
||||
// Check that the endpoint matches
|
||||
|
||||
let _ = new_disk.set_disk_id(Some(fm.erasure.this)).await;
|
||||
|
||||
@@ -1959,7 +1959,7 @@ impl SetDisks {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// 打乱顺序
|
||||
// Shuffle the order
|
||||
fn shuffle_disks_and_parts_metadata_by_index(
|
||||
disks: &[Option<DiskStore>],
|
||||
parts_metadata: &[FileInfo],
|
||||
@@ -1998,7 +1998,7 @@ impl SetDisks {
|
||||
Self::shuffle_disks_and_parts_metadata(disks, parts_metadata, fi)
|
||||
}
|
||||
|
||||
// 打乱顺序
|
||||
// Shuffle the order
|
||||
fn shuffle_disks_and_parts_metadata(
|
||||
disks: &[Option<DiskStore>],
|
||||
parts_metadata: &[FileInfo],
|
||||
@@ -2075,7 +2075,7 @@ impl SetDisks {
|
||||
|
||||
let vid = opts.version_id.clone().unwrap_or_default();
|
||||
|
||||
// TODO: 优化并发 可用数量中断
|
||||
// TODO: optimize concurrency and break once enough slots are available
|
||||
let (parts_metadata, errs) = Self::read_all_fileinfo(&disks, "", bucket, object, vid.as_str(), read_data, false).await?;
|
||||
// warn!("get_object_fileinfo parts_metadata {:?}", &parts_metadata);
|
||||
// warn!("get_object_fileinfo {}/{} errs {:?}", bucket, object, &errs);
|
||||
@@ -3722,7 +3722,7 @@ impl ObjectIO for SetDisks {
|
||||
error!("encode err {:?}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}; // TODO: 出错,删除临时目录
|
||||
}; // TODO: delete temporary directory on error
|
||||
|
||||
let _ = mem::replace(&mut data.stream, reader);
|
||||
// if let Err(err) = close_bitrot_writers(&mut writers).await {
|
||||
@@ -4050,7 +4050,7 @@ impl StorageAPI for SetDisks {
|
||||
objects: Vec<ObjectToDelete>,
|
||||
opts: ObjectOptions,
|
||||
) -> (Vec<DeletedObject>, Vec<Option<Error>>) {
|
||||
// 默认返回值
|
||||
// Default return value
|
||||
let mut del_objects = vec![DeletedObject::default(); objects.len()];
|
||||
|
||||
let mut del_errs = Vec::with_capacity(objects.len());
|
||||
@@ -4107,7 +4107,7 @@ impl StorageAPI for SetDisks {
|
||||
|
||||
vr.set_tier_free_version_id(&Uuid::new_v4().to_string());
|
||||
|
||||
// 删除
|
||||
// Delete
|
||||
// del_objects[i].object_name.clone_from(&vr.name);
|
||||
// del_objects[i].version_id = vr.version_id.map(|v| v.to_string());
|
||||
|
||||
@@ -4200,9 +4200,9 @@ impl StorageAPI for SetDisks {
|
||||
|
||||
let mut del_obj_errs: Vec<Vec<Option<DiskError>>> = vec![vec![None; objects.len()]; disks.len()];
|
||||
|
||||
// 每个磁盘, 删除所有对象
|
||||
// For each disk delete all objects
|
||||
for (disk_idx, errors) in results.into_iter().enumerate() {
|
||||
// 所有对象的删除结果
|
||||
// Deletion results for all objects
|
||||
for idx in 0..vers.len() {
|
||||
if errors[idx].is_some() {
|
||||
for fi in vers[idx].versions.iter() {
|
||||
@@ -4964,7 +4964,7 @@ impl StorageAPI for SetDisks {
|
||||
HashReader::new(Box::new(WarpReader::new(Cursor::new(Vec::new()))), 0, 0, None, None, false)?,
|
||||
);
|
||||
|
||||
let (reader, w_size) = Arc::new(erasure).encode(stream, &mut writers, write_quorum).await?; // TODO: 出错,删除临时目录
|
||||
let (reader, w_size) = Arc::new(erasure).encode(stream, &mut writers, write_quorum).await?; // TODO: delete temporary directory on error
|
||||
|
||||
let _ = mem::replace(&mut data.stream, reader);
|
||||
|
||||
@@ -5453,7 +5453,7 @@ impl StorageAPI for SetDisks {
|
||||
|
||||
self.delete_all(RUSTFS_META_MULTIPART_BUCKET, &upload_id_path).await
|
||||
}
|
||||
// complete_multipart_upload 完成
|
||||
// complete_multipart_upload finished
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn complete_multipart_upload(
|
||||
self: Arc<Self>,
|
||||
@@ -6567,7 +6567,7 @@ mod tests {
|
||||
// Test that all CHECK_PART constants have expected values
|
||||
assert_eq!(CHECK_PART_UNKNOWN, 0);
|
||||
assert_eq!(CHECK_PART_SUCCESS, 1);
|
||||
assert_eq!(CHECK_PART_FILE_NOT_FOUND, 4); // 实际值是 4,不是 2
|
||||
assert_eq!(CHECK_PART_FILE_NOT_FOUND, 4); // The actual value is 4, not 2
|
||||
assert_eq!(CHECK_PART_VOLUME_NOT_FOUND, 3);
|
||||
assert_eq!(CHECK_PART_FILE_CORRUPT, 5);
|
||||
}
|
||||
@@ -6847,7 +6847,7 @@ mod tests {
|
||||
assert_eq!(conv_part_err_to_int(&Some(disk_err)), CHECK_PART_FILE_NOT_FOUND);
|
||||
|
||||
let other_err = DiskError::other("other error");
|
||||
assert_eq!(conv_part_err_to_int(&Some(other_err)), CHECK_PART_UNKNOWN); // other 错误应该返回 UNKNOWN,不是 SUCCESS
|
||||
assert_eq!(conv_part_err_to_int(&Some(other_err)), CHECK_PART_UNKNOWN); // Other errors should return UNKNOWN, not SUCCESS
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -6919,7 +6919,7 @@ mod tests {
|
||||
let errs = vec![None, Some(DiskError::other("error1")), Some(DiskError::other("error2"))];
|
||||
let joined = join_errs(&errs);
|
||||
assert!(joined.contains("<nil>"));
|
||||
assert!(joined.contains("io error")); // DiskError::other 显示为 "io error"
|
||||
assert!(joined.contains("io error")); // DiskError::other is rendered as "io error"
|
||||
|
||||
// Test with different error types
|
||||
let errs2 = vec![None, Some(DiskError::FileNotFound), Some(DiskError::FileCorrupt)];
|
||||
|
||||
+16
-16
@@ -219,7 +219,7 @@ impl ECStore {
|
||||
disk_map.insert(i, disks);
|
||||
}
|
||||
|
||||
// 替换本地磁盘
|
||||
// Replace the local disk
|
||||
if !is_dist_erasure().await {
|
||||
let mut global_local_disk_map = GLOBAL_LOCAL_DISK_MAP.write().await;
|
||||
for disk in local_disks {
|
||||
@@ -243,7 +243,7 @@ impl ECStore {
|
||||
decommission_cancelers,
|
||||
});
|
||||
|
||||
// 只有在全局部署ID尚未设置时才设置它
|
||||
// Only set it when the global deployment ID is not yet configured
|
||||
if let Some(dep_id) = deployment_id {
|
||||
if get_global_deployment_id().is_none() {
|
||||
set_global_deployment_id(dep_id);
|
||||
@@ -383,7 +383,7 @@ impl ECStore {
|
||||
// Ok(info)
|
||||
// }
|
||||
|
||||
// 读所有
|
||||
// Read all entries
|
||||
// define in store_list_objects.rs
|
||||
// async fn list_merged(&self, opts: &ListPathOptions, delimiter: &str) -> Result<Vec<ObjectInfo>> {
|
||||
// let walk_opts = WalkDirOptions {
|
||||
@@ -425,7 +425,7 @@ impl ECStore {
|
||||
|
||||
// if !uniq.contains(&entry.name) {
|
||||
// uniq.insert(entry.name.clone());
|
||||
// // TODO: 过滤
|
||||
// // TODO: filter
|
||||
|
||||
// if opts.limit > 0 && ress.len() as i32 >= opts.limit {
|
||||
// return Ok(ress);
|
||||
@@ -516,7 +516,7 @@ impl ECStore {
|
||||
}
|
||||
|
||||
async fn get_available_pool_idx(&self, bucket: &str, object: &str, size: i64) -> Option<usize> {
|
||||
// // 先随机返回一个
|
||||
// // Return a random one first
|
||||
|
||||
let mut server_pools = self.get_server_pools_available_space(bucket, object, size).await;
|
||||
server_pools.filter_max_used(100 - (100_f64 * DISK_RESERVE_FRACTION) as u64);
|
||||
@@ -546,7 +546,7 @@ impl ECStore {
|
||||
let mut n_sets = vec![0; self.pools.len()];
|
||||
let mut infos = vec![Vec::new(); self.pools.len()];
|
||||
|
||||
// TODO: 并发
|
||||
// TODO: add concurrency
|
||||
for (idx, pool) in self.pools.iter().enumerate() {
|
||||
if self.is_suspended(idx).await || self.is_pool_rebalancing(idx).await {
|
||||
continue;
|
||||
@@ -713,7 +713,7 @@ impl ECStore {
|
||||
|
||||
let mut ress = Vec::new();
|
||||
|
||||
// join_all 结果跟输入顺序一致
|
||||
// join_all preserves the input order
|
||||
for (i, res) in results.into_iter().enumerate() {
|
||||
let index = i;
|
||||
|
||||
@@ -984,7 +984,7 @@ pub async fn all_local_disk() -> Vec<DiskStore> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
// init_local_disks 初始化本地磁盘,server 启动前必须初始化成功
|
||||
// init_local_disks must succeed before the server starts
|
||||
pub async fn init_local_disks(endpoint_pools: EndpointServerPools) -> Result<()> {
|
||||
let opt = &DiskOption {
|
||||
cleanup: true,
|
||||
@@ -1317,7 +1317,7 @@ impl StorageAPI for ECStore {
|
||||
|
||||
// TODO: replication opts.srdelete_op
|
||||
|
||||
// 删除 meta
|
||||
// Delete the metadata
|
||||
self.delete_all(RUSTFS_META_BUCKET, format!("{BUCKET_META_PREFIX}/{bucket}").as_str())
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -1469,7 +1469,7 @@ impl StorageAPI for ECStore {
|
||||
let mut gopts = opts.clone();
|
||||
gopts.no_lock = true;
|
||||
|
||||
// 查询在哪个 pool
|
||||
// Determine which pool contains it
|
||||
let (mut pinfo, errs) = self
|
||||
.get_pool_info_existing_with_opts(bucket, object, &gopts)
|
||||
.await
|
||||
@@ -1543,7 +1543,7 @@ impl StorageAPI for ECStore {
|
||||
})
|
||||
.collect();
|
||||
|
||||
// 默认返回值
|
||||
// Default return value
|
||||
let mut del_objects = vec![DeletedObject::default(); objects.len()];
|
||||
|
||||
let mut del_errs = Vec::with_capacity(objects.len());
|
||||
@@ -1625,7 +1625,7 @@ impl StorageAPI for ECStore {
|
||||
// // results.push(jh.await.unwrap());
|
||||
// // }
|
||||
|
||||
// // 记录 pool Index 对应的 objects pool_idx -> objects idx
|
||||
// // Record the mapping pool_idx -> object index
|
||||
// let mut pool_obj_idx_map = HashMap::new();
|
||||
// let mut orig_index_map = HashMap::new();
|
||||
|
||||
@@ -1675,9 +1675,9 @@ impl StorageAPI for ECStore {
|
||||
|
||||
// if !pool_obj_idx_map.is_empty() {
|
||||
// for (i, sets) in self.pools.iter().enumerate() {
|
||||
// // 取 pool idx 对应的 objects index
|
||||
// // Retrieve the object index for a pool idx
|
||||
// if let Some(objs) = pool_obj_idx_map.get(&i) {
|
||||
// // 取对应 obj,理论上不会 none
|
||||
// // Fetch the corresponding object (should never be None)
|
||||
// // let objs: Vec<ObjectToDelete> = obj_idxs.iter().filter_map(|&idx| objects.get(idx).cloned()).collect();
|
||||
|
||||
// if objs.is_empty() {
|
||||
@@ -1686,10 +1686,10 @@ impl StorageAPI for ECStore {
|
||||
|
||||
// let (pdel_objs, perrs) = sets.delete_objects(bucket, objs.clone(), opts.clone()).await?;
|
||||
|
||||
// // 同时存入不可能为 none
|
||||
// // Insert simultaneously (should never be None)
|
||||
// let org_indexes = orig_index_map.get(&i).unwrap();
|
||||
|
||||
// // perrs 的顺序理论上跟 obj_idxs 顺序一致
|
||||
// // perrs should follow the same order as obj_idxs
|
||||
// for (i, err) in perrs.into_iter().enumerate() {
|
||||
// let obj_idx = org_indexes[i];
|
||||
|
||||
|
||||
@@ -37,17 +37,17 @@ pub fn clean_metadata_keys(metadata: &mut HashMap<String, String>, key_names: &[
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否为 元数据桶
|
||||
// Check whether the bucket is the metadata bucket
|
||||
fn is_meta_bucket(bucket_name: &str) -> bool {
|
||||
bucket_name == RUSTFS_META_BUCKET
|
||||
}
|
||||
|
||||
// 检查是否为 保留桶
|
||||
// Check whether the bucket is reserved
|
||||
fn is_reserved_bucket(bucket_name: &str) -> bool {
|
||||
bucket_name == "rustfs"
|
||||
}
|
||||
|
||||
// 检查桶名是否为保留名或无效名
|
||||
// Check whether the bucket name is reserved or invalid
|
||||
pub fn is_reserved_or_invalid_bucket(bucket_entry: &str, strict: bool) -> bool {
|
||||
if bucket_entry.is_empty() {
|
||||
return true;
|
||||
@@ -59,7 +59,7 @@ pub fn is_reserved_or_invalid_bucket(bucket_entry: &str, strict: bool) -> bool {
|
||||
result || is_meta_bucket(bucket_entry) || is_reserved_bucket(bucket_entry)
|
||||
}
|
||||
|
||||
// 检查桶名是否有效
|
||||
// Check whether the bucket name is valid
|
||||
fn check_bucket_name(bucket_name: &str, strict: bool) -> Result<()> {
|
||||
if bucket_name.trim().is_empty() {
|
||||
return Err(Error::other("Bucket name cannot be empty"));
|
||||
@@ -86,7 +86,7 @@ fn check_bucket_name(bucket_name: &str, strict: bool) -> Result<()> {
|
||||
return Err(Error::other("Bucket name contains invalid characters"));
|
||||
}
|
||||
|
||||
// 检查包含 "..", ".-", "-."
|
||||
// Check for "..", ".-", "-."
|
||||
if bucket_name.contains("..") || bucket_name.contains(".-") || bucket_name.contains("-.") {
|
||||
return Err(Error::other("Bucket name contains invalid characters"));
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ impl WarmBackendS3 {
|
||||
creds = Credentials::new(
|
||||
conf.access_key.clone(), // access_key_id
|
||||
conf.secret_key.clone(), // secret_access_key
|
||||
None, // session_token (可选)
|
||||
None, // session_token (optional)
|
||||
None,
|
||||
"Static",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user