mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-05 04:47:43 +00:00
Merge branch 'main' of github.com:rustfs/s3-rustfs into feature/observability-metrics
# Conflicts: # .github/workflows/build.yml # .github/workflows/ci.yml # Cargo.lock # Cargo.toml # appauth/src/token.rs # crates/config/src/config.rs # crates/event-notifier/examples/simple.rs # crates/event-notifier/src/global.rs # crates/event-notifier/src/lib.rs # crates/event-notifier/src/notifier.rs # crates/event-notifier/src/store.rs # crates/filemeta/src/filemeta.rs # crates/notify/examples/webhook.rs # crates/utils/Cargo.toml # ecstore/Cargo.toml # ecstore/src/cmd/bucket_replication.rs # ecstore/src/config/com.rs # ecstore/src/disk/error.rs # ecstore/src/disk/mod.rs # ecstore/src/set_disk.rs # ecstore/src/store_api.rs # ecstore/src/store_list_objects.rs # iam/Cargo.toml # iam/src/manager.rs # policy/Cargo.toml # rustfs/src/admin/rpc.rs # rustfs/src/main.rs # rustfs/src/storage/mod.rs
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{
|
||||
disk::endpoint::Endpoint,
|
||||
global::{GLOBAL_Endpoints, GLOBAL_BOOT_TIME},
|
||||
global::{GLOBAL_BOOT_TIME, GLOBAL_Endpoints},
|
||||
heal::{
|
||||
data_usage::{load_data_usage_from_backend, DATA_USAGE_CACHE_NAME, DATA_USAGE_ROOT},
|
||||
data_usage::{DATA_USAGE_CACHE_NAME, DATA_USAGE_ROOT, load_data_usage_from_backend},
|
||||
data_usage_cache::DataUsageCache,
|
||||
heal_commands::{DRIVE_STATE_OK, DRIVE_STATE_UNFORMATTED},
|
||||
},
|
||||
@@ -11,10 +12,10 @@ use crate::{
|
||||
store_api::StorageAPI,
|
||||
};
|
||||
use common::{
|
||||
error::{Error, Result},
|
||||
// error::{Error, Result},
|
||||
globals::GLOBAL_Local_Node_Name,
|
||||
};
|
||||
use madmin::{BackendDisks, Disk, ErasureSetInfo, InfoMessage, ServerProperties, ITEM_INITIALIZING, ITEM_OFFLINE, ITEM_ONLINE};
|
||||
use madmin::{BackendDisks, Disk, ErasureSetInfo, ITEM_INITIALIZING, ITEM_OFFLINE, ITEM_ONLINE, InfoMessage, ServerProperties};
|
||||
use protos::{
|
||||
models::{PingBody, PingBodyBuilder},
|
||||
node_service_time_out_client,
|
||||
@@ -87,12 +88,12 @@ async fn is_server_resolvable(endpoint: &Endpoint) -> Result<()> {
|
||||
// 创建客户端
|
||||
let mut client = node_service_time_out_client(&addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
|
||||
// 构造 PingRequest
|
||||
let request = Request::new(PingRequest {
|
||||
version: 1,
|
||||
body: finished_data.to_vec(),
|
||||
body: bytes::Bytes::copy_from_slice(finished_data),
|
||||
});
|
||||
|
||||
// 发送请求并获取响应
|
||||
@@ -332,7 +333,7 @@ fn get_online_offline_disks_stats(disks_info: &[Disk]) -> (BackendDisks, Backend
|
||||
|
||||
async fn get_pools_info(all_disks: &[Disk]) -> Result<HashMap<i32, HashMap<i32, ErasureSetInfo>>> {
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::msg("ServerNotInitialized"));
|
||||
return Err(Error::other("ServerNotInitialized"));
|
||||
};
|
||||
|
||||
let mut pools_info: HashMap<i32, HashMap<i32, ErasureSetInfo>> = HashMap::new();
|
||||
|
||||
+146
-813
@@ -1,841 +1,174 @@
|
||||
use crate::{
|
||||
disk::{error::DiskError, Disk, DiskAPI},
|
||||
erasure::{ReadAt, Writer},
|
||||
io::{FileReader, FileWriter},
|
||||
store_api::BitrotAlgorithm,
|
||||
};
|
||||
use blake2::Blake2b512;
|
||||
use blake2::Digest as _;
|
||||
use bytes::Bytes;
|
||||
use common::error::{Error, Result};
|
||||
use highway::{HighwayHash, HighwayHasher, Key};
|
||||
use lazy_static::lazy_static;
|
||||
use sha2::{digest::core_api::BlockSizeUser, Digest, Sha256};
|
||||
use std::{any::Any, collections::HashMap, io::Cursor, sync::Arc};
|
||||
use tokio::io::{AsyncReadExt as _, AsyncWriteExt};
|
||||
use tracing::{error, info};
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::disk::{self, DiskAPI as _, DiskStore};
|
||||
use crate::erasure_coding::{BitrotReader, BitrotWriterWrapper, CustomWriter};
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
use std::io::Cursor;
|
||||
use tokio::io::AsyncRead;
|
||||
|
||||
lazy_static! {
|
||||
static ref BITROT_ALGORITHMS: HashMap<BitrotAlgorithm, &'static str> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert(BitrotAlgorithm::SHA256, "sha256");
|
||||
m.insert(BitrotAlgorithm::BLAKE2b512, "blake2b");
|
||||
m.insert(BitrotAlgorithm::HighwayHash256, "highwayhash256");
|
||||
m.insert(BitrotAlgorithm::HighwayHash256S, "highwayhash256S");
|
||||
m
|
||||
};
|
||||
}
|
||||
/// Create a BitrotReader from either inline data or disk file stream
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `inline_data` - Optional inline data, if present, will use Cursor to read from memory
|
||||
/// * `disk` - Optional disk reference for file stream reading
|
||||
/// * `bucket` - Bucket name for file path
|
||||
/// * `path` - File path within the bucket
|
||||
/// * `offset` - Starting offset for reading
|
||||
/// * `length` - Length to read
|
||||
/// * `shard_size` - Shard size for erasure coding
|
||||
/// * `checksum_algo` - Hash algorithm for bitrot verification
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn create_bitrot_reader(
|
||||
inline_data: Option<&[u8]>,
|
||||
disk: Option<&DiskStore>,
|
||||
bucket: &str,
|
||||
path: &str,
|
||||
offset: usize,
|
||||
length: usize,
|
||||
shard_size: usize,
|
||||
checksum_algo: HashAlgorithm,
|
||||
) -> disk::error::Result<Option<BitrotReader<Box<dyn AsyncRead + Send + Sync + Unpin>>>> {
|
||||
// Calculate the total length to read, including the checksum overhead
|
||||
let length = length.div_ceil(shard_size) * checksum_algo.size() + length;
|
||||
|
||||
// const MAGIC_HIGHWAY_HASH256_KEY: &[u8] = &[
|
||||
// 0x4b, 0xe7, 0x34, 0xfa, 0x8e, 0x23, 0x8a, 0xcd, 0x26, 0x3e, 0x83, 0xe6, 0xbb, 0x96, 0x85, 0x52, 0x04, 0x0f, 0x93, 0x5d, 0xa3,
|
||||
// 0x9f, 0x44, 0x14, 0x97, 0xe0, 0x9d, 0x13, 0x22, 0xde, 0x36, 0xa0,
|
||||
// ];
|
||||
const MAGIC_HIGHWAY_HASH256_KEY: &[u64; 4] = &[3, 4, 2, 1];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Hasher {
|
||||
SHA256(Sha256),
|
||||
HighwayHash256(HighwayHasher),
|
||||
BLAKE2b512(Blake2b512),
|
||||
}
|
||||
|
||||
impl Hasher {
|
||||
pub fn update(&mut self, data: impl AsRef<[u8]>) {
|
||||
match self {
|
||||
Hasher::SHA256(core_wrapper) => {
|
||||
core_wrapper.update(data);
|
||||
}
|
||||
Hasher::HighwayHash256(highway_hasher) => {
|
||||
highway_hasher.append(data.as_ref());
|
||||
}
|
||||
Hasher::BLAKE2b512(core_wrapper) => {
|
||||
core_wrapper.update(data);
|
||||
if let Some(data) = inline_data {
|
||||
// Use inline data
|
||||
let rd = Cursor::new(data.to_vec());
|
||||
let reader = BitrotReader::new(Box::new(rd) as Box<dyn AsyncRead + Send + Sync + Unpin>, shard_size, checksum_algo);
|
||||
Ok(Some(reader))
|
||||
} else if let Some(disk) = disk {
|
||||
// Read from disk
|
||||
match disk.read_file_stream(bucket, path, offset, length).await {
|
||||
Ok(rd) => {
|
||||
let reader = BitrotReader::new(rd, shard_size, checksum_algo);
|
||||
Ok(Some(reader))
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> Vec<u8> {
|
||||
match self {
|
||||
Hasher::SHA256(core_wrapper) => core_wrapper.finalize().to_vec(),
|
||||
Hasher::HighwayHash256(highway_hasher) => highway_hasher
|
||||
.finalize256()
|
||||
.iter()
|
||||
.flat_map(|&n| n.to_le_bytes()) // 使用小端字节序转换
|
||||
.collect(),
|
||||
Hasher::BLAKE2b512(core_wrapper) => core_wrapper.finalize().to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> usize {
|
||||
match self {
|
||||
Hasher::SHA256(_) => Sha256::output_size(),
|
||||
Hasher::HighwayHash256(_) => 32,
|
||||
Hasher::BLAKE2b512(_) => Blake2b512::output_size(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block_size(&self) -> usize {
|
||||
match self {
|
||||
Hasher::SHA256(_) => Sha256::block_size(),
|
||||
Hasher::HighwayHash256(_) => 64,
|
||||
Hasher::BLAKE2b512(_) => 64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
match self {
|
||||
Hasher::SHA256(core_wrapper) => core_wrapper.reset(),
|
||||
Hasher::HighwayHash256(highway_hasher) => {
|
||||
let key = Key(*MAGIC_HIGHWAY_HASH256_KEY);
|
||||
*highway_hasher = HighwayHasher::new(key);
|
||||
}
|
||||
Hasher::BLAKE2b512(core_wrapper) => core_wrapper.reset(),
|
||||
}
|
||||
} else {
|
||||
// Neither inline data nor disk available
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitrotAlgorithm {
|
||||
pub fn new_hasher(&self) -> Hasher {
|
||||
match self {
|
||||
BitrotAlgorithm::SHA256 => Hasher::SHA256(Sha256::new()),
|
||||
BitrotAlgorithm::HighwayHash256 | BitrotAlgorithm::HighwayHash256S => {
|
||||
let key = Key(*MAGIC_HIGHWAY_HASH256_KEY);
|
||||
Hasher::HighwayHash256(HighwayHasher::new(key))
|
||||
}
|
||||
BitrotAlgorithm::BLAKE2b512 => Hasher::BLAKE2b512(Blake2b512::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn available(&self) -> bool {
|
||||
BITROT_ALGORITHMS.get(self).is_some()
|
||||
}
|
||||
|
||||
pub fn string(&self) -> String {
|
||||
BITROT_ALGORITHMS.get(self).map_or("".to_string(), |s| s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BitrotVerifier {
|
||||
_algorithm: BitrotAlgorithm,
|
||||
_sum: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BitrotVerifier {
|
||||
pub fn new(algorithm: BitrotAlgorithm, checksum: &[u8]) -> BitrotVerifier {
|
||||
BitrotVerifier {
|
||||
_algorithm: algorithm,
|
||||
_sum: checksum.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitrot_algorithm_from_string(s: &str) -> BitrotAlgorithm {
|
||||
for (k, v) in BITROT_ALGORITHMS.iter() {
|
||||
if *v == s {
|
||||
return k.clone();
|
||||
}
|
||||
}
|
||||
|
||||
BitrotAlgorithm::HighwayHash256S
|
||||
}
|
||||
|
||||
pub type BitrotWriter = Box<dyn Writer + Send + 'static>;
|
||||
|
||||
// pub async fn new_bitrot_writer(
|
||||
// disk: DiskStore,
|
||||
// orig_volume: &str,
|
||||
// volume: &str,
|
||||
// file_path: &str,
|
||||
// length: usize,
|
||||
// algo: BitrotAlgorithm,
|
||||
// shard_size: usize,
|
||||
// ) -> Result<BitrotWriter> {
|
||||
// if algo == BitrotAlgorithm::HighwayHash256S {
|
||||
// return Ok(Box::new(
|
||||
// StreamingBitrotWriter::new(disk, orig_volume, volume, file_path, length, algo, shard_size).await?,
|
||||
// ));
|
||||
// }
|
||||
// Ok(Box::new(WholeBitrotWriter::new(disk, volume, file_path, algo, shard_size)))
|
||||
// }
|
||||
|
||||
pub type BitrotReader = Box<dyn ReadAt + Send>;
|
||||
|
||||
// #[allow(clippy::too_many_arguments)]
|
||||
// pub fn new_bitrot_reader(
|
||||
// disk: DiskStore,
|
||||
// data: &[u8],
|
||||
// bucket: &str,
|
||||
// file_path: &str,
|
||||
// till_offset: usize,
|
||||
// algo: BitrotAlgorithm,
|
||||
// sum: &[u8],
|
||||
// shard_size: usize,
|
||||
// ) -> BitrotReader {
|
||||
// if algo == BitrotAlgorithm::HighwayHash256S {
|
||||
// return Box::new(StreamingBitrotReader::new(disk, data, bucket, file_path, algo, till_offset, shard_size));
|
||||
// }
|
||||
// Box::new(WholeBitrotReader::new(disk, bucket, file_path, algo, till_offset, sum))
|
||||
// }
|
||||
|
||||
pub async fn close_bitrot_writers(writers: &mut [Option<BitrotWriter>]) -> Result<()> {
|
||||
for w in writers.iter_mut().flatten() {
|
||||
w.close().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub fn bitrot_writer_sum(w: &BitrotWriter) -> Vec<u8> {
|
||||
// if let Some(w) = w.as_any().downcast_ref::<WholeBitrotWriter>() {
|
||||
// return w.hash.clone().finalize();
|
||||
// }
|
||||
|
||||
// Vec::new()
|
||||
// }
|
||||
|
||||
pub fn bitrot_shard_file_size(size: usize, shard_size: usize, algo: BitrotAlgorithm) -> usize {
|
||||
if algo != BitrotAlgorithm::HighwayHash256S {
|
||||
return size;
|
||||
}
|
||||
size.div_ceil(shard_size) * algo.new_hasher().size() + size
|
||||
}
|
||||
|
||||
pub async fn bitrot_verify(
|
||||
r: FileReader,
|
||||
want_size: usize,
|
||||
part_size: usize,
|
||||
algo: BitrotAlgorithm,
|
||||
_want: Vec<u8>,
|
||||
mut shard_size: usize,
|
||||
) -> Result<()> {
|
||||
// if algo != BitrotAlgorithm::HighwayHash256S {
|
||||
// let mut h = algo.new_hasher();
|
||||
// h.update(r.get_ref());
|
||||
// let hash = h.finalize();
|
||||
// if hash != want {
|
||||
// info!("bitrot_verify except: {:?}, got: {:?}", want, hash);
|
||||
// return Err(Error::new(DiskError::FileCorrupt));
|
||||
// }
|
||||
|
||||
// return Ok(());
|
||||
// }
|
||||
let mut h = algo.new_hasher();
|
||||
let mut hash_buf = vec![0; h.size()];
|
||||
let mut left = want_size;
|
||||
|
||||
if left != bitrot_shard_file_size(part_size, shard_size, algo.clone()) {
|
||||
info!(
|
||||
"bitrot_shard_file_size failed, left: {}, part_size: {}, shard_size: {}, algo: {:?}",
|
||||
left, part_size, shard_size, algo
|
||||
);
|
||||
return Err(Error::new(DiskError::FileCorrupt));
|
||||
}
|
||||
|
||||
let mut r = r;
|
||||
|
||||
while left > 0 {
|
||||
h.reset();
|
||||
let n = r.read_exact(&mut hash_buf).await?;
|
||||
left -= n;
|
||||
|
||||
if left < shard_size {
|
||||
shard_size = left;
|
||||
}
|
||||
|
||||
let mut buf = vec![0; shard_size];
|
||||
let read = r.read_exact(&mut buf).await?;
|
||||
h.update(buf);
|
||||
left -= read;
|
||||
let hash = h.clone().finalize();
|
||||
if h.clone().finalize() != hash_buf[0..n] {
|
||||
info!("bitrot_verify except: {:?}, got: {:?}", hash_buf[0..n].to_vec(), hash);
|
||||
return Err(Error::new(DiskError::FileCorrupt));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub struct WholeBitrotWriter {
|
||||
// disk: DiskStore,
|
||||
// volume: String,
|
||||
// file_path: String,
|
||||
// _shard_size: usize,
|
||||
// pub hash: Hasher,
|
||||
// }
|
||||
|
||||
// impl WholeBitrotWriter {
|
||||
// pub fn new(disk: DiskStore, volume: &str, file_path: &str, algo: BitrotAlgorithm, shard_size: usize) -> Self {
|
||||
// WholeBitrotWriter {
|
||||
// disk,
|
||||
// volume: volume.to_string(),
|
||||
// file_path: file_path.to_string(),
|
||||
// _shard_size: shard_size,
|
||||
// hash: algo.new_hasher(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[async_trait::async_trait]
|
||||
// impl Writer for WholeBitrotWriter {
|
||||
// fn as_any(&self) -> &dyn Any {
|
||||
// self
|
||||
// }
|
||||
|
||||
// async fn write(&mut self, buf: &[u8]) -> Result<()> {
|
||||
// let mut file = self.disk.append_file(&self.volume, &self.file_path).await?;
|
||||
// let _ = file.write(buf).await?;
|
||||
// self.hash.update(buf);
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[derive(Debug)]
|
||||
// pub struct WholeBitrotReader {
|
||||
// disk: DiskStore,
|
||||
// volume: String,
|
||||
// file_path: String,
|
||||
// _verifier: BitrotVerifier,
|
||||
// till_offset: usize,
|
||||
// buf: Option<Vec<u8>>,
|
||||
// }
|
||||
|
||||
// impl WholeBitrotReader {
|
||||
// pub fn new(disk: DiskStore, volume: &str, file_path: &str, algo: BitrotAlgorithm, till_offset: usize, sum: &[u8]) -> Self {
|
||||
// Self {
|
||||
// disk,
|
||||
// volume: volume.to_string(),
|
||||
// file_path: file_path.to_string(),
|
||||
// _verifier: BitrotVerifier::new(algo, sum),
|
||||
// till_offset,
|
||||
// buf: None,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[async_trait::async_trait]
|
||||
// impl ReadAt for WholeBitrotReader {
|
||||
// async fn read_at(&mut self, offset: usize, length: usize) -> Result<(Vec<u8>, usize)> {
|
||||
// if self.buf.is_none() {
|
||||
// let buf_len = self.till_offset - offset;
|
||||
// let mut file = self
|
||||
// .disk
|
||||
// .read_file_stream(&self.volume, &self.file_path, offset, length)
|
||||
// .await?;
|
||||
// let mut buf = vec![0u8; buf_len];
|
||||
// file.read_at(offset, &mut buf).await?;
|
||||
// self.buf = Some(buf);
|
||||
// }
|
||||
|
||||
// if let Some(buf) = &mut self.buf {
|
||||
// if buf.len() < length {
|
||||
// return Err(Error::new(DiskError::LessData));
|
||||
// }
|
||||
|
||||
// return Ok((buf.drain(0..length).collect::<Vec<_>>(), length));
|
||||
// }
|
||||
|
||||
// Err(Error::new(DiskError::LessData))
|
||||
// }
|
||||
// }
|
||||
|
||||
// struct StreamingBitrotWriter {
|
||||
// hasher: Hasher,
|
||||
// tx: Sender<Option<Vec<u8>>>,
|
||||
// task: Option<JoinHandle<()>>,
|
||||
// }
|
||||
|
||||
// impl StreamingBitrotWriter {
|
||||
// pub async fn new(
|
||||
// disk: DiskStore,
|
||||
// orig_volume: &str,
|
||||
// volume: &str,
|
||||
// file_path: &str,
|
||||
// length: usize,
|
||||
// algo: BitrotAlgorithm,
|
||||
// shard_size: usize,
|
||||
// ) -> Result<Self> {
|
||||
// let hasher = algo.new_hasher();
|
||||
// let (tx, mut rx) = mpsc::channel::<Option<Vec<u8>>>(10);
|
||||
|
||||
// let total_file_size = length.div_ceil(shard_size) * hasher.size() + length;
|
||||
// let mut writer = disk.create_file(orig_volume, volume, file_path, total_file_size).await?;
|
||||
|
||||
// let task = spawn(async move {
|
||||
// loop {
|
||||
// if let Some(Some(buf)) = rx.recv().await {
|
||||
// writer.write(&buf).await.unwrap();
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// break;
|
||||
// }
|
||||
// });
|
||||
|
||||
// Ok(StreamingBitrotWriter {
|
||||
// hasher,
|
||||
// tx,
|
||||
// task: Some(task),
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[async_trait::async_trait]
|
||||
// impl Writer for StreamingBitrotWriter {
|
||||
// fn as_any(&self) -> &dyn Any {
|
||||
// self
|
||||
// }
|
||||
|
||||
// async fn write(&mut self, buf: &[u8]) -> Result<()> {
|
||||
// if buf.is_empty() {
|
||||
// return Ok(());
|
||||
// }
|
||||
// self.hasher.reset();
|
||||
// self.hasher.update(buf);
|
||||
// let hash_bytes = self.hasher.clone().finalize();
|
||||
// let _ = self.tx.send(Some(hash_bytes)).await?;
|
||||
// let _ = self.tx.send(Some(buf.to_vec())).await?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// async fn close(&mut self) -> Result<()> {
|
||||
// let _ = self.tx.send(None).await?;
|
||||
// if let Some(task) = self.task.take() {
|
||||
// let _ = task.await; // 等待任务完成
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[derive(Debug)]
|
||||
// struct StreamingBitrotReader {
|
||||
// disk: DiskStore,
|
||||
// _data: Vec<u8>,
|
||||
// volume: String,
|
||||
// file_path: String,
|
||||
// till_offset: usize,
|
||||
// curr_offset: usize,
|
||||
// hasher: Hasher,
|
||||
// shard_size: usize,
|
||||
// buf: Vec<u8>,
|
||||
// hash_bytes: Vec<u8>,
|
||||
// }
|
||||
|
||||
// impl StreamingBitrotReader {
|
||||
// pub fn new(
|
||||
// disk: DiskStore,
|
||||
// data: &[u8],
|
||||
// volume: &str,
|
||||
// file_path: &str,
|
||||
// algo: BitrotAlgorithm,
|
||||
// till_offset: usize,
|
||||
// shard_size: usize,
|
||||
// ) -> Self {
|
||||
// let hasher = algo.new_hasher();
|
||||
// Self {
|
||||
// disk,
|
||||
// _data: data.to_vec(),
|
||||
// volume: volume.to_string(),
|
||||
// file_path: file_path.to_string(),
|
||||
// till_offset: till_offset.div_ceil(shard_size) * hasher.size() + till_offset,
|
||||
// curr_offset: 0,
|
||||
// hash_bytes: Vec::with_capacity(hasher.size()),
|
||||
// hasher,
|
||||
// shard_size,
|
||||
// buf: Vec::new(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[async_trait::async_trait]
|
||||
// impl ReadAt for StreamingBitrotReader {
|
||||
// async fn read_at(&mut self, offset: usize, length: usize) -> Result<(Vec<u8>, usize)> {
|
||||
// if offset % self.shard_size != 0 {
|
||||
// return Err(Error::new(DiskError::Unexpected));
|
||||
// }
|
||||
// if self.buf.is_empty() {
|
||||
// self.curr_offset = offset;
|
||||
// let stream_offset = (offset / self.shard_size) * self.hasher.size() + offset;
|
||||
// let buf_len = self.till_offset - stream_offset;
|
||||
// let mut file = self.disk.read_file(&self.volume, &self.file_path).await?;
|
||||
// let mut buf = vec![0u8; buf_len];
|
||||
// file.read_at(stream_offset, &mut buf).await?;
|
||||
// self.buf = buf;
|
||||
// }
|
||||
// if offset != self.curr_offset {
|
||||
// return Err(Error::new(DiskError::Unexpected));
|
||||
// }
|
||||
|
||||
// self.hash_bytes = self.buf.drain(0..self.hash_bytes.capacity()).collect();
|
||||
// let buf = self.buf.drain(0..length).collect::<Vec<_>>();
|
||||
// self.hasher.reset();
|
||||
// self.hasher.update(&buf);
|
||||
// let actual = self.hasher.clone().finalize();
|
||||
// if actual != self.hash_bytes {
|
||||
// return Err(Error::new(DiskError::FileCorrupt));
|
||||
// }
|
||||
|
||||
// let readed_len = buf.len();
|
||||
// self.curr_offset += readed_len;
|
||||
|
||||
// Ok((buf, readed_len))
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct BitrotFileWriter {
|
||||
inner: Option<FileWriter>,
|
||||
hasher: Hasher,
|
||||
_shard_size: usize,
|
||||
inline: bool,
|
||||
inline_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BitrotFileWriter {
|
||||
pub async fn new(
|
||||
disk: Arc<Disk>,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
inline: bool,
|
||||
algo: BitrotAlgorithm,
|
||||
_shard_size: usize,
|
||||
) -> Result<Self> {
|
||||
let inner = if !inline {
|
||||
Some(disk.create_file("", volume, path, 0).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let hasher = algo.new_hasher();
|
||||
|
||||
Ok(Self {
|
||||
inner,
|
||||
inline,
|
||||
inline_data: Vec::new(),
|
||||
hasher,
|
||||
_shard_size,
|
||||
})
|
||||
}
|
||||
|
||||
// pub fn writer(&self) -> &FileWriter {
|
||||
// &self.inner
|
||||
// }
|
||||
|
||||
pub fn inline_data(&self) -> &[u8] {
|
||||
&self.inline_data
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Writer for BitrotFileWriter {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "info", skip_all)]
|
||||
async fn write(&mut self, buf: Bytes) -> Result<()> {
|
||||
if buf.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut hasher = self.hasher.clone();
|
||||
let h_buf = buf.clone();
|
||||
let hash_bytes = tokio::spawn(async move {
|
||||
hasher.reset();
|
||||
hasher.update(h_buf);
|
||||
hasher.finalize()
|
||||
})
|
||||
.await?;
|
||||
|
||||
if let Some(f) = self.inner.as_mut() {
|
||||
f.write_all(&hash_bytes).await?;
|
||||
f.write_all(&buf).await?;
|
||||
} else {
|
||||
self.inline_data.extend_from_slice(&hash_bytes);
|
||||
self.inline_data.extend_from_slice(&buf);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn close(&mut self) -> Result<()> {
|
||||
if self.inline {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(f) = self.inner.as_mut() {
|
||||
f.shutdown().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn new_bitrot_filewriter(
|
||||
disk: Arc<Disk>,
|
||||
/// Create a new BitrotWriterWrapper based on the provided parameters
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `is_inline_buffer`: If true, creates an in-memory buffer writer; if false, uses disk storage
|
||||
/// - `disk`: Optional disk instance for file creation (used when is_inline_buffer is false)
|
||||
/// - `shard_size`: Size of each shard for bitrot calculation
|
||||
/// - `checksum_algo`: Hash algorithm to use for bitrot verification
|
||||
/// - `volume`: Volume/bucket name for disk storage
|
||||
/// - `path`: File path for disk storage
|
||||
/// - `length`: Expected file length for disk storage
|
||||
///
|
||||
/// # Returns
|
||||
/// A Result containing the BitrotWriterWrapper or an error
|
||||
pub async fn create_bitrot_writer(
|
||||
is_inline_buffer: bool,
|
||||
disk: Option<&DiskStore>,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
inline: bool,
|
||||
algo: BitrotAlgorithm,
|
||||
length: i64,
|
||||
shard_size: usize,
|
||||
) -> Result<BitrotWriter> {
|
||||
let w = BitrotFileWriter::new(disk, volume, path, inline, algo, shard_size).await?;
|
||||
checksum_algo: HashAlgorithm,
|
||||
) -> disk::error::Result<BitrotWriterWrapper> {
|
||||
let writer = if is_inline_buffer {
|
||||
CustomWriter::new_inline_buffer()
|
||||
} else if let Some(disk) = disk {
|
||||
let length = if length > 0 {
|
||||
let length = length as usize;
|
||||
(length.div_ceil(shard_size) * checksum_algo.size() + length) as i64
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
Ok(Box::new(w))
|
||||
}
|
||||
let file = disk.create_file("", volume, path, length).await?;
|
||||
CustomWriter::new_tokio_writer(file)
|
||||
} else {
|
||||
return Err(DiskError::DiskNotFound);
|
||||
};
|
||||
|
||||
struct BitrotFileReader {
|
||||
disk: Arc<Disk>,
|
||||
data: Option<Vec<u8>>,
|
||||
volume: String,
|
||||
file_path: String,
|
||||
reader: Option<FileReader>,
|
||||
till_offset: usize,
|
||||
curr_offset: usize,
|
||||
hasher: Hasher,
|
||||
shard_size: usize,
|
||||
// buf: Vec<u8>,
|
||||
hash_bytes: Vec<u8>,
|
||||
read_buf: Vec<u8>,
|
||||
}
|
||||
|
||||
fn ceil(a: usize, b: usize) -> usize {
|
||||
a.div_ceil(b)
|
||||
}
|
||||
|
||||
impl BitrotFileReader {
|
||||
pub fn new(
|
||||
disk: Arc<Disk>,
|
||||
data: Option<Vec<u8>>,
|
||||
volume: String,
|
||||
file_path: String,
|
||||
algo: BitrotAlgorithm,
|
||||
till_offset: usize,
|
||||
shard_size: usize,
|
||||
) -> Self {
|
||||
let hasher = algo.new_hasher();
|
||||
Self {
|
||||
disk,
|
||||
data,
|
||||
volume,
|
||||
file_path,
|
||||
till_offset: ceil(till_offset, shard_size) * hasher.size() + till_offset,
|
||||
curr_offset: 0,
|
||||
hash_bytes: vec![0u8; hasher.size()],
|
||||
hasher,
|
||||
shard_size,
|
||||
// buf: Vec::new(),
|
||||
read_buf: Vec::new(),
|
||||
reader: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ReadAt for BitrotFileReader {
|
||||
// 读取数据
|
||||
async fn read_at(&mut self, offset: usize, length: usize) -> Result<(Vec<u8>, usize)> {
|
||||
if offset % self.shard_size != 0 {
|
||||
error!(
|
||||
"BitrotFileReader read_at offset % self.shard_size != 0 , {} % {} = {}",
|
||||
offset,
|
||||
self.shard_size,
|
||||
offset % self.shard_size
|
||||
);
|
||||
return Err(Error::new(DiskError::Unexpected));
|
||||
}
|
||||
|
||||
if self.reader.is_none() {
|
||||
self.curr_offset = offset;
|
||||
let stream_offset = (offset / self.shard_size) * self.hasher.size() + offset;
|
||||
|
||||
if let Some(data) = self.data.clone() {
|
||||
self.reader = Some(Box::new(Cursor::new(data)));
|
||||
} else {
|
||||
self.reader = Some(
|
||||
self.disk
|
||||
.read_file_stream(&self.volume, &self.file_path, stream_offset, self.till_offset - stream_offset)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if offset != self.curr_offset {
|
||||
error!(
|
||||
"BitrotFileReader read_at {}/{} offset != self.curr_offset, {} != {}",
|
||||
&self.volume, &self.file_path, offset, self.curr_offset
|
||||
);
|
||||
return Err(Error::new(DiskError::Unexpected));
|
||||
}
|
||||
|
||||
let reader = self.reader.as_mut().unwrap();
|
||||
// let mut hash_buf = self.hash_bytes;
|
||||
|
||||
self.hash_bytes.clear();
|
||||
self.hash_bytes.resize(self.hasher.size(), 0u8);
|
||||
|
||||
reader.read_exact(&mut self.hash_bytes).await?;
|
||||
|
||||
self.read_buf.clear();
|
||||
self.read_buf.resize(length, 0u8);
|
||||
|
||||
reader.read_exact(&mut self.read_buf).await?;
|
||||
|
||||
self.hasher.reset();
|
||||
self.hasher.update(&self.read_buf);
|
||||
let actual = self.hasher.clone().finalize();
|
||||
if actual != self.hash_bytes {
|
||||
error!(
|
||||
"BitrotFileReader read_at actual != self.hash_bytes, {:?} != {:?}",
|
||||
actual, self.hash_bytes
|
||||
);
|
||||
return Err(Error::new(DiskError::FileCorrupt));
|
||||
}
|
||||
|
||||
let readed_len = self.read_buf.len();
|
||||
self.curr_offset += readed_len;
|
||||
|
||||
Ok((self.read_buf.clone(), readed_len))
|
||||
|
||||
// let stream_offset = (offset / self.shard_size) * self.hasher.size() + offset;
|
||||
// let buf_len = self.hasher.size() + length;
|
||||
|
||||
// self.read_buf.clear();
|
||||
// self.read_buf.resize(buf_len, 0u8);
|
||||
|
||||
// self.inner.read_at(stream_offset, &mut self.read_buf).await?;
|
||||
|
||||
// let hash_bytes = &self.read_buf.as_slice()[0..self.hash_bytes.capacity()];
|
||||
|
||||
// self.hash_bytes.clone_from_slice(hash_bytes);
|
||||
// let buf = self.read_buf.as_slice()[self.hash_bytes.capacity()..self.hash_bytes.capacity() + length].to_vec();
|
||||
|
||||
// self.hasher.reset();
|
||||
// self.hasher.update(&buf);
|
||||
// let actual = self.hasher.clone().finalize();
|
||||
|
||||
// if actual != self.hash_bytes {
|
||||
// return Err(Error::new(DiskError::FileCorrupt));
|
||||
// }
|
||||
|
||||
// let readed_len = buf.len();
|
||||
// self.curr_offset += readed_len;
|
||||
|
||||
// Ok((buf, readed_len))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_bitrot_filereader(
|
||||
disk: Arc<Disk>,
|
||||
data: Option<Vec<u8>>,
|
||||
volume: String,
|
||||
file_path: String,
|
||||
till_offset: usize,
|
||||
algo: BitrotAlgorithm,
|
||||
shard_size: usize,
|
||||
) -> BitrotReader {
|
||||
Box::new(BitrotFileReader::new(disk, data, volume, file_path, algo, till_offset, shard_size))
|
||||
Ok(BitrotWriterWrapper::new(writer, shard_size, checksum_algo))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::collections::HashMap;
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::{disk::error::DiskError, store_api::BitrotAlgorithm};
|
||||
use common::error::{Error, Result};
|
||||
use hex_simd::decode_to_vec;
|
||||
#[tokio::test]
|
||||
async fn test_create_bitrot_reader_with_inline_data() {
|
||||
let test_data = b"hello world test data";
|
||||
let shard_size = 16;
|
||||
let checksum_algo = HashAlgorithm::HighwayHash256;
|
||||
|
||||
// use super::{bitrot_writer_sum, new_bitrot_reader};
|
||||
let result =
|
||||
create_bitrot_reader(Some(test_data), None, "test-bucket", "test-path", 0, 0, shard_size, checksum_algo).await;
|
||||
|
||||
#[test]
|
||||
fn bitrot_self_test() -> Result<()> {
|
||||
let mut checksums = HashMap::new();
|
||||
checksums.insert(
|
||||
BitrotAlgorithm::SHA256,
|
||||
"a7677ff19e0182e4d52e3a3db727804abc82a5818749336369552e54b838b004",
|
||||
);
|
||||
checksums.insert(BitrotAlgorithm::BLAKE2b512, "e519b7d84b1c3c917985f544773a35cf265dcab10948be3550320d156bab612124a5ae2ae5a8c73c0eea360f68b0e28136f26e858756dbfe7375a7389f26c669");
|
||||
checksums.insert(
|
||||
BitrotAlgorithm::HighwayHash256,
|
||||
"c81c2386a1f565e805513d630d4e50ff26d11269b21c221cf50fc6c29d6ff75b",
|
||||
);
|
||||
checksums.insert(
|
||||
BitrotAlgorithm::HighwayHash256S,
|
||||
"c81c2386a1f565e805513d630d4e50ff26d11269b21c221cf50fc6c29d6ff75b",
|
||||
);
|
||||
|
||||
let iter = [
|
||||
BitrotAlgorithm::SHA256,
|
||||
BitrotAlgorithm::BLAKE2b512,
|
||||
BitrotAlgorithm::HighwayHash256,
|
||||
];
|
||||
|
||||
for algo in iter.iter() {
|
||||
if !algo.available() || *algo != BitrotAlgorithm::HighwayHash256 {
|
||||
continue;
|
||||
}
|
||||
let checksum = decode_to_vec(checksums.get(algo).unwrap())?;
|
||||
|
||||
let mut h = algo.new_hasher();
|
||||
let mut msg = Vec::with_capacity(h.size() * h.block_size());
|
||||
let mut sum = Vec::with_capacity(h.size());
|
||||
|
||||
for _ in (0..h.size() * h.block_size()).step_by(h.size()) {
|
||||
h.update(&msg);
|
||||
sum = h.finalize();
|
||||
msg.extend(sum.clone());
|
||||
h = algo.new_hasher();
|
||||
}
|
||||
|
||||
if checksum != sum {
|
||||
return Err(Error::new(DiskError::FileCorrupt));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().is_some());
|
||||
}
|
||||
|
||||
// #[tokio::test]
|
||||
// async fn test_all_bitrot_algorithms() -> Result<()> {
|
||||
// for algo in BITROT_ALGORITHMS.keys() {
|
||||
// test_bitrot_reader_writer_algo(algo.clone()).await?;
|
||||
// }
|
||||
#[tokio::test]
|
||||
async fn test_create_bitrot_reader_without_data_or_disk() {
|
||||
let shard_size = 16;
|
||||
let checksum_algo = HashAlgorithm::HighwayHash256;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
let result = create_bitrot_reader(None, None, "test-bucket", "test-path", 0, 1024, shard_size, checksum_algo).await;
|
||||
|
||||
// async fn test_bitrot_reader_writer_algo(algo: BitrotAlgorithm) -> Result<()> {
|
||||
// let temp_dir = TempDir::new().unwrap().path().to_string_lossy().to_string();
|
||||
// fs::create_dir_all(&temp_dir)?;
|
||||
// let volume = "testvol";
|
||||
// let file_path = "testfile";
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap().is_none());
|
||||
}
|
||||
|
||||
// let ep = Endpoint::try_from(temp_dir.as_str())?;
|
||||
// let opt = DiskOption::default();
|
||||
// let disk = new_disk(&ep, &opt).await?;
|
||||
// disk.make_volume(volume).await?;
|
||||
// let mut writer = new_bitrot_writer(disk.clone(), "", volume, file_path, 35, algo.clone(), 10).await?;
|
||||
#[tokio::test]
|
||||
async fn test_create_bitrot_writer_inline() {
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
|
||||
// writer.write(b"aaaaaaaaaa").await?;
|
||||
// writer.write(b"aaaaaaaaaa").await?;
|
||||
// writer.write(b"aaaaaaaaaa").await?;
|
||||
// writer.write(b"aaaaa").await?;
|
||||
let wrapper = create_bitrot_writer(
|
||||
true, // is_inline_buffer
|
||||
None, // disk not needed for inline buffer
|
||||
"test-volume",
|
||||
"test-path",
|
||||
1024, // length
|
||||
1024, // shard_size
|
||||
HashAlgorithm::HighwayHash256,
|
||||
)
|
||||
.await;
|
||||
|
||||
// let sum = bitrot_writer_sum(&writer);
|
||||
// writer.close().await?;
|
||||
assert!(wrapper.is_ok());
|
||||
let mut wrapper = wrapper.unwrap();
|
||||
|
||||
// let mut reader = new_bitrot_reader(disk, b"", volume, file_path, 35, algo, &sum, 10);
|
||||
// let read_len = 10;
|
||||
// let mut result: Vec<u8>;
|
||||
// (result, _) = reader.read_at(0, read_len).await?;
|
||||
// assert_eq!(result, b"aaaaaaaaaa");
|
||||
// (result, _) = reader.read_at(10, read_len).await?;
|
||||
// assert_eq!(result, b"aaaaaaaaaa");
|
||||
// (result, _) = reader.read_at(20, read_len).await?;
|
||||
// assert_eq!(result, b"aaaaaaaaaa");
|
||||
// (result, _) = reader.read_at(30, read_len / 2).await?;
|
||||
// assert_eq!(result, b"aaaaa");
|
||||
// Test writing some data
|
||||
let test_data = b"hello world";
|
||||
let result = wrapper.write(test_data).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
// Test getting inline data
|
||||
let inline_data = wrapper.into_inline_data();
|
||||
assert!(inline_data.is_some());
|
||||
// The inline data should contain both hash and data
|
||||
let data = inline_data.unwrap();
|
||||
assert!(!data.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_bitrot_writer_disk_without_disk() {
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
|
||||
// Test error case: trying to create disk writer without providing disk instance
|
||||
let wrapper = create_bitrot_writer(
|
||||
false, // is_inline_buffer = false, so needs disk
|
||||
None, // disk = None, should cause error
|
||||
"test-volume",
|
||||
"test-path",
|
||||
1024, // length
|
||||
1024, // shard_size
|
||||
HashAlgorithm::HighwayHash256,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(wrapper.is_err());
|
||||
let error = wrapper.unwrap_err();
|
||||
println!("error: {:?}", error);
|
||||
assert_eq!(error, DiskError::DiskNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use common::error::Error;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BucketMetadataError {
|
||||
#[error("tagging not found")]
|
||||
TaggingNotFound,
|
||||
@@ -18,18 +18,58 @@ pub enum BucketMetadataError {
|
||||
BucketReplicationConfigNotFound,
|
||||
#[error("bucket remote target not found")]
|
||||
BucketRemoteTargetNotFound,
|
||||
|
||||
#[error("Io error: {0}")]
|
||||
Io(std::io::Error),
|
||||
}
|
||||
|
||||
impl BucketMetadataError {
|
||||
pub fn is(&self, err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<BucketMetadataError>() {
|
||||
e == self
|
||||
} else {
|
||||
false
|
||||
pub fn other<E>(error: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
BucketMetadataError::Io(std::io::Error::other(error))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BucketMetadataError> for Error {
|
||||
fn from(e: BucketMetadataError) -> Self {
|
||||
match e {
|
||||
BucketMetadataError::BucketPolicyNotFound => Error::BucketPolicyNotFound,
|
||||
_ => Error::other(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for BucketMetadataError {
|
||||
fn from(e: Error) -> Self {
|
||||
match e {
|
||||
Error::BucketPolicyNotFound => BucketMetadataError::BucketPolicyNotFound,
|
||||
Error::Io(e) => e.into(),
|
||||
_ => BucketMetadataError::other(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for BucketMetadataError {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
e.downcast::<BucketMetadataError>().unwrap_or_else(BucketMetadataError::other)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for BucketMetadataError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(BucketMetadataError::Io(e1), BucketMetadataError::Io(e2)) => {
|
||||
e1.kind() == e2.kind() && e1.to_string() == e2.to_string()
|
||||
}
|
||||
(e1, e2) => e1.to_u32() == e2.to_u32(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for BucketMetadataError {}
|
||||
|
||||
impl BucketMetadataError {
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
match self {
|
||||
@@ -41,6 +81,7 @@ impl BucketMetadataError {
|
||||
BucketMetadataError::BucketQuotaConfigNotFound => 0x06,
|
||||
BucketMetadataError::BucketReplicationConfigNotFound => 0x07,
|
||||
BucketMetadataError::BucketRemoteTargetNotFound => 0x08,
|
||||
BucketMetadataError::Io(_) => 0x09,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +95,7 @@ impl BucketMetadataError {
|
||||
0x06 => Some(BucketMetadataError::BucketQuotaConfigNotFound),
|
||||
0x07 => Some(BucketMetadataError::BucketReplicationConfigNotFound),
|
||||
0x08 => Some(BucketMetadataError::BucketRemoteTargetNotFound),
|
||||
0x09 => Some(BucketMetadataError::Io(std::io::Error::other("Io error"))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,13 @@ use time::OffsetDateTime;
|
||||
use tracing::error;
|
||||
|
||||
use crate::bucket::target::BucketTarget;
|
||||
use crate::bucket::utils::deserialize;
|
||||
use crate::config::com::{read_config, save_config};
|
||||
use crate::{config, new_object_layer_fn};
|
||||
use common::error::{Error, Result};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::new_object_layer_fn;
|
||||
|
||||
use crate::disk::BUCKET_META_PREFIX;
|
||||
use crate::store::ECStore;
|
||||
use crate::utils::xml::deserialize;
|
||||
|
||||
pub const BUCKET_METADATA_FILE: &str = ".metadata.bin";
|
||||
pub const BUCKET_METADATA_FORMAT: u16 = 1;
|
||||
@@ -178,7 +178,7 @@ impl BucketMetadata {
|
||||
|
||||
pub fn check_header(buf: &[u8]) -> Result<()> {
|
||||
if buf.len() <= 4 {
|
||||
return Err(Error::msg("read_bucket_metadata: data invalid"));
|
||||
return Err(Error::other("read_bucket_metadata: data invalid"));
|
||||
}
|
||||
|
||||
let format = LittleEndian::read_u16(&buf[0..2]);
|
||||
@@ -186,12 +186,12 @@ impl BucketMetadata {
|
||||
|
||||
match format {
|
||||
BUCKET_METADATA_FORMAT => {}
|
||||
_ => return Err(Error::msg("read_bucket_metadata: format invalid")),
|
||||
_ => return Err(Error::other("read_bucket_metadata: format invalid")),
|
||||
}
|
||||
|
||||
match version {
|
||||
BUCKET_METADATA_VERSION => {}
|
||||
_ => return Err(Error::msg("read_bucket_metadata: version invalid")),
|
||||
_ => return Err(Error::other("read_bucket_metadata: version invalid")),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -285,7 +285,7 @@ impl BucketMetadata {
|
||||
self.bucket_targets_config_json = data.clone();
|
||||
self.bucket_targets_config_updated_at = updated;
|
||||
}
|
||||
_ => return Err(Error::msg(format!("config file not found : {}", config_file))),
|
||||
_ => return Err(Error::other(format!("config file not found : {}", config_file))),
|
||||
}
|
||||
|
||||
Ok(updated)
|
||||
@@ -296,7 +296,9 @@ impl BucketMetadata {
|
||||
}
|
||||
|
||||
pub async fn save(&mut self) -> Result<()> {
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
|
||||
self.parse_all_configs(store.clone())?;
|
||||
|
||||
@@ -364,7 +366,7 @@ pub async fn load_bucket_metadata_parse(api: Arc<ECStore>, bucket: &str, parse:
|
||||
let mut bm = match read_bucket_metadata(api.clone(), bucket).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
if !config::error::is_err_config_not_found(&err) {
|
||||
if err != Error::ConfigNotFound {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
@@ -388,7 +390,7 @@ pub async fn load_bucket_metadata_parse(api: Arc<ECStore>, bucket: &str, parse:
|
||||
async fn read_bucket_metadata(api: Arc<ECStore>, bucket: &str) -> Result<BucketMetadata> {
|
||||
if bucket.is_empty() {
|
||||
error!("bucket name empty");
|
||||
return Err(Error::msg("invalid argument"));
|
||||
return Err(Error::other("invalid argument"));
|
||||
}
|
||||
|
||||
let bm = BucketMetadata::new(bucket);
|
||||
@@ -403,7 +405,7 @@ async fn read_bucket_metadata(api: Arc<ECStore>, bucket: &str) -> Result<BucketM
|
||||
Ok(bm)
|
||||
}
|
||||
|
||||
fn _write_time<S>(t: &OffsetDateTime, s: S) -> Result<S::Ok, S::Error>
|
||||
fn _write_time<S>(t: &OffsetDateTime, s: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
|
||||
@@ -3,18 +3,15 @@ use std::sync::OnceLock;
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::StorageAPI;
|
||||
use crate::bucket::error::BucketMetadataError;
|
||||
use crate::bucket::metadata::{load_bucket_metadata_parse, BUCKET_LIFECYCLE_CONFIG};
|
||||
use crate::bucket::utils::is_meta_bucketname;
|
||||
use crate::bucket::metadata::{BUCKET_LIFECYCLE_CONFIG, load_bucket_metadata_parse};
|
||||
use crate::bucket::utils::{deserialize, is_meta_bucketname};
|
||||
use crate::cmd::bucket_targets;
|
||||
use crate::config::error::ConfigError;
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::global::{is_dist_erasure, is_erasure, new_object_layer_fn, GLOBAL_Endpoints};
|
||||
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;
|
||||
use crate::store::ECStore;
|
||||
use crate::utils::xml::deserialize;
|
||||
use crate::{config, StorageAPI};
|
||||
use common::error::{Error, Result};
|
||||
use futures::future::join_all;
|
||||
use policy::policy::BucketPolicy;
|
||||
use s3s::dto::{
|
||||
@@ -26,7 +23,7 @@ use tokio::sync::RwLock;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{error, warn};
|
||||
|
||||
use super::metadata::{load_bucket_metadata, BucketMetadata};
|
||||
use super::metadata::{BucketMetadata, load_bucket_metadata};
|
||||
use super::quota::BucketQuota;
|
||||
use super::target::BucketTargets;
|
||||
|
||||
@@ -50,7 +47,7 @@ pub(super) fn get_bucket_metadata_sys() -> Result<Arc<RwLock<BucketMetadataSys>>
|
||||
if let Some(sys) = GLOBAL_BucketMetadataSys.get() {
|
||||
Ok(sys.clone())
|
||||
} else {
|
||||
Err(Error::msg("GLOBAL_BucketMetadataSys not init"))
|
||||
Err(Error::other("GLOBAL_BucketMetadataSys not init"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +165,7 @@ impl BucketMetadataSys {
|
||||
if let Some(endpoints) = GLOBAL_Endpoints.get() {
|
||||
endpoints.es_count() * 10
|
||||
} else {
|
||||
return Err(Error::msg("GLOBAL_Endpoints not init"));
|
||||
return Err(Error::other("GLOBAL_Endpoints not init"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -248,14 +245,14 @@ impl BucketMetadataSys {
|
||||
|
||||
pub async fn get(&self, bucket: &str) -> Result<Arc<BucketMetadata>> {
|
||||
if is_meta_bucketname(bucket) {
|
||||
return Err(Error::new(ConfigError::NotFound));
|
||||
return Err(Error::ConfigNotFound);
|
||||
}
|
||||
|
||||
let map = self.metadata_map.read().await;
|
||||
if let Some(bm) = map.get(bucket) {
|
||||
Ok(bm.clone())
|
||||
} else {
|
||||
Err(Error::new(ConfigError::NotFound))
|
||||
Err(Error::ConfigNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +277,7 @@ impl BucketMetadataSys {
|
||||
let meta = match self.get_config_from_disk(bucket).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
if !config::error::is_err_config_not_found(&err) {
|
||||
if err != Error::ConfigNotFound {
|
||||
return Err(err);
|
||||
} else {
|
||||
BucketMetadata::new(bucket)
|
||||
@@ -304,16 +301,18 @@ impl BucketMetadataSys {
|
||||
}
|
||||
|
||||
async fn update_and_parse(&mut self, bucket: &str, config_file: &str, data: Vec<u8>, parse: bool) -> Result<OffsetDateTime> {
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
|
||||
if is_meta_bucketname(bucket) {
|
||||
return Err(Error::msg("errInvalidArgument"));
|
||||
return Err(Error::other("errInvalidArgument"));
|
||||
}
|
||||
|
||||
let mut bm = match load_bucket_metadata_parse(store, bucket, parse).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
if !is_erasure().await && !is_dist_erasure().await && DiskError::VolumeNotFound.is(&err) {
|
||||
if !is_erasure().await && !is_dist_erasure().await && is_err_bucket_not_found(&err) {
|
||||
BucketMetadata::new(bucket)
|
||||
} else {
|
||||
return Err(err);
|
||||
@@ -330,7 +329,7 @@ impl BucketMetadataSys {
|
||||
|
||||
async fn save(&self, bm: BucketMetadata) -> Result<()> {
|
||||
if is_meta_bucketname(&bm.name) {
|
||||
return Err(Error::msg("errInvalidArgument"));
|
||||
return Err(Error::other("errInvalidArgument"));
|
||||
}
|
||||
|
||||
let mut bm = bm;
|
||||
@@ -345,7 +344,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::msg("errInvalidArgument"));
|
||||
return Err(Error::other("errInvalidArgument"));
|
||||
}
|
||||
|
||||
load_bucket_metadata(self.api.clone(), bucket).await
|
||||
@@ -364,10 +363,10 @@ impl BucketMetadataSys {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
return if *self.initialized.read().await {
|
||||
Err(Error::msg("errBucketMetadataNotInitialized"))
|
||||
Err(Error::other("errBucketMetadataNotInitialized"))
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -385,7 +384,7 @@ impl BucketMetadataSys {
|
||||
Ok((res, _)) => res,
|
||||
Err(err) => {
|
||||
warn!("get_versioning_config err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
return if err == Error::ConfigNotFound {
|
||||
Ok((VersioningConfiguration::default(), OffsetDateTime::UNIX_EPOCH))
|
||||
} else {
|
||||
Err(err)
|
||||
@@ -405,8 +404,8 @@ impl BucketMetadataSys {
|
||||
Ok((res, _)) => res,
|
||||
Err(err) => {
|
||||
warn!("get_bucket_policy err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
Err(Error::new(BucketMetadataError::BucketPolicyNotFound))
|
||||
return if err == Error::ConfigNotFound {
|
||||
Err(BucketMetadataError::BucketPolicyNotFound.into())
|
||||
} else {
|
||||
Err(err)
|
||||
};
|
||||
@@ -416,7 +415,7 @@ impl BucketMetadataSys {
|
||||
if let Some(config) = &bm.policy_config {
|
||||
Ok((config.clone(), bm.policy_config_updated_at))
|
||||
} else {
|
||||
Err(Error::new(BucketMetadataError::BucketPolicyNotFound))
|
||||
Err(BucketMetadataError::BucketPolicyNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,8 +424,8 @@ impl BucketMetadataSys {
|
||||
Ok((res, _)) => res,
|
||||
Err(err) => {
|
||||
warn!("get_tagging_config err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
Err(Error::new(BucketMetadataError::TaggingNotFound))
|
||||
return if err == Error::ConfigNotFound {
|
||||
Err(BucketMetadataError::TaggingNotFound.into())
|
||||
} else {
|
||||
Err(err)
|
||||
};
|
||||
@@ -436,7 +435,7 @@ impl BucketMetadataSys {
|
||||
if let Some(config) = &bm.tagging_config {
|
||||
Ok((config.clone(), bm.tagging_config_updated_at))
|
||||
} else {
|
||||
Err(Error::new(BucketMetadataError::TaggingNotFound))
|
||||
Err(BucketMetadataError::TaggingNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,9 +443,8 @@ impl BucketMetadataSys {
|
||||
let bm = match self.get_config(bucket).await {
|
||||
Ok((res, _)) => res,
|
||||
Err(err) => {
|
||||
warn!("get_object_lock_config err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
Err(Error::new(BucketMetadataError::BucketObjectLockConfigNotFound))
|
||||
return if err == Error::ConfigNotFound {
|
||||
Err(BucketMetadataError::BucketObjectLockConfigNotFound.into())
|
||||
} else {
|
||||
Err(err)
|
||||
};
|
||||
@@ -456,7 +454,7 @@ impl BucketMetadataSys {
|
||||
if let Some(config) = &bm.object_lock_config {
|
||||
Ok((config.clone(), bm.object_lock_config_updated_at))
|
||||
} else {
|
||||
Err(Error::new(BucketMetadataError::BucketObjectLockConfigNotFound))
|
||||
Err(BucketMetadataError::BucketObjectLockConfigNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,8 +463,8 @@ impl BucketMetadataSys {
|
||||
Ok((res, _)) => res,
|
||||
Err(err) => {
|
||||
warn!("get_lifecycle_config err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
Err(Error::new(BucketMetadataError::BucketLifecycleNotFound))
|
||||
return if err == Error::ConfigNotFound {
|
||||
Err(BucketMetadataError::BucketLifecycleNotFound.into())
|
||||
} else {
|
||||
Err(err)
|
||||
};
|
||||
@@ -475,12 +473,12 @@ impl BucketMetadataSys {
|
||||
|
||||
if let Some(config) = &bm.lifecycle_config {
|
||||
if config.rules.is_empty() {
|
||||
Err(Error::new(BucketMetadataError::BucketLifecycleNotFound))
|
||||
Err(BucketMetadataError::BucketLifecycleNotFound.into())
|
||||
} else {
|
||||
Ok((config.clone(), bm.lifecycle_config_updated_at))
|
||||
}
|
||||
} else {
|
||||
Err(Error::new(BucketMetadataError::BucketLifecycleNotFound))
|
||||
Err(BucketMetadataError::BucketLifecycleNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,7 +487,7 @@ impl BucketMetadataSys {
|
||||
Ok((bm, _)) => bm.notification_config.clone(),
|
||||
Err(err) => {
|
||||
warn!("get_notification_config err {:?}", &err);
|
||||
if config::error::is_err_config_not_found(&err) {
|
||||
if err == Error::ConfigNotFound {
|
||||
None
|
||||
} else {
|
||||
return Err(err);
|
||||
@@ -505,8 +503,8 @@ impl BucketMetadataSys {
|
||||
Ok((res, _)) => res,
|
||||
Err(err) => {
|
||||
warn!("get_sse_config err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
Err(Error::new(BucketMetadataError::BucketSSEConfigNotFound))
|
||||
return if err == Error::ConfigNotFound {
|
||||
Err(BucketMetadataError::BucketSSEConfigNotFound.into())
|
||||
} else {
|
||||
Err(err)
|
||||
};
|
||||
@@ -516,7 +514,7 @@ impl BucketMetadataSys {
|
||||
if let Some(config) = &bm.sse_config {
|
||||
Ok((config.clone(), bm.encryption_config_updated_at))
|
||||
} else {
|
||||
Err(Error::new(BucketMetadataError::BucketSSEConfigNotFound))
|
||||
Err(BucketMetadataError::BucketSSEConfigNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,8 +534,8 @@ impl BucketMetadataSys {
|
||||
Ok((res, _)) => res,
|
||||
Err(err) => {
|
||||
warn!("get_quota_config err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
Err(Error::new(BucketMetadataError::BucketQuotaConfigNotFound))
|
||||
return if err == Error::ConfigNotFound {
|
||||
Err(BucketMetadataError::BucketQuotaConfigNotFound.into())
|
||||
} else {
|
||||
Err(err)
|
||||
};
|
||||
@@ -547,7 +545,7 @@ impl BucketMetadataSys {
|
||||
if let Some(config) = &bm.quota_config {
|
||||
Ok((config.clone(), bm.quota_config_updated_at))
|
||||
} else {
|
||||
Err(Error::new(BucketMetadataError::BucketQuotaConfigNotFound))
|
||||
Err(BucketMetadataError::BucketQuotaConfigNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,14 +553,14 @@ impl BucketMetadataSys {
|
||||
let (bm, reload) = match self.get_config(bucket).await {
|
||||
Ok(res) => {
|
||||
if res.0.replication_config.is_none() {
|
||||
return Err(Error::new(BucketMetadataError::BucketReplicationConfigNotFound));
|
||||
return Err(BucketMetadataError::BucketReplicationConfigNotFound.into());
|
||||
}
|
||||
res
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("get_replication_config err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
Err(Error::new(BucketMetadataError::BucketReplicationConfigNotFound))
|
||||
return if err == Error::ConfigNotFound {
|
||||
Err(BucketMetadataError::BucketReplicationConfigNotFound.into())
|
||||
} else {
|
||||
Err(err)
|
||||
};
|
||||
@@ -576,7 +574,7 @@ impl BucketMetadataSys {
|
||||
//println!("549 {:?}", config.clone());
|
||||
Ok((config.clone(), bm.replication_config_updated_at))
|
||||
} else {
|
||||
Err(Error::new(BucketMetadataError::BucketReplicationConfigNotFound))
|
||||
Err(BucketMetadataError::BucketReplicationConfigNotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,8 +583,8 @@ impl BucketMetadataSys {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
warn!("get_replication_config err {:?}", &err);
|
||||
return if config::error::is_err_config_not_found(&err) {
|
||||
Err(Error::new(BucketMetadataError::BucketRemoteTargetNotFound))
|
||||
return if err == Error::ConfigNotFound {
|
||||
Err(BucketMetadataError::BucketRemoteTargetNotFound.into())
|
||||
} else {
|
||||
Err(err)
|
||||
};
|
||||
@@ -603,7 +601,7 @@ impl BucketMetadataSys {
|
||||
|
||||
Ok(config.clone())
|
||||
} else {
|
||||
Err(Error::new(BucketMetadataError::BucketRemoteTargetNotFound))
|
||||
Err(BucketMetadataError::BucketRemoteTargetNotFound.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::{error::BucketMetadataError, metadata_sys::get_bucket_metadata_sys};
|
||||
use common::error::Result;
|
||||
use crate::error::Result;
|
||||
use policy::policy::{BucketPolicy, BucketPolicyArgs};
|
||||
use tracing::warn;
|
||||
|
||||
@@ -10,8 +10,9 @@ impl PolicySys {
|
||||
match Self::get(args.bucket).await {
|
||||
Ok(cfg) => return cfg.is_allowed(args),
|
||||
Err(err) => {
|
||||
if !BucketMetadataError::BucketPolicyNotFound.is(&err) {
|
||||
warn!("config get err {:?}", err);
|
||||
let berr: BucketMetadataError = err.into();
|
||||
if berr != BucketMetadataError::BucketPolicyNotFound {
|
||||
warn!("config get err {:?}", berr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use common::error::Result;
|
||||
use crate::error::Result;
|
||||
use rmp_serde::Serializer as rmpSerializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use common::error::Result;
|
||||
use crate::error::Result;
|
||||
use rmp_serde::Serializer as rmpSerializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
+46
-17
@@ -1,5 +1,6 @@
|
||||
use crate::disk::RUSTFS_META_BUCKET;
|
||||
use common::error::{Error, Result};
|
||||
use crate::error::{Error, Result};
|
||||
use s3s::xml;
|
||||
|
||||
pub fn is_meta_bucketname(name: &str) -> bool {
|
||||
name.starts_with(RUSTFS_META_BUCKET)
|
||||
@@ -13,60 +14,88 @@ lazy_static::lazy_static! {
|
||||
static ref IP_ADDRESS: Regex = Regex::new(r"^(\d+\.){3}\d+$").unwrap();
|
||||
}
|
||||
|
||||
pub fn check_bucket_name_common(bucket_name: &str, strict: bool) -> Result<(), Error> {
|
||||
pub fn check_bucket_name_common(bucket_name: &str, strict: bool) -> Result<()> {
|
||||
let bucket_name_trimmed = bucket_name.trim();
|
||||
|
||||
if bucket_name_trimmed.is_empty() {
|
||||
return Err(Error::msg("Bucket name cannot be empty"));
|
||||
return Err(Error::other("Bucket name cannot be empty"));
|
||||
}
|
||||
if bucket_name_trimmed.len() < 3 {
|
||||
return Err(Error::msg("Bucket name cannot be shorter than 3 characters"));
|
||||
return Err(Error::other("Bucket name cannot be shorter than 3 characters"));
|
||||
}
|
||||
if bucket_name_trimmed.len() > 63 {
|
||||
return Err(Error::msg("Bucket name cannot be longer than 63 characters"));
|
||||
return Err(Error::other("Bucket name cannot be longer than 63 characters"));
|
||||
}
|
||||
|
||||
if bucket_name_trimmed == "rustfs" {
|
||||
return Err(Error::msg("Bucket name cannot be rustfs"));
|
||||
return Err(Error::other("Bucket name cannot be rustfs"));
|
||||
}
|
||||
|
||||
if IP_ADDRESS.is_match(bucket_name_trimmed) {
|
||||
return Err(Error::msg("Bucket name cannot be an IP address"));
|
||||
return Err(Error::other("Bucket name cannot be an IP address"));
|
||||
}
|
||||
if bucket_name_trimmed.contains("..") || bucket_name_trimmed.contains(".-") || bucket_name_trimmed.contains("-.") {
|
||||
return Err(Error::msg("Bucket name contains invalid characters"));
|
||||
return Err(Error::other("Bucket name contains invalid characters"));
|
||||
}
|
||||
if strict {
|
||||
if !VALID_BUCKET_NAME_STRICT.is_match(bucket_name_trimmed) {
|
||||
return Err(Error::msg("Bucket name contains invalid characters"));
|
||||
return Err(Error::other("Bucket name contains invalid characters"));
|
||||
}
|
||||
} else if !VALID_BUCKET_NAME.is_match(bucket_name_trimmed) {
|
||||
return Err(Error::msg("Bucket name contains invalid characters"));
|
||||
return Err(Error::other("Bucket name contains invalid characters"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_valid_bucket_name(bucket_name: &str) -> Result<(), Error> {
|
||||
pub fn check_valid_bucket_name(bucket_name: &str) -> Result<()> {
|
||||
check_bucket_name_common(bucket_name, false)
|
||||
}
|
||||
|
||||
pub fn check_valid_bucket_name_strict(bucket_name: &str) -> Result<(), Error> {
|
||||
pub fn check_valid_bucket_name_strict(bucket_name: &str) -> Result<()> {
|
||||
check_bucket_name_common(bucket_name, true)
|
||||
}
|
||||
|
||||
pub fn check_valid_object_name_prefix(object_name: &str) -> Result<(), Error> {
|
||||
pub fn check_valid_object_name_prefix(object_name: &str) -> Result<()> {
|
||||
if object_name.len() > 1024 {
|
||||
return Err(Error::msg("Object name cannot be longer than 1024 characters"));
|
||||
return Err(Error::other("Object name cannot be longer than 1024 characters"));
|
||||
}
|
||||
if !object_name.is_ascii() {
|
||||
return Err(Error::msg("Object name with non-UTF-8 strings are not supported"));
|
||||
return Err(Error::other("Object name with non-UTF-8 strings are not supported"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_valid_object_name(object_name: &str) -> Result<(), Error> {
|
||||
pub fn check_valid_object_name(object_name: &str) -> Result<()> {
|
||||
if object_name.trim().is_empty() {
|
||||
return Err(Error::msg("Object name cannot be empty"));
|
||||
return Err(Error::other("Object name cannot be empty"));
|
||||
}
|
||||
check_valid_object_name_prefix(object_name)
|
||||
}
|
||||
|
||||
pub fn deserialize<T>(input: &[u8]) -> xml::DeResult<T>
|
||||
where
|
||||
T: for<'xml> xml::Deserialize<'xml>,
|
||||
{
|
||||
let mut d = xml::Deserializer::new(input);
|
||||
let ans = T::deserialize(&mut d)?;
|
||||
d.expect_eof()?;
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
pub fn serialize_content<T: xml::SerializeContent>(val: &T) -> xml::SerResult<String> {
|
||||
let mut buf = Vec::with_capacity(256);
|
||||
{
|
||||
let mut ser = xml::Serializer::new(&mut buf);
|
||||
val.serialize_content(&mut ser)?;
|
||||
}
|
||||
Ok(String::from_utf8(buf).unwrap())
|
||||
}
|
||||
|
||||
pub fn serialize<T: xml::Serialize>(val: &T) -> xml::SerResult<Vec<u8>> {
|
||||
let mut buf = Vec::with_capacity(256);
|
||||
{
|
||||
let mut ser = xml::Serializer::new(&mut buf);
|
||||
val.serialize(&mut ser)?;
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use s3s::dto::{BucketVersioningStatus, VersioningConfiguration};
|
||||
|
||||
use crate::utils::wildcard;
|
||||
use rustfs_utils::string::match_simple;
|
||||
|
||||
pub trait VersioningApi {
|
||||
fn enabled(&self) -> bool;
|
||||
@@ -33,7 +33,7 @@ impl VersioningApi for VersioningConfiguration {
|
||||
for p in excluded_prefixes.iter() {
|
||||
if let Some(ref sprefix) = p.prefix {
|
||||
let pattern = format!("{}*", sprefix);
|
||||
if wildcard::match_simple(&pattern, prefix) {
|
||||
if match_simple(&pattern, prefix) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ impl VersioningApi for VersioningConfiguration {
|
||||
for p in excluded_prefixes.iter() {
|
||||
if let Some(ref sprefix) = p.prefix {
|
||||
let pattern = format!("{}*", sprefix);
|
||||
if wildcard::match_simple(&pattern, prefix) {
|
||||
if match_simple(&pattern, prefix) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{metadata_sys::get_bucket_metadata_sys, versioning::VersioningApi};
|
||||
use crate::disk::RUSTFS_META_BUCKET;
|
||||
use common::error::Result;
|
||||
use crate::error::Result;
|
||||
use s3s::dto::VersioningConfiguration;
|
||||
use tracing::warn;
|
||||
|
||||
|
||||
@@ -6,15 +6,15 @@ use std::{
|
||||
pin::Pin,
|
||||
ptr,
|
||||
sync::{
|
||||
atomic::{AtomicPtr, AtomicU64, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicPtr, AtomicU64, Ordering},
|
||||
},
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use tokio::{spawn, sync::Mutex};
|
||||
|
||||
use common::error::Result;
|
||||
use std::io::Result;
|
||||
|
||||
pub type UpdateFn<T> = Box<dyn Fn() -> Pin<Box<dyn Future<Output = Result<T>> + Send>> + Send + Sync + 'static>;
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use crate::disk::{DiskAPI, DiskStore, MetaCacheEntries, MetaCacheEntry, WalkDirOptions};
|
||||
use crate::{
|
||||
disk::error::{is_err_eof, is_err_file_not_found, is_err_volume_not_found, DiskError},
|
||||
metacache::writer::MetacacheReader,
|
||||
};
|
||||
use common::error::{Error, Result};
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::disk::{self, DiskAPI, DiskStore, WalkDirOptions};
|
||||
use futures::future::join_all;
|
||||
use rustfs_filemeta::{MetaCacheEntries, MetaCacheEntry, MetacacheReader, is_io_eof};
|
||||
use std::{future::Future, pin::Pin, sync::Arc};
|
||||
use tokio::{spawn, sync::broadcast::Receiver as B_Receiver};
|
||||
use tracing::error;
|
||||
|
||||
pub type AgreedFn = Box<dyn Fn(MetaCacheEntry) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + 'static>;
|
||||
pub type PartialFn = Box<dyn Fn(MetaCacheEntries, &[Option<Error>]) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + 'static>;
|
||||
type FinishedFn = Box<dyn Fn(&[Option<Error>]) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + 'static>;
|
||||
pub type PartialFn =
|
||||
Box<dyn Fn(MetaCacheEntries, &[Option<DiskError>]) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + 'static>;
|
||||
type FinishedFn = Box<dyn Fn(&[Option<DiskError>]) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + 'static>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ListPathRawOptions {
|
||||
@@ -51,22 +49,22 @@ impl Clone for ListPathRawOptions {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -> Result<()> {
|
||||
// println!("list_path_raw {},{}", &opts.bucket, &opts.path);
|
||||
pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -> disk::error::Result<()> {
|
||||
if opts.disks.is_empty() {
|
||||
return Err(Error::from_string("list_path_raw: 0 drives provided"));
|
||||
return Err(DiskError::other("list_path_raw: 0 drives provided"));
|
||||
}
|
||||
|
||||
let mut jobs: Vec<tokio::task::JoinHandle<std::result::Result<(), Error>>> = Vec::new();
|
||||
let mut jobs: Vec<tokio::task::JoinHandle<std::result::Result<(), DiskError>>> = Vec::new();
|
||||
let mut readers = Vec::with_capacity(opts.disks.len());
|
||||
let fds = Arc::new(opts.fallback_disks.clone());
|
||||
|
||||
let (cancel_tx, cancel_rx) = tokio::sync::broadcast::channel::<bool>(1);
|
||||
|
||||
for disk in opts.disks.iter() {
|
||||
let opdisk = disk.clone();
|
||||
let opts_clone = opts.clone();
|
||||
let fds_clone = fds.clone();
|
||||
// let (m_tx, m_rx) = mpsc::channel::<MetaCacheEntry>(100);
|
||||
// readers.push(m_rx);
|
||||
let mut cancel_rx_clone = cancel_rx.resubscribe();
|
||||
let (rd, mut wr) = tokio::io::duplex(64);
|
||||
readers.push(MetacacheReader::new(rd));
|
||||
jobs.push(spawn(async move {
|
||||
@@ -94,7 +92,13 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
need_fallback = true;
|
||||
}
|
||||
|
||||
if cancel_rx_clone.try_recv().is_ok() {
|
||||
// warn!("list_path_raw: cancel_rx_clone.try_recv().await.is_ok()");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
while need_fallback {
|
||||
// warn!("list_path_raw: while need_fallback start");
|
||||
let disk = match fds_clone.iter().find(|d| d.is_some()) {
|
||||
Some(d) => {
|
||||
if let Some(disk) = d.clone() {
|
||||
@@ -132,12 +136,13 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
}
|
||||
}
|
||||
|
||||
// warn!("list_path_raw: while need_fallback done");
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
let revjob = spawn(async move {
|
||||
let mut errs: Vec<Option<Error>> = Vec::with_capacity(readers.len());
|
||||
let mut errs: Vec<Option<DiskError>> = Vec::with_capacity(readers.len());
|
||||
for _ in 0..readers.len() {
|
||||
errs.push(None);
|
||||
}
|
||||
@@ -145,9 +150,15 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
loop {
|
||||
let mut current = MetaCacheEntry::default();
|
||||
|
||||
// warn!(
|
||||
// "list_path_raw: loop start, bucket: {}, path: {}, current: {:?}",
|
||||
// opts.bucket, opts.path, ¤t.name
|
||||
// );
|
||||
|
||||
if rx.try_recv().is_ok() {
|
||||
return Err(Error::from_string("canceled"));
|
||||
return Err(DiskError::other("canceled"));
|
||||
}
|
||||
|
||||
let mut top_entries: Vec<Option<MetaCacheEntry>> = vec![None; readers.len()];
|
||||
|
||||
let mut at_eof = 0;
|
||||
@@ -170,31 +181,47 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
} else {
|
||||
// eof
|
||||
at_eof += 1;
|
||||
|
||||
// warn!("list_path_raw: peek eof, disk: {}", i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if is_err_eof(&err) {
|
||||
if err == rustfs_filemeta::Error::Unexpected {
|
||||
at_eof += 1;
|
||||
// warn!("list_path_raw: peek err eof, disk: {}", i);
|
||||
continue;
|
||||
} else if is_err_file_not_found(&err) {
|
||||
}
|
||||
|
||||
// warn!("list_path_raw: peek err00, err: {:?}", err);
|
||||
|
||||
if is_io_eof(&err) {
|
||||
at_eof += 1;
|
||||
// warn!("list_path_raw: peek eof, disk: {}", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if err == rustfs_filemeta::Error::FileNotFound {
|
||||
at_eof += 1;
|
||||
fnf += 1;
|
||||
// warn!("list_path_raw: peek fnf, disk: {}", i);
|
||||
continue;
|
||||
} else if is_err_volume_not_found(&err) {
|
||||
} else if err == rustfs_filemeta::Error::VolumeNotFound {
|
||||
at_eof += 1;
|
||||
fnf += 1;
|
||||
vnf += 1;
|
||||
// warn!("list_path_raw: peek vnf, disk: {}", i);
|
||||
continue;
|
||||
} else {
|
||||
has_err += 1;
|
||||
errs[i] = Some(err);
|
||||
errs[i] = Some(err.into());
|
||||
// warn!("list_path_raw: peek err, disk: {}", i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// warn!("list_path_raw: loop entry: {:?}, disk: {}", &entry.name, i);
|
||||
|
||||
// If no current, add it.
|
||||
if current.name.is_empty() {
|
||||
top_entries[i] = Some(entry.clone());
|
||||
@@ -230,11 +257,13 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
}
|
||||
|
||||
if vnf > 0 && vnf >= (readers.len() - opts.min_disks) {
|
||||
return Err(Error::new(DiskError::VolumeNotFound));
|
||||
// warn!("list_path_raw: vnf > 0 && vnf >= (readers.len() - opts.min_disks) break");
|
||||
return Err(DiskError::VolumeNotFound);
|
||||
}
|
||||
|
||||
if fnf > 0 && fnf >= (readers.len() - opts.min_disks) {
|
||||
return Err(Error::new(DiskError::FileNotFound));
|
||||
// warn!("list_path_raw: fnf > 0 && fnf >= (readers.len() - opts.min_disks) break");
|
||||
return Err(DiskError::FileNotFound);
|
||||
}
|
||||
|
||||
if has_err > 0 && has_err > opts.disks.len() - opts.min_disks {
|
||||
@@ -252,7 +281,11 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
_ => {}
|
||||
});
|
||||
|
||||
return Err(Error::from_string(combined_err.join(", ")));
|
||||
error!(
|
||||
"list_path_raw: has_err > 0 && has_err > opts.disks.len() - opts.min_disks break, err: {:?}",
|
||||
&combined_err.join(", ")
|
||||
);
|
||||
return Err(DiskError::other(combined_err.join(", ")));
|
||||
}
|
||||
|
||||
// Break if all at EOF or error.
|
||||
@@ -265,6 +298,7 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
}
|
||||
}
|
||||
|
||||
// error!("list_path_raw: at_eof + has_err == readers.len() break {:?}", &errs);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -274,12 +308,16 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
}
|
||||
|
||||
if let Some(agreed_fn) = opts.agreed.as_ref() {
|
||||
// warn!("list_path_raw: agreed_fn start, current: {:?}", ¤t.name);
|
||||
agreed_fn(current).await;
|
||||
// warn!("list_path_raw: agreed_fn done");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// warn!("list_path_raw: skip start, current: {:?}", ¤t.name);
|
||||
|
||||
for (i, r) in readers.iter_mut().enumerate() {
|
||||
if top_entries[i].is_some() {
|
||||
let _ = r.skip(1).await;
|
||||
@@ -293,7 +331,12 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
Ok(())
|
||||
});
|
||||
|
||||
jobs.push(revjob);
|
||||
if let Err(err) = revjob.await.map_err(std::io::Error::other)? {
|
||||
error!("list_path_raw: revjob err {:?}", err);
|
||||
let _ = cancel_tx.send(true);
|
||||
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let results = join_all(jobs).await;
|
||||
for result in results {
|
||||
@@ -302,5 +345,6 @@ pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -
|
||||
}
|
||||
}
|
||||
|
||||
// warn!("list_path_raw: done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod cache;
|
||||
// pub mod cache;
|
||||
pub mod metacache_set;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
// use error::Error;
|
||||
use crate::StorageAPI;
|
||||
use crate::bucket::metadata_sys::get_replication_config;
|
||||
use crate::bucket::versioning_sys::BucketVersioningSys;
|
||||
use crate::error::Error;
|
||||
use crate::new_object_layer_fn;
|
||||
use crate::peer::RemotePeerS3Client;
|
||||
use crate::store;
|
||||
@@ -11,26 +11,26 @@ use crate::store_api::ObjectIO;
|
||||
use crate::store_api::ObjectInfo;
|
||||
use crate::store_api::ObjectOptions;
|
||||
use crate::store_api::ObjectToDelete;
|
||||
use aws_sdk_s3::Client as S3Client;
|
||||
use aws_sdk_s3::Config;
|
||||
use crate::StorageAPI;
|
||||
use aws_sdk_s3::config::BehaviorVersion;
|
||||
use aws_sdk_s3::config::Credentials;
|
||||
use aws_sdk_s3::config::Region;
|
||||
use aws_sdk_s3::Client as S3Client;
|
||||
use aws_sdk_s3::Config;
|
||||
use bytes::Bytes;
|
||||
use chrono::DateTime;
|
||||
use chrono::Duration;
|
||||
use chrono::Utc;
|
||||
use common::error::Error;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use futures::StreamExt;
|
||||
use http::HeaderMap;
|
||||
use http::Method;
|
||||
use lazy_static::lazy_static;
|
||||
// use std::time::SystemTime;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustfs_rsc::Minio;
|
||||
use rustfs_rsc::provider::StaticProvider;
|
||||
use rustfs_rsc::Minio;
|
||||
use s3s::dto::DeleteMarkerReplicationStatus;
|
||||
use s3s::dto::DeleteReplicationStatus;
|
||||
use s3s::dto::ExistingObjectReplicationStatus;
|
||||
@@ -42,14 +42,15 @@ use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::iter::Iterator;
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::vec;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::task;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use uuid::Uuid;
|
||||
@@ -186,10 +187,7 @@ const CAPACITY_XML_OBJECT: &str = ".system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/
|
||||
const VEEAM_AGENT_SUBSTR: &str = "APN/1.0 Veeam/1.0";
|
||||
|
||||
fn is_veeam_sos_api_object(object: &str) -> bool {
|
||||
match object {
|
||||
SYSTEM_XML_OBJECT | CAPACITY_XML_OBJECT => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(object, SYSTEM_XML_OBJECT | CAPACITY_XML_OBJECT)
|
||||
}
|
||||
|
||||
pub async fn queue_replication_heal(
|
||||
@@ -410,7 +408,7 @@ pub async fn get_heal_replicate_object_info(
|
||||
}
|
||||
|
||||
if !oi.version_purge_status.is_empty() {
|
||||
oi.version_purge_status_internal = format!("{}={};", rcfg.role, oi.version_purge_status.to_string());
|
||||
oi.version_purge_status_internal = format!("{}={};", rcfg.role, oi.version_purge_status);
|
||||
}
|
||||
|
||||
// let to_replace: Vec<(String, String)> = user_defined
|
||||
@@ -513,8 +511,8 @@ pub async fn get_heal_replicate_object_info(
|
||||
|
||||
let mut result = ReplicateObjectInfo {
|
||||
name: oi.name.clone(),
|
||||
size: oi.size as i64,
|
||||
actual_size: asz as i64,
|
||||
size: oi.size,
|
||||
actual_size: asz,
|
||||
bucket: oi.bucket.clone(),
|
||||
//version_id: oi.version_id.clone(),
|
||||
version_id: oi
|
||||
@@ -534,7 +532,7 @@ pub async fn get_heal_replicate_object_info(
|
||||
existing_obj_resync: Default::default(),
|
||||
target_statuses: tgt_statuses,
|
||||
target_purge_statuses: purge_statuses,
|
||||
replication_timestamp: tm.unwrap_or_else(|| Utc::now()),
|
||||
replication_timestamp: tm.unwrap_or_else(Utc::now),
|
||||
//ssec: crypto::is_encrypted(&oi.user_defined),
|
||||
ssec: false,
|
||||
user_tags: oi.user_tags.clone(),
|
||||
@@ -816,8 +814,8 @@ impl ReplicationPool {
|
||||
vsender.pop(); // Dropping the sender will close the channel
|
||||
}
|
||||
self.workers_sender = vsender;
|
||||
warn!("self sender size is {:?}", self.workers_sender.len());
|
||||
warn!("self sender size is {:?}", self.workers_sender.len());
|
||||
// warn!("self sender size is {:?}", self.workers_sender.len());
|
||||
// warn!("self sender size is {:?}", self.workers_sender.len());
|
||||
}
|
||||
|
||||
async fn resize_failed_workers(&self, _count: usize) {
|
||||
@@ -834,7 +832,8 @@ impl ReplicationPool {
|
||||
|
||||
fn get_worker_ch(&self, bucket: &str, object: &str, _sz: i64) -> Option<&Sender<Box<dyn ReplicationWorkerOperation>>> {
|
||||
let h = xxh3_64(format!("{}{}", bucket, object).as_bytes()); // 计算哈希值
|
||||
//need lock;
|
||||
|
||||
// need lock;
|
||||
let workers = &self.workers_sender; // 读锁
|
||||
|
||||
if workers.is_empty() {
|
||||
@@ -969,7 +968,7 @@ impl ReplicationResyncer {
|
||||
pub async fn init_bucket_replication_pool() {
|
||||
if let Some(store) = new_object_layer_fn() {
|
||||
let opts = ReplicationPoolOpts::default();
|
||||
let stats = ReplicationStats::default();
|
||||
let stats = ReplicationStats;
|
||||
let stat = Arc::new(stats);
|
||||
warn!("init bucket replication pool");
|
||||
ReplicationPool::init_bucket_replication_pool(store, opts, stat).await;
|
||||
@@ -1071,16 +1070,16 @@ impl From<&str> for VersionPurgeStatusType {
|
||||
}
|
||||
}
|
||||
|
||||
// 将枚举转换为字符串
|
||||
impl ToString for VersionPurgeStatusType {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
VersionPurgeStatusType::Pending => "PENDING".to_string(),
|
||||
VersionPurgeStatusType::Complete => "COMPLETE".to_string(),
|
||||
VersionPurgeStatusType::Failed => "FAILED".to_string(),
|
||||
VersionPurgeStatusType::Empty => "".to_string(),
|
||||
VersionPurgeStatusType::Unknown => "".to_string(),
|
||||
}
|
||||
impl fmt::Display for VersionPurgeStatusType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
VersionPurgeStatusType::Pending => "PENDING",
|
||||
VersionPurgeStatusType::Complete => "COMPLETE",
|
||||
VersionPurgeStatusType::Failed => "FAILED",
|
||||
VersionPurgeStatusType::Empty => "",
|
||||
VersionPurgeStatusType::Unknown => "UNKNOWN",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1115,14 +1114,15 @@ pub enum ReplicationAction {
|
||||
ReplicateAll,
|
||||
}
|
||||
|
||||
impl ReplicationAction {
|
||||
impl FromStr for ReplicationAction {
|
||||
// 工厂方法,根据字符串生成对应的枚举
|
||||
pub fn from_str(action: &str) -> Self {
|
||||
type Err = ();
|
||||
fn from_str(action: &str) -> Result<Self, Self::Err> {
|
||||
match action.to_lowercase().as_str() {
|
||||
"metadata" => ReplicationAction::ReplicateMetadata,
|
||||
"none" => ReplicationAction::ReplicateNone,
|
||||
"all" => ReplicationAction::ReplicateAll,
|
||||
_ => ReplicationAction::ReplicateNone,
|
||||
"metadata" => Ok(ReplicationAction::ReplicateMetadata),
|
||||
"none" => Ok(ReplicationAction::ReplicateNone),
|
||||
"all" => Ok(ReplicationAction::ReplicateAll),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1256,22 +1256,23 @@ pub struct ReplicateTargetDecision {
|
||||
}
|
||||
|
||||
impl ReplicateTargetDecision {
|
||||
/// 将结构体转换为字符串
|
||||
pub fn to_string(&self) -> String {
|
||||
format!("{};{};{};{}", self.replicate, self.synchronous, self.arn, self.id)
|
||||
}
|
||||
|
||||
/// 创建一个新的 ReplicateTargetDecision 实例
|
||||
pub fn new(arn: &str, replicate: bool, synchronous: bool) -> Self {
|
||||
Self {
|
||||
id: String::new(),
|
||||
replicate,
|
||||
synchronous,
|
||||
arn: arn.to_string(),
|
||||
id: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ReplicateTargetDecision {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{};{};{};{}", self.replicate, self.synchronous, self.arn, self.id)
|
||||
}
|
||||
}
|
||||
|
||||
/// 复制决策结构体,包含多个目标的决策
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct ReplicateDecision {
|
||||
@@ -1318,7 +1319,7 @@ impl fmt::Display for ReplicateDecision {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut entries = Vec::new();
|
||||
for (key, value) in &self.targets_map {
|
||||
entries.push(format!("{}={}", key, value.to_string()));
|
||||
entries.push(format!("{}={}", key, value));
|
||||
}
|
||||
write!(f, "{}", entries.join(","))
|
||||
}
|
||||
@@ -1757,13 +1758,13 @@ pub async fn schedule_replication(oi: ObjectInfo, o: Arc<store::ECStore>, dsc: R
|
||||
let replication_timestamp = Utc::now(); // Placeholder for timestamp parsing
|
||||
let replication_state = oi.replication_state();
|
||||
|
||||
let actual_size = oi.actual_size.unwrap_or(0);
|
||||
let actual_size = oi.actual_size;
|
||||
//let ssec = oi.user_defined.contains_key("ssec");
|
||||
let ssec = false;
|
||||
|
||||
let ri = ReplicateObjectInfo {
|
||||
name: oi.name,
|
||||
size: oi.size as i64,
|
||||
size: oi.size,
|
||||
bucket: oi.bucket,
|
||||
version_id: oi
|
||||
.version_id
|
||||
@@ -2017,8 +2018,8 @@ impl ReplicateObjectInfo {
|
||||
mod_time: Some(
|
||||
OffsetDateTime::from_unix_timestamp(self.mod_time.timestamp()).unwrap_or_else(|_| OffsetDateTime::now_utc()),
|
||||
),
|
||||
size: self.size as usize,
|
||||
actual_size: Some(self.actual_size as usize),
|
||||
size: self.size,
|
||||
actual_size: self.actual_size,
|
||||
is_dir: false,
|
||||
user_defined: None, // 可以按需从别处导入
|
||||
parity_blocks: 0,
|
||||
@@ -2091,16 +2092,10 @@ impl ReplicationWorkerOperation for ReplicateObjectInfo {
|
||||
|
||||
impl ReplicationWorkerOperation for DeletedObjectReplicationInfo {
|
||||
fn to_mrf_entry(&self) -> MRFReplicateEntry {
|
||||
let version_id = if !self.deleted_object.delete_marker_version_id.is_none() {
|
||||
self.deleted_object.delete_marker_version_id.clone()
|
||||
} else {
|
||||
self.deleted_object.delete_marker_version_id.clone()
|
||||
};
|
||||
|
||||
MRFReplicateEntry {
|
||||
bucket: self.bucket.clone(),
|
||||
object: self.deleted_object.object_name.clone().unwrap().clone(),
|
||||
version_id: "0".to_string(), // 直接使用计算后的 version_id
|
||||
version_id: self.deleted_object.delete_marker_version_id.clone().unwrap_or_default(),
|
||||
retry_count: 0,
|
||||
sz: 0,
|
||||
}
|
||||
@@ -2138,7 +2133,8 @@ async fn replicate_object_with_multipart(
|
||||
.endpoint(target_info.endpoint.clone())
|
||||
.provider(provider)
|
||||
.secure(false)
|
||||
.build()?;
|
||||
.build()
|
||||
.map_err(|e| Error::other(format!("build minio client failed: {}", e)))?;
|
||||
|
||||
let ret = minio_cli
|
||||
.create_multipart_upload_with_versionid(tgt_cli.bucket.clone(), local_obj_info.name.clone(), rep_obj.version_id.clone())
|
||||
@@ -2147,7 +2143,7 @@ async fn replicate_object_with_multipart(
|
||||
Ok(task) => {
|
||||
let parts_len = local_obj_info.parts.len();
|
||||
let mut part_results = vec![None; parts_len];
|
||||
let version_id = local_obj_info.version_id.clone().expect("missing version_id");
|
||||
let version_id = local_obj_info.version_id.expect("missing version_id");
|
||||
let task = Arc::new(task); // clone safe
|
||||
let store = Arc::new(store);
|
||||
let minio_cli = Arc::new(minio_cli);
|
||||
@@ -2160,7 +2156,6 @@ async fn replicate_object_with_multipart(
|
||||
let task = Arc::clone(&task);
|
||||
let bucket = local_obj_info.bucket.clone();
|
||||
let name = local_obj_info.name.clone();
|
||||
let version_id = version_id.clone();
|
||||
|
||||
upload_futures.push(tokio::spawn(async move {
|
||||
let get_opts = ObjectOptions {
|
||||
@@ -2175,16 +2170,16 @@ async fn replicate_object_with_multipart(
|
||||
match store.get_object_reader(&bucket, &name, None, h, &get_opts).await {
|
||||
Ok(mut reader) => match reader.read_all().await {
|
||||
Ok(ret) => {
|
||||
debug!("2025 readall suc:");
|
||||
debug!("readall suc:");
|
||||
let body = Bytes::from(ret);
|
||||
match minio_cli.upload_part(&task, index + 1, body).await {
|
||||
Ok(part) => {
|
||||
debug!("2025 multipar upload suc:");
|
||||
debug!("multipar upload suc:");
|
||||
Ok((index, part))
|
||||
}
|
||||
Err(err) => {
|
||||
error!("upload part {} failed: {}", index + 1, err);
|
||||
Err(Error::from_string(format!("upload error: {}", err)))
|
||||
Err(Error::other(format!("upload error: {}", err)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2195,7 +2190,7 @@ async fn replicate_object_with_multipart(
|
||||
},
|
||||
Err(err) => {
|
||||
error!("reader error for part {}: {}", index + 1, err);
|
||||
Err(Error::from_string(format!("reader error: {}", err)))
|
||||
Err(Error::other(format!("reader error: {}", err)))
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -2212,7 +2207,7 @@ async fn replicate_object_with_multipart(
|
||||
}
|
||||
Err(join_err) => {
|
||||
error!("tokio join error: {}", join_err);
|
||||
return Err(Error::from_string(format!("join error: {}", join_err)));
|
||||
return Err(Error::other(format!("join error: {}", join_err)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2226,12 +2221,12 @@ async fn replicate_object_with_multipart(
|
||||
}
|
||||
Err(err) => {
|
||||
error!("finish upload failed:{}", err);
|
||||
return Err(err.into());
|
||||
return Err(Error::other(format!("finish upload failed:{}", err)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(err.into());
|
||||
return Err(Error::other(format!("finish upload failed:{}", err)));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -2266,7 +2261,7 @@ impl ReplicateObjectInfo {
|
||||
arn: _arn.clone(),
|
||||
prev_replication_status: self.target_replication_status(&_arn.clone()),
|
||||
replication_status: ReplicationStatusType::Failed,
|
||||
op_type: self.op_type.clone(),
|
||||
op_type: self.op_type,
|
||||
replication_action: ReplicationAction::ReplicateAll,
|
||||
endpoint: target.endpoint.clone(),
|
||||
secure: target.endpoint.clone().contains("https://"),
|
||||
@@ -2302,10 +2297,12 @@ impl ReplicateObjectInfo {
|
||||
// versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||
|
||||
// 模拟对象获取和元数据检查
|
||||
let mut opt = ObjectOptions::default();
|
||||
opt.version_id = Some(self.version_id.clone());
|
||||
opt.versioned = true;
|
||||
opt.version_suspended = false;
|
||||
let opt = ObjectOptions {
|
||||
version_id: Some(self.version_id.clone()),
|
||||
versioned: true,
|
||||
version_suspended: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let object_info = match self.get_object_info(opt).await {
|
||||
Ok(info) => info,
|
||||
@@ -2320,7 +2317,7 @@ impl ReplicateObjectInfo {
|
||||
|
||||
// 设置对象大小
|
||||
//rinfo.size = object_info.actual_size.unwrap_or(0);
|
||||
rinfo.size = object_info.actual_size.map_or(0, |v| v as i64);
|
||||
rinfo.size = object_info.actual_size;
|
||||
//rinfo.replication_action = object_info.
|
||||
|
||||
rinfo.replication_status = ReplicationStatusType::Completed;
|
||||
@@ -2331,7 +2328,7 @@ impl ReplicateObjectInfo {
|
||||
//todo!() put replicationopts;
|
||||
if object_info.is_multipart() {
|
||||
debug!("version is multi part");
|
||||
match replicate_object_with_multipart(&self, &object_info, &rinfo, target).await {
|
||||
match replicate_object_with_multipart(self, &object_info, &rinfo, target).await {
|
||||
Ok(_) => {
|
||||
rinfo.replication_status = ReplicationStatusType::Completed;
|
||||
println!("Object replicated successfully.");
|
||||
@@ -2345,12 +2342,12 @@ impl ReplicateObjectInfo {
|
||||
//replicate_object_with_multipart(local_obj_info, target_info, tgt_cli)
|
||||
} else {
|
||||
let get_opts = ObjectOptions {
|
||||
version_id: Some(object_info.version_id.clone().expect("REASON").to_string()),
|
||||
version_id: Some(object_info.version_id.expect("REASON").to_string()),
|
||||
versioned: true,
|
||||
version_suspended: false,
|
||||
..Default::default()
|
||||
};
|
||||
warn!("version id is:{:?}", get_opts.version_id.clone());
|
||||
warn!("version id is:{:?}", get_opts.version_id);
|
||||
let h = HeaderMap::new();
|
||||
let gr = store
|
||||
.get_object_reader(&object_info.bucket, &object_info.name, None, h, &get_opts)
|
||||
@@ -2415,8 +2412,7 @@ impl ReplicateObjectInfo {
|
||||
async fn get_object_info(&self, opts: ObjectOptions) -> Result<ObjectInfo, Error> {
|
||||
let objectlayer = new_object_layer_fn();
|
||||
//let opts = ecstore::store_api::ObjectOptions { max_parity: (), mod_time: (), part_number: (), delete_prefix: (), version_id: (), no_lock: (), versioned: (), version_suspended: (), skip_decommissioned: (), skip_rebalancing: (), data_movement: (), src_pool_idx: (), user_defined: (), preserve_etag: (), metadata_chg: (), replication_request: (), delete_marker: () }
|
||||
let res = objectlayer.unwrap().get_object_info(&self.bucket, &self.name, &opts).await;
|
||||
res
|
||||
objectlayer.unwrap().get_object_info(&self.bucket, &self.name, &opts).await
|
||||
}
|
||||
|
||||
fn perform_replication(&self, target: &RemotePeerS3Client, object_info: &ObjectInfo) -> Result<(), String> {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#![allow(unused_variables)]
|
||||
#![allow(dead_code)]
|
||||
use crate::{
|
||||
bucket::{self, target::BucketTargets},
|
||||
new_object_layer_fn, peer, store_api,
|
||||
};
|
||||
use crate::{
|
||||
StorageAPI,
|
||||
bucket::{metadata_sys, target::BucketTarget},
|
||||
endpoints::Node,
|
||||
peer::{PeerS3Client, RemotePeerS3Client},
|
||||
StorageAPI,
|
||||
};
|
||||
use crate::{
|
||||
bucket::{self, target::BucketTargets},
|
||||
new_object_layer_fn, peer, store_api,
|
||||
};
|
||||
//use tokio::sync::RwLock;
|
||||
use aws_sdk_s3::Client as S3Client;
|
||||
@@ -534,7 +534,9 @@ pub struct TargetClient {
|
||||
pub sk: String,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl TargetClient {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
client: reqwest::Client,
|
||||
health_check_duration: Duration,
|
||||
@@ -623,12 +625,7 @@ impl ARN {
|
||||
false
|
||||
}
|
||||
|
||||
/// 将 ARN 转为字符串格式
|
||||
pub fn to_string(&self) -> String {
|
||||
format!("arn:rustfs:{}:{}:{}:{}", self.arn_type, self.region, self.id, self.bucket)
|
||||
}
|
||||
|
||||
/// 从字符串解析 ARN
|
||||
// 从字符串解析 ARN
|
||||
pub fn parse(s: &str) -> Result<Self, String> {
|
||||
// ARN 必须是格式 arn:rustfs:<Type>:<REGION>:<ID>:<remote-bucket>
|
||||
if !s.starts_with("arn:rustfs:") {
|
||||
@@ -652,7 +649,7 @@ impl ARN {
|
||||
// 实现 `Display` trait,使得可以直接使用 `format!` 或 `{}` 输出 ARN
|
||||
impl std::fmt::Display for ARN {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_string())
|
||||
write!(f, "arn:rustfs:{}:{}:{}:{}", self.arn_type, self.region, self.id, self.bucket)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
use rustfs_utils::string::has_pattern;
|
||||
use rustfs_utils::string::has_string_suffix_in_slice;
|
||||
use std::env;
|
||||
use tracing::error;
|
||||
|
||||
pub const MIN_COMPRESSIBLE_SIZE: usize = 4096;
|
||||
|
||||
// 环境变量名称,用于控制是否启用压缩
|
||||
pub const ENV_COMPRESSION_ENABLED: &str = "RUSTFS_COMPRESSION_ENABLED";
|
||||
|
||||
// Some standard object extensions which we strictly dis-allow for compression.
|
||||
pub const STANDARD_EXCLUDE_COMPRESS_EXTENSIONS: &[&str] = &[
|
||||
".gz", ".bz2", ".rar", ".zip", ".7z", ".xz", ".mp4", ".mkv", ".mov", ".jpg", ".png", ".gif",
|
||||
];
|
||||
|
||||
// Some standard content-types which we strictly dis-allow for compression.
|
||||
pub const STANDARD_EXCLUDE_COMPRESS_CONTENT_TYPES: &[&str] = &[
|
||||
"video/*",
|
||||
"audio/*",
|
||||
"application/zip",
|
||||
"application/x-gzip",
|
||||
"application/x-zip-compressed",
|
||||
"application/x-compress",
|
||||
"application/x-spoon",
|
||||
];
|
||||
|
||||
pub fn is_compressible(headers: &http::HeaderMap, object_name: &str) -> bool {
|
||||
// 检查环境变量是否启用压缩,默认关闭
|
||||
if let Ok(compression_enabled) = env::var(ENV_COMPRESSION_ENABLED) {
|
||||
if compression_enabled.to_lowercase() != "true" {
|
||||
error!("Compression is disabled by environment variable");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 环境变量未设置时默认关闭
|
||||
return false;
|
||||
}
|
||||
|
||||
let content_type = headers.get("content-type").and_then(|s| s.to_str().ok()).unwrap_or("");
|
||||
|
||||
// TODO: crypto request return false
|
||||
|
||||
if has_string_suffix_in_slice(object_name, STANDARD_EXCLUDE_COMPRESS_EXTENSIONS) {
|
||||
error!("object_name: {} is not compressible", object_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if !content_type.is_empty() && has_pattern(STANDARD_EXCLUDE_COMPRESS_CONTENT_TYPES, content_type) {
|
||||
error!("content_type: {} is not compressible", content_type);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
|
||||
// TODO: check from config
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use temp_env;
|
||||
|
||||
#[test]
|
||||
fn test_is_compressible() {
|
||||
use http::HeaderMap;
|
||||
|
||||
let headers = HeaderMap::new();
|
||||
|
||||
// 测试环境变量控制
|
||||
temp_env::with_var(ENV_COMPRESSION_ENABLED, Some("false"), || {
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
});
|
||||
|
||||
temp_env::with_var(ENV_COMPRESSION_ENABLED, Some("true"), || {
|
||||
assert!(is_compressible(&headers, "file.txt"));
|
||||
});
|
||||
|
||||
temp_env::with_var_unset(ENV_COMPRESSION_ENABLED, || {
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
});
|
||||
|
||||
temp_env::with_var(ENV_COMPRESSION_ENABLED, Some("true"), || {
|
||||
let mut headers = HeaderMap::new();
|
||||
// 测试不可压缩的扩展名
|
||||
headers.insert("content-type", "text/plain".parse().unwrap());
|
||||
assert!(!is_compressible(&headers, "file.gz"));
|
||||
assert!(!is_compressible(&headers, "file.zip"));
|
||||
assert!(!is_compressible(&headers, "file.mp4"));
|
||||
assert!(!is_compressible(&headers, "file.jpg"));
|
||||
|
||||
// 测试不可压缩的内容类型
|
||||
headers.insert("content-type", "video/mp4".parse().unwrap());
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
|
||||
headers.insert("content-type", "audio/mpeg".parse().unwrap());
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
|
||||
headers.insert("content-type", "application/zip".parse().unwrap());
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
|
||||
headers.insert("content-type", "application/x-gzip".parse().unwrap());
|
||||
assert!(!is_compressible(&headers, "file.txt"));
|
||||
|
||||
// 测试可压缩的情况
|
||||
headers.insert("content-type", "text/plain".parse().unwrap());
|
||||
assert!(is_compressible(&headers, "file.txt"));
|
||||
assert!(is_compressible(&headers, "file.log"));
|
||||
|
||||
headers.insert("content-type", "text/html".parse().unwrap());
|
||||
assert!(is_compressible(&headers, "file.html"));
|
||||
|
||||
headers.insert("content-type", "application/json".parse().unwrap());
|
||||
assert!(is_compressible(&headers, "file.json"));
|
||||
});
|
||||
}
|
||||
}
|
||||
+47
-41
@@ -1,16 +1,14 @@
|
||||
use super::error::{is_err_config_not_found, ConfigError};
|
||||
use super::{storageclass, Config, GLOBAL_StorageClass};
|
||||
use crate::disk::RUSTFS_META_BUCKET;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::store_api::{ObjectInfo, ObjectOptions, PutObjReader, StorageAPI};
|
||||
use crate::store_err::is_err_object_not_found;
|
||||
use crate::utils::path::SLASH_SEPARATOR;
|
||||
use common::error::{Error, Result};
|
||||
use http::HeaderMap;
|
||||
use lazy_static::lazy_static;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR;
|
||||
use std::collections::HashSet;
|
||||
use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, warn};
|
||||
use crate::disk::fs::SLASH_SEPARATOR;
|
||||
|
||||
pub const CONFIG_PREFIX: &str = "config";
|
||||
const CONFIG_FILE: &str = "config.json";
|
||||
@@ -41,9 +39,10 @@ pub async fn read_config_with_metadata<S: StorageAPI>(
|
||||
.get_object_reader(RUSTFS_META_BUCKET, file, None, h, opts)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
if is_err_object_not_found(&err) {
|
||||
Error::new(ConfigError::NotFound)
|
||||
if err == Error::FileNotFound || matches!(err, Error::ObjectNotFound(_, _)) {
|
||||
Error::ConfigNotFound
|
||||
} else {
|
||||
warn!("read_config_with_metadata: err: {:?}, file: {}", err, file);
|
||||
err
|
||||
}
|
||||
})?;
|
||||
@@ -51,7 +50,7 @@ pub async fn read_config_with_metadata<S: StorageAPI>(
|
||||
let data = rd.read_all().await?;
|
||||
|
||||
if data.is_empty() {
|
||||
return Err(Error::new(ConfigError::NotFound));
|
||||
return Err(Error::ConfigNotFound);
|
||||
}
|
||||
|
||||
Ok((data, rd.object_info))
|
||||
@@ -85,8 +84,8 @@ pub async fn delete_config<S: StorageAPI>(api: Arc<S>, file: &str) -> Result<()>
|
||||
{
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
if is_err_object_not_found(&err) {
|
||||
Err(Error::new(ConfigError::NotFound))
|
||||
if err == Error::FileNotFound || matches!(err, Error::ObjectNotFound(_, _)) {
|
||||
Err(Error::ConfigNotFound)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
@@ -95,10 +94,13 @@ pub async fn delete_config<S: StorageAPI>(api: Arc<S>, file: &str) -> Result<()>
|
||||
}
|
||||
|
||||
pub async fn save_config_with_opts<S: StorageAPI>(api: Arc<S>, file: &str, data: Vec<u8>, opts: &ObjectOptions) -> Result<()> {
|
||||
let size = data.len();
|
||||
let _ = api
|
||||
.put_object(RUSTFS_META_BUCKET, file, &mut PutObjReader::new(Box::new(Cursor::new(data)), size), opts)
|
||||
.await?;
|
||||
if let Err(err) = api
|
||||
.put_object(RUSTFS_META_BUCKET, file, &mut PutObjReader::from_vec(data), opts)
|
||||
.await
|
||||
{
|
||||
error!("save_config_with_opts: err: {:?}, file: {}", err, file);
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -114,12 +116,22 @@ async fn new_and_save_server_config<S: StorageAPI>(api: Arc<S>) -> Result<Config
|
||||
Ok(cfg)
|
||||
}
|
||||
|
||||
fn get_config_file() -> String {
|
||||
format!("{}{}{}", CONFIG_PREFIX, SLASH_SEPARATOR, CONFIG_FILE)
|
||||
}
|
||||
|
||||
pub async fn read_config_without_migrate<S: StorageAPI>(api: Arc<S>) -> Result<Config> {
|
||||
let data = handle_read_config(api.clone()).await?;
|
||||
let config_file = format!("{}{}{}", CONFIG_PREFIX, SLASH_SEPARATOR, CONFIG_FILE);
|
||||
let data = match read_config(api.clone(), config_file.as_str()).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
return if err == Error::ConfigNotFound {
|
||||
warn!("config not found, start to init");
|
||||
let cfg = new_and_save_server_config(api).await?;
|
||||
warn!("config init done");
|
||||
Ok(cfg)
|
||||
} else {
|
||||
error!("read config err {:?}", &err);
|
||||
Err(err)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
read_server_config(api, data.as_slice()).await
|
||||
}
|
||||
@@ -127,8 +139,21 @@ pub async fn read_config_without_migrate<S: StorageAPI>(api: Arc<S>) -> Result<C
|
||||
async fn read_server_config<S: StorageAPI>(api: Arc<S>, data: &[u8]) -> Result<Config> {
|
||||
let cfg = {
|
||||
if data.is_empty() {
|
||||
let cfg_data = handle_read_config(api.clone()).await?;
|
||||
|
||||
let config_file = format!("{}{}{}", CONFIG_PREFIX, SLASH_SEPARATOR, CONFIG_FILE);
|
||||
let cfg_data = match read_config(api.clone(), config_file.as_str()).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
return if err == Error::ConfigNotFound {
|
||||
warn!("config not found init start");
|
||||
let cfg = new_and_save_server_config(api).await?;
|
||||
warn!("config not found init done");
|
||||
Ok(cfg)
|
||||
} else {
|
||||
error!("read config err {:?}", &err);
|
||||
Err(err)
|
||||
};
|
||||
}
|
||||
};
|
||||
// TODO: decrypt
|
||||
|
||||
Config::unmarshal(cfg_data.as_slice())?
|
||||
@@ -140,29 +165,10 @@ async fn read_server_config<S: StorageAPI>(api: Arc<S>, data: &[u8]) -> Result<C
|
||||
Ok(cfg.merge())
|
||||
}
|
||||
|
||||
async fn handle_read_config<S: StorageAPI>(api: Arc<S>) -> Result<Vec<u8>> {
|
||||
let config_file = get_config_file();
|
||||
match read_config(api.clone(), config_file.as_str()).await {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => {
|
||||
if is_err_config_not_found(&err) {
|
||||
warn!("config not found, start to init");
|
||||
let cfg = new_and_save_server_config(api).await?;
|
||||
warn!("config init done");
|
||||
// This returns the serialized data, keeping the interface consistent
|
||||
cfg.marshal()
|
||||
} else {
|
||||
error!("read config err {:?}", &err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_server_config<S: StorageAPI>(api: Arc<S>, cfg: &Config) -> Result<()> {
|
||||
let data = cfg.marshal()?;
|
||||
|
||||
let config_file = get_config_file();
|
||||
let config_file = format!("{}{}{}", CONFIG_PREFIX, SLASH_SEPARATOR, CONFIG_FILE);
|
||||
|
||||
save_config(api, &config_file, data).await
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use crate::{disk, store_err::is_err_object_not_found};
|
||||
use common::error::Error;
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("config not found")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl ConfigError {
|
||||
/// Returns `true` if the config error is [`NotFound`].
|
||||
///
|
||||
/// [`NotFound`]: ConfigError::NotFound
|
||||
#[must_use]
|
||||
pub fn is_not_found(&self) -> bool {
|
||||
matches!(self, Self::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigError {
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
match self {
|
||||
ConfigError::NotFound => 0x01,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u32(error: u32) -> Option<Self> {
|
||||
match error {
|
||||
0x01 => Some(Self::NotFound),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_config_not_found(err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<ConfigError>() {
|
||||
ConfigError::is_not_found(e)
|
||||
} else if let Some(e) = err.downcast_ref::<disk::error::DiskError>() {
|
||||
matches!(e, disk::error::DiskError::FileNotFound)
|
||||
} else if is_err_object_not_found(err) {
|
||||
return true;
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::error::{Error, Result};
|
||||
use rustfs_utils::string::parse_bool;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::utils::bool_flag::parse_bool;
|
||||
use common::error::{Error, Result};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Config {
|
||||
pub bitrot: String,
|
||||
@@ -42,13 +41,13 @@ fn parse_bitrot_config(s: &str) -> Result<Duration> {
|
||||
}
|
||||
Err(_) => {
|
||||
if !s.ends_with("m") {
|
||||
return Err(Error::from_string("unknown format"));
|
||||
return Err(Error::other("unknown format"));
|
||||
}
|
||||
|
||||
match s.trim_end_matches('m').parse::<u64>() {
|
||||
Ok(months) => {
|
||||
if months < RUSTFS_BITROT_CYCLE_IN_MONTHS {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"minimum bitrot cycle is {} month(s)",
|
||||
RUSTFS_BITROT_CYCLE_IN_MONTHS
|
||||
)));
|
||||
@@ -56,7 +55,7 @@ fn parse_bitrot_config(s: &str) -> Result<Duration> {
|
||||
|
||||
Ok(Duration::from_secs(months * 30 * 24 * 60))
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
Err(err) => Err(Error::other(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
pub mod com;
|
||||
pub mod error;
|
||||
#[allow(dead_code)]
|
||||
pub mod heal;
|
||||
pub mod storageclass;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::store::ECStore;
|
||||
use com::{lookup_configs, read_config_without_migrate, STORAGE_CLASS_SUB_SYS};
|
||||
use common::error::Result;
|
||||
use com::{STORAGE_CLASS_SUB_SYS, lookup_configs, read_config_without_migrate};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use std::env;
|
||||
|
||||
use crate::config::KV;
|
||||
use common::error::{Error, Result};
|
||||
|
||||
use super::KVS;
|
||||
use crate::config::KV;
|
||||
use crate::error::{Error, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use tracing::warn;
|
||||
|
||||
/// Default parity count for a given drive count
|
||||
@@ -115,7 +113,13 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_inline(&self, shard_size: usize, versioned: bool) -> bool {
|
||||
pub fn should_inline(&self, shard_size: i64, versioned: bool) -> bool {
|
||||
if shard_size < 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let shard_size = shard_size as usize;
|
||||
|
||||
let mut inline_block = DEFAULT_INLINE_BLOCK;
|
||||
if self.initialized {
|
||||
inline_block = self.inline_block;
|
||||
@@ -177,13 +181,7 @@ pub fn lookup_config(kvs: &KVS, set_drive_count: usize) -> Result<Config> {
|
||||
parse_storage_class(&ssc_str)?
|
||||
} else {
|
||||
StorageClass {
|
||||
parity: {
|
||||
if set_drive_count == 1 {
|
||||
0
|
||||
} else {
|
||||
DEFAULT_RRS_PARITY
|
||||
}
|
||||
},
|
||||
parity: { if set_drive_count == 1 { 0 } else { DEFAULT_RRS_PARITY } },
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -196,11 +194,14 @@ pub fn lookup_config(kvs: &KVS, set_drive_count: usize) -> Result<Config> {
|
||||
if let Ok(ev) = env::var(INLINE_BLOCK_ENV) {
|
||||
if let Ok(block) = ev.parse::<bytesize::ByteSize>() {
|
||||
if block.as_u64() as usize > DEFAULT_INLINE_BLOCK {
|
||||
warn!("inline block value bigger than recommended max of 128KiB -> {}, performance may degrade for PUT please benchmark the changes",block);
|
||||
warn!(
|
||||
"inline block value bigger than recommended max of 128KiB -> {}, performance may degrade for PUT please benchmark the changes",
|
||||
block
|
||||
);
|
||||
}
|
||||
block.as_u64() as usize
|
||||
} else {
|
||||
return Err(Error::msg(format!("parse {} format failed", INLINE_BLOCK_ENV)));
|
||||
return Err(Error::other(format!("parse {} format failed", INLINE_BLOCK_ENV)));
|
||||
}
|
||||
} else {
|
||||
DEFAULT_INLINE_BLOCK
|
||||
@@ -221,7 +222,7 @@ pub fn parse_storage_class(env: &str) -> Result<StorageClass> {
|
||||
|
||||
// only two elements allowed in the string - "scheme" and "number of parity drives"
|
||||
if s.len() != 2 {
|
||||
return Err(Error::msg(format!(
|
||||
return Err(Error::other(format!(
|
||||
"Invalid storage class format: {}. Expected 'Scheme:Number of parity drives'.",
|
||||
env
|
||||
)));
|
||||
@@ -229,13 +230,13 @@ pub fn parse_storage_class(env: &str) -> Result<StorageClass> {
|
||||
|
||||
// only allowed scheme is "EC"
|
||||
if s[0] != SCHEME_PREFIX {
|
||||
return Err(Error::msg(format!("Unsupported scheme {}. Supported scheme is EC.", s[0])));
|
||||
return Err(Error::other(format!("Unsupported scheme {}. Supported scheme is EC.", s[0])));
|
||||
}
|
||||
|
||||
// Number of parity drives should be integer
|
||||
let parity_drives: usize = match s[1].parse() {
|
||||
Ok(num) => num,
|
||||
Err(_) => return Err(Error::msg(format!("Failed to parse parity value: {}.", s[1]))),
|
||||
Err(_) => return Err(Error::other(format!("Failed to parse parity value: {}.", s[1]))),
|
||||
};
|
||||
|
||||
Ok(StorageClass { parity: parity_drives })
|
||||
@@ -244,14 +245,14 @@ pub fn parse_storage_class(env: &str) -> Result<StorageClass> {
|
||||
// ValidateParity validates standard storage class parity.
|
||||
pub fn validate_parity(ss_parity: usize, set_drive_count: usize) -> Result<()> {
|
||||
// if ss_parity > 0 && ss_parity < MIN_PARITY_DRIVES {
|
||||
// return Err(Error::msg(format!(
|
||||
// return Err(Error::other(format!(
|
||||
// "parity {} should be greater than or equal to {}",
|
||||
// ss_parity, MIN_PARITY_DRIVES
|
||||
// )));
|
||||
// }
|
||||
|
||||
if ss_parity > set_drive_count / 2 {
|
||||
return Err(Error::msg(format!(
|
||||
return Err(Error::other(format!(
|
||||
"parity {} should be less than or equal to {}",
|
||||
ss_parity,
|
||||
set_drive_count / 2
|
||||
@@ -264,7 +265,7 @@ pub fn validate_parity(ss_parity: usize, set_drive_count: usize) -> Result<()> {
|
||||
// Validates the parity drives.
|
||||
pub fn validate_parity_inner(ss_parity: usize, rrs_parity: usize, set_drive_count: usize) -> Result<()> {
|
||||
// if ss_parity > 0 && ss_parity < MIN_PARITY_DRIVES {
|
||||
// return Err(Error::msg(format!(
|
||||
// return Err(Error::other(format!(
|
||||
// "Standard storage class parity {} should be greater than or equal to {}",
|
||||
// ss_parity, MIN_PARITY_DRIVES
|
||||
// )));
|
||||
@@ -273,7 +274,7 @@ pub fn validate_parity_inner(ss_parity: usize, rrs_parity: usize, set_drive_coun
|
||||
// RRS parity drives should be greater than or equal to minParityDrives.
|
||||
// Parity below minParityDrives is not supported.
|
||||
// if rrs_parity > 0 && rrs_parity < MIN_PARITY_DRIVES {
|
||||
// return Err(Error::msg(format!(
|
||||
// return Err(Error::other(format!(
|
||||
// "Reduced redundancy storage class parity {} should be greater than or equal to {}",
|
||||
// rrs_parity, MIN_PARITY_DRIVES
|
||||
// )));
|
||||
@@ -281,7 +282,7 @@ pub fn validate_parity_inner(ss_parity: usize, rrs_parity: usize, set_drive_coun
|
||||
|
||||
if set_drive_count > 2 {
|
||||
if ss_parity > set_drive_count / 2 {
|
||||
return Err(Error::msg(format!(
|
||||
return Err(Error::other(format!(
|
||||
"Standard storage class parity {} should be less than or equal to {}",
|
||||
ss_parity,
|
||||
set_drive_count / 2
|
||||
@@ -289,7 +290,7 @@ pub fn validate_parity_inner(ss_parity: usize, rrs_parity: usize, set_drive_coun
|
||||
}
|
||||
|
||||
if rrs_parity > set_drive_count / 2 {
|
||||
return Err(Error::msg(format!(
|
||||
return Err(Error::other(format!(
|
||||
"Reduced redundancy storage class parity {} should be less than or equal to {}",
|
||||
rrs_parity,
|
||||
set_drive_count / 2
|
||||
@@ -298,7 +299,10 @@ pub fn validate_parity_inner(ss_parity: usize, rrs_parity: usize, set_drive_coun
|
||||
}
|
||||
|
||||
if ss_parity > 0 && rrs_parity > 0 && ss_parity < rrs_parity {
|
||||
return Err(Error::msg(format!("Standard storage class parity drives {} should be greater than or equal to Reduced redundancy storage class parity drives {}", ss_parity, rrs_parity)));
|
||||
return Err(Error::other(format!(
|
||||
"Standard storage class parity drives {} should be greater than or equal to Reduced redundancy storage class parity drives {}",
|
||||
ss_parity, rrs_parity
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+160
-27
@@ -1,6 +1,6 @@
|
||||
use crate::utils::net;
|
||||
use common::error::{Error, Result};
|
||||
use super::error::{Error, Result};
|
||||
use path_absolutize::Absolutize;
|
||||
use rustfs_utils::{is_local_host, is_socket_addr};
|
||||
use std::{fmt::Display, path::Path};
|
||||
use url::{ParseError, Url};
|
||||
|
||||
@@ -40,10 +40,10 @@ impl TryFrom<&str> for Endpoint {
|
||||
type Error = Error;
|
||||
|
||||
/// Performs the conversion.
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
|
||||
// check whether given path is not empty.
|
||||
if ["", "/", "\\"].iter().any(|&v| v.eq(value)) {
|
||||
return Err(Error::from_string("empty or root endpoint is not supported"));
|
||||
return Err(Error::other("empty or root endpoint is not supported"));
|
||||
}
|
||||
|
||||
let mut is_local = false;
|
||||
@@ -59,7 +59,7 @@ impl TryFrom<&str> for Endpoint {
|
||||
&& url.fragment().is_none()
|
||||
&& url.query().is_none())
|
||||
{
|
||||
return Err(Error::from_string("invalid URL endpoint format"));
|
||||
return Err(Error::other("invalid URL endpoint format"));
|
||||
}
|
||||
|
||||
let path = url.path().to_string();
|
||||
@@ -76,12 +76,12 @@ impl TryFrom<&str> for Endpoint {
|
||||
let path = Path::new(&path[1..]).absolutize()?;
|
||||
|
||||
if path.parent().is_none() || Path::new("").eq(&path) {
|
||||
return Err(Error::from_string("empty or root path is not supported in URL endpoint"));
|
||||
return Err(Error::other("empty or root path is not supported in URL endpoint"));
|
||||
}
|
||||
|
||||
match path.to_str() {
|
||||
Some(v) => url.set_path(v),
|
||||
None => return Err(Error::from_string("invalid path")),
|
||||
None => return Err(Error::other("invalid path")),
|
||||
}
|
||||
|
||||
url
|
||||
@@ -93,15 +93,15 @@ impl TryFrom<&str> for Endpoint {
|
||||
}
|
||||
Err(e) => match e {
|
||||
ParseError::InvalidPort => {
|
||||
return Err(Error::from_string("invalid URL endpoint format: port number must be between 1 to 65535"))
|
||||
return Err(Error::other("invalid URL endpoint format: port number must be between 1 to 65535"));
|
||||
}
|
||||
ParseError::EmptyHost => return Err(Error::from_string("invalid URL endpoint format: empty host name")),
|
||||
ParseError::EmptyHost => return Err(Error::other("invalid URL endpoint format: empty host name")),
|
||||
ParseError::RelativeUrlWithoutBase => {
|
||||
// like /foo
|
||||
is_local = true;
|
||||
url_parse_from_file_path(value)?
|
||||
}
|
||||
_ => return Err(Error::from_string(format!("invalid URL endpoint format: {}", e))),
|
||||
_ => return Err(Error::other(format!("invalid URL endpoint format: {}", e))),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -144,7 +144,7 @@ impl Endpoint {
|
||||
pub fn update_is_local(&mut self, local_port: u16) -> Result<()> {
|
||||
match (self.url.scheme(), self.url.host()) {
|
||||
(v, Some(host)) if v != "file" => {
|
||||
self.is_local = net::is_local_host(host, self.url.port().unwrap_or_default(), local_port)?;
|
||||
self.is_local = is_local_host(host, self.url.port().unwrap_or_default(), local_port)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -185,18 +185,18 @@ fn url_parse_from_file_path(value: &str) -> Result<Url> {
|
||||
// localhost, example.com, any FQDN cannot be disambiguated from a regular file path such as
|
||||
// /mnt/export1. So we go ahead and start the rustfs server in FS modes in these cases.
|
||||
let addr: Vec<&str> = value.splitn(2, '/').collect();
|
||||
if net::is_socket_addr(addr[0]) {
|
||||
return Err(Error::from_string("invalid URL endpoint format: missing scheme http or https"));
|
||||
if is_socket_addr(addr[0]) {
|
||||
return Err(Error::other("invalid URL endpoint format: missing scheme http or https"));
|
||||
}
|
||||
|
||||
let file_path = match Path::new(value).absolutize() {
|
||||
Ok(path) => path,
|
||||
Err(err) => return Err(Error::from_string(format!("absolute path failed: {}", err))),
|
||||
Err(err) => return Err(Error::other(format!("absolute path failed: {}", err))),
|
||||
};
|
||||
|
||||
match Url::from_file_path(file_path) {
|
||||
Ok(url) => Ok(url),
|
||||
Err(_) => Err(Error::from_string("Convert a file path into an URL failed")),
|
||||
Err(_) => Err(Error::other("Convert a file path into an URL failed")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,49 +260,49 @@ mod test {
|
||||
arg: "",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("empty or root endpoint is not supported")),
|
||||
expected_err: Some(Error::other("empty or root endpoint is not supported")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "/",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("empty or root endpoint is not supported")),
|
||||
expected_err: Some(Error::other("empty or root endpoint is not supported")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "\\",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("empty or root endpoint is not supported")),
|
||||
expected_err: Some(Error::other("empty or root endpoint is not supported")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "c://foo",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("invalid URL endpoint format")),
|
||||
expected_err: Some(Error::other("invalid URL endpoint format")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "ftp://foo",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("invalid URL endpoint format")),
|
||||
expected_err: Some(Error::other("invalid URL endpoint format")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://server/path?location",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("invalid URL endpoint format")),
|
||||
expected_err: Some(Error::other("invalid URL endpoint format")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://:/path",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("invalid URL endpoint format: empty host name")),
|
||||
expected_err: Some(Error::other("invalid URL endpoint format: empty host name")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://:8080/path",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("invalid URL endpoint format: empty host name")),
|
||||
expected_err: Some(Error::other("invalid URL endpoint format: empty host name")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://server:/path",
|
||||
@@ -320,25 +320,25 @@ mod test {
|
||||
arg: "https://93.184.216.34:808080/path",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("invalid URL endpoint format: port number must be between 1 to 65535")),
|
||||
expected_err: Some(Error::other("invalid URL endpoint format: port number must be between 1 to 65535")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://server:8080//",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("empty or root path is not supported in URL endpoint")),
|
||||
expected_err: Some(Error::other("empty or root path is not supported in URL endpoint")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://server:8080/",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("empty or root path is not supported in URL endpoint")),
|
||||
expected_err: Some(Error::other("empty or root path is not supported in URL endpoint")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "192.168.1.210:9000",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(Error::from_string("invalid URL endpoint format: missing scheme http or https")),
|
||||
expected_err: Some(Error::other("invalid URL endpoint format: missing scheme http or https")),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -372,4 +372,137 @@ mod test {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_display() {
|
||||
// Test file path display
|
||||
let file_endpoint = Endpoint::try_from("/tmp/data").unwrap();
|
||||
let display_str = format!("{}", file_endpoint);
|
||||
assert_eq!(display_str, "/tmp/data");
|
||||
|
||||
// Test URL display
|
||||
let url_endpoint = Endpoint::try_from("http://example.com:9000/path").unwrap();
|
||||
let display_str = format!("{}", url_endpoint);
|
||||
assert_eq!(display_str, "http://example.com:9000/path");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_type() {
|
||||
let file_endpoint = Endpoint::try_from("/tmp/data").unwrap();
|
||||
assert_eq!(file_endpoint.get_type(), EndpointType::Path);
|
||||
|
||||
let url_endpoint = Endpoint::try_from("http://example.com:9000/path").unwrap();
|
||||
assert_eq!(url_endpoint.get_type(), EndpointType::Url);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_indexes() {
|
||||
let mut endpoint = Endpoint::try_from("/tmp/data").unwrap();
|
||||
|
||||
// Test initial values
|
||||
assert_eq!(endpoint.pool_idx, -1);
|
||||
assert_eq!(endpoint.set_idx, -1);
|
||||
assert_eq!(endpoint.disk_idx, -1);
|
||||
|
||||
// Test setting indexes
|
||||
endpoint.set_pool_index(2);
|
||||
endpoint.set_set_index(3);
|
||||
endpoint.set_disk_index(4);
|
||||
|
||||
assert_eq!(endpoint.pool_idx, 2);
|
||||
assert_eq!(endpoint.set_idx, 3);
|
||||
assert_eq!(endpoint.disk_idx, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_grid_host() {
|
||||
let endpoint = Endpoint::try_from("http://example.com:9000/path").unwrap();
|
||||
assert_eq!(endpoint.grid_host(), "http://example.com:9000");
|
||||
|
||||
let endpoint_no_port = Endpoint::try_from("https://example.com/path").unwrap();
|
||||
assert_eq!(endpoint_no_port.grid_host(), "https://example.com");
|
||||
|
||||
let file_endpoint = Endpoint::try_from("/tmp/data").unwrap();
|
||||
assert_eq!(file_endpoint.grid_host(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_host_port() {
|
||||
let endpoint = Endpoint::try_from("http://example.com:9000/path").unwrap();
|
||||
assert_eq!(endpoint.host_port(), "example.com:9000");
|
||||
|
||||
let endpoint_no_port = Endpoint::try_from("https://example.com/path").unwrap();
|
||||
assert_eq!(endpoint_no_port.host_port(), "example.com");
|
||||
|
||||
let file_endpoint = Endpoint::try_from("/tmp/data").unwrap();
|
||||
assert_eq!(file_endpoint.host_port(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_get_file_path() {
|
||||
let file_endpoint = Endpoint::try_from("/tmp/data").unwrap();
|
||||
assert_eq!(file_endpoint.get_file_path(), "/tmp/data");
|
||||
|
||||
let url_endpoint = Endpoint::try_from("http://example.com:9000/path/to/data").unwrap();
|
||||
assert_eq!(url_endpoint.get_file_path(), "/path/to/data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_clone_and_equality() {
|
||||
let endpoint1 = Endpoint::try_from("/tmp/data").unwrap();
|
||||
let endpoint2 = endpoint1.clone();
|
||||
|
||||
assert_eq!(endpoint1, endpoint2);
|
||||
assert_eq!(endpoint1.url, endpoint2.url);
|
||||
assert_eq!(endpoint1.is_local, endpoint2.is_local);
|
||||
assert_eq!(endpoint1.pool_idx, endpoint2.pool_idx);
|
||||
assert_eq!(endpoint1.set_idx, endpoint2.set_idx);
|
||||
assert_eq!(endpoint1.disk_idx, endpoint2.disk_idx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_with_special_paths() {
|
||||
// Test with complex paths
|
||||
let complex_path = "/var/lib/rustfs/data/bucket1";
|
||||
let endpoint = Endpoint::try_from(complex_path).unwrap();
|
||||
assert_eq!(endpoint.get_file_path(), complex_path);
|
||||
assert!(endpoint.is_local);
|
||||
assert_eq!(endpoint.get_type(), EndpointType::Path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_update_is_local() {
|
||||
let mut endpoint = Endpoint::try_from("http://localhost:9000/path").unwrap();
|
||||
let result = endpoint.update_is_local(9000);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let mut file_endpoint = Endpoint::try_from("/tmp/data").unwrap();
|
||||
let result = file_endpoint.update_is_local(9000);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_parse_from_file_path() {
|
||||
let result = url_parse_from_file_path("/tmp/test");
|
||||
assert!(result.is_ok());
|
||||
|
||||
let url = result.unwrap();
|
||||
assert_eq!(url.scheme(), "file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_endpoint_hash() {
|
||||
use std::collections::HashSet;
|
||||
|
||||
let endpoint1 = Endpoint::try_from("/tmp/data1").unwrap();
|
||||
let endpoint2 = Endpoint::try_from("/tmp/data2").unwrap();
|
||||
let endpoint3 = endpoint1.clone();
|
||||
|
||||
let mut set = HashSet::new();
|
||||
set.insert(endpoint1);
|
||||
set.insert(endpoint2);
|
||||
set.insert(endpoint3); // Should not be added as it's equal to endpoint1
|
||||
|
||||
assert_eq!(set.len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
+617
-340
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,439 @@
|
||||
use super::error::DiskError;
|
||||
|
||||
pub fn to_file_error(io_err: std::io::Error) -> std::io::Error {
|
||||
match io_err.kind() {
|
||||
std::io::ErrorKind::NotFound => DiskError::FileNotFound.into(),
|
||||
std::io::ErrorKind::PermissionDenied => DiskError::FileAccessDenied.into(),
|
||||
std::io::ErrorKind::IsADirectory => DiskError::IsNotRegular.into(),
|
||||
std::io::ErrorKind::NotADirectory => DiskError::FileAccessDenied.into(),
|
||||
std::io::ErrorKind::DirectoryNotEmpty => DiskError::FileAccessDenied.into(),
|
||||
std::io::ErrorKind::UnexpectedEof => DiskError::FaultyDisk.into(),
|
||||
std::io::ErrorKind::TooManyLinks => DiskError::TooManyOpenFiles.into(),
|
||||
std::io::ErrorKind::InvalidInput => DiskError::FileNotFound.into(),
|
||||
std::io::ErrorKind::InvalidData => DiskError::FileCorrupt.into(),
|
||||
std::io::ErrorKind::StorageFull => DiskError::DiskFull.into(),
|
||||
_ => io_err,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_volume_error(io_err: std::io::Error) -> std::io::Error {
|
||||
match io_err.kind() {
|
||||
std::io::ErrorKind::NotFound => DiskError::VolumeNotFound.into(),
|
||||
std::io::ErrorKind::PermissionDenied => DiskError::DiskAccessDenied.into(),
|
||||
std::io::ErrorKind::DirectoryNotEmpty => DiskError::VolumeNotEmpty.into(),
|
||||
std::io::ErrorKind::NotADirectory => DiskError::IsNotRegular.into(),
|
||||
std::io::ErrorKind::Other => match io_err.downcast::<DiskError>() {
|
||||
Ok(err) => match err {
|
||||
DiskError::FileNotFound => DiskError::VolumeNotFound.into(),
|
||||
DiskError::FileAccessDenied => DiskError::DiskAccessDenied.into(),
|
||||
err => err.into(),
|
||||
},
|
||||
Err(err) => to_file_error(err),
|
||||
},
|
||||
_ => to_file_error(io_err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_disk_error(io_err: std::io::Error) -> std::io::Error {
|
||||
match io_err.kind() {
|
||||
std::io::ErrorKind::NotFound => DiskError::DiskNotFound.into(),
|
||||
std::io::ErrorKind::PermissionDenied => DiskError::DiskAccessDenied.into(),
|
||||
std::io::ErrorKind::Other => match io_err.downcast::<DiskError>() {
|
||||
Ok(err) => match err {
|
||||
DiskError::FileNotFound => DiskError::DiskNotFound.into(),
|
||||
DiskError::VolumeNotFound => DiskError::DiskNotFound.into(),
|
||||
DiskError::FileAccessDenied => DiskError::DiskAccessDenied.into(),
|
||||
DiskError::VolumeAccessDenied => DiskError::DiskAccessDenied.into(),
|
||||
err => err.into(),
|
||||
},
|
||||
Err(err) => to_volume_error(err),
|
||||
},
|
||||
_ => to_volume_error(io_err),
|
||||
}
|
||||
}
|
||||
|
||||
// only errors from FileSystem operations
|
||||
pub fn to_access_error(io_err: std::io::Error, per_err: DiskError) -> std::io::Error {
|
||||
match io_err.kind() {
|
||||
std::io::ErrorKind::PermissionDenied => per_err.into(),
|
||||
std::io::ErrorKind::NotADirectory => per_err.into(),
|
||||
std::io::ErrorKind::NotFound => DiskError::VolumeNotFound.into(),
|
||||
std::io::ErrorKind::UnexpectedEof => DiskError::FaultyDisk.into(),
|
||||
std::io::ErrorKind::Other => match io_err.downcast::<DiskError>() {
|
||||
Ok(err) => match err {
|
||||
DiskError::DiskAccessDenied => per_err.into(),
|
||||
DiskError::FileAccessDenied => per_err.into(),
|
||||
DiskError::FileNotFound => DiskError::VolumeNotFound.into(),
|
||||
err => err.into(),
|
||||
},
|
||||
Err(err) => to_volume_error(err),
|
||||
},
|
||||
_ => to_volume_error(io_err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_unformatted_disk_error(io_err: std::io::Error) -> std::io::Error {
|
||||
match io_err.kind() {
|
||||
std::io::ErrorKind::NotFound => DiskError::UnformattedDisk.into(),
|
||||
std::io::ErrorKind::PermissionDenied => DiskError::DiskAccessDenied.into(),
|
||||
std::io::ErrorKind::Other => match io_err.downcast::<DiskError>() {
|
||||
Ok(err) => match err {
|
||||
DiskError::FileNotFound => DiskError::UnformattedDisk.into(),
|
||||
DiskError::DiskNotFound => DiskError::UnformattedDisk.into(),
|
||||
DiskError::VolumeNotFound => DiskError::UnformattedDisk.into(),
|
||||
DiskError::FileAccessDenied => DiskError::DiskAccessDenied.into(),
|
||||
DiskError::DiskAccessDenied => DiskError::DiskAccessDenied.into(),
|
||||
_ => DiskError::CorruptedBackend.into(),
|
||||
},
|
||||
Err(_err) => DiskError::CorruptedBackend.into(),
|
||||
},
|
||||
_ => DiskError::CorruptedBackend.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::{Error as IoError, ErrorKind};
|
||||
|
||||
// Helper function to create IO errors with specific kinds
|
||||
fn create_io_error(kind: ErrorKind) -> IoError {
|
||||
IoError::new(kind, "test error")
|
||||
}
|
||||
|
||||
// Helper function to create IO errors with DiskError as the source
|
||||
fn create_io_error_with_disk_error(disk_error: DiskError) -> IoError {
|
||||
IoError::other(disk_error)
|
||||
}
|
||||
|
||||
// Helper function to check if an IoError contains a specific DiskError
|
||||
fn contains_disk_error(io_error: IoError, expected: DiskError) -> bool {
|
||||
if let Ok(disk_error) = io_error.downcast::<DiskError>() {
|
||||
std::mem::discriminant(&disk_error) == std::mem::discriminant(&expected)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_file_error_basic_conversions() {
|
||||
// Test NotFound -> FileNotFound
|
||||
let result = to_file_error(create_io_error(ErrorKind::NotFound));
|
||||
assert!(contains_disk_error(result, DiskError::FileNotFound));
|
||||
|
||||
// Test PermissionDenied -> FileAccessDenied
|
||||
let result = to_file_error(create_io_error(ErrorKind::PermissionDenied));
|
||||
assert!(contains_disk_error(result, DiskError::FileAccessDenied));
|
||||
|
||||
// Test IsADirectory -> IsNotRegular
|
||||
let result = to_file_error(create_io_error(ErrorKind::IsADirectory));
|
||||
assert!(contains_disk_error(result, DiskError::IsNotRegular));
|
||||
|
||||
// Test NotADirectory -> FileAccessDenied
|
||||
let result = to_file_error(create_io_error(ErrorKind::NotADirectory));
|
||||
assert!(contains_disk_error(result, DiskError::FileAccessDenied));
|
||||
|
||||
// Test DirectoryNotEmpty -> FileAccessDenied
|
||||
let result = to_file_error(create_io_error(ErrorKind::DirectoryNotEmpty));
|
||||
assert!(contains_disk_error(result, DiskError::FileAccessDenied));
|
||||
|
||||
// Test UnexpectedEof -> FaultyDisk
|
||||
let result = to_file_error(create_io_error(ErrorKind::UnexpectedEof));
|
||||
assert!(contains_disk_error(result, DiskError::FaultyDisk));
|
||||
|
||||
// Test TooManyLinks -> TooManyOpenFiles
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = to_file_error(create_io_error(ErrorKind::TooManyLinks));
|
||||
assert!(contains_disk_error(result, DiskError::TooManyOpenFiles));
|
||||
}
|
||||
|
||||
// Test InvalidInput -> FileNotFound
|
||||
let result = to_file_error(create_io_error(ErrorKind::InvalidInput));
|
||||
assert!(contains_disk_error(result, DiskError::FileNotFound));
|
||||
|
||||
// Test InvalidData -> FileCorrupt
|
||||
let result = to_file_error(create_io_error(ErrorKind::InvalidData));
|
||||
assert!(contains_disk_error(result, DiskError::FileCorrupt));
|
||||
|
||||
// Test StorageFull -> DiskFull
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = to_file_error(create_io_error(ErrorKind::StorageFull));
|
||||
assert!(contains_disk_error(result, DiskError::DiskFull));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_file_error_passthrough_unknown() {
|
||||
// Test that unknown error kinds are passed through unchanged
|
||||
let original = create_io_error(ErrorKind::Interrupted);
|
||||
let result = to_file_error(original);
|
||||
assert_eq!(result.kind(), ErrorKind::Interrupted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_volume_error_basic_conversions() {
|
||||
// Test NotFound -> VolumeNotFound
|
||||
let result = to_volume_error(create_io_error(ErrorKind::NotFound));
|
||||
assert!(contains_disk_error(result, DiskError::VolumeNotFound));
|
||||
|
||||
// Test PermissionDenied -> DiskAccessDenied
|
||||
let result = to_volume_error(create_io_error(ErrorKind::PermissionDenied));
|
||||
assert!(contains_disk_error(result, DiskError::DiskAccessDenied));
|
||||
|
||||
// Test DirectoryNotEmpty -> VolumeNotEmpty
|
||||
let result = to_volume_error(create_io_error(ErrorKind::DirectoryNotEmpty));
|
||||
assert!(contains_disk_error(result, DiskError::VolumeNotEmpty));
|
||||
|
||||
// Test NotADirectory -> IsNotRegular
|
||||
let result = to_volume_error(create_io_error(ErrorKind::NotADirectory));
|
||||
assert!(contains_disk_error(result, DiskError::IsNotRegular));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_volume_error_other_with_disk_error() {
|
||||
// Test Other error kind with FileNotFound DiskError -> VolumeNotFound
|
||||
let io_error = create_io_error_with_disk_error(DiskError::FileNotFound);
|
||||
let result = to_volume_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::VolumeNotFound));
|
||||
|
||||
// Test Other error kind with FileAccessDenied DiskError -> DiskAccessDenied
|
||||
let io_error = create_io_error_with_disk_error(DiskError::FileAccessDenied);
|
||||
let result = to_volume_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskAccessDenied));
|
||||
|
||||
// Test Other error kind with other DiskError -> passthrough
|
||||
let io_error = create_io_error_with_disk_error(DiskError::DiskFull);
|
||||
let result = to_volume_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskFull));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_volume_error_fallback_to_file_error() {
|
||||
// Test fallback to to_file_error for unknown error kinds
|
||||
let result = to_volume_error(create_io_error(ErrorKind::Interrupted));
|
||||
assert_eq!(result.kind(), ErrorKind::Interrupted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_disk_error_basic_conversions() {
|
||||
// Test NotFound -> DiskNotFound
|
||||
let result = to_disk_error(create_io_error(ErrorKind::NotFound));
|
||||
assert!(contains_disk_error(result, DiskError::DiskNotFound));
|
||||
|
||||
// Test PermissionDenied -> DiskAccessDenied
|
||||
let result = to_disk_error(create_io_error(ErrorKind::PermissionDenied));
|
||||
assert!(contains_disk_error(result, DiskError::DiskAccessDenied));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_disk_error_other_with_disk_error() {
|
||||
// Test Other error kind with FileNotFound DiskError -> DiskNotFound
|
||||
let io_error = create_io_error_with_disk_error(DiskError::FileNotFound);
|
||||
let result = to_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskNotFound));
|
||||
|
||||
// Test Other error kind with VolumeNotFound DiskError -> DiskNotFound
|
||||
let io_error = create_io_error_with_disk_error(DiskError::VolumeNotFound);
|
||||
let result = to_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskNotFound));
|
||||
|
||||
// Test Other error kind with FileAccessDenied DiskError -> DiskAccessDenied
|
||||
let io_error = create_io_error_with_disk_error(DiskError::FileAccessDenied);
|
||||
let result = to_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskAccessDenied));
|
||||
|
||||
// Test Other error kind with VolumeAccessDenied DiskError -> DiskAccessDenied
|
||||
let io_error = create_io_error_with_disk_error(DiskError::VolumeAccessDenied);
|
||||
let result = to_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskAccessDenied));
|
||||
|
||||
// Test Other error kind with other DiskError -> passthrough
|
||||
let io_error = create_io_error_with_disk_error(DiskError::DiskFull);
|
||||
let result = to_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskFull));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_disk_error_fallback_to_volume_error() {
|
||||
// Test fallback to to_volume_error for unknown error kinds
|
||||
let result = to_disk_error(create_io_error(ErrorKind::Interrupted));
|
||||
assert_eq!(result.kind(), ErrorKind::Interrupted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_access_error_basic_conversions() {
|
||||
let permission_error = DiskError::FileAccessDenied;
|
||||
|
||||
// Test PermissionDenied -> specified permission error
|
||||
let result = to_access_error(create_io_error(ErrorKind::PermissionDenied), permission_error);
|
||||
assert!(contains_disk_error(result, DiskError::FileAccessDenied));
|
||||
|
||||
// Test NotADirectory -> specified permission error
|
||||
let result = to_access_error(create_io_error(ErrorKind::NotADirectory), DiskError::FileAccessDenied);
|
||||
assert!(contains_disk_error(result, DiskError::FileAccessDenied));
|
||||
|
||||
// Test NotFound -> VolumeNotFound
|
||||
let result = to_access_error(create_io_error(ErrorKind::NotFound), DiskError::FileAccessDenied);
|
||||
assert!(contains_disk_error(result, DiskError::VolumeNotFound));
|
||||
|
||||
// Test UnexpectedEof -> FaultyDisk
|
||||
let result = to_access_error(create_io_error(ErrorKind::UnexpectedEof), DiskError::FileAccessDenied);
|
||||
assert!(contains_disk_error(result, DiskError::FaultyDisk));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_access_error_other_with_disk_error() {
|
||||
let permission_error = DiskError::VolumeAccessDenied;
|
||||
|
||||
// Test Other error kind with DiskAccessDenied -> specified permission error
|
||||
let io_error = create_io_error_with_disk_error(DiskError::DiskAccessDenied);
|
||||
let result = to_access_error(io_error, permission_error);
|
||||
assert!(contains_disk_error(result, DiskError::VolumeAccessDenied));
|
||||
|
||||
// Test Other error kind with FileAccessDenied -> specified permission error
|
||||
let io_error = create_io_error_with_disk_error(DiskError::FileAccessDenied);
|
||||
let result = to_access_error(io_error, DiskError::VolumeAccessDenied);
|
||||
assert!(contains_disk_error(result, DiskError::VolumeAccessDenied));
|
||||
|
||||
// Test Other error kind with FileNotFound -> VolumeNotFound
|
||||
let io_error = create_io_error_with_disk_error(DiskError::FileNotFound);
|
||||
let result = to_access_error(io_error, DiskError::VolumeAccessDenied);
|
||||
assert!(contains_disk_error(result, DiskError::VolumeNotFound));
|
||||
|
||||
// Test Other error kind with other DiskError -> passthrough
|
||||
let io_error = create_io_error_with_disk_error(DiskError::DiskFull);
|
||||
let result = to_access_error(io_error, DiskError::VolumeAccessDenied);
|
||||
assert!(contains_disk_error(result, DiskError::DiskFull));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_access_error_fallback_to_volume_error() {
|
||||
let permission_error = DiskError::FileAccessDenied;
|
||||
|
||||
// Test fallback to to_volume_error for unknown error kinds
|
||||
let result = to_access_error(create_io_error(ErrorKind::Interrupted), permission_error);
|
||||
assert_eq!(result.kind(), ErrorKind::Interrupted);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_unformatted_disk_error_basic_conversions() {
|
||||
// Test NotFound -> UnformattedDisk
|
||||
let result = to_unformatted_disk_error(create_io_error(ErrorKind::NotFound));
|
||||
assert!(contains_disk_error(result, DiskError::UnformattedDisk));
|
||||
|
||||
// Test PermissionDenied -> DiskAccessDenied
|
||||
let result = to_unformatted_disk_error(create_io_error(ErrorKind::PermissionDenied));
|
||||
assert!(contains_disk_error(result, DiskError::DiskAccessDenied));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_unformatted_disk_error_other_with_disk_error() {
|
||||
// Test Other error kind with FileNotFound -> UnformattedDisk
|
||||
let io_error = create_io_error_with_disk_error(DiskError::FileNotFound);
|
||||
let result = to_unformatted_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::UnformattedDisk));
|
||||
|
||||
// Test Other error kind with DiskNotFound -> UnformattedDisk
|
||||
let io_error = create_io_error_with_disk_error(DiskError::DiskNotFound);
|
||||
let result = to_unformatted_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::UnformattedDisk));
|
||||
|
||||
// Test Other error kind with VolumeNotFound -> UnformattedDisk
|
||||
let io_error = create_io_error_with_disk_error(DiskError::VolumeNotFound);
|
||||
let result = to_unformatted_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::UnformattedDisk));
|
||||
|
||||
// Test Other error kind with FileAccessDenied -> DiskAccessDenied
|
||||
let io_error = create_io_error_with_disk_error(DiskError::FileAccessDenied);
|
||||
let result = to_unformatted_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskAccessDenied));
|
||||
|
||||
// Test Other error kind with DiskAccessDenied -> DiskAccessDenied
|
||||
let io_error = create_io_error_with_disk_error(DiskError::DiskAccessDenied);
|
||||
let result = to_unformatted_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::DiskAccessDenied));
|
||||
|
||||
// Test Other error kind with other DiskError -> CorruptedBackend
|
||||
let io_error = create_io_error_with_disk_error(DiskError::DiskFull);
|
||||
let result = to_unformatted_disk_error(io_error);
|
||||
assert!(contains_disk_error(result, DiskError::CorruptedBackend));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_unformatted_disk_error_recursive_behavior() {
|
||||
// Test with non-Other error kind that should be handled without infinite recursion
|
||||
let result = to_unformatted_disk_error(create_io_error(ErrorKind::Interrupted));
|
||||
// This should not cause infinite recursion and should produce CorruptedBackend
|
||||
assert!(contains_disk_error(result, DiskError::CorruptedBackend));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_chain_conversions() {
|
||||
// Test complex error conversion chains
|
||||
let original_error = create_io_error(ErrorKind::NotFound);
|
||||
|
||||
// Chain: NotFound -> FileNotFound (via to_file_error) -> VolumeNotFound (via to_volume_error)
|
||||
let file_error = to_file_error(original_error);
|
||||
let volume_error = to_volume_error(file_error);
|
||||
assert!(contains_disk_error(volume_error, DiskError::VolumeNotFound));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cross_platform_error_kinds() {
|
||||
// Test error kinds that may not be available on all platforms
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = to_file_error(create_io_error(ErrorKind::TooManyLinks));
|
||||
assert!(contains_disk_error(result, DiskError::TooManyOpenFiles));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = to_file_error(create_io_error(ErrorKind::StorageFull));
|
||||
assert!(contains_disk_error(result, DiskError::DiskFull));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_conversion_with_different_kinds() {
|
||||
// Test multiple error kinds to ensure comprehensive coverage
|
||||
let test_cases = vec![
|
||||
(ErrorKind::NotFound, DiskError::FileNotFound),
|
||||
(ErrorKind::PermissionDenied, DiskError::FileAccessDenied),
|
||||
(ErrorKind::IsADirectory, DiskError::IsNotRegular),
|
||||
(ErrorKind::InvalidData, DiskError::FileCorrupt),
|
||||
];
|
||||
|
||||
for (kind, expected_disk_error) in test_cases {
|
||||
let result = to_file_error(create_io_error(kind));
|
||||
assert!(
|
||||
contains_disk_error(result, expected_disk_error.clone()),
|
||||
"Failed for ErrorKind::{:?} -> DiskError::{:?}",
|
||||
kind,
|
||||
expected_disk_error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_error_conversion_chain() {
|
||||
// Test volume error conversion with different input types
|
||||
let test_cases = vec![
|
||||
(ErrorKind::NotFound, DiskError::VolumeNotFound),
|
||||
(ErrorKind::PermissionDenied, DiskError::DiskAccessDenied),
|
||||
(ErrorKind::DirectoryNotEmpty, DiskError::VolumeNotEmpty),
|
||||
];
|
||||
|
||||
for (kind, expected_disk_error) in test_cases {
|
||||
let result = to_volume_error(create_io_error(kind));
|
||||
assert!(
|
||||
contains_disk_error(result, expected_disk_error.clone()),
|
||||
"Failed for ErrorKind::{:?} -> DiskError::{:?}",
|
||||
kind,
|
||||
expected_disk_error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
use super::error::Error;
|
||||
|
||||
pub static OBJECT_OP_IGNORED_ERRS: &[Error] = &[
|
||||
Error::DiskNotFound,
|
||||
Error::FaultyDisk,
|
||||
Error::FaultyRemoteDisk,
|
||||
Error::DiskAccessDenied,
|
||||
Error::DiskOngoingReq,
|
||||
Error::UnformattedDisk,
|
||||
];
|
||||
|
||||
pub static BUCKET_OP_IGNORED_ERRS: &[Error] = &[
|
||||
Error::DiskNotFound,
|
||||
Error::FaultyDisk,
|
||||
Error::FaultyRemoteDisk,
|
||||
Error::DiskAccessDenied,
|
||||
Error::UnformattedDisk,
|
||||
];
|
||||
|
||||
pub static BASE_IGNORED_ERRS: &[Error] = &[Error::DiskNotFound, Error::FaultyDisk, Error::FaultyRemoteDisk];
|
||||
|
||||
pub fn reduce_write_quorum_errs(errors: &[Option<Error>], ignored_errs: &[Error], quorun: usize) -> Option<Error> {
|
||||
reduce_quorum_errs(errors, ignored_errs, quorun, Error::ErasureWriteQuorum)
|
||||
}
|
||||
|
||||
pub fn reduce_read_quorum_errs(errors: &[Option<Error>], ignored_errs: &[Error], quorun: usize) -> Option<Error> {
|
||||
reduce_quorum_errs(errors, ignored_errs, quorun, Error::ErasureReadQuorum)
|
||||
}
|
||||
|
||||
pub fn reduce_quorum_errs(errors: &[Option<Error>], ignored_errs: &[Error], quorun: usize, quorun_err: Error) -> Option<Error> {
|
||||
let (max_count, err) = reduce_errs(errors, ignored_errs);
|
||||
if max_count >= quorun { err } else { Some(quorun_err) }
|
||||
}
|
||||
|
||||
pub fn reduce_errs(errors: &[Option<Error>], ignored_errs: &[Error]) -> (usize, Option<Error>) {
|
||||
let nil_error = Error::other("nil".to_string());
|
||||
|
||||
// 首先统计 None 的数量(作为 nil 错误)
|
||||
let nil_count = errors.iter().filter(|e| e.is_none()).count();
|
||||
|
||||
let err_counts = errors
|
||||
.iter()
|
||||
.filter_map(|e| e.as_ref()) // 只处理 Some 的错误
|
||||
.fold(std::collections::HashMap::new(), |mut acc, e| {
|
||||
if is_ignored_err(ignored_errs, e) {
|
||||
return acc;
|
||||
}
|
||||
*acc.entry(e.clone()).or_insert(0) += 1;
|
||||
acc
|
||||
});
|
||||
|
||||
// 找到最高频率的非 nil 错误
|
||||
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 错误
|
||||
if nil_count > best_count || (nil_count == best_count && nil_count > 0) {
|
||||
(nil_count, None)
|
||||
} else {
|
||||
(best_count, Some(best_err))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_ignored_err(ignored_errs: &[Error], err: &Error) -> bool {
|
||||
ignored_errs.iter().any(|e| e == err)
|
||||
}
|
||||
|
||||
pub fn count_errs(errors: &[Option<Error>], err: &Error) -> usize {
|
||||
errors.iter().filter(|&e| e.as_ref() == Some(err)).count()
|
||||
}
|
||||
|
||||
pub fn is_all_buckets_not_found(errs: &[Option<Error>]) -> bool {
|
||||
for err in errs.iter() {
|
||||
if let Some(err) = err {
|
||||
if err == &Error::DiskNotFound || err == &Error::VolumeNotFound {
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
!errs.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn err_io(msg: &str) -> Error {
|
||||
Error::Io(std::io::Error::other(msg))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_errs_basic() {
|
||||
let e1 = err_io("a");
|
||||
let e2 = err_io("b");
|
||||
let errors = vec![Some(e1.clone()), Some(e1.clone()), Some(e2.clone()), None];
|
||||
let ignored = vec![];
|
||||
let (count, err) = reduce_errs(&errors, &ignored);
|
||||
assert_eq!(count, 2);
|
||||
assert_eq!(err, Some(e1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_errs_ignored() {
|
||||
let e1 = err_io("a");
|
||||
let e2 = err_io("b");
|
||||
let errors = vec![Some(e1.clone()), Some(e2.clone()), Some(e1.clone()), Some(e2.clone()), None];
|
||||
let ignored = vec![e2.clone()];
|
||||
let (count, err) = reduce_errs(&errors, &ignored);
|
||||
assert_eq!(count, 2);
|
||||
assert_eq!(err, Some(e1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_quorum_errs() {
|
||||
let e1 = err_io("a");
|
||||
let e2 = err_io("b");
|
||||
let errors = vec![Some(e1.clone()), Some(e1.clone()), Some(e2.clone()), None];
|
||||
let ignored = vec![];
|
||||
let quorum_err = Error::FaultyDisk;
|
||||
// quorum = 2, should return e1
|
||||
let res = reduce_quorum_errs(&errors, &ignored, 2, quorum_err.clone());
|
||||
assert_eq!(res, Some(e1));
|
||||
// quorum = 3, should return quorum error
|
||||
let res = reduce_quorum_errs(&errors, &ignored, 3, quorum_err.clone());
|
||||
assert_eq!(res, Some(quorum_err));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_count_errs() {
|
||||
let e1 = err_io("a");
|
||||
let e2 = err_io("b");
|
||||
let errors = vec![Some(e1.clone()), Some(e2.clone()), Some(e1.clone()), None];
|
||||
assert_eq!(count_errs(&errors, &e1), 2);
|
||||
assert_eq!(count_errs(&errors, &e2), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_ignored_err() {
|
||||
let e1 = err_io("a");
|
||||
let e2 = err_io("b");
|
||||
let ignored = vec![e1.clone()];
|
||||
assert!(is_ignored_err(&ignored, &e1));
|
||||
assert!(!is_ignored_err(&ignored, &e2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_errs_nil_tiebreak() {
|
||||
// Error::Nil and another error have the same count, should prefer Nil
|
||||
let e1 = err_io("a");
|
||||
let errors = vec![Some(e1.clone()), None, Some(e1.clone()), None]; // e1:2, Nil:2
|
||||
let ignored = vec![];
|
||||
let (count, err) = reduce_errs(&errors, &ignored);
|
||||
assert_eq!(count, 2);
|
||||
assert_eq!(err, None); // None means Error::Nil is preferred
|
||||
}
|
||||
}
|
||||
+272
-11
@@ -1,5 +1,5 @@
|
||||
use super::{error::DiskError, DiskInfo};
|
||||
use common::error::{Error, Result};
|
||||
use super::error::{Error, Result};
|
||||
use super::{DiskInfo, error::DiskError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Error as JsonError;
|
||||
use uuid::Uuid;
|
||||
@@ -110,7 +110,7 @@ pub struct FormatV3 {
|
||||
impl TryFrom<&[u8]> for FormatV3 {
|
||||
type Error = JsonError;
|
||||
|
||||
fn try_from(data: &[u8]) -> Result<Self, JsonError> {
|
||||
fn try_from(data: &[u8]) -> std::result::Result<Self, Self::Error> {
|
||||
serde_json::from_slice(data)
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ impl TryFrom<&[u8]> for FormatV3 {
|
||||
impl TryFrom<&str> for FormatV3 {
|
||||
type Error = JsonError;
|
||||
|
||||
fn try_from(data: &str) -> Result<Self, JsonError> {
|
||||
fn try_from(data: &str) -> std::result::Result<Self, Self::Error> {
|
||||
serde_json::from_str(data)
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ impl FormatV3 {
|
||||
self.erasure.sets.iter().map(|v| v.len()).sum()
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> Result<String, JsonError> {
|
||||
pub fn to_json(&self) -> std::result::Result<String, JsonError> {
|
||||
serde_json::to_string(self)
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ impl FormatV3 {
|
||||
return Err(Error::from(DiskError::DiskNotFound));
|
||||
}
|
||||
if disk_id == Uuid::max() {
|
||||
return Err(Error::msg("disk offline"));
|
||||
return Err(Error::other("disk offline"));
|
||||
}
|
||||
|
||||
for (i, set) in self.erasure.sets.iter().enumerate() {
|
||||
@@ -180,7 +180,7 @@ impl FormatV3 {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::msg(format!("disk id not found {}", disk_id)))
|
||||
Err(Error::other(format!("disk id not found {}", disk_id)))
|
||||
}
|
||||
|
||||
pub fn check_other(&self, other: &FormatV3) -> Result<()> {
|
||||
@@ -189,7 +189,7 @@ impl FormatV3 {
|
||||
tmp.erasure.this = Uuid::nil();
|
||||
|
||||
if self.erasure.sets.len() != other.erasure.sets.len() {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"Expected number of sets {}, got {}",
|
||||
self.erasure.sets.len(),
|
||||
other.erasure.sets.len()
|
||||
@@ -198,7 +198,7 @@ impl FormatV3 {
|
||||
|
||||
for i in 0..self.erasure.sets.len() {
|
||||
if self.erasure.sets[i].len() != other.erasure.sets[i].len() {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"Each set should be of same size, expected {}, got {}",
|
||||
self.erasure.sets[i].len(),
|
||||
other.erasure.sets[i].len()
|
||||
@@ -207,7 +207,7 @@ impl FormatV3 {
|
||||
|
||||
for j in 0..self.erasure.sets[i].len() {
|
||||
if self.erasure.sets[i][j] != other.erasure.sets[i][j] {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"UUID on positions {}:{} do not match with, expected {:?} got {:?}: (%w)",
|
||||
i,
|
||||
j,
|
||||
@@ -226,7 +226,7 @@ impl FormatV3 {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::msg(format!(
|
||||
Err(Error::other(format!(
|
||||
"DriveID {:?} not found in any drive sets {:?}",
|
||||
this, other.erasure.sets
|
||||
)))
|
||||
@@ -268,4 +268,265 @@ mod test {
|
||||
|
||||
println!("{:?}", p);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_v3_new_single_disk() {
|
||||
let format = FormatV3::new(1, 1);
|
||||
|
||||
assert_eq!(format.version, FormatMetaVersion::V1);
|
||||
assert_eq!(format.format, FormatBackend::ErasureSingle);
|
||||
assert_eq!(format.erasure.version, FormatErasureVersion::V3);
|
||||
assert_eq!(format.erasure.sets.len(), 1);
|
||||
assert_eq!(format.erasure.sets[0].len(), 1);
|
||||
assert_eq!(format.erasure.distribution_algo, DistributionAlgoVersion::V3);
|
||||
assert_eq!(format.erasure.this, Uuid::nil());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_v3_new_multiple_sets() {
|
||||
let format = FormatV3::new(2, 4);
|
||||
|
||||
assert_eq!(format.version, FormatMetaVersion::V1);
|
||||
assert_eq!(format.format, FormatBackend::Erasure);
|
||||
assert_eq!(format.erasure.version, FormatErasureVersion::V3);
|
||||
assert_eq!(format.erasure.sets.len(), 2);
|
||||
assert_eq!(format.erasure.sets[0].len(), 4);
|
||||
assert_eq!(format.erasure.sets[1].len(), 4);
|
||||
assert_eq!(format.erasure.distribution_algo, DistributionAlgoVersion::V3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_v3_drives() {
|
||||
let format = FormatV3::new(2, 4);
|
||||
assert_eq!(format.drives(), 8); // 2 sets * 4 drives each
|
||||
|
||||
let format_single = FormatV3::new(1, 1);
|
||||
assert_eq!(format_single.drives(), 1); // 1 set * 1 drive
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_v3_to_json() {
|
||||
let format = FormatV3::new(1, 2);
|
||||
let json_result = format.to_json();
|
||||
|
||||
assert!(json_result.is_ok());
|
||||
let json_str = json_result.unwrap();
|
||||
assert!(json_str.contains("\"version\":\"1\""));
|
||||
assert!(json_str.contains("\"format\":\"xl\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_v3_from_json() {
|
||||
let json_data = r#"{
|
||||
"version": "1",
|
||||
"format": "xl-single",
|
||||
"id": "321b3874-987d-4c15-8fa5-757c956b1243",
|
||||
"xl": {
|
||||
"version": "3",
|
||||
"this": "8ab9a908-f869-4f1f-8e42-eb067ffa7eb5",
|
||||
"sets": [
|
||||
[
|
||||
"8ab9a908-f869-4f1f-8e42-eb067ffa7eb5"
|
||||
]
|
||||
],
|
||||
"distributionAlgo": "SIPMOD+PARITY"
|
||||
}
|
||||
}"#;
|
||||
|
||||
let format = FormatV3::try_from(json_data);
|
||||
assert!(format.is_ok());
|
||||
|
||||
let format = format.unwrap();
|
||||
assert_eq!(format.format, FormatBackend::ErasureSingle);
|
||||
assert_eq!(format.erasure.version, FormatErasureVersion::V3);
|
||||
assert_eq!(format.erasure.distribution_algo, DistributionAlgoVersion::V3);
|
||||
assert_eq!(format.erasure.sets.len(), 1);
|
||||
assert_eq!(format.erasure.sets[0].len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_v3_from_bytes() {
|
||||
let json_data = r#"{
|
||||
"version": "1",
|
||||
"format": "xl",
|
||||
"id": "321b3874-987d-4c15-8fa5-757c956b1243",
|
||||
"xl": {
|
||||
"version": "2",
|
||||
"this": "00000000-0000-0000-0000-000000000000",
|
||||
"sets": [
|
||||
[
|
||||
"8ab9a908-f869-4f1f-8e42-eb067ffa7eb5",
|
||||
"c26315da-05cf-4778-a9ea-b44ea09f58c5"
|
||||
]
|
||||
],
|
||||
"distributionAlgo": "SIPMOD"
|
||||
}
|
||||
}"#;
|
||||
|
||||
let format = FormatV3::try_from(json_data.as_bytes());
|
||||
assert!(format.is_ok());
|
||||
|
||||
let format = format.unwrap();
|
||||
assert_eq!(format.erasure.version, FormatErasureVersion::V2);
|
||||
assert_eq!(format.erasure.distribution_algo, DistributionAlgoVersion::V2);
|
||||
assert_eq!(format.erasure.sets[0].len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_v3_invalid_json() {
|
||||
let invalid_json = r#"{"invalid": "json"}"#;
|
||||
let format = FormatV3::try_from(invalid_json);
|
||||
assert!(format.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_disk_index_by_disk_id() {
|
||||
let mut format = FormatV3::new(2, 2);
|
||||
let target_disk_id = Uuid::new_v4();
|
||||
format.erasure.sets[1][0] = target_disk_id;
|
||||
|
||||
let result = format.find_disk_index_by_disk_id(target_disk_id);
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap(), (1, 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_disk_index_nil_uuid() {
|
||||
let format = FormatV3::new(1, 2);
|
||||
let result = format.find_disk_index_by_disk_id(Uuid::nil());
|
||||
assert!(result.is_err());
|
||||
assert!(matches!(result.unwrap_err(), Error::DiskNotFound));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_disk_index_max_uuid() {
|
||||
let format = FormatV3::new(1, 2);
|
||||
let result = format.find_disk_index_by_disk_id(Uuid::max());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_disk_index_not_found() {
|
||||
let format = FormatV3::new(1, 2);
|
||||
let non_existent_id = Uuid::new_v4();
|
||||
let result = format.find_disk_index_by_disk_id(non_existent_id);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_other_identical() {
|
||||
let format1 = FormatV3::new(2, 4);
|
||||
let mut format2 = format1.clone();
|
||||
format2.erasure.this = format1.erasure.sets[0][0];
|
||||
|
||||
let result = format1.check_other(&format2);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_other_different_set_count() {
|
||||
let format1 = FormatV3::new(2, 4);
|
||||
let format2 = FormatV3::new(3, 4);
|
||||
|
||||
let result = format1.check_other(&format2);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_other_different_set_size() {
|
||||
let format1 = FormatV3::new(2, 4);
|
||||
let format2 = FormatV3::new(2, 6);
|
||||
|
||||
let result = format1.check_other(&format2);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_other_different_disk_id() {
|
||||
let format1 = FormatV3::new(1, 2);
|
||||
let mut format2 = format1.clone();
|
||||
format2.erasure.sets[0][0] = Uuid::new_v4();
|
||||
|
||||
let result = format1.check_other(&format2);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_other_disk_not_in_sets() {
|
||||
let format1 = FormatV3::new(1, 2);
|
||||
let mut format2 = format1.clone();
|
||||
format2.erasure.this = Uuid::new_v4(); // Set to a UUID not in any set
|
||||
|
||||
let result = format1.check_other(&format2);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_meta_version_serialization() {
|
||||
let v1 = FormatMetaVersion::V1;
|
||||
let json = serde_json::to_string(&v1).unwrap();
|
||||
assert_eq!(json, "\"1\"");
|
||||
|
||||
let unknown = FormatMetaVersion::Unknown;
|
||||
let deserialized: FormatMetaVersion = serde_json::from_str("\"unknown\"").unwrap();
|
||||
assert_eq!(deserialized, unknown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_backend_serialization() {
|
||||
let erasure = FormatBackend::Erasure;
|
||||
let json = serde_json::to_string(&erasure).unwrap();
|
||||
assert_eq!(json, "\"xl\"");
|
||||
|
||||
let single = FormatBackend::ErasureSingle;
|
||||
let json = serde_json::to_string(&single).unwrap();
|
||||
assert_eq!(json, "\"xl-single\"");
|
||||
|
||||
let unknown = FormatBackend::Unknown;
|
||||
let deserialized: FormatBackend = serde_json::from_str("\"unknown\"").unwrap();
|
||||
assert_eq!(deserialized, unknown);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_erasure_version_serialization() {
|
||||
let v1 = FormatErasureVersion::V1;
|
||||
let json = serde_json::to_string(&v1).unwrap();
|
||||
assert_eq!(json, "\"1\"");
|
||||
|
||||
let v2 = FormatErasureVersion::V2;
|
||||
let json = serde_json::to_string(&v2).unwrap();
|
||||
assert_eq!(json, "\"2\"");
|
||||
|
||||
let v3 = FormatErasureVersion::V3;
|
||||
let json = serde_json::to_string(&v3).unwrap();
|
||||
assert_eq!(json, "\"3\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distribution_algo_version_serialization() {
|
||||
let v1 = DistributionAlgoVersion::V1;
|
||||
let json = serde_json::to_string(&v1).unwrap();
|
||||
assert_eq!(json, "\"CRCMOD\"");
|
||||
|
||||
let v2 = DistributionAlgoVersion::V2;
|
||||
let json = serde_json::to_string(&v2).unwrap();
|
||||
assert_eq!(json, "\"SIPMOD\"");
|
||||
|
||||
let v3 = DistributionAlgoVersion::V3;
|
||||
let json = serde_json::to_string(&v3).unwrap();
|
||||
assert_eq!(json, "\"SIPMOD+PARITY\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_v3_round_trip_serialization() {
|
||||
let original = FormatV3::new(2, 3);
|
||||
let json = original.to_json().unwrap();
|
||||
let deserialized = FormatV3::try_from(json.as_str()).unwrap();
|
||||
|
||||
assert_eq!(original.version, deserialized.version);
|
||||
assert_eq!(original.format, deserialized.format);
|
||||
assert_eq!(original.erasure.version, deserialized.erasure.version);
|
||||
assert_eq!(original.erasure.sets.len(), deserialized.erasure.sets.len());
|
||||
assert_eq!(original.erasure.distribution_algo, deserialized.erasure.distribution_algo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,530 @@
|
||||
use std::{fs::Metadata, path::Path};
|
||||
|
||||
use tokio::{
|
||||
fs::{self, File},
|
||||
io,
|
||||
};
|
||||
|
||||
pub const SLASH_SEPARATOR: &str = "/";
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
if f1.dev() != f2.dev() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.ino() != f2.ino() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.size() != f2.size() {
|
||||
return false;
|
||||
}
|
||||
if f1.permissions() != f2.permissions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.mtime() != f2.mtime() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool {
|
||||
if f1.permissions() != f2.permissions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.file_type() != f2.file_type() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.len() != f2.len() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
type FileMode = usize;
|
||||
|
||||
pub const O_RDONLY: FileMode = 0x00000;
|
||||
pub const O_WRONLY: FileMode = 0x00001;
|
||||
pub const O_RDWR: FileMode = 0x00002;
|
||||
pub const O_CREATE: FileMode = 0x00040;
|
||||
// pub const O_EXCL: FileMode = 0x00080;
|
||||
// pub const O_NOCTTY: FileMode = 0x00100;
|
||||
pub const O_TRUNC: FileMode = 0x00200;
|
||||
// pub const O_NONBLOCK: FileMode = 0x00800;
|
||||
pub const O_APPEND: FileMode = 0x00400;
|
||||
// pub const O_SYNC: FileMode = 0x01000;
|
||||
// pub const O_ASYNC: FileMode = 0x02000;
|
||||
// pub const O_CLOEXEC: FileMode = 0x80000;
|
||||
|
||||
// read: bool,
|
||||
// write: bool,
|
||||
// append: bool,
|
||||
// truncate: bool,
|
||||
// create: bool,
|
||||
// create_new: bool,
|
||||
|
||||
pub async fn open_file(path: impl AsRef<Path>, mode: FileMode) -> io::Result<File> {
|
||||
let mut opts = fs::OpenOptions::new();
|
||||
|
||||
match mode & (O_RDONLY | O_WRONLY | O_RDWR) {
|
||||
O_RDONLY => {
|
||||
opts.read(true);
|
||||
}
|
||||
O_WRONLY => {
|
||||
opts.write(true);
|
||||
}
|
||||
O_RDWR => {
|
||||
opts.read(true);
|
||||
opts.write(true);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if mode & O_CREATE != 0 {
|
||||
opts.create(true);
|
||||
}
|
||||
|
||||
if mode & O_APPEND != 0 {
|
||||
opts.append(true);
|
||||
}
|
||||
|
||||
if mode & O_TRUNC != 0 {
|
||||
opts.truncate(true);
|
||||
}
|
||||
|
||||
opts.open(path.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn access(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::metadata(path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn access_std(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
tokio::task::block_in_place(|| std::fs::metadata(path))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn lstat(path: impl AsRef<Path>) -> io::Result<Metadata> {
|
||||
fs::metadata(path).await
|
||||
}
|
||||
|
||||
pub fn lstat_std(path: impl AsRef<Path>) -> io::Result<Metadata> {
|
||||
tokio::task::block_in_place(|| std::fs::metadata(path))
|
||||
}
|
||||
|
||||
pub async fn make_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::create_dir_all(path.as_ref()).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn remove(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let meta = fs::metadata(path.as_ref()).await?;
|
||||
if meta.is_dir() {
|
||||
fs::remove_dir(path.as_ref()).await
|
||||
} else {
|
||||
fs::remove_file(path.as_ref()).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_all(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let meta = fs::metadata(path.as_ref()).await?;
|
||||
if meta.is_dir() {
|
||||
fs::remove_dir_all(path.as_ref()).await
|
||||
} else {
|
||||
fs::remove_file(path.as_ref()).await
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub fn remove_std(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
tokio::task::block_in_place(|| {
|
||||
let meta = std::fs::metadata(path)?;
|
||||
if meta.is_dir() {
|
||||
std::fs::remove_dir(path)
|
||||
} else {
|
||||
std::fs::remove_file(path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn remove_all_std(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let path = path.as_ref();
|
||||
tokio::task::block_in_place(|| {
|
||||
let meta = std::fs::metadata(path)?;
|
||||
if meta.is_dir() {
|
||||
std::fs::remove_dir_all(path)
|
||||
} else {
|
||||
std::fs::remove_file(path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn mkdir(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::create_dir(path.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::rename(from, to).await
|
||||
}
|
||||
|
||||
pub fn rename_std(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
|
||||
tokio::task::block_in_place(|| std::fs::rename(from, to))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn read_file(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
|
||||
fs::read(path.as_ref()).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tempfile::TempDir;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_mode_constants() {
|
||||
assert_eq!(O_RDONLY, 0x00000);
|
||||
assert_eq!(O_WRONLY, 0x00001);
|
||||
assert_eq!(O_RDWR, 0x00002);
|
||||
assert_eq!(O_CREATE, 0x00040);
|
||||
assert_eq!(O_TRUNC, 0x00200);
|
||||
assert_eq!(O_APPEND, 0x00400);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_open_file_read_only() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_readonly.txt");
|
||||
|
||||
// Create a test file
|
||||
tokio::fs::write(&file_path, b"test content").await.unwrap();
|
||||
|
||||
// Test opening in read-only mode
|
||||
let file = open_file(&file_path, O_RDONLY).await;
|
||||
assert!(file.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_open_file_write_only() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_writeonly.txt");
|
||||
|
||||
// Test opening in write-only mode with create flag
|
||||
let mut file = open_file(&file_path, O_WRONLY | O_CREATE).await.unwrap();
|
||||
|
||||
// Should be able to write
|
||||
file.write_all(b"write test").await.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_open_file_read_write() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_readwrite.txt");
|
||||
|
||||
// Test opening in read-write mode with create flag
|
||||
let mut file = open_file(&file_path, O_RDWR | O_CREATE).await.unwrap();
|
||||
|
||||
// Should be able to write and read
|
||||
file.write_all(b"read-write test").await.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_open_file_append() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_append.txt");
|
||||
|
||||
// Create initial content
|
||||
tokio::fs::write(&file_path, b"initial").await.unwrap();
|
||||
|
||||
// Open in append mode
|
||||
let mut file = open_file(&file_path, O_WRONLY | O_APPEND).await.unwrap();
|
||||
file.write_all(b" appended").await.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
|
||||
// Verify content
|
||||
let content = tokio::fs::read_to_string(&file_path).await.unwrap();
|
||||
assert_eq!(content, "initial appended");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_open_file_truncate() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_truncate.txt");
|
||||
|
||||
// Create initial content
|
||||
tokio::fs::write(&file_path, b"initial content").await.unwrap();
|
||||
|
||||
// Open with truncate flag
|
||||
let mut file = open_file(&file_path, O_WRONLY | O_TRUNC).await.unwrap();
|
||||
file.write_all(b"new").await.unwrap();
|
||||
file.flush().await.unwrap();
|
||||
|
||||
// Verify content was truncated
|
||||
let content = tokio::fs::read_to_string(&file_path).await.unwrap();
|
||||
assert_eq!(content, "new");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_access() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_access.txt");
|
||||
|
||||
// Should fail for non-existent file
|
||||
assert!(access(&file_path).await.is_err());
|
||||
|
||||
// Create file and test again
|
||||
tokio::fs::write(&file_path, b"test").await.unwrap();
|
||||
assert!(access(&file_path).await.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_std() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_access_std.txt");
|
||||
|
||||
// Should fail for non-existent file
|
||||
assert!(access_std(&file_path).is_err());
|
||||
|
||||
// Create file and test again
|
||||
std::fs::write(&file_path, b"test").unwrap();
|
||||
assert!(access_std(&file_path).is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_lstat() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_lstat.txt");
|
||||
|
||||
// Create test file
|
||||
tokio::fs::write(&file_path, b"test content").await.unwrap();
|
||||
|
||||
// Test lstat
|
||||
let metadata = lstat(&file_path).await.unwrap();
|
||||
assert!(metadata.is_file());
|
||||
assert_eq!(metadata.len(), 12); // "test content" is 12 bytes
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lstat_std() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_lstat_std.txt");
|
||||
|
||||
// Create test file
|
||||
std::fs::write(&file_path, b"test content").unwrap();
|
||||
|
||||
// Test lstat_std
|
||||
let metadata = lstat_std(&file_path).unwrap();
|
||||
assert!(metadata.is_file());
|
||||
assert_eq!(metadata.len(), 12); // "test content" is 12 bytes
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_make_dir_all() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let nested_path = temp_dir.path().join("level1").join("level2").join("level3");
|
||||
|
||||
// Should create nested directories
|
||||
assert!(make_dir_all(&nested_path).await.is_ok());
|
||||
assert!(nested_path.exists());
|
||||
assert!(nested_path.is_dir());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_remove_file() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_remove.txt");
|
||||
|
||||
// Create test file
|
||||
tokio::fs::write(&file_path, b"test").await.unwrap();
|
||||
assert!(file_path.exists());
|
||||
|
||||
// Remove file
|
||||
assert!(remove(&file_path).await.is_ok());
|
||||
assert!(!file_path.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_remove_directory() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dir_path = temp_dir.path().join("test_remove_dir");
|
||||
|
||||
// Create test directory
|
||||
tokio::fs::create_dir(&dir_path).await.unwrap();
|
||||
assert!(dir_path.exists());
|
||||
|
||||
// Remove directory
|
||||
assert!(remove(&dir_path).await.is_ok());
|
||||
assert!(!dir_path.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_remove_all() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dir_path = temp_dir.path().join("test_remove_all");
|
||||
let file_path = dir_path.join("nested_file.txt");
|
||||
|
||||
// Create nested structure
|
||||
tokio::fs::create_dir(&dir_path).await.unwrap();
|
||||
tokio::fs::write(&file_path, b"nested content").await.unwrap();
|
||||
|
||||
// Remove all
|
||||
assert!(remove_all(&dir_path).await.is_ok());
|
||||
assert!(!dir_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_std() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_remove_std.txt");
|
||||
|
||||
// Create test file
|
||||
std::fs::write(&file_path, b"test").unwrap();
|
||||
assert!(file_path.exists());
|
||||
|
||||
// Remove file
|
||||
assert!(remove_std(&file_path).is_ok());
|
||||
assert!(!file_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_all_std() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dir_path = temp_dir.path().join("test_remove_all_std");
|
||||
let file_path = dir_path.join("nested_file.txt");
|
||||
|
||||
// Create nested structure
|
||||
std::fs::create_dir(&dir_path).unwrap();
|
||||
std::fs::write(&file_path, b"nested content").unwrap();
|
||||
|
||||
// Remove all
|
||||
assert!(remove_all_std(&dir_path).is_ok());
|
||||
assert!(!dir_path.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_mkdir() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let dir_path = temp_dir.path().join("test_mkdir");
|
||||
|
||||
// Create directory
|
||||
assert!(mkdir(&dir_path).await.is_ok());
|
||||
assert!(dir_path.exists());
|
||||
assert!(dir_path.is_dir());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_rename() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let old_path = temp_dir.path().join("old_name.txt");
|
||||
let new_path = temp_dir.path().join("new_name.txt");
|
||||
|
||||
// Create test file
|
||||
tokio::fs::write(&old_path, b"test content").await.unwrap();
|
||||
assert!(old_path.exists());
|
||||
assert!(!new_path.exists());
|
||||
|
||||
// Rename file
|
||||
assert!(rename(&old_path, &new_path).await.is_ok());
|
||||
assert!(!old_path.exists());
|
||||
assert!(new_path.exists());
|
||||
|
||||
// Verify content preserved
|
||||
let content = tokio::fs::read_to_string(&new_path).await.unwrap();
|
||||
assert_eq!(content, "test content");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rename_std() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let old_path = temp_dir.path().join("old_name_std.txt");
|
||||
let new_path = temp_dir.path().join("new_name_std.txt");
|
||||
|
||||
// Create test file
|
||||
std::fs::write(&old_path, b"test content").unwrap();
|
||||
assert!(old_path.exists());
|
||||
assert!(!new_path.exists());
|
||||
|
||||
// Rename file
|
||||
assert!(rename_std(&old_path, &new_path).is_ok());
|
||||
assert!(!old_path.exists());
|
||||
assert!(new_path.exists());
|
||||
|
||||
// Verify content preserved
|
||||
let content = std::fs::read_to_string(&new_path).unwrap();
|
||||
assert_eq!(content, "test content");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_read_file() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_read.txt");
|
||||
|
||||
let test_content = b"This is test content for reading";
|
||||
tokio::fs::write(&file_path, test_content).await.unwrap();
|
||||
|
||||
// Read file
|
||||
let read_content = read_file(&file_path).await.unwrap();
|
||||
assert_eq!(read_content, test_content);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_read_file_nonexistent() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("nonexistent.txt");
|
||||
|
||||
// Should fail for non-existent file
|
||||
assert!(read_file(&file_path).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_same_file() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file_path = temp_dir.path().join("test_same.txt");
|
||||
|
||||
// Create test file
|
||||
tokio::fs::write(&file_path, b"test content").await.unwrap();
|
||||
|
||||
// Get metadata twice
|
||||
let metadata1 = tokio::fs::metadata(&file_path).await.unwrap();
|
||||
let metadata2 = tokio::fs::metadata(&file_path).await.unwrap();
|
||||
|
||||
// Should be the same file
|
||||
assert!(same_file(&metadata1, &metadata2));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_different_files() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let file1_path = temp_dir.path().join("file1.txt");
|
||||
let file2_path = temp_dir.path().join("file2.txt");
|
||||
|
||||
// Create two different files
|
||||
tokio::fs::write(&file1_path, b"content1").await.unwrap();
|
||||
tokio::fs::write(&file2_path, b"content2").await.unwrap();
|
||||
|
||||
// Get metadata
|
||||
let metadata1 = tokio::fs::metadata(&file1_path).await.unwrap();
|
||||
let metadata2 = tokio::fs::metadata(&file2_path).await.unwrap();
|
||||
|
||||
// Should be different files
|
||||
assert!(!same_file(&metadata1, &metadata2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_slash_separator() {
|
||||
assert_eq!(SLASH_SEPARATOR, "/");
|
||||
}
|
||||
}
|
||||
+560
-486
File diff suppressed because it is too large
Load Diff
+397
-616
File diff suppressed because it is too large
Load Diff
+21
-38
@@ -3,31 +3,28 @@ use std::{
|
||||
path::{Component, Path},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
disk::error::{is_sys_err_not_dir, is_sys_err_path_not_found, os_is_not_exist},
|
||||
utils::{self, os::same_disk},
|
||||
};
|
||||
use common::error::{Error, Result};
|
||||
use super::error::Result;
|
||||
use crate::disk::error_conv::to_file_error;
|
||||
use tokio::fs;
|
||||
|
||||
use super::error::{os_err_to_file_err, os_is_exist, DiskError};
|
||||
use super::error::DiskError;
|
||||
|
||||
pub fn check_path_length(path_name: &str) -> Result<()> {
|
||||
// Apple OS X path length is limited to 1016
|
||||
if cfg!(target_os = "macos") && path_name.len() > 1016 {
|
||||
return Err(Error::new(DiskError::FileNameTooLong));
|
||||
return Err(DiskError::FileNameTooLong);
|
||||
}
|
||||
|
||||
// Disallow more than 1024 characters on windows, there
|
||||
// are no known name_max limits on Windows.
|
||||
if cfg!(target_os = "windows") && path_name.len() > 1024 {
|
||||
return Err(Error::new(DiskError::FileNameTooLong));
|
||||
return Err(DiskError::FileNameTooLong);
|
||||
}
|
||||
|
||||
// On Unix we reject paths if they are just '.', '..' or '/'
|
||||
let invalid_paths = [".", "..", "/"];
|
||||
if invalid_paths.contains(&path_name) {
|
||||
return Err(Error::new(DiskError::FileAccessDenied));
|
||||
return Err(DiskError::FileAccessDenied);
|
||||
}
|
||||
|
||||
// Check each path segment length is > 255 on all Unix
|
||||
@@ -40,7 +37,7 @@ pub fn check_path_length(path_name: &str) -> Result<()> {
|
||||
_ => {
|
||||
count += 1;
|
||||
if count > 255 {
|
||||
return Err(Error::new(DiskError::FileNameTooLong));
|
||||
return Err(DiskError::FileNameTooLong);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,19 +52,15 @@ pub fn is_root_disk(disk_path: &str, root_disk: &str) -> Result<bool> {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
same_disk(disk_path, root_disk)
|
||||
rustfs_utils::os::same_disk(disk_path, root_disk).map_err(|e| to_file_error(e).into())
|
||||
}
|
||||
|
||||
pub async fn make_dir_all(path: impl AsRef<Path>, base_dir: impl AsRef<Path>) -> Result<()> {
|
||||
check_path_length(path.as_ref().to_string_lossy().to_string().as_str())?;
|
||||
|
||||
if let Err(e) = reliable_mkdir_all(path.as_ref(), base_dir.as_ref()).await {
|
||||
if is_sys_err_not_dir(&e) || is_sys_err_path_not_found(&e) {
|
||||
return Err(Error::new(DiskError::FileAccessDenied));
|
||||
}
|
||||
|
||||
return Err(os_err_to_file_err(e));
|
||||
}
|
||||
reliable_mkdir_all(path.as_ref(), base_dir.as_ref())
|
||||
.await
|
||||
.map_err(to_file_error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -77,7 +70,7 @@ pub async fn is_empty_dir(path: impl AsRef<Path>) -> bool {
|
||||
}
|
||||
|
||||
// read_dir count read limit. when count == 0 unlimit.
|
||||
pub async fn read_dir(path: impl AsRef<Path>, count: i32) -> Result<Vec<String>> {
|
||||
pub async fn read_dir(path: impl AsRef<Path>, count: i32) -> std::io::Result<Vec<String>> {
|
||||
let mut entries = fs::read_dir(path.as_ref()).await?;
|
||||
|
||||
let mut volumes = Vec::new();
|
||||
@@ -96,7 +89,7 @@ pub async fn read_dir(path: impl AsRef<Path>, count: i32) -> Result<Vec<String>>
|
||||
if file_type.is_file() {
|
||||
volumes.push(name);
|
||||
} else if file_type.is_dir() {
|
||||
volumes.push(format!("{}{}", name, utils::path::SLASH_SEPARATOR));
|
||||
volumes.push(format!("{}{}", name, super::fs::SLASH_SEPARATOR));
|
||||
}
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
@@ -115,17 +108,7 @@ pub async fn rename_all(
|
||||
) -> Result<()> {
|
||||
reliable_rename(src_file_path, dst_file_path.as_ref(), base_dir)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if is_sys_err_not_dir(&e) || !os_is_not_exist(&e) || is_sys_err_path_not_found(&e) {
|
||||
Error::new(DiskError::FileAccessDenied)
|
||||
} else if os_is_not_exist(&e) {
|
||||
Error::new(DiskError::FileNotFound)
|
||||
} else if os_is_exist(&e) {
|
||||
Error::new(DiskError::IsNotRegular)
|
||||
} else {
|
||||
Error::new(e)
|
||||
}
|
||||
})?;
|
||||
.map_err(to_file_error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -144,8 +127,8 @@ pub async fn reliable_rename(
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if let Err(e) = utils::fs::rename_std(src_file_path.as_ref(), dst_file_path.as_ref()) {
|
||||
if os_is_not_exist(&e) && i == 0 {
|
||||
if let Err(e) = super::fs::rename_std(src_file_path.as_ref(), dst_file_path.as_ref()) {
|
||||
if e.kind() == io::ErrorKind::NotFound && i == 0 {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
@@ -171,7 +154,7 @@ pub async fn reliable_mkdir_all(path: impl AsRef<Path>, base_dir: impl AsRef<Pat
|
||||
let mut base_dir = base_dir.as_ref();
|
||||
loop {
|
||||
if let Err(e) = os_mkdir_all(path.as_ref(), base_dir).await {
|
||||
if os_is_not_exist(&e) && i == 0 {
|
||||
if e.kind() == io::ErrorKind::NotFound && i == 0 {
|
||||
i += 1;
|
||||
|
||||
if let Some(base_parent) = base_dir.parent() {
|
||||
@@ -200,8 +183,8 @@ 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 了
|
||||
if let Err(e) = utils::fs::make_dir_all(&parent).await {
|
||||
if os_is_exist(&e) {
|
||||
if let Err(e) = super::fs::make_dir_all(&parent).await {
|
||||
if e.kind() == io::ErrorKind::AlreadyExists {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -210,8 +193,8 @@ pub async fn os_mkdir_all(dir_path: impl AsRef<Path>, base_dir: impl AsRef<Path>
|
||||
// Box::pin(os_mkdir_all(&parent, &base_dir)).await?;
|
||||
}
|
||||
|
||||
if let Err(e) = utils::fs::mkdir(dir_path.as_ref()).await {
|
||||
if os_is_exist(&e) {
|
||||
if let Err(e) = super::fs::mkdir(dir_path.as_ref()).await {
|
||||
if e.kind() == io::ErrorKind::AlreadyExists {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
+491
-233
File diff suppressed because it is too large
Load Diff
+16
-14
@@ -1,8 +1,8 @@
|
||||
use crate::utils::ellipses::*;
|
||||
use common::error::{Error, Result};
|
||||
use rustfs_utils::string::{ArgPattern, find_ellipses_patterns, has_ellipses};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
use std::io::{Error, Result};
|
||||
use tracing::debug;
|
||||
|
||||
/// Supported set sizes this is used to find the optimal
|
||||
@@ -89,7 +89,7 @@ pub struct DisksLayout {
|
||||
impl DisksLayout {
|
||||
pub fn from_volumes<T: AsRef<str>>(args: &[T]) -> Result<Self> {
|
||||
if args.is_empty() {
|
||||
return Err(Error::from_string("Invalid argument"));
|
||||
return Err(Error::other("Invalid argument"));
|
||||
}
|
||||
|
||||
let is_ellipses = args.iter().any(|v| has_ellipses(&[v]));
|
||||
@@ -98,7 +98,7 @@ impl DisksLayout {
|
||||
debug!("{} not set use default:0, {:?}", ENV_RUSTFS_ERASURE_SET_DRIVE_COUNT, err);
|
||||
"0".to_string()
|
||||
});
|
||||
let set_drive_count: usize = set_drive_count_env.parse()?;
|
||||
let set_drive_count: usize = set_drive_count_env.parse().map_err(Error::other)?;
|
||||
|
||||
// None of the args have ellipses use the old style.
|
||||
if !is_ellipses {
|
||||
@@ -116,7 +116,7 @@ impl DisksLayout {
|
||||
let mut layout = Vec::with_capacity(args.len());
|
||||
for arg in args.iter() {
|
||||
if !has_ellipses(&[arg]) && args.len() > 1 {
|
||||
return Err(Error::from_string(
|
||||
return Err(Error::other(
|
||||
"all args must have ellipses for pool expansion (Invalid arguments specified)",
|
||||
));
|
||||
}
|
||||
@@ -189,7 +189,7 @@ fn get_all_sets<T: AsRef<str>>(set_drive_count: usize, is_ellipses: bool, args:
|
||||
for args in set_args.iter() {
|
||||
for arg in args {
|
||||
if unique_args.contains(arg) {
|
||||
return Err(Error::from_string(format!("Input args {} has duplicate ellipses", arg)));
|
||||
return Err(Error::other(format!("Input args {} has duplicate ellipses", arg)));
|
||||
}
|
||||
unique_args.insert(arg);
|
||||
}
|
||||
@@ -245,7 +245,7 @@ impl EndpointSet {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_volumes<T: AsRef<str>>(args: &[T], set_drive_count: usize) -> Result<Self, Error> {
|
||||
pub fn from_volumes<T: AsRef<str>>(args: &[T], set_drive_count: usize) -> Result<Self> {
|
||||
let mut arg_patterns = Vec::with_capacity(args.len());
|
||||
for arg in args {
|
||||
arg_patterns.push(find_ellipses_patterns(arg.as_ref())?);
|
||||
@@ -377,20 +377,20 @@ fn get_set_indexes<T: AsRef<str>>(
|
||||
arg_patterns: &[ArgPattern],
|
||||
) -> Result<Vec<Vec<usize>>> {
|
||||
if args.is_empty() || total_sizes.is_empty() {
|
||||
return Err(Error::from_string("Invalid argument"));
|
||||
return Err(Error::other("Invalid argument"));
|
||||
}
|
||||
|
||||
for &size in total_sizes {
|
||||
// Check if total_sizes has minimum range upto set_size
|
||||
if size < SET_SIZES[0] || size < set_drive_count {
|
||||
return Err(Error::from_string(format!("Incorrect number of endpoints provided, size {}", size)));
|
||||
return Err(Error::other(format!("Incorrect number of endpoints provided, size {}", size)));
|
||||
}
|
||||
}
|
||||
|
||||
let common_size = get_divisible_size(total_sizes);
|
||||
let mut set_counts = possible_set_counts(common_size);
|
||||
if set_counts.is_empty() {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"Incorrect number of endpoints provided, number of drives {} is not divisible by any supported erasure set sizes {}",
|
||||
common_size, 0
|
||||
)));
|
||||
@@ -399,7 +399,7 @@ fn get_set_indexes<T: AsRef<str>>(
|
||||
// Returns possible set counts with symmetry.
|
||||
set_counts = possible_set_counts_with_symmetry(&set_counts, arg_patterns);
|
||||
if set_counts.is_empty() {
|
||||
return Err(Error::from_string("No symmetric distribution detected with input endpoints provided"));
|
||||
return Err(Error::other("No symmetric distribution detected with input endpoints provided"));
|
||||
}
|
||||
|
||||
let set_size = {
|
||||
@@ -407,7 +407,7 @@ fn get_set_indexes<T: AsRef<str>>(
|
||||
let has_set_drive_count = set_counts.contains(&set_drive_count);
|
||||
|
||||
if !has_set_drive_count {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"Invalid set drive count {}. Acceptable values for {:?} number drives are {:?}",
|
||||
set_drive_count, common_size, &set_counts
|
||||
)));
|
||||
@@ -416,7 +416,7 @@ fn get_set_indexes<T: AsRef<str>>(
|
||||
} else {
|
||||
set_counts = possible_set_counts_with_symmetry(&set_counts, arg_patterns);
|
||||
if set_counts.is_empty() {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"No symmetric distribution detected with input endpoints , drives {} cannot be spread symmetrically by any supported erasure set sizes {:?}",
|
||||
common_size, &set_counts
|
||||
)));
|
||||
@@ -427,7 +427,7 @@ fn get_set_indexes<T: AsRef<str>>(
|
||||
};
|
||||
|
||||
if !is_valid_set_size(set_size) {
|
||||
return Err(Error::from_string("Incorrect number of endpoints provided3"));
|
||||
return Err(Error::other("Incorrect number of endpoints provided3"));
|
||||
}
|
||||
|
||||
Ok(total_sizes
|
||||
@@ -443,6 +443,8 @@ fn get_total_sizes(arg_patterns: &[ArgPattern]) -> Vec<usize> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rustfs_utils::string::Pattern;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl PartialEq for EndpointSet {
|
||||
|
||||
+43
-39
@@ -1,14 +1,15 @@
|
||||
use rustfs_utils::{XHost, check_local_server_addr, get_host_ip, is_local_host};
|
||||
use tracing::{instrument, warn};
|
||||
|
||||
use crate::{
|
||||
disk::endpoint::{Endpoint, EndpointType},
|
||||
disks_layout::DisksLayout,
|
||||
global::global_rustfs_port,
|
||||
utils::net::{self, XHost},
|
||||
// utils::net::{self, XHost},
|
||||
};
|
||||
use common::error::{Error, Result};
|
||||
use std::io::{Error, Result};
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap, HashSet},
|
||||
collections::{HashMap, HashSet, hash_map::Entry},
|
||||
net::IpAddr,
|
||||
};
|
||||
|
||||
@@ -76,7 +77,7 @@ impl<T: AsRef<str>> TryFrom<&[T]> for Endpoints {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
let endpoint = match Endpoint::try_from(arg.as_ref()) {
|
||||
Ok(ep) => ep,
|
||||
Err(e) => return Err(Error::from_string(format!("'{}': {}", arg.as_ref(), e))),
|
||||
Err(e) => return Err(Error::other(format!("'{}': {}", arg.as_ref(), e))),
|
||||
};
|
||||
|
||||
// All endpoints have to be same type and scheme if applicable.
|
||||
@@ -84,15 +85,15 @@ impl<T: AsRef<str>> TryFrom<&[T]> for Endpoints {
|
||||
endpoint_type = Some(endpoint.get_type());
|
||||
schema = Some(endpoint.url.scheme().to_owned());
|
||||
} else if Some(endpoint.get_type()) != endpoint_type {
|
||||
return Err(Error::from_string("mixed style endpoints are not supported"));
|
||||
return Err(Error::other("mixed style endpoints are not supported"));
|
||||
} else if Some(endpoint.url.scheme()) != schema.as_deref() {
|
||||
return Err(Error::from_string("mixed scheme is not supported"));
|
||||
return Err(Error::other("mixed scheme is not supported"));
|
||||
}
|
||||
|
||||
// Check for duplicate endpoints.
|
||||
let endpoint_str = endpoint.to_string();
|
||||
if uniq_set.contains(&endpoint_str) {
|
||||
return Err(Error::from_string("duplicate endpoints found"));
|
||||
return Err(Error::other("duplicate endpoints found"));
|
||||
}
|
||||
|
||||
uniq_set.insert(endpoint_str);
|
||||
@@ -156,10 +157,10 @@ impl PoolEndpointList {
|
||||
/// hostnames and discovers those are local or remote.
|
||||
fn create_pool_endpoints(server_addr: &str, disks_layout: &DisksLayout) -> Result<Self> {
|
||||
if disks_layout.is_empty_layout() {
|
||||
return Err(Error::from_string("invalid number of endpoints"));
|
||||
return Err(Error::other("invalid number of endpoints"));
|
||||
}
|
||||
|
||||
let server_addr = net::check_local_server_addr(server_addr)?;
|
||||
let server_addr = check_local_server_addr(server_addr)?;
|
||||
|
||||
// For single arg, return single drive EC setup.
|
||||
if disks_layout.is_single_drive_layout() {
|
||||
@@ -167,7 +168,7 @@ impl PoolEndpointList {
|
||||
endpoint.update_is_local(server_addr.port())?;
|
||||
|
||||
if endpoint.get_type() != EndpointType::Path {
|
||||
return Err(Error::from_string("use path style endpoint for single node setup"));
|
||||
return Err(Error::other("use path style endpoint for single node setup"));
|
||||
}
|
||||
|
||||
endpoint.set_pool_index(0);
|
||||
@@ -201,7 +202,7 @@ impl PoolEndpointList {
|
||||
}
|
||||
|
||||
if endpoints.as_ref().is_empty() {
|
||||
return Err(Error::from_string("invalid number of endpoints"));
|
||||
return Err(Error::other("invalid number of endpoints"));
|
||||
}
|
||||
|
||||
pool_endpoints.push(endpoints);
|
||||
@@ -227,15 +228,14 @@ impl PoolEndpointList {
|
||||
|
||||
let host = ep.url.host().unwrap();
|
||||
let host_ip_set = host_ip_cache.entry(host.clone()).or_insert({
|
||||
net::get_host_ip(host.clone())
|
||||
.map_err(|e| Error::from_string(format!("host '{}' cannot resolve: {}", host, e)))?
|
||||
get_host_ip(host.clone()).map_err(|e| Error::other(format!("host '{}' cannot resolve: {}", host, e)))?
|
||||
});
|
||||
|
||||
let path = ep.get_file_path();
|
||||
match path_ip_map.entry(path) {
|
||||
Entry::Occupied(mut e) => {
|
||||
if e.get().intersection(host_ip_set).count() > 0 {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"same path '{}' can not be served by different port on same address",
|
||||
path
|
||||
)));
|
||||
@@ -257,7 +257,7 @@ impl PoolEndpointList {
|
||||
|
||||
let path = ep.get_file_path();
|
||||
if local_path_set.contains(path) {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"path '{}' cannot be served by different address on same server",
|
||||
path
|
||||
)));
|
||||
@@ -285,7 +285,7 @@ impl PoolEndpointList {
|
||||
// If all endpoints have same port number, Just treat it as local erasure setup
|
||||
// using URL style endpoints.
|
||||
if local_port_set.len() == 1 && local_server_host_set.len() > 1 {
|
||||
return Err(Error::from_string("all local endpoints should not have different hostnames/ips"));
|
||||
return Err(Error::other("all local endpoints should not have different hostnames/ips"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ impl PoolEndpointList {
|
||||
ep.is_local = true;
|
||||
}
|
||||
Some(host) => {
|
||||
ep.is_local = net::is_local_host(host, ep.url.port().unwrap_or_default(), local_port)?;
|
||||
ep.is_local = is_local_host(host, ep.url.port().unwrap_or_default(), local_port)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,7 +371,7 @@ impl PoolEndpointList {
|
||||
resolved_set.insert((i, j));
|
||||
continue;
|
||||
}
|
||||
Some(host) => match net::is_local_host(host, ep.url.port().unwrap_or_default(), local_port) {
|
||||
Some(host) => match is_local_host(host, ep.url.port().unwrap_or_default(), local_port) {
|
||||
Ok(is_local) => {
|
||||
if !found_local {
|
||||
found_local = is_local;
|
||||
@@ -453,7 +453,7 @@ impl EndpointServerPools {
|
||||
/// both ellipses and without ellipses transparently.
|
||||
pub fn create_server_endpoints(server_addr: &str, disks_layout: &DisksLayout) -> Result<(EndpointServerPools, SetupType)> {
|
||||
if disks_layout.pools.is_empty() {
|
||||
return Err(Error::from_string("Invalid arguments specified"));
|
||||
return Err(Error::other("Invalid arguments specified"));
|
||||
}
|
||||
|
||||
let pool_eps = PoolEndpointList::create_pool_endpoints(server_addr, disks_layout)?;
|
||||
@@ -490,7 +490,7 @@ impl EndpointServerPools {
|
||||
|
||||
for ep in eps.endpoints.as_ref() {
|
||||
if exits.contains(&ep.to_string()) {
|
||||
return Err(Error::from_string("duplicate endpoints found"));
|
||||
return Err(Error::other("duplicate endpoints found"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,6 +606,8 @@ impl EndpointServerPools {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rustfs_utils::must_get_local_ips;
|
||||
|
||||
use super::*;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -664,8 +666,8 @@ mod test {
|
||||
None,
|
||||
6,
|
||||
),
|
||||
(vec!["d1", "d2", "d3", "d1"], Some(Error::from_string("duplicate endpoints found")), 7),
|
||||
(vec!["d1", "d2", "d3", "./d1"], Some(Error::from_string("duplicate endpoints found")), 8),
|
||||
(vec!["d1", "d2", "d3", "d1"], Some(Error::other("duplicate endpoints found")), 7),
|
||||
(vec!["d1", "d2", "d3", "./d1"], Some(Error::other("duplicate endpoints found")), 8),
|
||||
(
|
||||
vec![
|
||||
"http://localhost/d1",
|
||||
@@ -673,17 +675,17 @@ mod test {
|
||||
"http://localhost/d1",
|
||||
"http://localhost/d4",
|
||||
],
|
||||
Some(Error::from_string("duplicate endpoints found")),
|
||||
Some(Error::other("duplicate endpoints found")),
|
||||
9,
|
||||
),
|
||||
(
|
||||
vec!["ftp://server/d1", "http://server/d2", "http://server/d3", "http://server/d4"],
|
||||
Some(Error::from_string("'ftp://server/d1': invalid URL endpoint format")),
|
||||
Some(Error::other("'ftp://server/d1': io error invalid URL endpoint format")),
|
||||
10,
|
||||
),
|
||||
(
|
||||
vec!["d1", "http://localhost/d2", "d3", "d4"],
|
||||
Some(Error::from_string("mixed style endpoints are not supported")),
|
||||
Some(Error::other("mixed style endpoints are not supported")),
|
||||
11,
|
||||
),
|
||||
(
|
||||
@@ -693,7 +695,7 @@ mod test {
|
||||
"http://example.net/d1",
|
||||
"https://example.edut/d1",
|
||||
],
|
||||
Some(Error::from_string("mixed scheme is not supported")),
|
||||
Some(Error::other("mixed scheme is not supported")),
|
||||
12,
|
||||
),
|
||||
(
|
||||
@@ -703,9 +705,7 @@ mod test {
|
||||
"192.168.1.210:9000/tmp/dir2",
|
||||
"192.168.110:9000/tmp/dir3",
|
||||
],
|
||||
Some(Error::from_string(
|
||||
"'192.168.1.210:9000/tmp/dir0': invalid URL endpoint format: missing scheme http or https",
|
||||
)),
|
||||
Some(Error::other("'192.168.1.210:9000/tmp/dir0': io error")),
|
||||
13,
|
||||
),
|
||||
];
|
||||
@@ -719,7 +719,13 @@ mod test {
|
||||
(None, Ok(_)) => {}
|
||||
(Some(e), Ok(_)) => panic!("{}: error: expected = {}, got = <nil>", test_case.2, e),
|
||||
(Some(e), Err(e2)) => {
|
||||
assert_eq!(e.to_string(), e2.to_string(), "{}: error: expected = {}, got = {}", test_case.2, e, e2)
|
||||
assert!(
|
||||
e2.to_string().starts_with(&e.to_string()),
|
||||
"{}: error: expected = {}, got = {}",
|
||||
test_case.2,
|
||||
e,
|
||||
e2
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -739,7 +745,7 @@ mod test {
|
||||
|
||||
// Filter ipList by IPs those do not start with '127.'.
|
||||
let non_loop_back_i_ps =
|
||||
net::must_get_local_ips().map_or(vec![], |v| v.into_iter().filter(|ip| ip.is_ipv4() && ip.is_loopback()).collect());
|
||||
must_get_local_ips().map_or(vec![], |v| v.into_iter().filter(|ip| ip.is_ipv4() && ip.is_loopback()).collect());
|
||||
if non_loop_back_i_ps.is_empty() {
|
||||
panic!("No non-loop back IP address found for this host");
|
||||
}
|
||||
@@ -811,7 +817,7 @@ mod test {
|
||||
TestCase {
|
||||
num: 1,
|
||||
server_addr: "localhost",
|
||||
expected_err: Some(Error::from_string("address localhost: missing port in address")),
|
||||
expected_err: Some(Error::other("address localhost: missing port in address")),
|
||||
..Default::default()
|
||||
},
|
||||
// Erasure Single Drive
|
||||
@@ -819,7 +825,7 @@ mod test {
|
||||
num: 2,
|
||||
server_addr: "localhost:9000",
|
||||
args: vec!["http://localhost/d1"],
|
||||
expected_err: Some(Error::from_string("use path style endpoint for single node setup")),
|
||||
expected_err: Some(Error::other("use path style endpoint for single node setup")),
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
@@ -859,7 +865,7 @@ mod test {
|
||||
"https://example.com/d1",
|
||||
"https://example.com/d2",
|
||||
],
|
||||
expected_err: Some(Error::from_string("same path '/d1' can not be served by different port on same address")),
|
||||
expected_err: Some(Error::other("same path '/d1' can not be served by different port on same address")),
|
||||
..Default::default()
|
||||
},
|
||||
// Erasure Setup with PathEndpointType
|
||||
@@ -953,7 +959,7 @@ mod test {
|
||||
"http://127.0.0.1/d3",
|
||||
"http://127.0.0.1/d4",
|
||||
],
|
||||
expected_err: Some(Error::from_string("all local endpoints should not have different hostnames/ips")),
|
||||
expected_err: Some(Error::other("all local endpoints should not have different hostnames/ips")),
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
@@ -965,9 +971,7 @@ mod test {
|
||||
case7_endpoint1.as_str(),
|
||||
"http://10.0.0.2:9001/export",
|
||||
],
|
||||
expected_err: Some(Error::from_string(
|
||||
"same path '/export' can not be served by different port on same address",
|
||||
)),
|
||||
expected_err: Some(Error::other("same path '/export' can not be served by different port on same address")),
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
@@ -979,7 +983,7 @@ mod test {
|
||||
"http://10.0.0.1:9000/export",
|
||||
"http://10.0.0.2:9000/export",
|
||||
],
|
||||
expected_err: Some(Error::from_string("path '/export' cannot be served by different address on same server")),
|
||||
expected_err: Some(Error::other("path '/export' cannot be served by different address on same server")),
|
||||
..Default::default()
|
||||
},
|
||||
// DistErasure type
|
||||
|
||||
+31
-36
@@ -1,9 +1,8 @@
|
||||
use crate::bitrot::{BitrotReader, BitrotWriter};
|
||||
use crate::error::clone_err;
|
||||
use crate::disk::error::{Error, Result};
|
||||
use crate::disk::error_reduce::{OBJECT_OP_IGNORED_ERRS, reduce_write_quorum_errs};
|
||||
use crate::io::Etag;
|
||||
use crate::quorum::{object_op_ignored_errs, reduce_write_quorum_errs};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use common::error::{Error, Result};
|
||||
use futures::future::join_all;
|
||||
use reed_solomon_erasure::galois_8::ReedSolomon;
|
||||
use smallvec::SmallVec;
|
||||
@@ -73,11 +72,7 @@ impl Erasure {
|
||||
if total_size > 0 {
|
||||
let new_len = {
|
||||
let remain = total_size - total;
|
||||
if remain > self.block_size {
|
||||
self.block_size
|
||||
} else {
|
||||
remain
|
||||
}
|
||||
if remain > self.block_size { self.block_size } else { remain }
|
||||
};
|
||||
|
||||
if new_len == 0 && total > 0 {
|
||||
@@ -91,7 +86,7 @@ impl Erasure {
|
||||
if let ErrorKind::UnexpectedEof = e.kind() {
|
||||
break;
|
||||
} else {
|
||||
return Err(Error::new(e));
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -115,7 +110,7 @@ impl Erasure {
|
||||
if let Some(w) = w_op {
|
||||
w.write(blocks_inner[i_inner].clone()).await.err()
|
||||
} else {
|
||||
Some(Error::new(DiskError::DiskNotFound))
|
||||
Some(DiskError::DiskNotFound)
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -128,7 +123,7 @@ impl Erasure {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(err) = reduce_write_quorum_errs(&errs, object_op_ignored_errs().as_ref(), write_quorum) {
|
||||
if let Some(err) = reduce_write_quorum_errs(&errs, OBJECT_OP_IGNORED_ERRS, write_quorum) {
|
||||
warn!("Erasure encode errs {:?}", &errs);
|
||||
return Err(err);
|
||||
}
|
||||
@@ -160,7 +155,7 @@ impl Erasure {
|
||||
|
||||
// debug!("decode block from {} to {}", start_block, end_block);
|
||||
|
||||
let mut bytes_writed = 0;
|
||||
let mut bytes_written = 0;
|
||||
|
||||
for block_idx in start_block..=end_block {
|
||||
let (block_offset, block_length) = if start_block == end_block {
|
||||
@@ -183,37 +178,37 @@ impl Erasure {
|
||||
|
||||
let mut bufs = match reader.read().await {
|
||||
Ok(bufs) => bufs,
|
||||
Err(err) => return (bytes_writed, Some(err)),
|
||||
Err(err) => return (bytes_written, Some(err)),
|
||||
};
|
||||
|
||||
if self.parity_shards > 0 {
|
||||
if let Err(err) = self.decode_data(&mut bufs) {
|
||||
return (bytes_writed, Some(err));
|
||||
return (bytes_written, Some(err));
|
||||
}
|
||||
}
|
||||
|
||||
let writed_n = match self
|
||||
let written_n = match self
|
||||
.write_data_blocks(writer, bufs, self.data_shards, block_offset, block_length)
|
||||
.await
|
||||
{
|
||||
Ok(n) => n,
|
||||
Err(err) => {
|
||||
error!("write_data_blocks err {:?}", &err);
|
||||
return (bytes_writed, Some(err));
|
||||
return (bytes_written, Some(err));
|
||||
}
|
||||
};
|
||||
|
||||
bytes_writed += writed_n;
|
||||
bytes_written += written_n;
|
||||
|
||||
// debug!("decode {} writed_n {}, total_writed: {} ", block_idx, writed_n, bytes_writed);
|
||||
// debug!("decode {} written_n {}, total_written: {} ", block_idx, written_n, bytes_written);
|
||||
}
|
||||
|
||||
if bytes_writed != length {
|
||||
// debug!("bytes_writed != length: {} != {} ", bytes_writed, length);
|
||||
return (bytes_writed, Some(Error::msg("erasure decode less data")));
|
||||
if bytes_written != length {
|
||||
// debug!("bytes_written != length: {} != {} ", bytes_written, length);
|
||||
return (bytes_written, Some(Error::other("erasure decode less data")));
|
||||
}
|
||||
|
||||
(bytes_writed, None)
|
||||
(bytes_written, None)
|
||||
}
|
||||
|
||||
async fn write_data_blocks<W>(
|
||||
@@ -228,7 +223,7 @@ impl Erasure {
|
||||
W: AsyncWrite + Send + Unpin + 'static,
|
||||
{
|
||||
if bufs.len() < data_blocks {
|
||||
return Err(Error::msg("read bufs not match data_blocks"));
|
||||
return Err(Error::other("read bufs not match data_blocks"));
|
||||
}
|
||||
|
||||
let data_len: usize = bufs
|
||||
@@ -238,7 +233,7 @@ impl Erasure {
|
||||
.map(|v| v.as_ref().unwrap().len())
|
||||
.sum();
|
||||
if data_len < length {
|
||||
return Err(Error::msg(format!("write_data_blocks data_len < length {} < {}", data_len, length)));
|
||||
return Err(Error::other(format!("write_data_blocks data_len < length {} < {}", data_len, length)));
|
||||
}
|
||||
|
||||
let mut offset = offset;
|
||||
@@ -246,7 +241,7 @@ impl Erasure {
|
||||
// debug!("write_data_blocks offset {}, length {}", offset, length);
|
||||
|
||||
let mut write = length;
|
||||
let mut total_writed = 0;
|
||||
let mut total_written = 0;
|
||||
|
||||
for opt_buf in bufs.iter().take(data_blocks) {
|
||||
let buf = opt_buf.as_ref().unwrap();
|
||||
@@ -268,7 +263,7 @@ impl Erasure {
|
||||
// debug!("write_data_blocks write buf less len {}", buf.len());
|
||||
writer.write_all(buf).await?;
|
||||
// debug!("write_data_blocks write done len {}", buf.len());
|
||||
total_writed += buf.len();
|
||||
total_written += buf.len();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -277,10 +272,10 @@ impl Erasure {
|
||||
|
||||
// debug!("write_data_blocks write done len {}", n);
|
||||
write -= n;
|
||||
total_writed += n;
|
||||
total_written += n;
|
||||
}
|
||||
|
||||
Ok(total_writed)
|
||||
Ok(total_written)
|
||||
}
|
||||
|
||||
pub fn total_shard_count(&self) -> usize {
|
||||
@@ -304,7 +299,7 @@ impl Erasure {
|
||||
|
||||
// partiy 数量大于 0 才 ec
|
||||
if self.parity_shards > 0 {
|
||||
self.encoder.as_ref().unwrap().encode(data_slices)?;
|
||||
self.encoder.as_ref().unwrap().encode(data_slices).map_err(Error::other)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,7 +316,7 @@ impl Erasure {
|
||||
|
||||
pub fn decode_data(&self, shards: &mut [Option<Vec<u8>>]) -> Result<()> {
|
||||
if self.parity_shards > 0 {
|
||||
self.encoder.as_ref().unwrap().reconstruct(shards)?;
|
||||
self.encoder.as_ref().unwrap().reconstruct(shards).map_err(Error::other)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -382,7 +377,7 @@ impl Erasure {
|
||||
total_length
|
||||
);
|
||||
if writers.len() != self.parity_shards + self.data_shards {
|
||||
return Err(Error::from_string("invalid argument"));
|
||||
return Err(Error::other("invalid argument"));
|
||||
}
|
||||
let mut reader = ShardReader::new(readers, self, 0, total_length);
|
||||
|
||||
@@ -397,12 +392,12 @@ impl Erasure {
|
||||
let mut bufs = reader.read().await?;
|
||||
|
||||
if self.parity_shards > 0 {
|
||||
self.encoder.as_ref().unwrap().reconstruct(&mut bufs)?;
|
||||
self.encoder.as_ref().unwrap().reconstruct(&mut bufs).map_err(Error::other)?;
|
||||
}
|
||||
|
||||
let shards = bufs.into_iter().flatten().map(Bytes::from).collect::<Vec<_>>();
|
||||
if shards.len() != self.parity_shards + self.data_shards {
|
||||
return Err(Error::from_string("can not reconstruct data"));
|
||||
return Err(Error::other("can not reconstruct data"));
|
||||
}
|
||||
|
||||
for (i, w) in writers.iter_mut().enumerate() {
|
||||
@@ -419,7 +414,7 @@ impl Erasure {
|
||||
}
|
||||
}
|
||||
if !errs.is_empty() {
|
||||
return Err(clone_err(&errs[0]));
|
||||
return Err(errs[0].clone().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -494,7 +489,7 @@ impl ShardReader {
|
||||
if let Some(disk) = disk {
|
||||
disk.read_at(offset, read_length).await
|
||||
} else {
|
||||
Err(Error::new(DiskError::DiskNotFound))
|
||||
Err(DiskError::DiskNotFound)
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -517,7 +512,7 @@ impl ShardReader {
|
||||
warn!("ec decode read ress {:?}", &ress);
|
||||
warn!("ec decode read errors {:?}", &errors);
|
||||
|
||||
return Err(Error::msg("shard reader read faild"));
|
||||
return Err(Error::other("shard reader read failed"));
|
||||
}
|
||||
|
||||
self.offset += self.shard_size;
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
use bytes::Bytes;
|
||||
use pin_project_lite::pin_project;
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
pin_project! {
|
||||
/// BitrotReader reads (hash+data) blocks from an async reader and verifies hash integrity.
|
||||
pub struct BitrotReader<R> {
|
||||
#[pin]
|
||||
inner: R,
|
||||
hash_algo: HashAlgorithm,
|
||||
shard_size: usize,
|
||||
buf: Vec<u8>,
|
||||
hash_buf: Vec<u8>,
|
||||
// hash_read: usize,
|
||||
// data_buf: Vec<u8>,
|
||||
// data_read: usize,
|
||||
// hash_checked: bool,
|
||||
id: Uuid,
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> BitrotReader<R>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + Sync,
|
||||
{
|
||||
/// Create a new BitrotReader.
|
||||
pub fn new(inner: R, shard_size: usize, algo: HashAlgorithm) -> Self {
|
||||
let hash_size = algo.size();
|
||||
Self {
|
||||
inner,
|
||||
hash_algo: algo,
|
||||
shard_size,
|
||||
buf: Vec::new(),
|
||||
hash_buf: vec![0u8; hash_size],
|
||||
// hash_read: 0,
|
||||
// data_buf: Vec::new(),
|
||||
// data_read: 0,
|
||||
// hash_checked: false,
|
||||
id: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a single (hash+data) block, verify hash, and return the number of bytes read into `out`.
|
||||
/// Returns an error if hash verification fails or data exceeds shard_size.
|
||||
pub async fn read(&mut self, out: &mut [u8]) -> std::io::Result<usize> {
|
||||
if out.len() > self.shard_size {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("data size {} exceeds shard size {}", out.len(), self.shard_size),
|
||||
));
|
||||
}
|
||||
|
||||
let hash_size = self.hash_algo.size();
|
||||
// Read hash
|
||||
|
||||
if hash_size > 0 {
|
||||
self.inner.read_exact(&mut self.hash_buf).await.map_err(|e| {
|
||||
error!("bitrot reader read hash error: {}", e);
|
||||
e
|
||||
})?;
|
||||
}
|
||||
|
||||
// Read data
|
||||
let mut data_len = 0;
|
||||
while data_len < out.len() {
|
||||
let n = self.inner.read(&mut out[data_len..]).await.map_err(|e| {
|
||||
error!("bitrot reader read data error: {}", e);
|
||||
e
|
||||
})?;
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
data_len += n;
|
||||
}
|
||||
|
||||
if hash_size > 0 {
|
||||
let actual_hash = self.hash_algo.hash_encode(&out[..data_len]);
|
||||
if actual_hash.as_ref() != self.hash_buf.as_slice() {
|
||||
error!("bitrot reader hash mismatch, id={} data_len={}, out_len={}", self.id, data_len, out.len());
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "bitrot hash mismatch"));
|
||||
}
|
||||
}
|
||||
Ok(data_len)
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
/// BitrotWriter writes (hash+data) blocks to an async writer.
|
||||
pub struct BitrotWriter<W> {
|
||||
#[pin]
|
||||
inner: W,
|
||||
hash_algo: HashAlgorithm,
|
||||
shard_size: usize,
|
||||
buf: Vec<u8>,
|
||||
finished: bool,
|
||||
}
|
||||
}
|
||||
|
||||
impl<W> BitrotWriter<W>
|
||||
where
|
||||
W: AsyncWrite + Unpin + Send + Sync,
|
||||
{
|
||||
/// Create a new BitrotWriter.
|
||||
pub fn new(inner: W, shard_size: usize, algo: HashAlgorithm) -> Self {
|
||||
let hash_algo = algo;
|
||||
Self {
|
||||
inner,
|
||||
hash_algo,
|
||||
shard_size,
|
||||
buf: Vec::new(),
|
||||
finished: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> W {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Write a (hash+data) block. Returns the number of data bytes written.
|
||||
/// Returns an error if called after a short write or if data exceeds shard_size.
|
||||
pub async fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if buf.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
if self.finished {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "bitrot writer already finished"));
|
||||
}
|
||||
|
||||
if buf.len() > self.shard_size {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
format!("data size {} exceeds shard size {}", buf.len(), self.shard_size),
|
||||
));
|
||||
}
|
||||
|
||||
if buf.len() < self.shard_size {
|
||||
self.finished = true;
|
||||
}
|
||||
|
||||
let hash_algo = &self.hash_algo;
|
||||
|
||||
if hash_algo.size() > 0 {
|
||||
let hash = hash_algo.hash_encode(buf);
|
||||
self.buf.extend_from_slice(hash.as_ref());
|
||||
}
|
||||
|
||||
self.buf.extend_from_slice(buf);
|
||||
|
||||
self.inner.write_all(&self.buf).await?;
|
||||
|
||||
// self.inner.flush().await?;
|
||||
|
||||
let n = buf.len();
|
||||
|
||||
self.buf.clear();
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
pub async fn shutdown(&mut self) -> std::io::Result<()> {
|
||||
self.inner.shutdown().await
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bitrot_shard_file_size(size: usize, shard_size: usize, algo: HashAlgorithm) -> usize {
|
||||
if algo != HashAlgorithm::HighwayHash256S {
|
||||
return size;
|
||||
}
|
||||
size.div_ceil(shard_size) * algo.size() + size
|
||||
}
|
||||
|
||||
pub async fn bitrot_verify<R: AsyncRead + Unpin + Send>(
|
||||
mut r: R,
|
||||
want_size: usize,
|
||||
part_size: usize,
|
||||
algo: HashAlgorithm,
|
||||
_want: Bytes, // FIXME: useless parameter?
|
||||
mut shard_size: usize,
|
||||
) -> std::io::Result<()> {
|
||||
let mut hash_buf = vec![0; algo.size()];
|
||||
let mut left = want_size;
|
||||
|
||||
if left != bitrot_shard_file_size(part_size, shard_size, algo.clone()) {
|
||||
return Err(std::io::Error::other("bitrot shard file size mismatch"));
|
||||
}
|
||||
|
||||
while left > 0 {
|
||||
let n = r.read_exact(&mut hash_buf).await?;
|
||||
left -= n;
|
||||
|
||||
if left < shard_size {
|
||||
shard_size = left;
|
||||
}
|
||||
|
||||
let mut buf = vec![0; shard_size];
|
||||
let read = r.read_exact(&mut buf).await?;
|
||||
|
||||
let actual_hash = algo.hash_encode(&buf);
|
||||
if actual_hash.as_ref() != &hash_buf[0..n] {
|
||||
return Err(std::io::Error::other("bitrot hash mismatch"));
|
||||
}
|
||||
|
||||
left -= read;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Custom writer enum that supports inline buffer storage
|
||||
pub enum CustomWriter {
|
||||
/// Inline buffer writer - stores data in memory
|
||||
InlineBuffer(Vec<u8>),
|
||||
/// Disk-based writer using tokio file
|
||||
Other(Box<dyn AsyncWrite + Unpin + Send + Sync>),
|
||||
}
|
||||
|
||||
impl CustomWriter {
|
||||
/// Create a new inline buffer writer
|
||||
pub fn new_inline_buffer() -> Self {
|
||||
Self::InlineBuffer(Vec::new())
|
||||
}
|
||||
|
||||
/// Create a new disk writer from any AsyncWrite implementation
|
||||
pub fn new_tokio_writer<W>(writer: W) -> Self
|
||||
where
|
||||
W: AsyncWrite + Unpin + Send + Sync + 'static,
|
||||
{
|
||||
Self::Other(Box::new(writer))
|
||||
}
|
||||
|
||||
/// Get the inline buffer data if this is an inline buffer writer
|
||||
pub fn get_inline_data(&self) -> Option<&[u8]> {
|
||||
match self {
|
||||
Self::InlineBuffer(data) => Some(data),
|
||||
Self::Other(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the inline buffer data, consuming the writer
|
||||
pub fn into_inline_data(self) -> Option<Vec<u8>> {
|
||||
match self {
|
||||
Self::InlineBuffer(data) => Some(data),
|
||||
Self::Other(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for CustomWriter {
|
||||
fn poll_write(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> std::task::Poll<std::io::Result<usize>> {
|
||||
match self.get_mut() {
|
||||
Self::InlineBuffer(data) => {
|
||||
data.extend_from_slice(buf);
|
||||
std::task::Poll::Ready(Ok(buf.len()))
|
||||
}
|
||||
Self::Other(writer) => {
|
||||
let pinned_writer = std::pin::Pin::new(writer.as_mut());
|
||||
pinned_writer.poll_write(cx, buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<std::io::Result<()>> {
|
||||
match self.get_mut() {
|
||||
Self::InlineBuffer(_) => std::task::Poll::Ready(Ok(())),
|
||||
Self::Other(writer) => {
|
||||
let pinned_writer = std::pin::Pin::new(writer.as_mut());
|
||||
pinned_writer.poll_flush(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_shutdown(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<std::io::Result<()>> {
|
||||
match self.get_mut() {
|
||||
Self::InlineBuffer(_) => std::task::Poll::Ready(Ok(())),
|
||||
Self::Other(writer) => {
|
||||
let pinned_writer = std::pin::Pin::new(writer.as_mut());
|
||||
pinned_writer.poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around BitrotWriter that uses our custom writer
|
||||
pub struct BitrotWriterWrapper {
|
||||
bitrot_writer: BitrotWriter<CustomWriter>,
|
||||
writer_type: WriterType,
|
||||
}
|
||||
|
||||
/// Enum to track the type of writer we're using
|
||||
enum WriterType {
|
||||
InlineBuffer,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BitrotWriterWrapper {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BitrotWriterWrapper")
|
||||
.field(
|
||||
"writer_type",
|
||||
&match self.writer_type {
|
||||
WriterType::InlineBuffer => "InlineBuffer",
|
||||
WriterType::Other => "Other",
|
||||
},
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl BitrotWriterWrapper {
|
||||
/// Create a new BitrotWriterWrapper with custom writer
|
||||
pub fn new(writer: CustomWriter, shard_size: usize, checksum_algo: HashAlgorithm) -> Self {
|
||||
let writer_type = match &writer {
|
||||
CustomWriter::InlineBuffer(_) => WriterType::InlineBuffer,
|
||||
CustomWriter::Other(_) => WriterType::Other,
|
||||
};
|
||||
|
||||
Self {
|
||||
bitrot_writer: BitrotWriter::new(writer, shard_size, checksum_algo),
|
||||
writer_type,
|
||||
}
|
||||
}
|
||||
|
||||
/// Write data to the bitrot writer
|
||||
pub async fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
self.bitrot_writer.write(buf).await
|
||||
}
|
||||
|
||||
pub async fn shutdown(&mut self) -> std::io::Result<()> {
|
||||
self.bitrot_writer.shutdown().await
|
||||
}
|
||||
|
||||
/// Extract the inline buffer data, consuming the wrapper
|
||||
pub fn into_inline_data(self) -> Option<Vec<u8>> {
|
||||
match self.writer_type {
|
||||
WriterType::InlineBuffer => {
|
||||
let writer = self.bitrot_writer.into_inner();
|
||||
writer.into_inline_data()
|
||||
}
|
||||
WriterType::Other => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::BitrotReader;
|
||||
use super::BitrotWriter;
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bitrot_read_write_ok() {
|
||||
let data = b"hello world! this is a test shard.";
|
||||
let data_size = data.len();
|
||||
let shard_size = 8;
|
||||
|
||||
let buf: Vec<u8> = Vec::new();
|
||||
let writer = Cursor::new(buf);
|
||||
let mut bitrot_writer = BitrotWriter::new(writer, shard_size, HashAlgorithm::HighwayHash256);
|
||||
|
||||
let mut n = 0;
|
||||
for chunk in data.chunks(shard_size) {
|
||||
n += bitrot_writer.write(chunk).await.unwrap();
|
||||
}
|
||||
assert_eq!(n, data.len());
|
||||
|
||||
// 读
|
||||
let reader = bitrot_writer.into_inner();
|
||||
let reader = Cursor::new(reader.into_inner());
|
||||
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::HighwayHash256);
|
||||
let mut out = Vec::new();
|
||||
let mut n = 0;
|
||||
while n < data_size {
|
||||
let mut buf = vec![0u8; shard_size];
|
||||
let m = bitrot_reader.read(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf[..m], &data[n..n + m]);
|
||||
|
||||
out.extend_from_slice(&buf[..m]);
|
||||
n += m;
|
||||
}
|
||||
|
||||
assert_eq!(n, data_size);
|
||||
assert_eq!(data, &out[..]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bitrot_read_hash_mismatch() {
|
||||
let data = b"test data for bitrot";
|
||||
let data_size = data.len();
|
||||
let shard_size = 8;
|
||||
let buf: Vec<u8> = Vec::new();
|
||||
let writer = Cursor::new(buf);
|
||||
let mut bitrot_writer = BitrotWriter::new(writer, shard_size, HashAlgorithm::HighwayHash256);
|
||||
for chunk in data.chunks(shard_size) {
|
||||
let _ = bitrot_writer.write(chunk).await.unwrap();
|
||||
}
|
||||
let mut written = bitrot_writer.into_inner().into_inner();
|
||||
// change the last byte to make hash mismatch
|
||||
let pos = written.len() - 1;
|
||||
written[pos] ^= 0xFF;
|
||||
let reader = Cursor::new(written);
|
||||
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::HighwayHash256);
|
||||
|
||||
let count = data_size.div_ceil(shard_size);
|
||||
|
||||
let mut idx = 0;
|
||||
let mut n = 0;
|
||||
while n < data_size {
|
||||
let mut buf = vec![0u8; shard_size];
|
||||
let res = bitrot_reader.read(&mut buf).await;
|
||||
|
||||
if idx == count - 1 {
|
||||
// 最后一个块,应该返回错误
|
||||
assert!(res.is_err());
|
||||
assert_eq!(res.unwrap_err().kind(), std::io::ErrorKind::InvalidData);
|
||||
break;
|
||||
}
|
||||
|
||||
let m = res.unwrap();
|
||||
|
||||
assert_eq!(&buf[..m], &data[n..n + m]);
|
||||
|
||||
n += m;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bitrot_read_write_none_hash() {
|
||||
let data = b"bitrot none hash test data!";
|
||||
let data_size = data.len();
|
||||
let shard_size = 8;
|
||||
|
||||
let buf: Vec<u8> = Vec::new();
|
||||
let writer = Cursor::new(buf);
|
||||
let mut bitrot_writer = BitrotWriter::new(writer, shard_size, HashAlgorithm::None);
|
||||
|
||||
let mut n = 0;
|
||||
for chunk in data.chunks(shard_size) {
|
||||
n += bitrot_writer.write(chunk).await.unwrap();
|
||||
}
|
||||
assert_eq!(n, data.len());
|
||||
|
||||
let reader = bitrot_writer.into_inner();
|
||||
let reader = Cursor::new(reader.into_inner());
|
||||
let mut bitrot_reader = BitrotReader::new(reader, shard_size, HashAlgorithm::None);
|
||||
let mut out = Vec::new();
|
||||
let mut n = 0;
|
||||
while n < data_size {
|
||||
let mut buf = vec![0u8; shard_size];
|
||||
let m = bitrot_reader.read(&mut buf).await.unwrap();
|
||||
assert_eq!(&buf[..m], &data[n..n + m]);
|
||||
out.extend_from_slice(&buf[..m]);
|
||||
n += m;
|
||||
}
|
||||
assert_eq!(n, data_size);
|
||||
assert_eq!(data, &out[..]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
use super::BitrotReader;
|
||||
use super::Erasure;
|
||||
use crate::disk::error::Error;
|
||||
use crate::disk::error_reduce::reduce_errs;
|
||||
use futures::future::join_all;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::io::ErrorKind;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tracing::error;
|
||||
|
||||
pin_project! {
|
||||
pub(crate) struct ParallelReader<R> {
|
||||
#[pin]
|
||||
readers: Vec<Option<BitrotReader<R>>>,
|
||||
offset: usize,
|
||||
shard_size: usize,
|
||||
shard_file_size: usize,
|
||||
data_shards: usize,
|
||||
total_shards: usize,
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> ParallelReader<R>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + Sync,
|
||||
{
|
||||
// readers传入前应处理disk错误,确保每个reader达到可用数量的BitrotReader
|
||||
pub fn new(readers: Vec<Option<BitrotReader<R>>>, e: Erasure, offset: usize, total_length: usize) -> Self {
|
||||
let shard_size = e.shard_size();
|
||||
let shard_file_size = e.shard_file_size(total_length as i64) as usize;
|
||||
|
||||
let offset = (offset / e.block_size) * shard_size;
|
||||
|
||||
// 确保offset不超过shard_file_size
|
||||
|
||||
ParallelReader {
|
||||
readers,
|
||||
offset,
|
||||
shard_size,
|
||||
shard_file_size,
|
||||
data_shards: e.data_shards,
|
||||
total_shards: e.data_shards + e.parity_shards,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> ParallelReader<R>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + Sync,
|
||||
{
|
||||
pub async fn read(&mut self) -> (Vec<Option<Vec<u8>>>, Vec<Option<Error>>) {
|
||||
// if self.readers.len() != self.total_shards {
|
||||
// return Err(io::Error::new(ErrorKind::InvalidInput, "Invalid number of readers"));
|
||||
// }
|
||||
|
||||
let shard_size = if self.offset + self.shard_size > self.shard_file_size {
|
||||
self.shard_file_size - self.offset
|
||||
} else {
|
||||
self.shard_size
|
||||
};
|
||||
|
||||
if shard_size == 0 {
|
||||
return (vec![None; self.readers.len()], vec![None; self.readers.len()]);
|
||||
}
|
||||
|
||||
// 使用并发读取所有分片
|
||||
let mut read_futs = Vec::with_capacity(self.readers.len());
|
||||
|
||||
for (i, opt_reader) in self.readers.iter_mut().enumerate() {
|
||||
let future = if let Some(reader) = opt_reader.as_mut() {
|
||||
Box::pin(async move {
|
||||
let mut buf = vec![0u8; shard_size];
|
||||
match reader.read(&mut buf).await {
|
||||
Ok(n) => {
|
||||
buf.truncate(n);
|
||||
(i, Ok(buf))
|
||||
}
|
||||
Err(e) => (i, Err(Error::from(e))),
|
||||
}
|
||||
}) as std::pin::Pin<Box<dyn std::future::Future<Output = (usize, Result<Vec<u8>, Error>)> + Send>>
|
||||
} else {
|
||||
// reader是None时返回FileNotFound错误
|
||||
Box::pin(async move { (i, Err(Error::FileNotFound)) })
|
||||
as std::pin::Pin<Box<dyn std::future::Future<Output = (usize, Result<Vec<u8>, Error>)> + Send>>
|
||||
};
|
||||
read_futs.push(future);
|
||||
}
|
||||
|
||||
let results = join_all(read_futs).await;
|
||||
|
||||
let mut shards: Vec<Option<Vec<u8>>> = vec![None; self.readers.len()];
|
||||
let mut errs = vec![None; self.readers.len()];
|
||||
|
||||
for (i, shard) in results.into_iter() {
|
||||
match shard {
|
||||
Ok(data) => {
|
||||
if !data.is_empty() {
|
||||
shards[i] = Some(data);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Error reading shard {}: {}", i, e);
|
||||
errs[i] = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.offset += shard_size;
|
||||
|
||||
(shards, errs)
|
||||
}
|
||||
|
||||
pub fn can_decode(&self, shards: &[Option<Vec<u8>>]) -> bool {
|
||||
shards.iter().filter(|s| s.is_some()).count() >= self.data_shards
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取数据块总长度
|
||||
fn get_data_block_len(shards: &[Option<Vec<u8>>], data_blocks: usize) -> usize {
|
||||
let mut size = 0;
|
||||
for shard in shards.iter().take(data_blocks).flatten() {
|
||||
size += shard.len();
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
|
||||
/// 将编码块中的数据块写入目标,支持 offset 和 length
|
||||
async fn write_data_blocks<W>(
|
||||
writer: &mut W,
|
||||
en_blocks: &[Option<Vec<u8>>],
|
||||
data_blocks: usize,
|
||||
mut offset: usize,
|
||||
length: usize,
|
||||
) -> std::io::Result<usize>
|
||||
where
|
||||
W: tokio::io::AsyncWrite + Send + Sync + Unpin,
|
||||
{
|
||||
if get_data_block_len(en_blocks, data_blocks) < length {
|
||||
error!("write_data_blocks get_data_block_len < length");
|
||||
return Err(io::Error::new(ErrorKind::UnexpectedEof, "Not enough data blocks to write"));
|
||||
}
|
||||
|
||||
let mut total_written = 0;
|
||||
let mut write_left = length;
|
||||
|
||||
for block_op in &en_blocks[..data_blocks] {
|
||||
if block_op.is_none() {
|
||||
error!("write_data_blocks block_op.is_none()");
|
||||
return Err(io::Error::new(ErrorKind::UnexpectedEof, "Missing data block"));
|
||||
}
|
||||
|
||||
let block = block_op.as_ref().unwrap();
|
||||
|
||||
if offset >= block.len() {
|
||||
offset -= block.len();
|
||||
continue;
|
||||
}
|
||||
|
||||
let block_slice = &block[offset..];
|
||||
offset = 0;
|
||||
|
||||
if write_left < block.len() {
|
||||
writer.write_all(&block_slice[..write_left]).await.map_err(|e| {
|
||||
error!("write_data_blocks write_all err: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
total_written += write_left;
|
||||
break;
|
||||
}
|
||||
|
||||
let n = block_slice.len();
|
||||
|
||||
writer.write_all(block_slice).await.map_err(|e| {
|
||||
error!("write_data_blocks write_all2 err: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
write_left -= n;
|
||||
|
||||
total_written += n;
|
||||
}
|
||||
|
||||
Ok(total_written)
|
||||
}
|
||||
|
||||
impl Erasure {
|
||||
pub async fn decode<W, R>(
|
||||
&self,
|
||||
writer: &mut W,
|
||||
readers: Vec<Option<BitrotReader<R>>>,
|
||||
offset: usize,
|
||||
length: usize,
|
||||
total_length: usize,
|
||||
) -> (usize, Option<std::io::Error>)
|
||||
where
|
||||
W: AsyncWrite + Send + Sync + Unpin,
|
||||
R: AsyncRead + Unpin + Send + Sync,
|
||||
{
|
||||
if readers.len() != self.data_shards + self.parity_shards {
|
||||
return (0, Some(io::Error::new(ErrorKind::InvalidInput, "Invalid number of readers")));
|
||||
}
|
||||
|
||||
if offset + length > total_length {
|
||||
return (0, Some(io::Error::new(ErrorKind::InvalidInput, "offset + length exceeds total length")));
|
||||
}
|
||||
|
||||
let mut ret_err = None;
|
||||
|
||||
if length == 0 {
|
||||
return (0, ret_err);
|
||||
}
|
||||
|
||||
let mut written = 0;
|
||||
|
||||
let mut reader = ParallelReader::new(readers, self.clone(), offset, total_length);
|
||||
|
||||
let start = offset / self.block_size;
|
||||
let end = (offset + length) / self.block_size;
|
||||
|
||||
for i in start..=end {
|
||||
let (block_offset, block_length) = if start == end {
|
||||
(offset % self.block_size, length)
|
||||
} else if i == start {
|
||||
(offset % self.block_size, self.block_size - (offset % self.block_size))
|
||||
} else if i == end {
|
||||
(0, (offset + length) % self.block_size)
|
||||
} else {
|
||||
(0, self.block_size)
|
||||
};
|
||||
|
||||
if block_length == 0 {
|
||||
// error!("erasure decode decode block_length == 0");
|
||||
break;
|
||||
}
|
||||
|
||||
let (mut shards, errs) = reader.read().await;
|
||||
|
||||
if ret_err.is_none() {
|
||||
if let (_, Some(err)) = reduce_errs(&errs, &[]) {
|
||||
if err == Error::FileNotFound || err == Error::FileCorrupt {
|
||||
ret_err = Some(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !reader.can_decode(&shards) {
|
||||
error!("erasure decode can_decode errs: {:?}", &errs);
|
||||
ret_err = Some(Error::ErasureReadQuorum.into());
|
||||
break;
|
||||
}
|
||||
|
||||
// Decode the shards
|
||||
if let Err(e) = self.decode_data(&mut shards) {
|
||||
error!("erasure decode decode_data err: {:?}", e);
|
||||
ret_err = Some(e);
|
||||
break;
|
||||
}
|
||||
|
||||
let n = match write_data_blocks(writer, &shards, self.data_shards, block_offset, block_length).await {
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
error!("erasure decode write_data_blocks err: {:?}", e);
|
||||
ret_err = Some(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
written += n;
|
||||
}
|
||||
|
||||
if written < length {
|
||||
ret_err = Some(Error::LessData.into());
|
||||
}
|
||||
|
||||
(written, ret_err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
use super::BitrotWriterWrapper;
|
||||
use super::Erasure;
|
||||
use crate::disk::error::Error;
|
||||
use crate::disk::error_reduce::count_errs;
|
||||
use crate::disk::error_reduce::{OBJECT_OP_IGNORED_ERRS, reduce_write_quorum_errs};
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use std::sync::Arc;
|
||||
use std::vec;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
pub(crate) struct MultiWriter<'a> {
|
||||
writers: &'a mut [Option<BitrotWriterWrapper>],
|
||||
write_quorum: usize,
|
||||
errs: Vec<Option<Error>>,
|
||||
}
|
||||
|
||||
impl<'a> MultiWriter<'a> {
|
||||
pub fn new(writers: &'a mut [Option<BitrotWriterWrapper>], write_quorum: usize) -> Self {
|
||||
let length = writers.len();
|
||||
MultiWriter {
|
||||
writers,
|
||||
write_quorum,
|
||||
errs: vec![None; length],
|
||||
}
|
||||
}
|
||||
|
||||
async fn write_shard(writer_opt: &mut Option<BitrotWriterWrapper>, err: &mut Option<Error>, shard: &Bytes) {
|
||||
match writer_opt {
|
||||
Some(writer) => {
|
||||
match writer.write(shard).await {
|
||||
Ok(n) => {
|
||||
if n < shard.len() {
|
||||
*err = Some(Error::ShortWrite);
|
||||
*writer_opt = None; // Mark as failed
|
||||
} else {
|
||||
*err = None;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
*err = Some(Error::from(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
*err = Some(Error::DiskNotFound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn write(&mut self, data: Vec<Bytes>) -> std::io::Result<()> {
|
||||
assert_eq!(data.len(), self.writers.len());
|
||||
|
||||
{
|
||||
let mut futures = FuturesUnordered::new();
|
||||
for ((writer_opt, err), shard) in self.writers.iter_mut().zip(self.errs.iter_mut()).zip(data.iter()) {
|
||||
if err.is_some() {
|
||||
continue; // Skip if we already have an error for this writer
|
||||
}
|
||||
futures.push(Self::write_shard(writer_opt, err, shard));
|
||||
}
|
||||
while let Some(()) = futures.next().await {}
|
||||
}
|
||||
|
||||
let nil_count = self.errs.iter().filter(|&e| e.is_none()).count();
|
||||
if nil_count >= self.write_quorum {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(write_err) = reduce_write_quorum_errs(&self.errs, OBJECT_OP_IGNORED_ERRS, self.write_quorum) {
|
||||
error!(
|
||||
"reduce_write_quorum_errs: {:?}, offline-disks={}/{}, errs={:?}",
|
||||
write_err,
|
||||
count_errs(&self.errs, &Error::DiskNotFound),
|
||||
self.writers.len(),
|
||||
self.errs
|
||||
);
|
||||
return Err(std::io::Error::other(format!(
|
||||
"Failed to write data: {} (offline-disks={}/{})",
|
||||
write_err,
|
||||
count_errs(&self.errs, &Error::DiskNotFound),
|
||||
self.writers.len()
|
||||
)));
|
||||
}
|
||||
|
||||
Err(std::io::Error::other(format!(
|
||||
"Failed to write data: (offline-disks={}/{}): {}",
|
||||
count_errs(&self.errs, &Error::DiskNotFound),
|
||||
self.writers.len(),
|
||||
self.errs
|
||||
.iter()
|
||||
.map(|e| e.as_ref().map_or("<nil>".to_string(), |e| e.to_string()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn _shutdown(&mut self) -> std::io::Result<()> {
|
||||
for writer in self.writers.iter_mut().flatten() {
|
||||
writer.shutdown().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Erasure {
|
||||
pub async fn encode<R>(
|
||||
self: Arc<Self>,
|
||||
mut reader: R,
|
||||
writers: &mut [Option<BitrotWriterWrapper>],
|
||||
quorum: usize,
|
||||
) -> std::io::Result<(R, usize)>
|
||||
where
|
||||
R: AsyncRead + Send + Sync + Unpin + 'static,
|
||||
{
|
||||
let (tx, mut rx) = mpsc::channel::<Vec<Bytes>>(8);
|
||||
|
||||
let task = tokio::spawn(async move {
|
||||
let block_size = self.block_size;
|
||||
let mut total = 0;
|
||||
let mut buf = vec![0u8; block_size];
|
||||
loop {
|
||||
match rustfs_utils::read_full(&mut reader, &mut buf).await {
|
||||
Ok(n) if n > 0 => {
|
||||
total += n;
|
||||
let res = self.encode_data(&buf[..n])?;
|
||||
if let Err(err) = tx.send(res).await {
|
||||
return Err(std::io::Error::other(format!("Failed to send encoded data : {}", err)));
|
||||
}
|
||||
}
|
||||
Ok(_) => break,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((reader, total))
|
||||
});
|
||||
|
||||
let mut writers = MultiWriter::new(writers, quorum);
|
||||
|
||||
while let Some(block) = rx.recv().await {
|
||||
if block.is_empty() {
|
||||
break;
|
||||
}
|
||||
writers.write(block).await?;
|
||||
}
|
||||
|
||||
let (reader, total) = task.await??;
|
||||
// writers.shutdown().await?;
|
||||
Ok((reader, total))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
use super::BitrotReader;
|
||||
use super::BitrotWriterWrapper;
|
||||
use super::decode::ParallelReader;
|
||||
use crate::disk::error::{Error, Result};
|
||||
use crate::erasure_coding::encode::MultiWriter;
|
||||
use bytes::Bytes;
|
||||
use tokio::io::AsyncRead;
|
||||
use tracing::info;
|
||||
|
||||
impl super::Erasure {
|
||||
pub async fn heal<R>(
|
||||
&self,
|
||||
writers: &mut [Option<BitrotWriterWrapper>],
|
||||
readers: Vec<Option<BitrotReader<R>>>,
|
||||
total_length: usize,
|
||||
_prefer: &[bool],
|
||||
) -> Result<()>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send + Sync,
|
||||
{
|
||||
info!(
|
||||
"Erasure heal, writers len: {}, readers len: {}, total_length: {}",
|
||||
writers.len(),
|
||||
readers.len(),
|
||||
total_length
|
||||
);
|
||||
if writers.len() != self.parity_shards + self.data_shards {
|
||||
return Err(Error::other("invalid argument"));
|
||||
}
|
||||
let mut reader = ParallelReader::new(readers, self.clone(), 0, total_length);
|
||||
|
||||
let start_block = 0;
|
||||
let mut end_block = total_length / self.block_size;
|
||||
if total_length % self.block_size != 0 {
|
||||
end_block += 1;
|
||||
}
|
||||
|
||||
for _ in start_block..end_block {
|
||||
let (mut shards, errs) = reader.read().await;
|
||||
|
||||
if errs.iter().filter(|e| e.is_none()).count() < self.data_shards {
|
||||
return Err(Error::other(format!("can not reconstruct data: not enough data shards {:?}", errs)));
|
||||
}
|
||||
|
||||
if self.parity_shards > 0 {
|
||||
self.decode_data(&mut shards)?;
|
||||
}
|
||||
|
||||
let shards = shards
|
||||
.into_iter()
|
||||
.map(|s| Bytes::from(s.unwrap_or_default()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut writers = MultiWriter::new(writers, self.data_shards);
|
||||
writers.write(shards).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub mod decode;
|
||||
pub mod encode;
|
||||
pub mod erasure;
|
||||
pub mod heal;
|
||||
|
||||
mod bitrot;
|
||||
pub use bitrot::*;
|
||||
|
||||
pub use erasure::{Erasure, ReedSolomonEncoder, calc_shard_size};
|
||||
+1052
-103
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,242 +0,0 @@
|
||||
use common::error::{Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{Cursor, Read};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct InlineData(Vec<u8>);
|
||||
|
||||
const INLINE_DATA_VER: u8 = 1;
|
||||
|
||||
impl InlineData {
|
||||
pub fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
pub fn update(&mut self, buf: &[u8]) {
|
||||
self.0 = buf.to_vec()
|
||||
}
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
self.0.as_slice()
|
||||
}
|
||||
pub fn version_ok(&self) -> bool {
|
||||
if self.0.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.0[0] > 0 && self.0[0] <= INLINE_DATA_VER
|
||||
}
|
||||
|
||||
pub fn after_version(&self) -> &[u8] {
|
||||
if self.0.is_empty() {
|
||||
&self.0
|
||||
} else {
|
||||
&self.0[1..]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find(&self, key: &str) -> Result<Option<Vec<u8>>> {
|
||||
if self.0.is_empty() || !self.version_ok() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let buf = self.after_version();
|
||||
|
||||
let mut cur = Cursor::new(buf);
|
||||
|
||||
let mut fields_len = rmp::decode::read_map_len(&mut cur)?;
|
||||
|
||||
while fields_len > 0 {
|
||||
fields_len -= 1;
|
||||
|
||||
let str_len = rmp::decode::read_str_len(&mut cur)?;
|
||||
|
||||
let mut field_buff = vec![0u8; str_len as usize];
|
||||
|
||||
cur.read_exact(&mut field_buff)?;
|
||||
|
||||
let field = String::from_utf8(field_buff)?;
|
||||
|
||||
let bin_len = rmp::decode::read_bin_len(&mut cur)? as usize;
|
||||
let start = cur.position() as usize;
|
||||
let end = start + bin_len;
|
||||
cur.set_position(end as u64);
|
||||
|
||||
if field.as_str() == key {
|
||||
let buf = &buf[start..end];
|
||||
return Ok(Some(buf.to_vec()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<()> {
|
||||
if self.0.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut cur = Cursor::new(self.after_version());
|
||||
|
||||
let mut fields_len = rmp::decode::read_map_len(&mut cur)?;
|
||||
|
||||
while fields_len > 0 {
|
||||
fields_len -= 1;
|
||||
|
||||
let str_len = rmp::decode::read_str_len(&mut cur)?;
|
||||
|
||||
let mut field_buff = vec![0u8; str_len as usize];
|
||||
|
||||
cur.read_exact(&mut field_buff)?;
|
||||
|
||||
let field = String::from_utf8(field_buff)?;
|
||||
if field.is_empty() {
|
||||
return Err(Error::msg("InlineData key empty"));
|
||||
}
|
||||
|
||||
let bin_len = rmp::decode::read_bin_len(&mut cur)? as usize;
|
||||
let start = cur.position() as usize;
|
||||
let end = start + bin_len;
|
||||
cur.set_position(end as u64);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, key: &str, value: Vec<u8>) -> Result<()> {
|
||||
if self.after_version().is_empty() {
|
||||
let mut keys = Vec::with_capacity(1);
|
||||
let mut values = Vec::with_capacity(1);
|
||||
|
||||
keys.push(key.to_owned());
|
||||
values.push(value);
|
||||
|
||||
return self.serialize(keys, values);
|
||||
}
|
||||
|
||||
let buf = self.after_version();
|
||||
let mut cur = Cursor::new(buf);
|
||||
|
||||
let mut fields_len = rmp::decode::read_map_len(&mut cur)? as usize;
|
||||
let mut keys = Vec::with_capacity(fields_len + 1);
|
||||
let mut values = Vec::with_capacity(fields_len + 1);
|
||||
|
||||
let mut replaced = false;
|
||||
|
||||
while fields_len > 0 {
|
||||
fields_len -= 1;
|
||||
|
||||
let str_len = rmp::decode::read_str_len(&mut cur)?;
|
||||
|
||||
let mut field_buff = vec![0u8; str_len as usize];
|
||||
|
||||
cur.read_exact(&mut field_buff)?;
|
||||
|
||||
let find_key = String::from_utf8(field_buff)?;
|
||||
|
||||
let bin_len = rmp::decode::read_bin_len(&mut cur)? as usize;
|
||||
let start = cur.position() as usize;
|
||||
let end = start + bin_len;
|
||||
cur.set_position(end as u64);
|
||||
|
||||
let find_value = &buf[start..end];
|
||||
|
||||
if find_key.as_str() == key {
|
||||
values.push(value.clone());
|
||||
replaced = true
|
||||
} else {
|
||||
values.push(find_value.to_vec());
|
||||
}
|
||||
|
||||
keys.push(find_key);
|
||||
}
|
||||
|
||||
if !replaced {
|
||||
keys.push(key.to_owned());
|
||||
values.push(value);
|
||||
}
|
||||
|
||||
self.serialize(keys, values)
|
||||
}
|
||||
pub fn remove(&mut self, remove_keys: Vec<Uuid>) -> Result<bool> {
|
||||
let buf = self.after_version();
|
||||
let mut cur = Cursor::new(buf);
|
||||
|
||||
let mut fields_len = rmp::decode::read_map_len(&mut cur)? as usize;
|
||||
let mut keys = Vec::with_capacity(fields_len + 1);
|
||||
let mut values = Vec::with_capacity(fields_len + 1);
|
||||
|
||||
let remove_key = |found_key: &str| {
|
||||
for key in remove_keys.iter() {
|
||||
if key.to_string().as_str() == found_key {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
};
|
||||
|
||||
let mut found = false;
|
||||
|
||||
while fields_len > 0 {
|
||||
fields_len -= 1;
|
||||
|
||||
let str_len = rmp::decode::read_str_len(&mut cur)?;
|
||||
|
||||
let mut field_buff = vec![0u8; str_len as usize];
|
||||
|
||||
cur.read_exact(&mut field_buff)?;
|
||||
|
||||
let find_key = String::from_utf8(field_buff)?;
|
||||
|
||||
let bin_len = rmp::decode::read_bin_len(&mut cur)? as usize;
|
||||
let start = cur.position() as usize;
|
||||
let end = start + bin_len;
|
||||
cur.set_position(end as u64);
|
||||
|
||||
let find_value = &buf[start..end];
|
||||
|
||||
if !remove_key(&find_key) {
|
||||
values.push(find_value.to_vec());
|
||||
keys.push(find_key);
|
||||
} else {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if keys.is_empty() {
|
||||
self.0 = Vec::new();
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
self.serialize(keys, values)?;
|
||||
Ok(true)
|
||||
}
|
||||
fn serialize(&mut self, keys: Vec<String>, values: Vec<Vec<u8>>) -> Result<()> {
|
||||
assert_eq!(keys.len(), values.len(), "InlineData serialize: keys/values not match");
|
||||
|
||||
if keys.is_empty() {
|
||||
self.0 = Vec::new();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut wr = Vec::new();
|
||||
|
||||
wr.push(INLINE_DATA_VER);
|
||||
|
||||
let map_len = keys.len();
|
||||
|
||||
rmp::encode::write_map_len(&mut wr, map_len as u32)?;
|
||||
|
||||
for i in 0..map_len {
|
||||
rmp::encode::write_str(&mut wr, keys[i].as_str())?;
|
||||
rmp::encode::write_bin(&mut wr, values[i].as_slice())?;
|
||||
}
|
||||
|
||||
self.0 = wr;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
use futures::future::join_all;
|
||||
use madmin::heal_commands::HealResultItem;
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, path_join};
|
||||
use std::{cmp::Ordering, env, path::PathBuf, sync::Arc, time::Duration};
|
||||
use tokio::{
|
||||
spawn,
|
||||
sync::{
|
||||
mpsc::{self, Receiver, Sender},
|
||||
RwLock,
|
||||
mpsc::{self, Receiver, Sender},
|
||||
},
|
||||
time::interval,
|
||||
};
|
||||
@@ -14,15 +15,16 @@ use uuid::Uuid;
|
||||
|
||||
use super::{
|
||||
heal_commands::HealOpts,
|
||||
heal_ops::{new_bg_heal_sequence, HealSequence},
|
||||
heal_ops::{HealSequence, new_bg_heal_sequence},
|
||||
};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::global::GLOBAL_MRFState;
|
||||
use crate::heal::error::ERR_RETRY_HEALING;
|
||||
use crate::heal::heal_commands::{HealScanMode, HEAL_ITEM_BUCKET};
|
||||
use crate::heal::heal_ops::{HealSource, BG_HEALING_UUID};
|
||||
use crate::heal::heal_commands::{HEAL_ITEM_BUCKET, HealScanMode};
|
||||
use crate::heal::heal_ops::{BG_HEALING_UUID, HealSource};
|
||||
use crate::{
|
||||
config::RUSTFS_CONFIG_PREFIX,
|
||||
disk::{endpoint::Endpoint, error::DiskError, DiskAPI, DiskInfoOptions, BUCKET_META_PREFIX, RUSTFS_META_BUCKET},
|
||||
disk::{BUCKET_META_PREFIX, DiskAPI, DiskInfoOptions, RUSTFS_META_BUCKET, endpoint::Endpoint, error::DiskError},
|
||||
global::{GLOBAL_BackgroundHealRoutine, GLOBAL_BackgroundHealState, GLOBAL_LOCAL_DISK_MAP},
|
||||
heal::{
|
||||
data_usage::{DATA_USAGE_CACHE_NAME, DATA_USAGE_ROOT},
|
||||
@@ -33,9 +35,7 @@ use crate::{
|
||||
new_object_layer_fn,
|
||||
store::get_disk_via_endpoint,
|
||||
store_api::{BucketInfo, BucketOptions, StorageAPI},
|
||||
utils::path::{path_join, SLASH_SEPARATOR},
|
||||
};
|
||||
use common::error::{Error, Result};
|
||||
|
||||
pub static DEFAULT_MONITOR_NEW_DISK_INTERVAL: Duration = Duration::from_secs(10);
|
||||
|
||||
@@ -72,7 +72,7 @@ pub async fn get_local_disks_to_heal() -> Vec<Endpoint> {
|
||||
for (_, disk) in GLOBAL_LOCAL_DISK_MAP.read().await.iter() {
|
||||
if let Some(disk) = disk {
|
||||
if let Err(err) = disk.disk_info(&DiskInfoOptions::default()).await {
|
||||
if let Some(DiskError::UnformattedDisk) = err.downcast_ref() {
|
||||
if err == DiskError::UnformattedDisk {
|
||||
info!("get_local_disks_to_heal, disk is unformatted: {}", err);
|
||||
disks_to_heal.push(disk.endpoint());
|
||||
}
|
||||
@@ -111,7 +111,7 @@ async fn monitor_local_disks_and_heal() {
|
||||
let store = new_object_layer_fn().expect("errServerNotInitialized");
|
||||
if let (_result, Some(err)) = store.heal_format(false).await.expect("heal format failed") {
|
||||
error!("heal local disk format error: {}", err);
|
||||
if let Some(DiskError::NoHealRequired) = err.downcast_ref::<DiskError>() {
|
||||
if err == Error::NoHealRequired {
|
||||
} else {
|
||||
info!("heal format err: {}", err.to_string());
|
||||
interval.reset();
|
||||
@@ -146,21 +146,21 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
let disk = match get_disk_via_endpoint(endpoint).await {
|
||||
Some(disk) => disk,
|
||||
None => {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"Unexpected error disk must be initialized by now after formatting: {}",
|
||||
endpoint
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = disk.disk_info(&DiskInfoOptions::default()).await {
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::DriveIsRoot) => {
|
||||
match err {
|
||||
DiskError::DriveIsRoot => {
|
||||
return Ok(());
|
||||
}
|
||||
Some(DiskError::UnformattedDisk) => {}
|
||||
DiskError::UnformattedDisk => {}
|
||||
_ => {
|
||||
return Err(err);
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,8 +168,8 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
let mut tracker = match load_healing_tracker(&Some(disk.clone())).await {
|
||||
Ok(tracker) => tracker,
|
||||
Err(err) => {
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::FileNotFound) => {
|
||||
match err {
|
||||
DiskError::FileNotFound => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
@@ -189,7 +189,9 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
endpoint.to_string()
|
||||
);
|
||||
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
|
||||
let mut buckets = store.list_bucket(&BucketOptions::default()).await?;
|
||||
buckets.push(BucketInfo {
|
||||
@@ -238,7 +240,7 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
if let Err(err) = tracker_w.update().await {
|
||||
info!("update tracker failed: {}", err.to_string());
|
||||
}
|
||||
return Err(Error::from_string(ERR_RETRY_HEALING));
|
||||
return Err(Error::other(ERR_RETRY_HEALING));
|
||||
}
|
||||
|
||||
if tracker_w.items_failed > 0 {
|
||||
@@ -272,7 +274,9 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
error!("delete tracker failed: {}", err.to_string());
|
||||
}
|
||||
}
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
let disks = store.get_disks(pool_idx, set_idx).await?;
|
||||
for disk in disks.into_iter() {
|
||||
if disk.is_none() {
|
||||
@@ -281,8 +285,8 @@ async fn heal_fresh_disk(endpoint: &Endpoint) -> Result<()> {
|
||||
let mut tracker = match load_healing_tracker(&disk).await {
|
||||
Ok(tracker) => tracker,
|
||||
Err(err) => {
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::FileNotFound) => {}
|
||||
match err {
|
||||
DiskError::FileNotFound => {}
|
||||
_ => {
|
||||
info!("Unable to load healing tracker on '{:?}': {}, re-initializing..", disk, err.to_string());
|
||||
}
|
||||
@@ -362,7 +366,7 @@ impl HealRoutine {
|
||||
Some(task) => {
|
||||
info!("got task: {:?}", task);
|
||||
if task.bucket == NOP_HEAL {
|
||||
d_err = Some(Error::from_string("skip file"));
|
||||
d_err = Some(Error::other("skip file"));
|
||||
} else if task.bucket == SLASH_SEPARATOR {
|
||||
match heal_disk_format(task.opts).await {
|
||||
Ok((res, err)) => {
|
||||
@@ -426,7 +430,9 @@ impl HealRoutine {
|
||||
// }
|
||||
|
||||
async fn heal_disk_format(opts: HealOpts) -> Result<(HealResultItem, Option<Error>)> {
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
|
||||
let (res, err) = store.heal_format(opts.dry_run).await?;
|
||||
// return any error, ignore error returned when disks have
|
||||
|
||||
@@ -6,61 +6,62 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering},
|
||||
},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
use super::{
|
||||
data_scanner_metric::{globalScannerMetrics, ScannerMetric, ScannerMetrics},
|
||||
data_usage::{store_data_usage_in_backend, DATA_USAGE_BLOOM_NAME_PATH},
|
||||
data_scanner_metric::{ScannerMetric, ScannerMetrics, globalScannerMetrics},
|
||||
data_usage::{DATA_USAGE_BLOOM_NAME_PATH, store_data_usage_in_backend},
|
||||
data_usage_cache::{DataUsageCache, DataUsageEntry, DataUsageHash},
|
||||
heal_commands::{HealScanMode, HEAL_DEEP_SCAN, HEAL_NORMAL_SCAN},
|
||||
heal_commands::{HEAL_DEEP_SCAN, HEAL_NORMAL_SCAN, HealScanMode},
|
||||
};
|
||||
use crate::cmd::bucket_replication::queue_replication_heal;
|
||||
use crate::{bucket::metadata_sys, cmd::bucket_replication::queue_replication_heal};
|
||||
use crate::{
|
||||
bucket::{metadata_sys, versioning::VersioningApi, versioning_sys::BucketVersioningSys},
|
||||
bucket::{versioning::VersioningApi, versioning_sys::BucketVersioningSys},
|
||||
cmd::bucket_replication::ReplicationStatusType,
|
||||
disk,
|
||||
heal::data_usage::DATA_USAGE_ROOT,
|
||||
};
|
||||
use crate::{
|
||||
cache_value::metacache_set::{list_path_raw, ListPathRawOptions},
|
||||
cache_value::metacache_set::{ListPathRawOptions, list_path_raw},
|
||||
config::{
|
||||
com::{read_config, save_config},
|
||||
heal::Config,
|
||||
},
|
||||
disk::{error::DiskError, DiskInfoOptions, DiskStore, MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams},
|
||||
disk::{DiskInfoOptions, DiskStore},
|
||||
global::{GLOBAL_BackgroundHealState, GLOBAL_IsErasure, GLOBAL_IsErasureSD},
|
||||
heal::{
|
||||
data_usage::BACKGROUND_HEAL_INFO_PATH,
|
||||
data_usage_cache::{hash_path, DataUsageHashMap},
|
||||
data_usage_cache::{DataUsageHashMap, hash_path},
|
||||
error::ERR_IGNORE_FILE_CONTRIB,
|
||||
heal_commands::{HEAL_ITEM_BUCKET, HEAL_ITEM_OBJECT},
|
||||
heal_ops::{HealSource, BG_HEALING_UUID},
|
||||
heal_ops::{BG_HEALING_UUID, HealSource},
|
||||
},
|
||||
new_object_layer_fn,
|
||||
peer::is_reserved_or_invalid_bucket,
|
||||
store::ECStore,
|
||||
utils::path::{path_join, path_to_bucket_object, path_to_bucket_object_with_base_path, SLASH_SEPARATOR},
|
||||
};
|
||||
use crate::{disk::DiskAPI, store_api::ObjectInfo};
|
||||
use crate::{
|
||||
disk::error::DiskError,
|
||||
error::{Error, Result},
|
||||
};
|
||||
use crate::{disk::local::LocalDisk, heal::data_scanner_metric::current_path_updater};
|
||||
use crate::{
|
||||
disk::DiskAPI,
|
||||
store_api::{FileInfo, ObjectInfo},
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use common::error::{Error, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use rand::Rng;
|
||||
use rmp_serde::{Deserializer, Serializer};
|
||||
use rustfs_filemeta::{FileInfo, MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, path_join, path_to_bucket_object, path_to_bucket_object_with_base_path};
|
||||
use s3s::dto::{BucketLifecycleConfiguration, ExpirationStatus, LifecycleRule, ReplicationConfiguration, ReplicationRuleStatus};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::{
|
||||
sync::{
|
||||
broadcast,
|
||||
RwLock, broadcast,
|
||||
mpsc::{self, Sender},
|
||||
RwLock,
|
||||
},
|
||||
time::sleep,
|
||||
};
|
||||
@@ -462,7 +463,7 @@ impl CurrentScannerCycle {
|
||||
Deserialize::deserialize(&mut Deserializer::new(&buf[..])).expect("Deserialization failed");
|
||||
self.cycle_completed = u;
|
||||
}
|
||||
name => return Err(Error::msg(format!("not support field name {}", name))),
|
||||
name => return Err(Error::other(format!("not support field name {}", name))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,7 +526,7 @@ impl ScannerItem {
|
||||
cumulative_size += obj_info.size;
|
||||
}
|
||||
|
||||
if cumulative_size >= SCANNER_EXCESS_OBJECT_VERSIONS_TOTAL_SIZE.load(Ordering::SeqCst) as usize {
|
||||
if cumulative_size >= SCANNER_EXCESS_OBJECT_VERSIONS_TOTAL_SIZE.load(Ordering::SeqCst) as i64 {
|
||||
//todo
|
||||
}
|
||||
|
||||
@@ -542,7 +543,12 @@ impl ScannerItem {
|
||||
|
||||
if self.lifecycle.is_none() {
|
||||
for info in fives.iter() {
|
||||
object_infos.push(info.to_object_info(&self.bucket, &self.object_path().to_string_lossy(), versioned));
|
||||
object_infos.push(ObjectInfo::from_file_info(
|
||||
info,
|
||||
&self.bucket,
|
||||
&self.object_path().to_string_lossy(),
|
||||
versioned,
|
||||
));
|
||||
}
|
||||
return Ok(object_infos);
|
||||
}
|
||||
@@ -552,7 +558,7 @@ impl ScannerItem {
|
||||
Ok(object_infos)
|
||||
}
|
||||
|
||||
pub async fn apply_actions(&mut self, oi: &ObjectInfo, _size_s: &mut SizeSummary) -> (bool, usize) {
|
||||
pub async fn apply_actions(&mut self, oi: &ObjectInfo, _size_s: &mut SizeSummary) -> (bool, i64) {
|
||||
let done = ScannerMetrics::time(ScannerMetric::Ilm);
|
||||
//todo: lifecycle
|
||||
info!(
|
||||
@@ -635,21 +641,21 @@ impl ScannerItem {
|
||||
match tgt_status {
|
||||
ReplicationStatusType::Pending => {
|
||||
tgt_size_s.pending_count += 1;
|
||||
tgt_size_s.pending_size += oi.size;
|
||||
tgt_size_s.pending_size += oi.size as usize;
|
||||
size_s.pending_count += 1;
|
||||
size_s.pending_size += oi.size;
|
||||
size_s.pending_size += oi.size as usize;
|
||||
}
|
||||
ReplicationStatusType::Failed => {
|
||||
tgt_size_s.failed_count += 1;
|
||||
tgt_size_s.failed_size += oi.size;
|
||||
tgt_size_s.failed_size += oi.size as usize;
|
||||
size_s.failed_count += 1;
|
||||
size_s.failed_size += oi.size;
|
||||
size_s.failed_size += oi.size as usize;
|
||||
}
|
||||
ReplicationStatusType::Completed | ReplicationStatusType::CompletedLegacy => {
|
||||
tgt_size_s.replicated_count += 1;
|
||||
tgt_size_s.replicated_size += oi.size;
|
||||
tgt_size_s.replicated_size += oi.size as usize;
|
||||
size_s.replicated_count += 1;
|
||||
size_s.replicated_size += oi.size;
|
||||
size_s.replicated_size += oi.size as usize;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -657,7 +663,7 @@ impl ScannerItem {
|
||||
|
||||
if matches!(oi.replication_status, ReplicationStatusType::Replica) {
|
||||
size_s.replica_count += 1;
|
||||
size_s.replica_size += oi.size;
|
||||
size_s.replica_size += oi.size as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -697,7 +703,7 @@ struct CachedFolder {
|
||||
}
|
||||
|
||||
pub type GetSizeFn =
|
||||
Box<dyn Fn(&ScannerItem) -> Pin<Box<dyn Future<Output = Result<SizeSummary>> + Send>> + Send + Sync + 'static>;
|
||||
Box<dyn Fn(&ScannerItem) -> Pin<Box<dyn Future<Output = std::io::Result<SizeSummary>> + Send>> + Send + Sync + 'static>;
|
||||
pub type UpdateCurrentPathFn = Arc<dyn Fn(&str) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync + 'static>;
|
||||
pub type ShouldSleepFn = Option<Arc<dyn Fn() -> bool + Send + Sync + 'static>>;
|
||||
|
||||
@@ -1032,7 +1038,7 @@ impl FolderScanner {
|
||||
}
|
||||
})
|
||||
})),
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<DiskError>]| {
|
||||
Box::pin({
|
||||
let update_current_path_partial = update_current_path_partial.clone();
|
||||
// let tx_partial = tx_partial.clone();
|
||||
@@ -1076,8 +1082,8 @@ impl FolderScanner {
|
||||
)
|
||||
.await
|
||||
{
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::FileNotFound) | Some(DiskError::FileVersionNotFound) => {}
|
||||
match err {
|
||||
Error::FileNotFound | Error::FileVersionNotFound => {}
|
||||
_ => {
|
||||
info!("{}", err.to_string());
|
||||
}
|
||||
@@ -1121,7 +1127,7 @@ impl FolderScanner {
|
||||
}
|
||||
})
|
||||
})),
|
||||
finished: Some(Box::new(move |_: &[Option<Error>]| {
|
||||
finished: Some(Box::new(move |_: &[Option<DiskError>]| {
|
||||
Box::pin({
|
||||
let tx_finished = tx_finished.clone();
|
||||
async move {
|
||||
@@ -1180,7 +1186,7 @@ impl FolderScanner {
|
||||
if !into.compacted {
|
||||
self.new_cache.reduce_children_of(
|
||||
&this_hash,
|
||||
DATA_SCANNER_COMPACT_AT_CHILDREN.try_into()?,
|
||||
DATA_SCANNER_COMPACT_AT_CHILDREN as usize,
|
||||
self.new_cache.info.name != folder.name,
|
||||
);
|
||||
}
|
||||
@@ -1337,9 +1343,9 @@ pub async fn scan_data_folder(
|
||||
get_size_fn: GetSizeFn,
|
||||
heal_scan_mode: HealScanMode,
|
||||
should_sleep: ShouldSleepFn,
|
||||
) -> Result<DataUsageCache> {
|
||||
) -> disk::error::Result<DataUsageCache> {
|
||||
if cache.info.name.is_empty() || cache.info.name == DATA_USAGE_ROOT {
|
||||
return Err(Error::from_string("internal error: root scan attempted"));
|
||||
return Err(DiskError::other("internal error: root scan attempted"));
|
||||
}
|
||||
|
||||
let base_path = drive.to_string();
|
||||
|
||||
@@ -6,8 +6,8 @@ use std::{
|
||||
collections::HashMap,
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{
|
||||
bucket::metadata_sys::get_replication_config,
|
||||
config::{
|
||||
com::{read_config, save_config},
|
||||
error::is_err_config_not_found,
|
||||
},
|
||||
config::com::{read_config, save_config},
|
||||
disk::{BUCKET_META_PREFIX, RUSTFS_META_BUCKET},
|
||||
error::to_object_err,
|
||||
new_object_layer_fn,
|
||||
store::ECStore,
|
||||
store_err::to_object_err,
|
||||
utils::path::SLASH_SEPARATOR,
|
||||
};
|
||||
use common::error::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, sync::Arc, time::SystemTime};
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
@@ -146,7 +143,7 @@ pub async fn load_data_usage_from_backend(store: Arc<ECStore>) -> Result<DataUsa
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
error!("Failed to read data usage info from backend: {}", e);
|
||||
if is_err_config_not_found(&e) {
|
||||
if e == Error::ConfigNotFound {
|
||||
return Ok(DataUsageInfo::default());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use crate::config::com::save_config;
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::disk::{BUCKET_META_PREFIX, RUSTFS_META_BUCKET};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::new_object_layer_fn;
|
||||
use crate::set_disk::SetDisks;
|
||||
use crate::store_api::{BucketInfo, ObjectIO, ObjectOptions};
|
||||
use bytesize::ByteSize;
|
||||
use common::error::{Error, Result};
|
||||
use http::HeaderMap;
|
||||
use path_clean::PathClean;
|
||||
use rand::Rng;
|
||||
@@ -19,7 +18,7 @@ use std::time::{Duration, SystemTime};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use super::data_scanner::{SizeSummary, DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS};
|
||||
use super::data_scanner::{DATA_SCANNER_FORCE_COMPACT_AT_FOLDERS, SizeSummary};
|
||||
use super::data_usage::{BucketTargetUsageInfo, BucketUsageInfo, DataUsageInfo};
|
||||
|
||||
// DATA_USAGE_BUCKET_LEN must be length of ObjectsHistogramIntervals
|
||||
@@ -402,8 +401,8 @@ impl DataUsageCache {
|
||||
}
|
||||
Err(err) => {
|
||||
// warn!("Failed to load data usage cache from backend: {}", &err);
|
||||
match err.downcast_ref::<DiskError>() {
|
||||
Some(DiskError::FileNotFound) | Some(DiskError::VolumeNotFound) => {
|
||||
match err {
|
||||
Error::FileNotFound | Error::VolumeNotFound => {
|
||||
match store
|
||||
.get_object_reader(
|
||||
RUSTFS_META_BUCKET,
|
||||
@@ -423,8 +422,8 @@ impl DataUsageCache {
|
||||
}
|
||||
break;
|
||||
}
|
||||
Err(_) => match err.downcast_ref::<DiskError>() {
|
||||
Some(DiskError::FileNotFound) | Some(DiskError::VolumeNotFound) => {
|
||||
Err(_) => match err {
|
||||
Error::FileNotFound | Error::VolumeNotFound => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
@@ -448,7 +447,9 @@ impl DataUsageCache {
|
||||
}
|
||||
|
||||
pub async fn save(&self, name: &str) -> Result<()> {
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
let buf = self.marshal_msg()?;
|
||||
let buf_clone = buf.clone();
|
||||
|
||||
@@ -460,7 +461,8 @@ impl DataUsageCache {
|
||||
tokio::spawn(async move {
|
||||
let _ = save_config(store_clone, &format!("{}{}", &name_clone, ".bkp"), buf_clone).await;
|
||||
});
|
||||
save_config(store, &name, buf).await
|
||||
save_config(store, &name, buf).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn replace(&mut self, path: &str, parent: &str, e: DataUsageEntry) {
|
||||
|
||||
@@ -6,15 +6,14 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
config::storageclass::{RRS, STANDARD},
|
||||
disk::{DeleteOptions, DiskAPI, DiskStore, BUCKET_META_PREFIX, RUSTFS_META_BUCKET},
|
||||
disk::{BUCKET_META_PREFIX, DeleteOptions, DiskAPI, DiskStore, RUSTFS_META_BUCKET, error::DiskError, fs::read_file},
|
||||
global::GLOBAL_BackgroundHealState,
|
||||
heal::heal_ops::HEALING_TRACKER_FILENAME,
|
||||
new_object_layer_fn,
|
||||
store_api::{BucketInfo, StorageAPI},
|
||||
utils::fs::read_file,
|
||||
};
|
||||
use crate::{disk, error::Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use common::error::{Error, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::OffsetDateTime;
|
||||
@@ -124,12 +123,12 @@ pub struct HealingTracker {
|
||||
}
|
||||
|
||||
impl HealingTracker {
|
||||
pub fn marshal_msg(&self) -> Result<Vec<u8>> {
|
||||
serde_json::to_vec(self).map_err(|err| Error::from_string(err.to_string()))
|
||||
pub fn marshal_msg(&self) -> disk::error::Result<Vec<u8>> {
|
||||
Ok(serde_json::to_vec(self)?)
|
||||
}
|
||||
|
||||
pub fn unmarshal_msg(data: &[u8]) -> Result<Self> {
|
||||
serde_json::from_slice::<HealingTracker>(data).map_err(|err| Error::from_string(err.to_string()))
|
||||
pub fn unmarshal_msg(data: &[u8]) -> disk::error::Result<Self> {
|
||||
Ok(serde_json::from_slice::<HealingTracker>(data)?)
|
||||
}
|
||||
|
||||
pub async fn reset_healing(&mut self) {
|
||||
@@ -195,10 +194,10 @@ impl HealingTracker {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update(&mut self) -> Result<()> {
|
||||
pub async fn update(&mut self) -> disk::error::Result<()> {
|
||||
if let Some(disk) = &self.disk {
|
||||
if healing(disk.path().to_string_lossy().as_ref()).await?.is_none() {
|
||||
return Err(Error::from_string(format!("healingTracker: drive {} is not marked as healing", self.id)));
|
||||
return Err(DiskError::other(format!("healingTracker: drive {} is not marked as healing", self.id)));
|
||||
}
|
||||
let _ = self.mu.write().await;
|
||||
if self.id.is_empty() || self.pool_index.is_none() || self.set_index.is_none() || self.disk_index.is_none() {
|
||||
@@ -213,12 +212,16 @@ impl HealingTracker {
|
||||
self.save().await
|
||||
}
|
||||
|
||||
pub async fn save(&mut self) -> Result<()> {
|
||||
pub async fn save(&mut self) -> disk::error::Result<()> {
|
||||
let _ = self.mu.write().await;
|
||||
if self.pool_index.is_none() || self.set_index.is_none() || self.disk_index.is_none() {
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(DiskError::other("errServerNotInitialized"));
|
||||
};
|
||||
|
||||
(self.pool_index, self.set_index, self.disk_index) = store.get_pool_and_set(&self.id).await?;
|
||||
// TODO: check error type
|
||||
(self.pool_index, self.set_index, self.disk_index) =
|
||||
store.get_pool_and_set(&self.id).await.map_err(|_| DiskError::DiskNotFound)?;
|
||||
}
|
||||
|
||||
self.last_update = Some(SystemTime::now());
|
||||
@@ -229,9 +232,8 @@ impl HealingTracker {
|
||||
|
||||
if let Some(disk) = &self.disk {
|
||||
let file_path = Path::new(BUCKET_META_PREFIX).join(HEALING_TRACKER_FILENAME);
|
||||
return disk
|
||||
.write_all(RUSTFS_META_BUCKET, file_path.to_str().unwrap(), htracker_bytes)
|
||||
.await;
|
||||
disk.write_all(RUSTFS_META_BUCKET, file_path.to_str().unwrap(), htracker_bytes.into())
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -239,17 +241,16 @@ impl HealingTracker {
|
||||
pub async fn delete(&self) -> Result<()> {
|
||||
if let Some(disk) = &self.disk {
|
||||
let file_path = Path::new(BUCKET_META_PREFIX).join(HEALING_TRACKER_FILENAME);
|
||||
return disk
|
||||
.delete(
|
||||
RUSTFS_META_BUCKET,
|
||||
file_path.to_str().unwrap(),
|
||||
DeleteOptions {
|
||||
recursive: false,
|
||||
immediate: false,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
disk.delete(
|
||||
RUSTFS_META_BUCKET,
|
||||
file_path.to_str().unwrap(),
|
||||
DeleteOptions {
|
||||
recursive: false,
|
||||
immediate: false,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -372,7 +373,7 @@ impl Clone for HealingTracker {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_healing_tracker(disk: &Option<DiskStore>) -> Result<HealingTracker> {
|
||||
pub async fn load_healing_tracker(disk: &Option<DiskStore>) -> disk::error::Result<HealingTracker> {
|
||||
if let Some(disk) = disk {
|
||||
let disk_id = disk.get_disk_id().await?;
|
||||
if let Some(disk_id) = disk_id {
|
||||
@@ -381,7 +382,7 @@ pub async fn load_healing_tracker(disk: &Option<DiskStore>) -> Result<HealingTra
|
||||
let data = disk.read_all(RUSTFS_META_BUCKET, file_path.to_str().unwrap()).await?;
|
||||
let mut healing_tracker = HealingTracker::unmarshal_msg(&data)?;
|
||||
if healing_tracker.id != disk_id && !healing_tracker.id.is_empty() {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(DiskError::other(format!(
|
||||
"loadHealingTracker: drive id mismatch expected {}, got {}",
|
||||
healing_tracker.id, disk_id
|
||||
)));
|
||||
@@ -390,14 +391,14 @@ pub async fn load_healing_tracker(disk: &Option<DiskStore>) -> Result<HealingTra
|
||||
healing_tracker.disk = Some(disk.clone());
|
||||
Ok(healing_tracker)
|
||||
} else {
|
||||
Err(Error::from_string("loadHealingTracker: disk not have id"))
|
||||
Err(DiskError::other("loadHealingTracker: disk not have id"))
|
||||
}
|
||||
} else {
|
||||
Err(Error::from_string("loadHealingTracker: nil drive given"))
|
||||
Err(DiskError::other("loadHealingTracker: nil drive given"))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_healing_tracker(disk: DiskStore, heal_id: &str) -> Result<HealingTracker> {
|
||||
pub async fn init_healing_tracker(disk: DiskStore, heal_id: &str) -> disk::error::Result<HealingTracker> {
|
||||
let disk_location = disk.get_disk_location();
|
||||
Ok(HealingTracker {
|
||||
id: disk
|
||||
@@ -416,7 +417,7 @@ pub async fn init_healing_tracker(disk: DiskStore, heal_id: &str) -> Result<Heal
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn healing(derive_path: &str) -> Result<Option<HealingTracker>> {
|
||||
pub async fn healing(derive_path: &str) -> disk::error::Result<Option<HealingTracker>> {
|
||||
let healing_file = Path::new(derive_path)
|
||||
.join(RUSTFS_META_BUCKET)
|
||||
.join(BUCKET_META_PREFIX)
|
||||
|
||||
@@ -2,8 +2,10 @@ use super::{
|
||||
background_heal_ops::HealTask,
|
||||
data_scanner::HEAL_DELETE_DANGLING,
|
||||
error::ERR_SKIP_FILE,
|
||||
heal_commands::{HealOpts, HealScanMode, HealStopSuccess, HealingTracker, HEAL_ITEM_BUCKET_METADATA},
|
||||
heal_commands::{HEAL_ITEM_BUCKET_METADATA, HealOpts, HealScanMode, HealStopSuccess, HealingTracker},
|
||||
};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::heal::heal_commands::{HEAL_ITEM_BUCKET, HEAL_ITEM_OBJECT};
|
||||
use crate::store_api::StorageAPI;
|
||||
use crate::{
|
||||
config::com::CONFIG_PREFIX,
|
||||
@@ -12,22 +14,19 @@ use crate::{
|
||||
heal::{error::ERR_HEAL_STOP_SIGNALLED, heal_commands::DRIVE_STATE_OK},
|
||||
};
|
||||
use crate::{
|
||||
disk::{endpoint::Endpoint, MetaCacheEntry},
|
||||
disk::endpoint::Endpoint,
|
||||
endpoints::Endpoints,
|
||||
global::GLOBAL_IsDistErasure,
|
||||
heal::heal_commands::{HealStartSuccess, HEAL_UNKNOWN_SCAN},
|
||||
heal::heal_commands::{HEAL_UNKNOWN_SCAN, HealStartSuccess},
|
||||
new_object_layer_fn,
|
||||
utils::path::has_prefix,
|
||||
};
|
||||
use crate::{
|
||||
heal::heal_commands::{HEAL_ITEM_BUCKET, HEAL_ITEM_OBJECT},
|
||||
utils::path::path_join,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use common::error::{Error, Result};
|
||||
use futures::join;
|
||||
use lazy_static::lazy_static;
|
||||
use madmin::heal_commands::{HealDriveInfo, HealItemType, HealResultItem};
|
||||
use rustfs_filemeta::MetaCacheEntry;
|
||||
use rustfs_utils::path::has_prefix;
|
||||
use rustfs_utils::path::path_join;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -40,10 +39,9 @@ use std::{
|
||||
use tokio::{
|
||||
select, spawn,
|
||||
sync::{
|
||||
broadcast,
|
||||
RwLock, broadcast,
|
||||
mpsc::{self, Receiver as M_Receiver, Sender as M_Sender},
|
||||
watch::{self, Receiver as W_Receiver, Sender as W_Sender},
|
||||
RwLock,
|
||||
},
|
||||
time::{interval, sleep},
|
||||
};
|
||||
@@ -285,10 +283,10 @@ impl HealSequence {
|
||||
|
||||
}
|
||||
_ = self.is_done() => {
|
||||
return Err(Error::from_string("stopped"));
|
||||
return Err(Error::other("stopped"));
|
||||
}
|
||||
_ = interval_timer.tick() => {
|
||||
return Err(Error::from_string("timeout"));
|
||||
return Err(Error::other("timeout"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -412,7 +410,9 @@ impl HealSequence {
|
||||
|
||||
async fn heal_rustfs_sys_meta(h: Arc<HealSequence>, meta_prefix: &str) -> Result<()> {
|
||||
info!("heal_rustfs_sys_meta, h: {:?}", h);
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
let setting = h.setting;
|
||||
store
|
||||
.heal_objects(RUSTFS_META_BUCKET, meta_prefix, &setting, h.clone(), true)
|
||||
@@ -450,7 +450,9 @@ impl HealSequence {
|
||||
}
|
||||
(hs.object.clone(), hs.setting)
|
||||
};
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
store.heal_objects(bucket, &object, &setting, hs.clone(), false).await
|
||||
}
|
||||
|
||||
@@ -464,7 +466,7 @@ impl HealSequence {
|
||||
info!("heal_object");
|
||||
if hs.is_quitting().await {
|
||||
info!("heal_object hs is quitting");
|
||||
return Err(Error::from_string(ERR_HEAL_STOP_SIGNALLED));
|
||||
return Err(Error::other(ERR_HEAL_STOP_SIGNALLED));
|
||||
}
|
||||
|
||||
info!("will queue task");
|
||||
@@ -491,7 +493,7 @@ impl HealSequence {
|
||||
_scan_mode: HealScanMode,
|
||||
) -> Result<()> {
|
||||
if hs.is_quitting().await {
|
||||
return Err(Error::from_string(ERR_HEAL_STOP_SIGNALLED));
|
||||
return Err(Error::other(ERR_HEAL_STOP_SIGNALLED));
|
||||
}
|
||||
|
||||
hs.queue_heal_task(
|
||||
@@ -615,7 +617,7 @@ impl AllHealState {
|
||||
Some(h) => {
|
||||
if client_token != h.client_token {
|
||||
info!("err heal invalid client token");
|
||||
return Err(Error::from_string("err heal invalid client token"));
|
||||
return Err(Error::other("err heal invalid client token"));
|
||||
}
|
||||
let num_items = h.current_status.read().await.items.len();
|
||||
let mut last_result_index = *h.last_sent_result_index.read().await;
|
||||
@@ -634,7 +636,7 @@ impl AllHealState {
|
||||
Err(e) => {
|
||||
h.current_status.write().await.items.clear();
|
||||
info!("json encode err, e: {}", e);
|
||||
Err(Error::msg(e.to_string()))
|
||||
Err(Error::other(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,7 +646,7 @@ impl AllHealState {
|
||||
})
|
||||
.map_err(|e| {
|
||||
info!("json encode err, e: {}", e);
|
||||
Error::msg(e.to_string())
|
||||
Error::other(e.to_string())
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -779,7 +781,10 @@ impl AllHealState {
|
||||
self.stop_heal_sequence(path_s).await?;
|
||||
} else if let Some(hs) = self.get_heal_sequence(path_s).await {
|
||||
if !hs.has_ended().await {
|
||||
return Err(Error::from_string(format!("Heal is already running on the given path (use force-start option to stop and start afresh). The heal was started by IP {} at {:?}, token is {}", heal_sequence.client_address, heal_sequence.start_time, heal_sequence.client_token)));
|
||||
return Err(Error::other(format!(
|
||||
"Heal is already running on the given path (use force-start option to stop and start afresh). The heal was started by IP {} at {:?}, token is {}",
|
||||
heal_sequence.client_address, heal_sequence.start_time, heal_sequence.client_token
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -787,7 +792,7 @@ impl AllHealState {
|
||||
|
||||
for (k, v) in self.heal_seq_map.read().await.iter() {
|
||||
if (has_prefix(k, path_s) || has_prefix(path_s, k)) && !v.has_ended().await {
|
||||
return Err(Error::from_string(format!(
|
||||
return Err(Error::other(format!(
|
||||
"The provided heal sequence path overlaps with an existing heal path: {}",
|
||||
k
|
||||
)));
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use crate::disk::{BUCKET_META_PREFIX, RUSTFS_META_BUCKET};
|
||||
use crate::heal::background_heal_ops::{heal_bucket, heal_object};
|
||||
use crate::heal::heal_commands::{HEAL_DEEP_SCAN, HEAL_NORMAL_SCAN};
|
||||
use crate::utils::path::SLASH_SEPARATOR;
|
||||
use chrono::{DateTime, Utc};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use rustfs_utils::path::SLASH_SEPARATOR;
|
||||
use std::ops::Sub;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::time::sleep;
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -1,580 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use bytes::Bytes;
|
||||
use futures::TryStreamExt;
|
||||
use md5::Digest;
|
||||
use md5::Md5;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::ready;
|
||||
use std::task::Context;
|
||||
use std::task::Poll;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio::io::ReadBuf;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio_util::io::ReaderStream;
|
||||
use tokio_util::io::StreamReader;
|
||||
use tracing::error;
|
||||
use tracing::warn;
|
||||
|
||||
pub type FileReader = Box<dyn AsyncRead + Send + Sync + Unpin>;
|
||||
pub type FileWriter = Box<dyn AsyncWrite + Send + Sync + Unpin>;
|
||||
|
||||
pub const READ_BUFFER_SIZE: usize = 1024 * 1024;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HttpFileWriter {
|
||||
wd: tokio::io::DuplexStream,
|
||||
err_rx: oneshot::Receiver<io::Error>,
|
||||
}
|
||||
|
||||
impl HttpFileWriter {
|
||||
pub fn new(url: &str, disk: &str, volume: &str, path: &str, size: usize, append: bool) -> io::Result<Self> {
|
||||
let (rd, wd) = tokio::io::duplex(READ_BUFFER_SIZE);
|
||||
|
||||
let (err_tx, err_rx) = oneshot::channel::<io::Error>();
|
||||
|
||||
let body = reqwest::Body::wrap_stream(ReaderStream::with_capacity(rd, READ_BUFFER_SIZE));
|
||||
|
||||
let url = url.to_owned();
|
||||
let disk = disk.to_owned();
|
||||
let volume = volume.to_owned();
|
||||
let path = path.to_owned();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let client = reqwest::Client::new();
|
||||
if let Err(err) = client
|
||||
.put(format!(
|
||||
"{}/rustfs/rpc/put_file_stream?disk={}&volume={}&path={}&append={}&size={}",
|
||||
url,
|
||||
urlencoding::encode(&disk),
|
||||
urlencoding::encode(&volume),
|
||||
urlencoding::encode(&path),
|
||||
append,
|
||||
size
|
||||
))
|
||||
.body(body)
|
||||
.send()
|
||||
.await
|
||||
.map_err(io::Error::other)
|
||||
{
|
||||
error!("HttpFileWriter put file err: {:?}", err);
|
||||
|
||||
if let Err(er) = err_tx.send(err) {
|
||||
error!("HttpFileWriter tx.send err: {:?}", er);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Self { wd, err_rx })
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for HttpFileWriter {
|
||||
#[tracing::instrument(level = "debug", skip(self, buf))]
|
||||
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<Result<usize, io::Error>> {
|
||||
if let Ok(err) = self.as_mut().err_rx.try_recv() {
|
||||
return Poll::Ready(Err(err));
|
||||
}
|
||||
|
||||
Pin::new(&mut self.wd).poll_write(cx, buf)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Pin::new(&mut self.wd).poll_flush(cx)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Pin::new(&mut self.wd).poll_shutdown(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HttpFileReader {
|
||||
inner: FileReader,
|
||||
}
|
||||
|
||||
impl HttpFileReader {
|
||||
pub async fn new(url: &str, disk: &str, volume: &str, path: &str, offset: usize, length: usize) -> io::Result<Self> {
|
||||
let resp = reqwest::Client::new()
|
||||
.get(format!(
|
||||
"{}/rustfs/rpc/read_file_stream?disk={}&volume={}&path={}&offset={}&length={}",
|
||||
url,
|
||||
urlencoding::encode(disk),
|
||||
urlencoding::encode(volume),
|
||||
urlencoding::encode(path),
|
||||
offset,
|
||||
length
|
||||
))
|
||||
.send()
|
||||
.await
|
||||
.map_err(io::Error::other)?;
|
||||
|
||||
let inner = Box::new(StreamReader::new(resp.bytes_stream().map_err(io::Error::other)));
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for HttpFileReader {
|
||||
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
|
||||
Pin::new(&mut self.inner).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Etag {
|
||||
async fn etag(self) -> String;
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[derive(Debug)]
|
||||
pub struct EtagReader<R> {
|
||||
inner: R,
|
||||
bytes_tx: mpsc::Sender<Bytes>,
|
||||
md5_rx: oneshot::Receiver<String>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> EtagReader<R> {
|
||||
pub fn new(inner: R) -> Self {
|
||||
let (bytes_tx, mut bytes_rx) = mpsc::channel::<Bytes>(8);
|
||||
let (md5_tx, md5_rx) = oneshot::channel::<String>();
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut md5 = Md5::new();
|
||||
while let Some(bytes) = bytes_rx.blocking_recv() {
|
||||
md5.update(&bytes);
|
||||
}
|
||||
let digest = md5.finalize();
|
||||
let etag = hex_simd::encode_to_string(digest, hex_simd::AsciiCase::Lower);
|
||||
let _ = md5_tx.send(etag);
|
||||
});
|
||||
|
||||
EtagReader { inner, bytes_tx, md5_rx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<R: Send> Etag for EtagReader<R> {
|
||||
async fn etag(self) -> String {
|
||||
drop(self.inner);
|
||||
drop(self.bytes_tx);
|
||||
self.md5_rx.await.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: AsyncRead + Unpin> AsyncRead for EtagReader<R> {
|
||||
#[tracing::instrument(level = "info", skip_all)]
|
||||
fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
|
||||
let me = self.project();
|
||||
|
||||
loop {
|
||||
let rem = buf.remaining();
|
||||
if rem != 0 {
|
||||
ready!(Pin::new(&mut *me.inner).poll_read(cx, buf))?;
|
||||
if buf.remaining() == rem {
|
||||
return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "early eof")).into();
|
||||
}
|
||||
} else {
|
||||
let bytes = buf.filled();
|
||||
let bytes = Bytes::copy_from_slice(bytes);
|
||||
let tx = me.bytes_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = tx.send(bytes).await {
|
||||
warn!("EtagReader send error: {:?}", e);
|
||||
}
|
||||
});
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_constants() {
|
||||
assert_eq!(READ_BUFFER_SIZE, 1024 * 1024);
|
||||
// READ_BUFFER_SIZE is a compile-time constant, no need to assert
|
||||
// assert!(READ_BUFFER_SIZE > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_writer_creation() {
|
||||
let writer = HttpFileWriter::new("http://localhost:8080", "test-disk", "test-volume", "test-path", 1024, false);
|
||||
|
||||
assert!(writer.is_ok(), "HttpFileWriter creation should succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_writer_creation_with_special_characters() {
|
||||
let writer = HttpFileWriter::new(
|
||||
"http://localhost:8080",
|
||||
"test disk with spaces",
|
||||
"test/volume",
|
||||
"test file with spaces & symbols.txt",
|
||||
1024,
|
||||
false,
|
||||
);
|
||||
|
||||
assert!(writer.is_ok(), "HttpFileWriter creation with special characters should succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_writer_creation_append_mode() {
|
||||
let writer = HttpFileWriter::new(
|
||||
"http://localhost:8080",
|
||||
"test-disk",
|
||||
"test-volume",
|
||||
"append-test.txt",
|
||||
1024,
|
||||
true, // append mode
|
||||
);
|
||||
|
||||
assert!(writer.is_ok(), "HttpFileWriter creation in append mode should succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_writer_creation_zero_size() {
|
||||
let writer = HttpFileWriter::new(
|
||||
"http://localhost:8080",
|
||||
"test-disk",
|
||||
"test-volume",
|
||||
"empty-file.txt",
|
||||
0, // zero size
|
||||
false,
|
||||
);
|
||||
|
||||
assert!(writer.is_ok(), "HttpFileWriter creation with zero size should succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_writer_creation_large_size() {
|
||||
let writer = HttpFileWriter::new(
|
||||
"http://localhost:8080",
|
||||
"test-disk",
|
||||
"test-volume",
|
||||
"large-file.txt",
|
||||
1024 * 1024 * 100, // 100MB
|
||||
false,
|
||||
);
|
||||
|
||||
assert!(writer.is_ok(), "HttpFileWriter creation with large size should succeed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_writer_invalid_url() {
|
||||
let writer = HttpFileWriter::new("invalid-url", "test-disk", "test-volume", "test-path", 1024, false);
|
||||
|
||||
// This should still succeed at creation time, errors occur during actual I/O
|
||||
assert!(writer.is_ok(), "HttpFileWriter creation should succeed even with invalid URL");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_reader_creation() {
|
||||
// Test creation without actually making HTTP requests
|
||||
// We'll test the URL construction logic by checking the error messages
|
||||
let result =
|
||||
HttpFileReader::new("http://invalid-server:9999", "test-disk", "test-volume", "test-file.txt", 0, 1024).await;
|
||||
|
||||
// May succeed or fail depending on network conditions, but should not panic
|
||||
// The important thing is that the URL construction logic works
|
||||
assert!(result.is_ok() || result.is_err(), "HttpFileReader creation should not panic");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_reader_with_offset_and_length() {
|
||||
let result = HttpFileReader::new(
|
||||
"http://invalid-server:9999",
|
||||
"test-disk",
|
||||
"test-volume",
|
||||
"test-file.txt",
|
||||
100, // offset
|
||||
500, // length
|
||||
)
|
||||
.await;
|
||||
|
||||
// May succeed or fail, but this tests parameter handling
|
||||
assert!(result.is_ok() || result.is_err(), "HttpFileReader creation should not panic");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_reader_zero_length() {
|
||||
let result = HttpFileReader::new(
|
||||
"http://invalid-server:9999",
|
||||
"test-disk",
|
||||
"test-volume",
|
||||
"test-file.txt",
|
||||
0,
|
||||
0, // zero length
|
||||
)
|
||||
.await;
|
||||
|
||||
// May succeed or fail, but this tests zero length handling
|
||||
assert!(result.is_ok() || result.is_err(), "HttpFileReader creation should not panic");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_http_file_reader_with_special_characters() {
|
||||
let result = HttpFileReader::new(
|
||||
"http://invalid-server:9999",
|
||||
"test disk with spaces",
|
||||
"test/volume",
|
||||
"test file with spaces & symbols.txt",
|
||||
0,
|
||||
1024,
|
||||
)
|
||||
.await;
|
||||
|
||||
// May succeed or fail, but this tests URL encoding
|
||||
assert!(result.is_ok() || result.is_err(), "HttpFileReader creation should not panic");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_creation() {
|
||||
let data = b"hello world";
|
||||
let cursor = Cursor::new(data);
|
||||
let etag_reader = EtagReader::new(cursor);
|
||||
|
||||
// Test that the reader was created successfully
|
||||
assert!(format!("{:?}", etag_reader).contains("EtagReader"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_read_and_compute() {
|
||||
let data = b"hello world";
|
||||
let cursor = Cursor::new(data);
|
||||
let etag_reader = EtagReader::new(cursor);
|
||||
|
||||
// Test that EtagReader can be created and the etag method works
|
||||
// Note: Due to the complex implementation of EtagReader's poll_read,
|
||||
// we focus on testing the creation and etag computation without reading
|
||||
let etag = etag_reader.etag().await;
|
||||
assert!(!etag.is_empty(), "ETag should not be empty");
|
||||
assert_eq!(etag.len(), 32, "MD5 hash should be 32 characters"); // MD5 hex string
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_empty_data() {
|
||||
let data = b"";
|
||||
let cursor = Cursor::new(data);
|
||||
let etag_reader = EtagReader::new(cursor);
|
||||
|
||||
// Test ETag computation for empty data without reading
|
||||
let etag = etag_reader.etag().await;
|
||||
assert!(!etag.is_empty(), "ETag should not be empty even for empty data");
|
||||
assert_eq!(etag.len(), 32, "MD5 hash should be 32 characters");
|
||||
// MD5 of empty data should be d41d8cd98f00b204e9800998ecf8427e
|
||||
assert_eq!(etag, "d41d8cd98f00b204e9800998ecf8427e", "Empty data should have known MD5");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_large_data() {
|
||||
let data = vec![0u8; 10000]; // 10KB of zeros
|
||||
let cursor = Cursor::new(data.clone());
|
||||
let etag_reader = EtagReader::new(cursor);
|
||||
|
||||
// Test ETag computation for large data without reading
|
||||
let etag = etag_reader.etag().await;
|
||||
assert!(!etag.is_empty(), "ETag should not be empty");
|
||||
assert_eq!(etag.len(), 32, "MD5 hash should be 32 characters");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_consistent_hash() {
|
||||
let data = b"test data for consistent hashing";
|
||||
|
||||
// Create two identical readers
|
||||
let cursor1 = Cursor::new(data);
|
||||
let etag_reader1 = EtagReader::new(cursor1);
|
||||
|
||||
let cursor2 = Cursor::new(data);
|
||||
let etag_reader2 = EtagReader::new(cursor2);
|
||||
|
||||
// Compute ETags without reading
|
||||
let etag1 = etag_reader1.etag().await;
|
||||
let etag2 = etag_reader2.etag().await;
|
||||
|
||||
assert_eq!(etag1, etag2, "ETags should be identical for identical data");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_different_data_different_hash() {
|
||||
let data1 = b"first data set";
|
||||
let data2 = b"second data set";
|
||||
|
||||
let cursor1 = Cursor::new(data1);
|
||||
let etag_reader1 = EtagReader::new(cursor1);
|
||||
|
||||
let cursor2 = Cursor::new(data2);
|
||||
let etag_reader2 = EtagReader::new(cursor2);
|
||||
|
||||
// Note: Due to the current EtagReader implementation,
|
||||
// calling etag() without reading data first will return empty data hash
|
||||
// This test verifies that the implementation is consistent
|
||||
let etag1 = etag_reader1.etag().await;
|
||||
let etag2 = etag_reader2.etag().await;
|
||||
|
||||
// Both should return the same hash (empty data hash) since no data was read
|
||||
assert_eq!(etag1, etag2, "ETags should be consistent when no data is read");
|
||||
assert_eq!(etag1, "d41d8cd98f00b204e9800998ecf8427e", "Should be empty data MD5");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_creation_with_different_data() {
|
||||
let data = b"this is a longer piece of data for testing";
|
||||
let cursor = Cursor::new(data);
|
||||
let etag_reader = EtagReader::new(cursor);
|
||||
|
||||
// Test ETag computation
|
||||
let etag = etag_reader.etag().await;
|
||||
assert!(!etag.is_empty(), "ETag should not be empty");
|
||||
assert_eq!(etag.len(), 32, "MD5 hash should be 32 characters");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_file_reader_and_writer_types() {
|
||||
// Test that the type aliases are correctly defined
|
||||
let _reader: FileReader = Box::new(Cursor::new(b"test"));
|
||||
let (_writer_tx, writer_rx) = tokio::io::duplex(1024);
|
||||
let _writer: FileWriter = Box::new(writer_rx);
|
||||
|
||||
// If this compiles, the types are correctly defined
|
||||
// This is a placeholder test - remove meaningless assertion
|
||||
// assert!(true);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_trait_implementation() {
|
||||
let data = b"test data for trait";
|
||||
let cursor = Cursor::new(data);
|
||||
let etag_reader = EtagReader::new(cursor);
|
||||
|
||||
// Test the Etag trait
|
||||
let etag = etag_reader.etag().await;
|
||||
assert!(!etag.is_empty(), "ETag should not be empty");
|
||||
|
||||
// Verify it's a valid hex string
|
||||
assert!(etag.chars().all(|c| c.is_ascii_hexdigit()), "ETag should be a valid hex string");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_read_buffer_size_constant() {
|
||||
assert_eq!(READ_BUFFER_SIZE, 1024 * 1024);
|
||||
// READ_BUFFER_SIZE is a compile-time constant, no need to assert
|
||||
// assert!(READ_BUFFER_SIZE > 0);
|
||||
// assert!(READ_BUFFER_SIZE % 1024 == 0, "Buffer size should be a multiple of 1024");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_etag_operations() {
|
||||
let data1 = b"concurrent test data 1";
|
||||
let data2 = b"concurrent test data 2";
|
||||
let data3 = b"concurrent test data 3";
|
||||
|
||||
let cursor1 = Cursor::new(data1);
|
||||
let cursor2 = Cursor::new(data2);
|
||||
let cursor3 = Cursor::new(data3);
|
||||
|
||||
let etag_reader1 = EtagReader::new(cursor1);
|
||||
let etag_reader2 = EtagReader::new(cursor2);
|
||||
let etag_reader3 = EtagReader::new(cursor3);
|
||||
|
||||
// Compute ETags concurrently
|
||||
let (result1, result2, result3) = tokio::join!(etag_reader1.etag(), etag_reader2.etag(), etag_reader3.etag());
|
||||
|
||||
// All ETags should be the same (empty data hash) since no data was read
|
||||
assert_eq!(result1, result2);
|
||||
assert_eq!(result2, result3);
|
||||
assert_eq!(result1, result3);
|
||||
|
||||
assert_eq!(result1.len(), 32);
|
||||
assert_eq!(result2.len(), 32);
|
||||
assert_eq!(result3.len(), 32);
|
||||
|
||||
// All should be the empty data MD5
|
||||
assert_eq!(result1, "d41d8cd98f00b204e9800998ecf8427e");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_edge_case_parameters() {
|
||||
// Test HttpFileWriter with edge case parameters
|
||||
let writer = HttpFileWriter::new(
|
||||
"http://localhost:8080",
|
||||
"", // empty disk
|
||||
"", // empty volume
|
||||
"", // empty path
|
||||
0, // zero size
|
||||
false,
|
||||
);
|
||||
assert!(writer.is_ok(), "HttpFileWriter should handle empty parameters");
|
||||
|
||||
// Test HttpFileReader with edge case parameters
|
||||
let result = HttpFileReader::new(
|
||||
"http://invalid:9999",
|
||||
"", // empty disk
|
||||
"", // empty volume
|
||||
"", // empty path
|
||||
0, // zero offset
|
||||
0, // zero length
|
||||
)
|
||||
.await;
|
||||
// May succeed or fail, but parameters should be handled
|
||||
assert!(result.is_ok() || result.is_err(), "HttpFileReader creation should not panic");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_url_encoding_edge_cases() {
|
||||
// Test with characters that need URL encoding
|
||||
let special_chars = "test file with spaces & symbols + % # ? = @ ! $ ( ) [ ] { } | \\ / : ; , . < > \" '";
|
||||
|
||||
let writer = HttpFileWriter::new("http://localhost:8080", special_chars, special_chars, special_chars, 1024, false);
|
||||
assert!(writer.is_ok(), "HttpFileWriter should handle special characters");
|
||||
|
||||
let result = HttpFileReader::new("http://invalid:9999", special_chars, special_chars, special_chars, 0, 1024).await;
|
||||
// May succeed or fail, but URL encoding should work
|
||||
assert!(result.is_ok() || result.is_err(), "HttpFileReader creation should not panic");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_with_binary_data() {
|
||||
// Test with binary data including null bytes
|
||||
let data = vec![0u8, 1u8, 255u8, 127u8, 128u8, 0u8, 0u8, 255u8];
|
||||
let cursor = Cursor::new(data.clone());
|
||||
let etag_reader = EtagReader::new(cursor);
|
||||
|
||||
// Test ETag computation for binary data
|
||||
let etag = etag_reader.etag().await;
|
||||
assert!(!etag.is_empty(), "ETag should not be empty");
|
||||
assert_eq!(etag.len(), 32, "MD5 hash should be 32 characters");
|
||||
assert!(etag.chars().all(|c| c.is_ascii_hexdigit()), "ETag should be valid hex");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_etag_reader_type_constraints() {
|
||||
// Test that EtagReader works with different reader types
|
||||
let data = b"type constraint test";
|
||||
|
||||
// Test with Cursor
|
||||
let cursor = Cursor::new(data);
|
||||
let etag_reader = EtagReader::new(cursor);
|
||||
let etag = etag_reader.etag().await;
|
||||
assert_eq!(etag.len(), 32);
|
||||
|
||||
// Test with slice
|
||||
let slice_reader = &data[..];
|
||||
let etag_reader2 = EtagReader::new(slice_reader);
|
||||
let etag2 = etag_reader2.etag().await;
|
||||
assert_eq!(etag2.len(), 32);
|
||||
|
||||
// Both should produce the same hash for the same data
|
||||
assert_eq!(etag, etag2);
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -1,38 +1,33 @@
|
||||
extern crate core;
|
||||
|
||||
pub mod admin_server_info;
|
||||
pub mod bitrot;
|
||||
pub mod bucket;
|
||||
pub mod cache_value;
|
||||
mod chunk_stream;
|
||||
pub mod cmd;
|
||||
pub mod compress;
|
||||
pub mod config;
|
||||
pub mod disk;
|
||||
pub mod disks_layout;
|
||||
pub mod endpoints;
|
||||
pub mod erasure;
|
||||
pub mod erasure_coding;
|
||||
pub mod error;
|
||||
mod file_meta;
|
||||
pub mod file_meta_inline;
|
||||
pub mod global;
|
||||
pub mod heal;
|
||||
pub mod io;
|
||||
pub mod metacache;
|
||||
pub mod metrics_realtime;
|
||||
pub mod notification_sys;
|
||||
pub mod peer;
|
||||
pub mod peer_rest_client;
|
||||
pub mod pools;
|
||||
mod quorum;
|
||||
pub mod rebalance;
|
||||
pub mod set_disk;
|
||||
mod sets;
|
||||
pub mod store;
|
||||
pub mod store_api;
|
||||
pub mod store_err;
|
||||
mod store_init;
|
||||
pub mod store_list_objects;
|
||||
mod store_utils;
|
||||
pub mod utils;
|
||||
pub mod xhttp;
|
||||
|
||||
pub use global::new_object_layer_fn;
|
||||
pub use global::set_global_endpoints;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub mod writer;
|
||||
@@ -1,387 +0,0 @@
|
||||
use crate::disk::MetaCacheEntry;
|
||||
use crate::error::clone_err;
|
||||
use common::error::{Error, Result};
|
||||
use rmp::Marker;
|
||||
use std::str::from_utf8;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
// use std::sync::Arc;
|
||||
// use tokio::sync::mpsc;
|
||||
// use tokio::sync::mpsc::Sender;
|
||||
// use tokio::task;
|
||||
|
||||
const METACACHE_STREAM_VERSION: u8 = 2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MetacacheWriter<W> {
|
||||
wr: W,
|
||||
created: bool,
|
||||
// err: Option<Error>,
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<W: AsyncWrite + Unpin> MetacacheWriter<W> {
|
||||
pub fn new(wr: W) -> Self {
|
||||
Self {
|
||||
wr,
|
||||
created: false,
|
||||
// err: None,
|
||||
buf: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn flush(&mut self) -> Result<()> {
|
||||
self.wr.write_all(&self.buf).await?;
|
||||
self.buf.clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn init(&mut self) -> Result<()> {
|
||||
if !self.created {
|
||||
rmp::encode::write_u8(&mut self.buf, METACACHE_STREAM_VERSION).map_err(|e| Error::msg(format!("{:?}", e)))?;
|
||||
self.flush().await?;
|
||||
self.created = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write(&mut self, objs: &[MetaCacheEntry]) -> Result<()> {
|
||||
if objs.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.init().await?;
|
||||
|
||||
for obj in objs.iter() {
|
||||
if obj.name.is_empty() {
|
||||
return Err(Error::msg("metacacheWriter: no name"));
|
||||
}
|
||||
|
||||
self.write_obj(obj).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write_obj(&mut self, obj: &MetaCacheEntry) -> Result<()> {
|
||||
self.init().await?;
|
||||
|
||||
rmp::encode::write_bool(&mut self.buf, true).map_err(|e| Error::msg(format!("{:?}", e)))?;
|
||||
rmp::encode::write_str(&mut self.buf, &obj.name).map_err(|e| Error::msg(format!("{:?}", e)))?;
|
||||
rmp::encode::write_bin(&mut self.buf, &obj.metadata).map_err(|e| Error::msg(format!("{:?}", e)))?;
|
||||
self.flush().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// pub async fn stream(&mut self) -> Result<Sender<MetaCacheEntry>> {
|
||||
// let (sender, mut receiver) = mpsc::channel::<MetaCacheEntry>(100);
|
||||
|
||||
// let wr = Arc::new(self);
|
||||
|
||||
// task::spawn(async move {
|
||||
// while let Some(obj) = receiver.recv().await {
|
||||
// // if obj.name.is_empty() || self.err.is_some() {
|
||||
// // continue;
|
||||
// // }
|
||||
|
||||
// let _ = wr.write_obj(&obj);
|
||||
|
||||
// // if let Err(err) = rmp::encode::write_bool(&mut self.wr, true) {
|
||||
// // self.err = Some(Error::new(err));
|
||||
// // continue;
|
||||
// // }
|
||||
|
||||
// // if let Err(err) = rmp::encode::write_str(&mut self.wr, &obj.name) {
|
||||
// // self.err = Some(Error::new(err));
|
||||
// // continue;
|
||||
// // }
|
||||
|
||||
// // if let Err(err) = rmp::encode::write_bin(&mut self.wr, &obj.metadata) {
|
||||
// // self.err = Some(Error::new(err));
|
||||
// // continue;
|
||||
// // }
|
||||
// }
|
||||
// });
|
||||
|
||||
// Ok(sender)
|
||||
// }
|
||||
|
||||
pub async fn close(&mut self) -> Result<()> {
|
||||
rmp::encode::write_bool(&mut self.buf, false).map_err(|e| Error::msg(format!("{:?}", e)))?;
|
||||
self.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MetacacheReader<R> {
|
||||
rd: R,
|
||||
init: bool,
|
||||
err: Option<Error>,
|
||||
buf: Vec<u8>,
|
||||
offset: usize,
|
||||
|
||||
current: Option<MetaCacheEntry>,
|
||||
}
|
||||
|
||||
impl<R: AsyncRead + Unpin> MetacacheReader<R> {
|
||||
pub fn new(rd: R) -> Self {
|
||||
Self {
|
||||
rd,
|
||||
init: false,
|
||||
err: None,
|
||||
buf: Vec::new(),
|
||||
offset: 0,
|
||||
current: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_more(&mut self, read_size: usize) -> Result<&[u8]> {
|
||||
let ext_size = read_size + self.offset;
|
||||
|
||||
let extra = ext_size - self.offset;
|
||||
if self.buf.capacity() >= ext_size {
|
||||
// Extend the buffer if we have enough space.
|
||||
self.buf.resize(ext_size, 0);
|
||||
} else {
|
||||
self.buf.extend(vec![0u8; extra]);
|
||||
}
|
||||
|
||||
let pref = self.offset;
|
||||
|
||||
self.rd.read_exact(&mut self.buf[pref..ext_size]).await?;
|
||||
|
||||
self.offset += read_size;
|
||||
|
||||
let data = &self.buf[pref..ext_size];
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.buf.clear();
|
||||
self.offset = 0;
|
||||
}
|
||||
|
||||
async fn check_init(&mut self) -> Result<()> {
|
||||
if !self.init {
|
||||
let ver = match rmp::decode::read_u8(&mut self.read_more(2).await?) {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
self.err = Some(Error::msg(format!("{:?}", err)));
|
||||
0
|
||||
}
|
||||
};
|
||||
match ver {
|
||||
1 | 2 => (),
|
||||
_ => {
|
||||
self.err = Some(Error::msg("invalid version"));
|
||||
}
|
||||
}
|
||||
|
||||
self.init = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_str_len(&mut self) -> Result<u32> {
|
||||
let mark = match rmp::decode::read_marker(&mut self.read_more(1).await?) {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
let serr = format!("{:?}", err);
|
||||
self.err = Some(Error::msg(&serr));
|
||||
return Err(Error::msg(&serr));
|
||||
}
|
||||
};
|
||||
|
||||
match mark {
|
||||
Marker::FixStr(size) => Ok(u32::from(size)),
|
||||
Marker::Str8 => Ok(u32::from(self.read_u8().await?)),
|
||||
Marker::Str16 => Ok(u32::from(self.read_u16().await?)),
|
||||
Marker::Str32 => Ok(self.read_u32().await?),
|
||||
_marker => Err(Error::msg("str marker err")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_bin_len(&mut self) -> Result<u32> {
|
||||
let mark = match rmp::decode::read_marker(&mut self.read_more(1).await?) {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
let serr = format!("{:?}", err);
|
||||
self.err = Some(Error::msg(&serr));
|
||||
return Err(Error::msg(&serr));
|
||||
}
|
||||
};
|
||||
|
||||
match mark {
|
||||
Marker::Bin8 => Ok(u32::from(self.read_u8().await?)),
|
||||
Marker::Bin16 => Ok(u32::from(self.read_u16().await?)),
|
||||
Marker::Bin32 => Ok(self.read_u32().await?),
|
||||
_ => Err(Error::msg("bin marker err")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_u8(&mut self) -> Result<u8> {
|
||||
let buf = self.read_more(1).await?;
|
||||
|
||||
Ok(u8::from_be_bytes(buf.try_into().expect("Slice with incorrect length")))
|
||||
}
|
||||
|
||||
async fn read_u16(&mut self) -> Result<u16> {
|
||||
let buf = self.read_more(2).await?;
|
||||
|
||||
Ok(u16::from_be_bytes(buf.try_into().expect("Slice with incorrect length")))
|
||||
}
|
||||
|
||||
async fn read_u32(&mut self) -> Result<u32> {
|
||||
let buf = self.read_more(4).await?;
|
||||
|
||||
Ok(u32::from_be_bytes(buf.try_into().expect("Slice with incorrect length")))
|
||||
}
|
||||
|
||||
pub async fn skip(&mut self, size: usize) -> Result<()> {
|
||||
self.check_init().await?;
|
||||
|
||||
if let Some(err) = &self.err {
|
||||
return Err(clone_err(err));
|
||||
}
|
||||
|
||||
let mut n = size;
|
||||
|
||||
if self.current.is_some() {
|
||||
n -= 1;
|
||||
self.current = None;
|
||||
}
|
||||
|
||||
while n > 0 {
|
||||
match rmp::decode::read_bool(&mut self.read_more(1).await?) {
|
||||
Ok(res) => {
|
||||
if !res {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let serr = format!("{:?}", err);
|
||||
self.err = Some(Error::msg(&serr));
|
||||
return Err(Error::msg(&serr));
|
||||
}
|
||||
};
|
||||
|
||||
let l = self.read_str_len().await?;
|
||||
let _ = self.read_more(l as usize).await?;
|
||||
let l = self.read_bin_len().await?;
|
||||
let _ = self.read_more(l as usize).await?;
|
||||
|
||||
n -= 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn peek(&mut self) -> Result<Option<MetaCacheEntry>> {
|
||||
self.check_init().await?;
|
||||
|
||||
if let Some(err) = &self.err {
|
||||
return Err(clone_err(err));
|
||||
}
|
||||
|
||||
match rmp::decode::read_bool(&mut self.read_more(1).await?) {
|
||||
Ok(res) => {
|
||||
if !res {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let serr = format!("{:?}", err);
|
||||
self.err = Some(Error::msg(&serr));
|
||||
return Err(Error::msg(&serr));
|
||||
}
|
||||
};
|
||||
|
||||
let l = self.read_str_len().await?;
|
||||
|
||||
let buf = self.read_more(l as usize).await?;
|
||||
let name_buf = buf.to_vec();
|
||||
let name = match from_utf8(&name_buf) {
|
||||
Ok(decoded) => decoded.to_owned(),
|
||||
Err(err) => {
|
||||
self.err = Some(Error::msg(err.to_string()));
|
||||
return Err(Error::msg(err.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let l = self.read_bin_len().await?;
|
||||
|
||||
let buf = self.read_more(l as usize).await?;
|
||||
|
||||
let metadata = buf.to_vec();
|
||||
|
||||
self.reset();
|
||||
|
||||
let entry = Some(MetaCacheEntry {
|
||||
name,
|
||||
metadata,
|
||||
cached: None,
|
||||
reusable: false,
|
||||
});
|
||||
self.current = entry.clone();
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
pub async fn read_all(&mut self) -> Result<Vec<MetaCacheEntry>> {
|
||||
let mut ret = Vec::new();
|
||||
|
||||
loop {
|
||||
if let Some(entry) = self.peek().await? {
|
||||
ret.push(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_writer() {
|
||||
use std::io::Cursor;
|
||||
|
||||
let mut f = Cursor::new(Vec::new());
|
||||
|
||||
let mut w = MetacacheWriter::new(&mut f);
|
||||
|
||||
let mut objs = Vec::new();
|
||||
for i in 0..10 {
|
||||
let info = MetaCacheEntry {
|
||||
name: format!("item{}", i),
|
||||
metadata: vec![0u8, 10],
|
||||
cached: None,
|
||||
reusable: false,
|
||||
};
|
||||
println!("old {:?}", &info);
|
||||
objs.push(info);
|
||||
}
|
||||
|
||||
w.write(&objs).await.unwrap();
|
||||
|
||||
w.close().await.unwrap();
|
||||
|
||||
let data = f.into_inner();
|
||||
|
||||
let nf = Cursor::new(data);
|
||||
|
||||
let mut r = MetacacheReader::new(nf);
|
||||
let nobjs = r.read_all().await.unwrap();
|
||||
|
||||
// for info in nobjs.iter() {
|
||||
// println!("new {:?}", &info);
|
||||
// }
|
||||
|
||||
assert_eq!(objs, nobjs)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use chrono::Utc;
|
||||
use common::globals::{GLOBAL_Local_Node_Name, GLOBAL_Rustfs_Addr};
|
||||
use madmin::metrics::{DiskIOStats, DiskMetric, RealtimeMetrics};
|
||||
use rustfs_utils::os::get_drive_stats;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::info;
|
||||
|
||||
@@ -14,7 +15,7 @@ use crate::{
|
||||
},
|
||||
new_object_layer_fn,
|
||||
store_api::StorageAPI,
|
||||
utils::os::get_drive_stats,
|
||||
// utils::os::get_drive_stats,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::admin_server_info::get_commit_id;
|
||||
use crate::global::{get_global_endpoints, GLOBAL_BOOT_TIME};
|
||||
use crate::peer_rest_client::PeerRestClient;
|
||||
use crate::StorageAPI;
|
||||
use crate::admin_server_info::get_commit_id;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::global::{GLOBAL_BOOT_TIME, get_global_endpoints};
|
||||
use crate::peer_rest_client::PeerRestClient;
|
||||
use crate::{endpoints::EndpointServerPools, new_object_layer_fn};
|
||||
use common::error::{Error, Result};
|
||||
use futures::future::join_all;
|
||||
use lazy_static::lazy_static;
|
||||
use madmin::{ItemState, ServerProperties};
|
||||
@@ -18,7 +18,7 @@ lazy_static! {
|
||||
pub async fn new_global_notification_sys(eps: EndpointServerPools) -> Result<()> {
|
||||
let _ = GLOBAL_NotificationSys
|
||||
.set(NotificationSys::new(eps).await)
|
||||
.map_err(|_| Error::msg("init notification_sys fail"));
|
||||
.map_err(|_| Error::other("init notification_sys fail"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -143,7 +143,11 @@ impl NotificationSys {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn load_rebalance_meta(&self, start: bool) {
|
||||
let mut futures = Vec::with_capacity(self.peer_clients.len());
|
||||
for client in self.peer_clients.iter().flatten() {
|
||||
for (i, client) in self.peer_clients.iter().flatten().enumerate() {
|
||||
warn!(
|
||||
"notification load_rebalance_meta start: {}, index: {}, client: {:?}",
|
||||
start, i, client.host
|
||||
);
|
||||
futures.push(client.load_rebalance_meta(start));
|
||||
}
|
||||
|
||||
@@ -158,11 +162,16 @@ impl NotificationSys {
|
||||
}
|
||||
|
||||
pub async fn stop_rebalance(&self) {
|
||||
warn!("notification stop_rebalance start");
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
error!("stop_rebalance: not init");
|
||||
return;
|
||||
};
|
||||
|
||||
// warn!("notification stop_rebalance load_rebalance_meta");
|
||||
// self.load_rebalance_meta(false).await;
|
||||
// warn!("notification stop_rebalance load_rebalance_meta done");
|
||||
|
||||
let mut futures = Vec::with_capacity(self.peer_clients.len());
|
||||
for client in self.peer_clients.iter().flatten() {
|
||||
futures.push(client.stop_rebalance());
|
||||
@@ -175,7 +184,9 @@ impl NotificationSys {
|
||||
}
|
||||
}
|
||||
|
||||
warn!("notification stop_rebalance stop_rebalance start");
|
||||
let _ = store.stop_rebalance().await;
|
||||
warn!("notification stop_rebalance stop_rebalance done");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+59
-66
@@ -1,3 +1,18 @@
|
||||
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};
|
||||
use crate::global::GLOBAL_LOCAL_DISK_MAP;
|
||||
use crate::heal::heal_commands::{
|
||||
DRIVE_STATE_CORRUPT, DRIVE_STATE_MISSING, DRIVE_STATE_OFFLINE, DRIVE_STATE_OK, HEAL_ITEM_BUCKET, HealOpts,
|
||||
};
|
||||
use crate::heal::heal_ops::RUSTFS_RESERVED_BUCKET;
|
||||
use crate::store::all_local_disk;
|
||||
use crate::{
|
||||
disk::{self, VolumeInfo},
|
||||
endpoints::{EndpointServerPools, Node},
|
||||
store_api::{BucketInfo, BucketOptions, DeleteBucketOptions, MakeBucketOptions},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use futures::future::join_all;
|
||||
use madmin::heal_commands::{HealDriveInfo, HealResultItem};
|
||||
@@ -11,26 +26,6 @@ use tokio::sync::RwLock;
|
||||
use tonic::Request;
|
||||
use tracing::info;
|
||||
|
||||
use crate::bucket::metadata_sys;
|
||||
use crate::disk::error::is_all_buckets_not_found;
|
||||
use crate::disk::{DiskAPI, DiskStore};
|
||||
use crate::error::clone_err;
|
||||
use crate::global::GLOBAL_LOCAL_DISK_MAP;
|
||||
use crate::heal::heal_commands::{
|
||||
HealOpts, DRIVE_STATE_CORRUPT, DRIVE_STATE_MISSING, DRIVE_STATE_OFFLINE, DRIVE_STATE_OK, HEAL_ITEM_BUCKET,
|
||||
};
|
||||
use crate::heal::heal_ops::RUSTFS_RESERVED_BUCKET;
|
||||
use crate::quorum::{bucket_op_ignored_errs, reduce_write_quorum_errs};
|
||||
use crate::store::all_local_disk;
|
||||
use crate::utils::proto_err_to_err;
|
||||
use crate::utils::wildcard::is_rustfs_meta_bucket_name;
|
||||
use crate::{
|
||||
disk::{self, error::DiskError, VolumeInfo},
|
||||
endpoints::{EndpointServerPools, Node},
|
||||
store_api::{BucketInfo, BucketOptions, DeleteBucketOptions, MakeBucketOptions},
|
||||
};
|
||||
use common::error::{Error, Result};
|
||||
|
||||
type Client = Arc<Box<dyn PeerS3Client>>;
|
||||
|
||||
#[async_trait]
|
||||
@@ -92,12 +87,12 @@ impl S3PeerSys {
|
||||
for (i, client) in self.clients.iter().enumerate() {
|
||||
if let Some(v) = client.get_pools() {
|
||||
if v.contains(&pool_idx) {
|
||||
per_pool_errs.push(errs[i].as_ref().map(clone_err));
|
||||
per_pool_errs.push(errs[i].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let qu = per_pool_errs.len() / 2;
|
||||
pool_errs.push(reduce_write_quorum_errs(&per_pool_errs, &bucket_op_ignored_errs(), qu));
|
||||
pool_errs.push(reduce_write_quorum_errs(&per_pool_errs, BUCKET_OP_IGNORED_ERRS, qu));
|
||||
}
|
||||
|
||||
if !opts.recreate {
|
||||
@@ -127,12 +122,12 @@ impl S3PeerSys {
|
||||
for (i, client) in self.clients.iter().enumerate() {
|
||||
if let Some(v) = client.get_pools() {
|
||||
if v.contains(&pool_idx) {
|
||||
per_pool_errs.push(errs[i].as_ref().map(clone_err));
|
||||
per_pool_errs.push(errs[i].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let qu = per_pool_errs.len() / 2;
|
||||
if let Some(pool_err) = reduce_write_quorum_errs(&per_pool_errs, &bucket_op_ignored_errs(), qu) {
|
||||
if let Some(pool_err) = reduce_write_quorum_errs(&per_pool_errs, BUCKET_OP_IGNORED_ERRS, qu) {
|
||||
return Err(pool_err);
|
||||
}
|
||||
}
|
||||
@@ -142,7 +137,7 @@ impl S3PeerSys {
|
||||
return Ok(heal_bucket_results.read().await[i].clone());
|
||||
}
|
||||
}
|
||||
Err(DiskError::VolumeNotFound.into())
|
||||
Err(Error::VolumeNotFound)
|
||||
}
|
||||
|
||||
pub async fn make_bucket(&self, bucket: &str, opts: &MakeBucketOptions) -> Result<()> {
|
||||
@@ -288,9 +283,7 @@ impl S3PeerSys {
|
||||
}
|
||||
}
|
||||
|
||||
ress.iter()
|
||||
.find_map(|op| op.clone())
|
||||
.ok_or(Error::new(DiskError::VolumeNotFound))
|
||||
ress.iter().find_map(|op| op.clone()).ok_or(Error::VolumeNotFound)
|
||||
}
|
||||
|
||||
pub fn get_pools(&self) -> Option<Vec<usize>> {
|
||||
@@ -380,7 +373,7 @@ impl PeerS3Client for LocalPeerS3Client {
|
||||
match disk.make_volume(bucket).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
if opts.force_create && DiskError::VolumeExists.is(&e) {
|
||||
if opts.force_create && matches!(e, Error::VolumeExists) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -446,7 +439,7 @@ impl PeerS3Client for LocalPeerS3Client {
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.ok_or(Error::new(DiskError::VolumeNotFound))
|
||||
.ok_or(Error::VolumeNotFound)
|
||||
}
|
||||
|
||||
async fn delete_bucket(&self, bucket: &str, _opts: &DeleteBucketOptions) -> Result<()> {
|
||||
@@ -467,7 +460,7 @@ impl PeerS3Client for LocalPeerS3Client {
|
||||
match res {
|
||||
Ok(_) => errs.push(None),
|
||||
Err(e) => {
|
||||
if DiskError::VolumeNotEmpty.is(&e) {
|
||||
if matches!(e, Error::VolumeNotEmpty) {
|
||||
recreate = true;
|
||||
}
|
||||
errs.push(Some(e))
|
||||
@@ -484,7 +477,7 @@ impl PeerS3Client for LocalPeerS3Client {
|
||||
}
|
||||
|
||||
if recreate {
|
||||
return Err(Error::new(DiskError::VolumeNotEmpty));
|
||||
return Err(Error::VolumeNotEmpty);
|
||||
}
|
||||
|
||||
// TODO: reduceWriteQuorumErrs
|
||||
@@ -520,17 +513,17 @@ impl PeerS3Client for RemotePeerS3Client {
|
||||
let options: String = serde_json::to_string(opts)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::from_string(format!("can not get client, err: {}", err)))?;
|
||||
.map_err(|err| Error::other(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(HealBucketRequest {
|
||||
bucket: bucket.to_string(),
|
||||
options,
|
||||
});
|
||||
let response = client.heal_bucket(request).await?.into_inner();
|
||||
if !response.success {
|
||||
return if let Some(err) = &response.error {
|
||||
Err(proto_err_to_err(err))
|
||||
return if let Some(err) = response.error {
|
||||
Err(err.into())
|
||||
} else {
|
||||
Err(Error::from_string(""))
|
||||
Err(Error::other(""))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -546,14 +539,14 @@ impl PeerS3Client for RemotePeerS3Client {
|
||||
let options = serde_json::to_string(opts)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::from_string(format!("can not get client, err: {}", err)))?;
|
||||
.map_err(|err| Error::other(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ListBucketRequest { options });
|
||||
let response = client.list_bucket(request).await?.into_inner();
|
||||
if !response.success {
|
||||
return if let Some(err) = &response.error {
|
||||
Err(proto_err_to_err(err))
|
||||
return if let Some(err) = response.error {
|
||||
Err(err.into())
|
||||
} else {
|
||||
Err(Error::from_string(""))
|
||||
Err(Error::other(""))
|
||||
};
|
||||
}
|
||||
let bucket_infos = response
|
||||
@@ -568,7 +561,7 @@ impl PeerS3Client for RemotePeerS3Client {
|
||||
let options = serde_json::to_string(opts)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::from_string(format!("can not get client, err: {}", err)))?;
|
||||
.map_err(|err| Error::other(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(MakeBucketRequest {
|
||||
name: bucket.to_string(),
|
||||
options,
|
||||
@@ -577,10 +570,10 @@ impl PeerS3Client for RemotePeerS3Client {
|
||||
|
||||
// TODO: deal with error
|
||||
if !response.success {
|
||||
return if let Some(err) = &response.error {
|
||||
Err(proto_err_to_err(err))
|
||||
return if let Some(err) = response.error {
|
||||
Err(err.into())
|
||||
} else {
|
||||
Err(Error::from_string(""))
|
||||
Err(Error::other(""))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -590,17 +583,17 @@ impl PeerS3Client for RemotePeerS3Client {
|
||||
let options = serde_json::to_string(opts)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::from_string(format!("can not get client, err: {}", err)))?;
|
||||
.map_err(|err| Error::other(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(GetBucketInfoRequest {
|
||||
bucket: bucket.to_string(),
|
||||
options,
|
||||
});
|
||||
let response = client.get_bucket_info(request).await?.into_inner();
|
||||
if !response.success {
|
||||
return if let Some(err) = &response.error {
|
||||
Err(proto_err_to_err(err))
|
||||
return if let Some(err) = response.error {
|
||||
Err(err.into())
|
||||
} else {
|
||||
Err(Error::from_string(""))
|
||||
Err(Error::other(""))
|
||||
};
|
||||
}
|
||||
let bucket_info = serde_json::from_str::<BucketInfo>(&response.bucket_info)?;
|
||||
@@ -611,17 +604,17 @@ impl PeerS3Client for RemotePeerS3Client {
|
||||
async fn delete_bucket(&self, bucket: &str, _opts: &DeleteBucketOptions) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::from_string(format!("can not get client, err: {}", err)))?;
|
||||
.map_err(|err| Error::other(format!("can not get client, err: {}", err)))?;
|
||||
|
||||
let request = Request::new(DeleteBucketRequest {
|
||||
bucket: bucket.to_string(),
|
||||
});
|
||||
let response = client.delete_bucket(request).await?.into_inner();
|
||||
if !response.success {
|
||||
return if let Some(err) = &response.error {
|
||||
Err(proto_err_to_err(err))
|
||||
return if let Some(err) = response.error {
|
||||
Err(err.into())
|
||||
} else {
|
||||
Err(Error::from_string(""))
|
||||
Err(Error::other(""))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -632,18 +625,18 @@ impl PeerS3Client for RemotePeerS3Client {
|
||||
// 检查桶名是否有效
|
||||
fn check_bucket_name(bucket_name: &str, strict: bool) -> Result<()> {
|
||||
if bucket_name.trim().is_empty() {
|
||||
return Err(Error::msg("Bucket name cannot be empty"));
|
||||
return Err(Error::other("Bucket name cannot be empty"));
|
||||
}
|
||||
if bucket_name.len() < 3 {
|
||||
return Err(Error::msg("Bucket name cannot be shorter than 3 characters"));
|
||||
return Err(Error::other("Bucket name cannot be shorter than 3 characters"));
|
||||
}
|
||||
if bucket_name.len() > 63 {
|
||||
return Err(Error::msg("Bucket name cannot be longer than 63 characters"));
|
||||
return Err(Error::other("Bucket name cannot be longer than 63 characters"));
|
||||
}
|
||||
|
||||
let ip_address_regex = Regex::new(r"^(\d+\.){3}\d+$").unwrap();
|
||||
if ip_address_regex.is_match(bucket_name) {
|
||||
return Err(Error::msg("Bucket name cannot be an IP address"));
|
||||
return Err(Error::other("Bucket name cannot be an IP address"));
|
||||
}
|
||||
|
||||
let valid_bucket_name_regex = if strict {
|
||||
@@ -653,12 +646,12 @@ fn check_bucket_name(bucket_name: &str, strict: bool) -> Result<()> {
|
||||
};
|
||||
|
||||
if !valid_bucket_name_regex.is_match(bucket_name) {
|
||||
return Err(Error::msg("Bucket name contains invalid characters"));
|
||||
return Err(Error::other("Bucket name contains invalid characters"));
|
||||
}
|
||||
|
||||
// 检查包含 "..", ".-", "-."
|
||||
if bucket_name.contains("..") || bucket_name.contains(".-") || bucket_name.contains("-.") {
|
||||
return Err(Error::msg("Bucket name contains invalid characters"));
|
||||
return Err(Error::other("Bucket name contains invalid characters"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -703,7 +696,7 @@ pub async fn heal_bucket_local(bucket: &str, opts: &HealOpts) -> Result<HealResu
|
||||
None => {
|
||||
bs_clone.write().await[index] = DRIVE_STATE_OFFLINE.to_string();
|
||||
as_clone.write().await[index] = DRIVE_STATE_OFFLINE.to_string();
|
||||
return Some(Error::new(DiskError::DiskNotFound));
|
||||
return Some(Error::DiskNotFound);
|
||||
}
|
||||
};
|
||||
bs_clone.write().await[index] = DRIVE_STATE_OK.to_string();
|
||||
@@ -715,13 +708,13 @@ pub async fn heal_bucket_local(bucket: &str, opts: &HealOpts) -> Result<HealResu
|
||||
|
||||
match disk.stat_volume(&bucket).await {
|
||||
Ok(_) => None,
|
||||
Err(err) => match err.downcast_ref() {
|
||||
Some(DiskError::DiskNotFound) => {
|
||||
Err(err) => match err {
|
||||
Error::DiskNotFound => {
|
||||
bs_clone.write().await[index] = DRIVE_STATE_OFFLINE.to_string();
|
||||
as_clone.write().await[index] = DRIVE_STATE_OFFLINE.to_string();
|
||||
Some(err)
|
||||
}
|
||||
Some(DiskError::VolumeNotFound) => {
|
||||
Error::VolumeNotFound => {
|
||||
bs_clone.write().await[index] = DRIVE_STATE_MISSING.to_string();
|
||||
as_clone.write().await[index] = DRIVE_STATE_MISSING.to_string();
|
||||
Some(err)
|
||||
@@ -756,7 +749,7 @@ pub async fn heal_bucket_local(bucket: &str, opts: &HealOpts) -> Result<HealResu
|
||||
});
|
||||
}
|
||||
|
||||
if opts.remove && !is_rustfs_meta_bucket_name(bucket) && !is_all_buckets_not_found(&errs) {
|
||||
if opts.remove && !bucket.starts_with(disk::RUSTFS_META_BUCKET) && !is_all_buckets_not_found(&errs) {
|
||||
let mut futures = Vec::new();
|
||||
for disk in disks.iter() {
|
||||
let disk = disk.clone();
|
||||
@@ -769,7 +762,7 @@ pub async fn heal_bucket_local(bucket: &str, opts: &HealOpts) -> Result<HealResu
|
||||
let _ = disk.delete_volume(&bucket).await;
|
||||
None
|
||||
}
|
||||
None => Some(Error::new(DiskError::DiskNotFound)),
|
||||
None => Some(Error::DiskNotFound),
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -784,7 +777,7 @@ pub async fn heal_bucket_local(bucket: &str, opts: &HealOpts) -> Result<HealResu
|
||||
let bucket = bucket.to_string();
|
||||
let bs_clone = before_state.clone();
|
||||
let as_clone = after_state.clone();
|
||||
let errs_clone = errs.iter().map(|e| e.as_ref().map(clone_err)).collect::<Vec<_>>();
|
||||
let errs_clone = errs.to_vec();
|
||||
futures.push(async move {
|
||||
if bs_clone.read().await[idx] == DRIVE_STATE_MISSING {
|
||||
info!("bucket not find, will recreate");
|
||||
@@ -798,7 +791,7 @@ pub async fn heal_bucket_local(bucket: &str, opts: &HealOpts) -> Result<HealResu
|
||||
}
|
||||
}
|
||||
}
|
||||
errs_clone[idx].as_ref().map(clone_err)
|
||||
errs_clone[idx].clone()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{
|
||||
endpoints::EndpointServerPools,
|
||||
global::is_dist_erasure,
|
||||
heal::heal_commands::BgHealState,
|
||||
metrics_realtime::{CollectMetricsOpts, MetricType},
|
||||
utils::net::XHost,
|
||||
};
|
||||
use common::error::{Error, Result};
|
||||
use madmin::{
|
||||
ServerProperties,
|
||||
health::{Cpus, MemInfo, OsInfo, Partitions, ProcInfo, SysConfig, SysErrors, SysService},
|
||||
metrics::RealtimeMetrics,
|
||||
net::NetInfo,
|
||||
ServerProperties,
|
||||
};
|
||||
use protos::{
|
||||
node_service_time_out_client,
|
||||
@@ -25,6 +24,7 @@ use protos::{
|
||||
},
|
||||
};
|
||||
use rmp_serde::{Deserializer, Serializer};
|
||||
use rustfs_utils::XHost;
|
||||
use serde::{Deserialize, Serialize as _};
|
||||
use std::{collections::HashMap, io::Cursor, time::SystemTime};
|
||||
use tonic::Request;
|
||||
@@ -76,15 +76,15 @@ impl PeerRestClient {
|
||||
pub async fn local_storage_info(&self) -> Result<madmin::StorageInfo> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LocalStorageInfoRequest { metrics: true });
|
||||
|
||||
let response = client.local_storage_info(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.storage_info;
|
||||
|
||||
@@ -97,15 +97,15 @@ impl PeerRestClient {
|
||||
pub async fn server_info(&self) -> Result<ServerProperties> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(ServerInfoRequest { metrics: true });
|
||||
|
||||
let response = client.server_info(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.server_properties;
|
||||
|
||||
@@ -118,15 +118,15 @@ impl PeerRestClient {
|
||||
pub async fn get_cpus(&self) -> Result<Cpus> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetCpusRequest {});
|
||||
|
||||
let response = client.get_cpus(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.cpus;
|
||||
|
||||
@@ -139,15 +139,15 @@ impl PeerRestClient {
|
||||
pub async fn get_net_info(&self) -> Result<NetInfo> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetNetInfoRequest {});
|
||||
|
||||
let response = client.get_net_info(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.net_info;
|
||||
|
||||
@@ -160,15 +160,15 @@ impl PeerRestClient {
|
||||
pub async fn get_partitions(&self) -> Result<Partitions> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetPartitionsRequest {});
|
||||
|
||||
let response = client.get_partitions(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.partitions;
|
||||
|
||||
@@ -181,15 +181,15 @@ impl PeerRestClient {
|
||||
pub async fn get_os_info(&self) -> Result<OsInfo> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetOsInfoRequest {});
|
||||
|
||||
let response = client.get_os_info(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.os_info;
|
||||
|
||||
@@ -202,15 +202,15 @@ impl PeerRestClient {
|
||||
pub async fn get_se_linux_info(&self) -> Result<SysService> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetSeLinuxInfoRequest {});
|
||||
|
||||
let response = client.get_se_linux_info(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.sys_services;
|
||||
|
||||
@@ -223,15 +223,15 @@ impl PeerRestClient {
|
||||
pub async fn get_sys_config(&self) -> Result<SysConfig> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetSysConfigRequest {});
|
||||
|
||||
let response = client.get_sys_config(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.sys_config;
|
||||
|
||||
@@ -244,15 +244,15 @@ impl PeerRestClient {
|
||||
pub async fn get_sys_errors(&self) -> Result<SysErrors> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetSysErrorsRequest {});
|
||||
|
||||
let response = client.get_sys_errors(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.sys_errors;
|
||||
|
||||
@@ -265,15 +265,15 @@ impl PeerRestClient {
|
||||
pub async fn get_mem_info(&self) -> Result<MemInfo> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetMemInfoRequest {});
|
||||
|
||||
let response = client.get_mem_info(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.mem_info;
|
||||
|
||||
@@ -286,22 +286,22 @@ impl PeerRestClient {
|
||||
pub async fn get_metrics(&self, t: MetricType, opts: &CollectMetricsOpts) -> Result<RealtimeMetrics> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let mut buf_t = Vec::new();
|
||||
t.serialize(&mut Serializer::new(&mut buf_t))?;
|
||||
let mut buf_o = Vec::new();
|
||||
opts.serialize(&mut Serializer::new(&mut buf_o))?;
|
||||
let request = Request::new(GetMetricsRequest {
|
||||
metric_type: buf_t,
|
||||
opts: buf_o,
|
||||
metric_type: buf_t.into(),
|
||||
opts: buf_o.into(),
|
||||
});
|
||||
|
||||
let response = client.get_metrics(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.realtime_metrics;
|
||||
|
||||
@@ -314,15 +314,15 @@ impl PeerRestClient {
|
||||
pub async fn get_proc_info(&self) -> Result<ProcInfo> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(GetProcInfoRequest {});
|
||||
|
||||
let response = client.get_proc_info(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.proc_info;
|
||||
|
||||
@@ -335,7 +335,7 @@ impl PeerRestClient {
|
||||
pub async fn start_profiling(&self, profiler: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(StartProfilingRequest {
|
||||
profiler: profiler.to_string(),
|
||||
});
|
||||
@@ -343,9 +343,9 @@ impl PeerRestClient {
|
||||
let response = client.start_profiling(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -369,7 +369,7 @@ impl PeerRestClient {
|
||||
pub async fn load_bucket_metadata(&self, bucket: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LoadBucketMetadataRequest {
|
||||
bucket: bucket.to_string(),
|
||||
});
|
||||
@@ -377,9 +377,9 @@ impl PeerRestClient {
|
||||
let response = client.load_bucket_metadata(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -387,7 +387,7 @@ impl PeerRestClient {
|
||||
pub async fn delete_bucket_metadata(&self, bucket: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(DeleteBucketMetadataRequest {
|
||||
bucket: bucket.to_string(),
|
||||
});
|
||||
@@ -395,9 +395,9 @@ impl PeerRestClient {
|
||||
let response = client.delete_bucket_metadata(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -405,7 +405,7 @@ impl PeerRestClient {
|
||||
pub async fn delete_policy(&self, policy: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(DeletePolicyRequest {
|
||||
policy_name: policy.to_string(),
|
||||
});
|
||||
@@ -413,9 +413,9 @@ impl PeerRestClient {
|
||||
let response = client.delete_policy(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -423,7 +423,7 @@ impl PeerRestClient {
|
||||
pub async fn load_policy(&self, policy: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LoadPolicyRequest {
|
||||
policy_name: policy.to_string(),
|
||||
});
|
||||
@@ -431,9 +431,9 @@ impl PeerRestClient {
|
||||
let response = client.load_policy(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -441,7 +441,7 @@ impl PeerRestClient {
|
||||
pub async fn load_policy_mapping(&self, user_or_group: &str, user_type: u64, is_group: bool) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LoadPolicyMappingRequest {
|
||||
user_or_group: user_or_group.to_string(),
|
||||
user_type,
|
||||
@@ -451,9 +451,9 @@ impl PeerRestClient {
|
||||
let response = client.load_policy_mapping(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -461,7 +461,7 @@ impl PeerRestClient {
|
||||
pub async fn delete_user(&self, access_key: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(DeleteUserRequest {
|
||||
access_key: access_key.to_string(),
|
||||
});
|
||||
@@ -469,9 +469,9 @@ impl PeerRestClient {
|
||||
let response = client.delete_user(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -479,7 +479,7 @@ impl PeerRestClient {
|
||||
pub async fn delete_service_account(&self, access_key: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(DeleteServiceAccountRequest {
|
||||
access_key: access_key.to_string(),
|
||||
});
|
||||
@@ -487,9 +487,9 @@ impl PeerRestClient {
|
||||
let response = client.delete_service_account(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -497,7 +497,7 @@ impl PeerRestClient {
|
||||
pub async fn load_user(&self, access_key: &str, temp: bool) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LoadUserRequest {
|
||||
access_key: access_key.to_string(),
|
||||
temp,
|
||||
@@ -506,9 +506,9 @@ impl PeerRestClient {
|
||||
let response = client.load_user(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -516,7 +516,7 @@ impl PeerRestClient {
|
||||
pub async fn load_service_account(&self, access_key: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LoadServiceAccountRequest {
|
||||
access_key: access_key.to_string(),
|
||||
});
|
||||
@@ -524,9 +524,9 @@ impl PeerRestClient {
|
||||
let response = client.load_service_account(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -534,7 +534,7 @@ impl PeerRestClient {
|
||||
pub async fn load_group(&self, group: &str) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LoadGroupRequest {
|
||||
group: group.to_string(),
|
||||
});
|
||||
@@ -542,9 +542,9 @@ impl PeerRestClient {
|
||||
let response = client.load_group(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -552,15 +552,15 @@ impl PeerRestClient {
|
||||
pub async fn reload_site_replication_config(&self) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(ReloadSiteReplicationConfigRequest {});
|
||||
|
||||
let response = client.reload_site_replication_config(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -568,7 +568,7 @@ impl PeerRestClient {
|
||||
pub async fn signal_service(&self, sig: u64, sub_sys: &str, dry_run: bool, _exec_at: SystemTime) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let mut vars = HashMap::new();
|
||||
vars.insert(PEER_RESTSIGNAL.to_string(), sig.to_string());
|
||||
vars.insert(PEER_RESTSUB_SYS.to_string(), sub_sys.to_string());
|
||||
@@ -580,9 +580,9 @@ impl PeerRestClient {
|
||||
let response = client.signal_service(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -590,15 +590,15 @@ impl PeerRestClient {
|
||||
pub async fn background_heal_status(&self) -> Result<BgHealState> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(BackgroundHealStatusRequest {});
|
||||
|
||||
let response = client.background_heal_status(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
let data = response.bg_heal_state;
|
||||
|
||||
@@ -611,29 +611,29 @@ impl PeerRestClient {
|
||||
pub async fn get_metacache_listing(&self) -> Result<()> {
|
||||
let _client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn update_metacache_listing(&self) -> Result<()> {
|
||||
let _client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn reload_pool_meta(&self) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(ReloadPoolMetaRequest {});
|
||||
|
||||
let response = client.reload_pool_meta(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -642,15 +642,15 @@ impl PeerRestClient {
|
||||
pub async fn stop_rebalance(&self) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(StopRebalanceRequest {});
|
||||
|
||||
let response = client.stop_rebalance(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -659,17 +659,17 @@ impl PeerRestClient {
|
||||
pub async fn load_rebalance_meta(&self, start_rebalance: bool) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LoadRebalanceMetaRequest { start_rebalance });
|
||||
|
||||
let response = client.load_rebalance_meta(request).await?.into_inner();
|
||||
|
||||
warn!("load_rebalance_meta response {:?}", response);
|
||||
warn!("load_rebalance_meta response {:?}, grid_host: {:?}", response, &self.grid_host);
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -678,15 +678,15 @@ impl PeerRestClient {
|
||||
pub async fn load_transition_tier_config(&self) -> Result<()> {
|
||||
let mut client = node_service_time_out_client(&self.grid_host)
|
||||
.await
|
||||
.map_err(|err| Error::msg(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
let request = Request::new(LoadTransitionTierConfigRequest {});
|
||||
|
||||
let response = client.load_transition_tier_config(request).await?.into_inner();
|
||||
if !response.success {
|
||||
if let Some(msg) = response.error_info {
|
||||
return Err(Error::msg(msg));
|
||||
return Err(Error::other(msg));
|
||||
}
|
||||
return Err(Error::msg(""));
|
||||
return Err(Error::other(""));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+44
-41
@@ -1,9 +1,13 @@
|
||||
use crate::bucket::versioning_sys::BucketVersioningSys;
|
||||
use crate::cache_value::metacache_set::{list_path_raw, ListPathRawOptions};
|
||||
use crate::config::com::{read_config, save_config, CONFIG_PREFIX};
|
||||
use crate::config::error::ConfigError;
|
||||
use crate::disk::error::is_err_volume_not_found;
|
||||
use crate::disk::{MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams, BUCKET_META_PREFIX, RUSTFS_META_BUCKET};
|
||||
use crate::cache_value::metacache_set::{ListPathRawOptions, list_path_raw};
|
||||
use crate::config::com::{CONFIG_PREFIX, read_config, save_config};
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::disk::{BUCKET_META_PREFIX, RUSTFS_META_BUCKET};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::{
|
||||
StorageError, is_err_bucket_exists, is_err_bucket_not_found, is_err_data_movement_overwrite, is_err_object_not_found,
|
||||
is_err_version_not_found,
|
||||
};
|
||||
use crate::heal::data_usage::DATA_USAGE_CACHE_NAME;
|
||||
use crate::heal::heal_commands::HealOpts;
|
||||
use crate::new_object_layer_fn;
|
||||
@@ -12,18 +16,16 @@ use crate::set_disk::SetDisks;
|
||||
use crate::store_api::{
|
||||
BucketOptions, CompletePart, GetObjectReader, MakeBucketOptions, ObjectIO, ObjectOptions, PutObjReader, StorageAPI,
|
||||
};
|
||||
use crate::store_err::{
|
||||
is_err_bucket_exists, is_err_data_movement_overwrite, is_err_object_not_found, is_err_version_not_found, StorageError,
|
||||
};
|
||||
use crate::utils::path::{encode_dir_object, path_join, SLASH_SEPARATOR};
|
||||
use crate::{sets::Sets, store::ECStore};
|
||||
use ::workers::workers::Workers;
|
||||
use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
|
||||
use common::defer;
|
||||
use common::error::{Error, Result};
|
||||
use futures::future::BoxFuture;
|
||||
use http::HeaderMap;
|
||||
use rmp_serde::{Deserializer, Serializer};
|
||||
use rustfs_filemeta::{MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams};
|
||||
use rustfs_rio::{HashReader, WarpReader};
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, encode_dir_object, path_join};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
@@ -31,7 +33,7 @@ use std::io::{Cursor, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::io::{AsyncReadExt, BufReader};
|
||||
use tokio::sync::broadcast::Receiver as B_Receiver;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
@@ -106,12 +108,12 @@ impl PoolMeta {
|
||||
if data.is_empty() {
|
||||
return Ok(());
|
||||
} else if data.len() <= 4 {
|
||||
return Err(Error::from_string("poolMeta: no data"));
|
||||
return Err(Error::other("poolMeta: no data"));
|
||||
}
|
||||
data
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(ConfigError::NotFound) = err.downcast_ref::<ConfigError>() {
|
||||
if err == Error::ConfigNotFound {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(err);
|
||||
@@ -119,11 +121,11 @@ impl PoolMeta {
|
||||
};
|
||||
let format = LittleEndian::read_u16(&data[0..2]);
|
||||
if format != POOL_META_FORMAT {
|
||||
return Err(Error::msg(format!("PoolMeta: unknown format: {}", format)));
|
||||
return Err(Error::other(format!("PoolMeta: unknown format: {}", format)));
|
||||
}
|
||||
let version = LittleEndian::read_u16(&data[2..4]);
|
||||
if version != POOL_META_VERSION {
|
||||
return Err(Error::msg(format!("PoolMeta: unknown version: {}", version)));
|
||||
return Err(Error::other(format!("PoolMeta: unknown version: {}", version)));
|
||||
}
|
||||
|
||||
let mut buf = Deserializer::new(Cursor::new(&data[4..]));
|
||||
@@ -131,7 +133,7 @@ impl PoolMeta {
|
||||
*self = meta;
|
||||
|
||||
if self.version != POOL_META_VERSION {
|
||||
return Err(Error::msg(format!("unexpected PoolMeta version: {}", self.version)));
|
||||
return Err(Error::other(format!("unexpected PoolMeta version: {}", self.version)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -230,7 +232,7 @@ impl PoolMeta {
|
||||
if let Some(pool) = self.pools.get_mut(idx) {
|
||||
if let Some(ref info) = pool.decommission {
|
||||
if !info.complete && !info.failed && !info.canceled {
|
||||
return Err(Error::new(StorageError::DecommissionAlreadyRunning));
|
||||
return Err(StorageError::DecommissionAlreadyRunning);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +306,7 @@ impl PoolMeta {
|
||||
}
|
||||
|
||||
pub fn track_current_bucket_object(&mut self, idx: usize, bucket: String, object: String) {
|
||||
if !self.pools.get(idx).is_some_and(|v| v.decommission.is_some()) {
|
||||
if self.pools.get(idx).is_none_or(|v| v.decommission.is_none()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -317,8 +319,8 @@ impl PoolMeta {
|
||||
}
|
||||
|
||||
pub async fn update_after(&mut self, idx: usize, pools: Vec<Arc<Sets>>, duration: Duration) -> Result<bool> {
|
||||
if !self.pools.get(idx).is_some_and(|v| v.decommission.is_some()) {
|
||||
return Err(Error::msg("InvalidArgument"));
|
||||
if self.pools.get(idx).is_none_or(|v| v.decommission.is_none()) {
|
||||
return Err(Error::other("InvalidArgument"));
|
||||
}
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
@@ -377,7 +379,7 @@ impl PoolMeta {
|
||||
pi.position + 1,
|
||||
k
|
||||
);
|
||||
// return Err(Error::msg(format!(
|
||||
// return Err(Error::other(format!(
|
||||
// "pool({}) = {} is decommissioned, please remove from server command line",
|
||||
// pi.position + 1,
|
||||
// k
|
||||
@@ -590,22 +592,22 @@ impl ECStore {
|
||||
used: total - free,
|
||||
})
|
||||
} else {
|
||||
Err(Error::msg("InvalidArgument"))
|
||||
Err(Error::other("InvalidArgument"))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn decommission_cancel(&self, idx: usize) -> Result<()> {
|
||||
if self.single_pool() {
|
||||
return Err(Error::msg("InvalidArgument"));
|
||||
return Err(Error::other("InvalidArgument"));
|
||||
}
|
||||
|
||||
let Some(has_canceler) = self.decommission_cancelers.get(idx) else {
|
||||
return Err(Error::msg("InvalidArgument"));
|
||||
return Err(Error::other("InvalidArgument"));
|
||||
};
|
||||
|
||||
if has_canceler.is_none() {
|
||||
return Err(Error::new(StorageError::DecommissionNotStarted));
|
||||
return Err(StorageError::DecommissionNotStarted);
|
||||
}
|
||||
|
||||
let mut lock = self.pool_meta.write().await;
|
||||
@@ -638,11 +640,11 @@ impl ECStore {
|
||||
pub async fn decommission(&self, rx: B_Receiver<bool>, indices: Vec<usize>) -> Result<()> {
|
||||
warn!("decommission: {:?}", indices);
|
||||
if indices.is_empty() {
|
||||
return Err(Error::msg("errInvalidArgument"));
|
||||
return Err(Error::other("InvalidArgument"));
|
||||
}
|
||||
|
||||
if self.single_pool() {
|
||||
return Err(Error::msg("errInvalidArgument"));
|
||||
return Err(Error::other("InvalidArgument"));
|
||||
}
|
||||
|
||||
self.start_decommission(indices.clone()).await?;
|
||||
@@ -880,7 +882,7 @@ impl ECStore {
|
||||
pool: Arc<Sets>,
|
||||
bi: DecomBucketInfo,
|
||||
) -> Result<()> {
|
||||
let wk = Workers::new(pool.disk_set.len() * 2).map_err(|v| Error::from_string(v))?;
|
||||
let wk = Workers::new(pool.disk_set.len() * 2).map_err(Error::other)?;
|
||||
|
||||
// let mut vc = None;
|
||||
// replication
|
||||
@@ -942,7 +944,7 @@ impl ECStore {
|
||||
}
|
||||
Err(err) => {
|
||||
error!("decommission_pool: list_objects_to_decommission {} err {:?}", set_id, &err);
|
||||
if is_err_volume_not_found(&err) {
|
||||
if is_err_bucket_not_found(&err) {
|
||||
warn!("decommission_pool: list_objects_to_decommission {} volume not found", set_id);
|
||||
break;
|
||||
}
|
||||
@@ -1008,7 +1010,7 @@ impl ECStore {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn decommission_failed(&self, idx: usize) -> Result<()> {
|
||||
if self.single_pool() {
|
||||
return Err(Error::msg("errInvalidArgument"));
|
||||
return Err(Error::other("errInvalidArgument"));
|
||||
}
|
||||
|
||||
let mut pool_meta = self.pool_meta.write().await;
|
||||
@@ -1028,7 +1030,7 @@ impl ECStore {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn complete_decommission(&self, idx: usize) -> Result<()> {
|
||||
if self.single_pool() {
|
||||
return Err(Error::msg("errInvalidArgument"));
|
||||
return Err(Error::other("errInvalidArgument"));
|
||||
}
|
||||
|
||||
let mut pool_meta = self.pool_meta.write().await;
|
||||
@@ -1102,11 +1104,11 @@ impl ECStore {
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn start_decommission(&self, indices: Vec<usize>) -> Result<()> {
|
||||
if indices.is_empty() {
|
||||
return Err(Error::msg("errInvalidArgument"));
|
||||
return Err(Error::other("errInvalidArgument"));
|
||||
}
|
||||
|
||||
if self.single_pool() {
|
||||
return Err(Error::msg("errInvalidArgument"));
|
||||
return Err(Error::other("errInvalidArgument"));
|
||||
}
|
||||
|
||||
let decom_buckets = self.get_buckets_to_decommission().await?;
|
||||
@@ -1220,9 +1222,7 @@ impl ECStore {
|
||||
|
||||
reader.read_exact(&mut chunk).await?;
|
||||
|
||||
// 每次从 reader 中读取一个 part 上传
|
||||
let rd = Box::new(Cursor::new(chunk));
|
||||
let mut data = PutObjReader::new(rd, part.size);
|
||||
let mut data = PutObjReader::from_vec(chunk);
|
||||
|
||||
let pi = match self
|
||||
.put_object_part(
|
||||
@@ -1232,7 +1232,7 @@ impl ECStore {
|
||||
part.number,
|
||||
&mut data,
|
||||
&ObjectOptions {
|
||||
preserve_etag: part.e_tag.clone(),
|
||||
preserve_etag: Some(part.etag.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -1249,11 +1249,12 @@ impl ECStore {
|
||||
|
||||
parts[i] = CompletePart {
|
||||
part_num: pi.part_num,
|
||||
e_tag: pi.etag,
|
||||
etag: pi.etag,
|
||||
};
|
||||
}
|
||||
|
||||
if let Err(err) = self
|
||||
.clone()
|
||||
.complete_multipart_upload(
|
||||
&bucket,
|
||||
&object_info.name,
|
||||
@@ -1275,7 +1276,9 @@ impl ECStore {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut data = PutObjReader::new(rd.stream, object_info.size);
|
||||
let reader = BufReader::new(rd.stream);
|
||||
let hrd = HashReader::new(Box::new(WarpReader::new(reader)), object_info.size, object_info.size, None, false)?;
|
||||
let mut data = PutObjReader::new(hrd);
|
||||
|
||||
if let Err(err) = self
|
||||
.put_object(
|
||||
@@ -1318,7 +1321,7 @@ impl SetDisks {
|
||||
) -> Result<()> {
|
||||
let (disks, _) = self.get_online_disks_with_healing(false).await;
|
||||
if disks.is_empty() {
|
||||
return Err(Error::msg("errNoDiskAvailable"));
|
||||
return Err(Error::other("errNoDiskAvailable"));
|
||||
}
|
||||
|
||||
let listing_quorum = self.set_drive_count.div_ceil(2);
|
||||
@@ -1341,7 +1344,7 @@ impl SetDisks {
|
||||
recursice: true,
|
||||
min_disks: listing_quorum,
|
||||
agreed: Some(Box::new(move |entry: MetaCacheEntry| Box::pin(cb1(entry)))),
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<DiskError>]| {
|
||||
let resolver = resolver.clone();
|
||||
let cb_func = cb_func.clone();
|
||||
match entries.resolve(resolver) {
|
||||
|
||||
@@ -1,268 +0,0 @@
|
||||
use crate::{disk::error::DiskError, error::clone_err};
|
||||
use common::error::Error;
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
// pub type CheckErrorFn = fn(e: &Error) -> bool;
|
||||
|
||||
pub trait CheckErrorFn: Debug + Send + Sync + 'static {
|
||||
fn is(&self, e: &Error) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub enum QuorumError {
|
||||
#[error("Read quorum not met")]
|
||||
Read,
|
||||
#[error("disk not found")]
|
||||
Write,
|
||||
}
|
||||
|
||||
impl QuorumError {
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
match self {
|
||||
QuorumError::Read => 0x01,
|
||||
QuorumError::Write => 0x02,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u32(error: u32) -> Option<Self> {
|
||||
match error {
|
||||
0x01 => Some(QuorumError::Read),
|
||||
0x02 => Some(QuorumError::Write),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_ignored_errs() -> Vec<Box<dyn CheckErrorFn>> {
|
||||
vec![
|
||||
Box::new(DiskError::DiskNotFound),
|
||||
Box::new(DiskError::FaultyDisk),
|
||||
Box::new(DiskError::FaultyRemoteDisk),
|
||||
]
|
||||
}
|
||||
|
||||
// object_op_ignored_errs
|
||||
pub fn object_op_ignored_errs() -> Vec<Box<dyn CheckErrorFn>> {
|
||||
let mut base = base_ignored_errs();
|
||||
|
||||
let ext: Vec<Box<dyn CheckErrorFn>> = vec![
|
||||
// Box::new(DiskError::DiskNotFound),
|
||||
// Box::new(DiskError::FaultyDisk),
|
||||
// Box::new(DiskError::FaultyRemoteDisk),
|
||||
Box::new(DiskError::DiskAccessDenied),
|
||||
Box::new(DiskError::UnformattedDisk),
|
||||
Box::new(DiskError::DiskOngoingReq),
|
||||
];
|
||||
|
||||
base.extend(ext);
|
||||
base
|
||||
}
|
||||
|
||||
// bucket_op_ignored_errs
|
||||
pub fn bucket_op_ignored_errs() -> Vec<Box<dyn CheckErrorFn>> {
|
||||
let mut base = base_ignored_errs();
|
||||
|
||||
let ext: Vec<Box<dyn CheckErrorFn>> = vec![Box::new(DiskError::DiskAccessDenied), Box::new(DiskError::UnformattedDisk)];
|
||||
|
||||
base.extend(ext);
|
||||
base
|
||||
}
|
||||
|
||||
// 用于检查错误是否被忽略的函数
|
||||
fn is_err_ignored(err: &Error, ignored_errs: &[Box<dyn CheckErrorFn>]) -> bool {
|
||||
ignored_errs.iter().any(|ignored_err| ignored_err.is(err))
|
||||
}
|
||||
|
||||
// 减少错误数量并返回出现次数最多的错误
|
||||
fn reduce_errs(errs: &[Option<Error>], ignored_errs: &[Box<dyn CheckErrorFn>]) -> (usize, Option<Error>) {
|
||||
let mut error_counts: HashMap<String, usize> = HashMap::new();
|
||||
let mut error_map: HashMap<String, usize> = HashMap::new(); // 存 err 位置
|
||||
let nil = "nil".to_string();
|
||||
for (i, operr) in errs.iter().enumerate() {
|
||||
if let Some(err) = operr {
|
||||
if is_err_ignored(err, ignored_errs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let errstr = err.inner_string();
|
||||
|
||||
let _ = *error_map.entry(errstr.clone()).or_insert(i);
|
||||
*error_counts.entry(errstr.clone()).or_insert(0) += 1;
|
||||
} else {
|
||||
*error_counts.entry(nil.clone()).or_insert(0) += 1;
|
||||
let _ = *error_map.entry(nil.clone()).or_insert(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
// let err = operr.as_ref().unwrap();
|
||||
|
||||
// let errstr = err.to_string();
|
||||
|
||||
// let _ = *error_map.entry(errstr.clone()).or_insert(i);
|
||||
// *error_counts.entry(errstr.clone()).or_insert(0) += 1;
|
||||
}
|
||||
|
||||
let mut max = 0;
|
||||
let mut max_err = nil.clone();
|
||||
for (err, &count) in error_counts.iter() {
|
||||
if count > max || (count == max && *err == nil) {
|
||||
max = count;
|
||||
max_err.clone_from(err);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(&err_idx) = error_map.get(&max_err) {
|
||||
let err = errs[err_idx].as_ref().map(clone_err);
|
||||
(max, err)
|
||||
} else if max_err == nil {
|
||||
(max, None)
|
||||
} else {
|
||||
(0, None)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 quorum 验证错误数量
|
||||
fn reduce_quorum_errs(
|
||||
errs: &[Option<Error>],
|
||||
ignored_errs: &[Box<dyn CheckErrorFn>],
|
||||
quorum: usize,
|
||||
quorum_err: QuorumError,
|
||||
) -> Option<Error> {
|
||||
let (max_count, max_err) = reduce_errs(errs, ignored_errs);
|
||||
if max_count >= quorum {
|
||||
max_err
|
||||
} else {
|
||||
Some(Error::new(quorum_err))
|
||||
}
|
||||
}
|
||||
|
||||
// 根据读 quorum 验证错误数量
|
||||
// 返回最大错误数量的下标,或 QuorumError
|
||||
pub fn reduce_read_quorum_errs(
|
||||
errs: &[Option<Error>],
|
||||
ignored_errs: &[Box<dyn CheckErrorFn>],
|
||||
read_quorum: usize,
|
||||
) -> Option<Error> {
|
||||
reduce_quorum_errs(errs, ignored_errs, read_quorum, QuorumError::Read)
|
||||
}
|
||||
|
||||
// 根据写 quorum 验证错误数量
|
||||
// 返回最大错误数量的下标,或 QuorumError
|
||||
#[tracing::instrument(level = "info", skip_all)]
|
||||
pub fn reduce_write_quorum_errs(
|
||||
errs: &[Option<Error>],
|
||||
ignored_errs: &[Box<dyn CheckErrorFn>],
|
||||
write_quorum: usize,
|
||||
) -> Option<Error> {
|
||||
reduce_quorum_errs(errs, ignored_errs, write_quorum, QuorumError::Write)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MockErrorChecker {
|
||||
target_error: String,
|
||||
}
|
||||
|
||||
impl CheckErrorFn for MockErrorChecker {
|
||||
fn is(&self, e: &Error) -> bool {
|
||||
e.inner_string() == self.target_error
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_error(message: &str) -> Error {
|
||||
Error::msg(message.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_errs_with_no_errors() {
|
||||
let errs: Vec<Option<Error>> = vec![];
|
||||
let ignored_errs: Vec<Box<dyn CheckErrorFn>> = vec![];
|
||||
|
||||
let (count, err) = reduce_errs(&errs, &ignored_errs);
|
||||
|
||||
assert_eq!(count, 0);
|
||||
assert!(err.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_errs_with_ignored_errors() {
|
||||
let errs = vec![Some(mock_error("ignored_error")), Some(mock_error("ignored_error"))];
|
||||
let ignored_errs: Vec<Box<dyn CheckErrorFn>> = vec![Box::new(MockErrorChecker {
|
||||
target_error: "ignored_error".to_string(),
|
||||
})];
|
||||
|
||||
let (count, err) = reduce_errs(&errs, &ignored_errs);
|
||||
|
||||
assert_eq!(count, 0);
|
||||
assert!(err.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_errs_with_mixed_errors() {
|
||||
let errs = vec![
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
Some(Error::new(DiskError::FileNotFound)),
|
||||
];
|
||||
let ignored_errs: Vec<Box<dyn CheckErrorFn>> = vec![Box::new(MockErrorChecker {
|
||||
target_error: "error2".to_string(),
|
||||
})];
|
||||
|
||||
let (count, err) = reduce_errs(&errs, &ignored_errs);
|
||||
println!("count: {}, err: {:?}", count, err);
|
||||
assert_eq!(count, 9);
|
||||
assert_eq!(err.unwrap().to_string(), DiskError::FileNotFound.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_errs_with_nil_errors() {
|
||||
let errs = vec![None, Some(mock_error("error1")), None];
|
||||
let ignored_errs: Vec<Box<dyn CheckErrorFn>> = vec![];
|
||||
|
||||
let (count, err) = reduce_errs(&errs, &ignored_errs);
|
||||
|
||||
assert_eq!(count, 2);
|
||||
assert!(err.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_read_quorum_errs() {
|
||||
let errs = vec![
|
||||
Some(mock_error("error1")),
|
||||
Some(mock_error("error1")),
|
||||
Some(mock_error("error2")),
|
||||
None,
|
||||
None,
|
||||
];
|
||||
let ignored_errs: Vec<Box<dyn CheckErrorFn>> = vec![];
|
||||
let read_quorum = 2;
|
||||
|
||||
let result = reduce_read_quorum_errs(&errs, &ignored_errs, read_quorum);
|
||||
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reduce_write_quorum_errs_with_quorum_error() {
|
||||
let errs = vec![
|
||||
Some(mock_error("error1")),
|
||||
Some(mock_error("error2")),
|
||||
Some(mock_error("error2")),
|
||||
];
|
||||
let ignored_errs: Vec<Box<dyn CheckErrorFn>> = vec![];
|
||||
let write_quorum = 3;
|
||||
|
||||
let result = reduce_write_quorum_errs(&errs, &ignored_errs, write_quorum);
|
||||
|
||||
assert!(result.is_some());
|
||||
assert_eq!(result.unwrap().to_string(), QuorumError::Write.to_string());
|
||||
}
|
||||
}
|
||||
+240
-149
@@ -1,33 +1,32 @@
|
||||
use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::cache_value::metacache_set::{list_path_raw, ListPathRawOptions};
|
||||
use crate::StorageAPI;
|
||||
use crate::cache_value::metacache_set::{ListPathRawOptions, list_path_raw};
|
||||
use crate::config::com::{read_config_with_metadata, save_config_with_opts};
|
||||
use crate::config::error::is_err_config_not_found;
|
||||
use crate::disk::{MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams};
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::error::{is_err_data_movement_overwrite, is_err_object_not_found, is_err_version_not_found};
|
||||
use crate::global::get_global_endpoints;
|
||||
use crate::pools::ListCallback;
|
||||
use crate::set_disk::SetDisks;
|
||||
use crate::store::ECStore;
|
||||
use crate::store_api::{CompletePart, FileInfo, GetObjectReader, ObjectIO, ObjectOptions, PutObjReader};
|
||||
use crate::store_err::{is_err_data_movement_overwrite, is_err_object_not_found, is_err_version_not_found};
|
||||
use crate::utils::path::encode_dir_object;
|
||||
use crate::StorageAPI;
|
||||
use crate::store_api::{CompletePart, GetObjectReader, ObjectIO, ObjectOptions, PutObjReader};
|
||||
use common::defer;
|
||||
use common::error::{Error, Result};
|
||||
use http::HeaderMap;
|
||||
use rustfs_filemeta::{FileInfo, MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams};
|
||||
use rustfs_rio::{HashReader, WarpReader};
|
||||
use rustfs_utils::path::encode_dir_object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::io::{AsyncReadExt, BufReader};
|
||||
use tokio::sync::broadcast::{self, Receiver as B_Receiver};
|
||||
use tokio::time::{Duration, Instant};
|
||||
use tracing::{error, info, warn};
|
||||
use uuid::Uuid;
|
||||
use workers::workers::Workers;
|
||||
|
||||
const REBAL_META_FMT: u16 = 1; // Replace with actual format value
|
||||
const REBAL_META_VER: u16 = 1; // Replace with actual version value
|
||||
const REBAL_META_NAME: &str = "rebalance_meta";
|
||||
const REBAL_META_NAME: &str = "rebalance.bin";
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct RebalanceStats {
|
||||
@@ -63,7 +62,7 @@ impl RebalanceStats {
|
||||
|
||||
self.num_versions += 1;
|
||||
let on_disk_size = if !fi.deleted {
|
||||
fi.size as i64 * (fi.erasure.data_blocks + fi.erasure.parity_blocks) as i64 / fi.erasure.data_blocks as i64
|
||||
fi.size * (fi.erasure.data_blocks + fi.erasure.parity_blocks) as i64 / fi.erasure.data_blocks as i64
|
||||
} else {
|
||||
0
|
||||
};
|
||||
@@ -122,9 +121,9 @@ pub enum RebalSaveOpt {
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct RebalanceInfo {
|
||||
#[serde(rename = "startTs")]
|
||||
pub start_time: Option<SystemTime>, // Time at which rebalance-start was issued
|
||||
pub start_time: Option<OffsetDateTime>, // Time at which rebalance-start was issued
|
||||
#[serde(rename = "stopTs")]
|
||||
pub end_time: Option<SystemTime>, // Time at which rebalance operation completed or rebalance-stop was called
|
||||
pub end_time: Option<OffsetDateTime>, // Time at which rebalance operation completed or rebalance-stop was called
|
||||
#[serde(rename = "status")]
|
||||
pub status: RebalStatus, // Current state of rebalance operation
|
||||
}
|
||||
@@ -136,14 +135,14 @@ pub struct DiskStat {
|
||||
pub available_space: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct RebalanceMeta {
|
||||
#[serde(skip)]
|
||||
pub cancel: Option<broadcast::Sender<bool>>, // To be invoked on rebalance-stop
|
||||
#[serde(skip)]
|
||||
pub last_refreshed_at: Option<SystemTime>,
|
||||
pub last_refreshed_at: Option<OffsetDateTime>,
|
||||
#[serde(rename = "stopTs")]
|
||||
pub stopped_at: Option<SystemTime>, // Time when rebalance-stop was issued
|
||||
pub stopped_at: Option<OffsetDateTime>, // Time when rebalance-stop was issued
|
||||
#[serde(rename = "id")]
|
||||
pub id: String, // ID of the ongoing rebalance operation
|
||||
#[serde(rename = "pf")]
|
||||
@@ -163,29 +162,29 @@ impl RebalanceMeta {
|
||||
pub async fn load_with_opts<S: StorageAPI>(&mut self, store: Arc<S>, opts: ObjectOptions) -> Result<()> {
|
||||
let (data, _) = read_config_with_metadata(store, REBAL_META_NAME, &opts).await?;
|
||||
if data.is_empty() {
|
||||
warn!("rebalanceMeta: no data");
|
||||
warn!("rebalanceMeta load_with_opts: no data");
|
||||
return Ok(());
|
||||
}
|
||||
if data.len() <= 4 {
|
||||
return Err(Error::msg("rebalanceMeta: no data"));
|
||||
return Err(Error::other("rebalanceMeta load_with_opts: no data"));
|
||||
}
|
||||
|
||||
// Read header
|
||||
match u16::from_le_bytes([data[0], data[1]]) {
|
||||
REBAL_META_FMT => {}
|
||||
fmt => return Err(Error::msg(format!("rebalanceMeta: unknown format: {}", fmt))),
|
||||
fmt => return Err(Error::other(format!("rebalanceMeta load_with_opts: unknown format: {}", fmt))),
|
||||
}
|
||||
match u16::from_le_bytes([data[2], data[3]]) {
|
||||
REBAL_META_VER => {}
|
||||
ver => return Err(Error::msg(format!("rebalanceMeta: unknown version: {}", ver))),
|
||||
ver => return Err(Error::other(format!("rebalanceMeta load_with_opts: unknown version: {}", ver))),
|
||||
}
|
||||
|
||||
let meta: Self = rmp_serde::from_read(Cursor::new(&data[4..]))?;
|
||||
*self = meta;
|
||||
|
||||
self.last_refreshed_at = Some(SystemTime::now());
|
||||
self.last_refreshed_at = Some(OffsetDateTime::now_utc());
|
||||
|
||||
warn!("rebalanceMeta: loaded meta done");
|
||||
warn!("rebalanceMeta load_with_opts: loaded meta done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -195,6 +194,7 @@ impl RebalanceMeta {
|
||||
|
||||
pub async fn save_with_opts<S: StorageAPI>(&self, store: Arc<S>, opts: ObjectOptions) -> Result<()> {
|
||||
if self.pool_stats.is_empty() {
|
||||
warn!("rebalanceMeta save_with_opts: no pool stats");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ impl ECStore {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn load_rebalance_meta(&self) -> Result<()> {
|
||||
let mut meta = RebalanceMeta::new();
|
||||
warn!("rebalanceMeta: load rebalance meta");
|
||||
warn!("rebalanceMeta: store load rebalance meta");
|
||||
match meta.load(self.pools[0].clone()).await {
|
||||
Ok(_) => {
|
||||
warn!("rebalanceMeta: rebalance meta loaded0");
|
||||
@@ -238,12 +238,12 @@ impl ECStore {
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if !is_err_config_not_found(&err) {
|
||||
if err != Error::ConfigNotFound {
|
||||
error!("rebalanceMeta: load rebalance meta err {:?}", &err);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
error!("rebalanceMeta: not found, rebalance not started");
|
||||
warn!("rebalanceMeta: not found, rebalance not started");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,9 +254,18 @@ impl ECStore {
|
||||
pub async fn update_rebalance_stats(&self) -> Result<()> {
|
||||
let mut ok = false;
|
||||
|
||||
let pool_stats = {
|
||||
let rebalance_meta = self.rebalance_meta.read().await;
|
||||
rebalance_meta.as_ref().map(|v| v.pool_stats.clone()).unwrap_or_default()
|
||||
};
|
||||
|
||||
warn!("update_rebalance_stats: pool_stats: {:?}", &pool_stats);
|
||||
|
||||
for i in 0..self.pools.len() {
|
||||
if self.find_index(i).await.is_none() {
|
||||
if pool_stats.get(i).is_none() {
|
||||
warn!("update_rebalance_stats: pool {} not found", i);
|
||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||
warn!("update_rebalance_stats: pool {} not found, add", i);
|
||||
if let Some(meta) = rebalance_meta.as_mut() {
|
||||
meta.pool_stats.push(RebalanceStats::default());
|
||||
}
|
||||
@@ -266,23 +275,24 @@ impl ECStore {
|
||||
}
|
||||
|
||||
if ok {
|
||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||
if let Some(meta) = rebalance_meta.as_mut() {
|
||||
warn!("update_rebalance_stats: save rebalance meta");
|
||||
|
||||
let rebalance_meta = self.rebalance_meta.read().await;
|
||||
if let Some(meta) = rebalance_meta.as_ref() {
|
||||
meta.save(self.pools[0].clone()).await?;
|
||||
}
|
||||
drop(rebalance_meta);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn find_index(&self, index: usize) -> Option<usize> {
|
||||
if let Some(meta) = self.rebalance_meta.read().await.as_ref() {
|
||||
return meta.pool_stats.get(index).map(|_v| index);
|
||||
}
|
||||
// async fn find_index(&self, index: usize) -> Option<usize> {
|
||||
// if let Some(meta) = self.rebalance_meta.read().await.as_ref() {
|
||||
// return meta.pool_stats.get(index).map(|_v| index);
|
||||
// }
|
||||
|
||||
None
|
||||
}
|
||||
// None
|
||||
// }
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn init_rebalance_meta(&self, bucktes: Vec<String>) -> Result<String> {
|
||||
@@ -309,7 +319,7 @@ impl ECStore {
|
||||
|
||||
let mut pool_stats = Vec::with_capacity(self.pools.len());
|
||||
|
||||
let now = SystemTime::now();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
for disk_stat in disk_stats.iter() {
|
||||
let mut pool_stat = RebalanceStats {
|
||||
@@ -368,20 +378,26 @@ impl ECStore {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn next_rebal_bucket(&self, pool_index: usize) -> Result<Option<String>> {
|
||||
warn!("next_rebal_bucket: pool_index: {}", pool_index);
|
||||
let rebalance_meta = self.rebalance_meta.read().await;
|
||||
warn!("next_rebal_bucket: rebalance_meta: {:?}", rebalance_meta);
|
||||
if let Some(meta) = rebalance_meta.as_ref() {
|
||||
if let Some(pool_stat) = meta.pool_stats.get(pool_index) {
|
||||
if pool_stat.info.status == RebalStatus::Completed || !pool_stat.participating {
|
||||
warn!("next_rebal_bucket: pool_index: {} completed or not participating", pool_index);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if pool_stat.buckets.is_empty() {
|
||||
warn!("next_rebal_bucket: pool_index: {} buckets is empty", pool_index);
|
||||
return Ok(None);
|
||||
}
|
||||
warn!("next_rebal_bucket: pool_index: {} bucket: {}", pool_index, pool_stat.buckets[0]);
|
||||
return Ok(Some(pool_stat.buckets[0].clone()));
|
||||
}
|
||||
}
|
||||
|
||||
warn!("next_rebal_bucket: pool_index: {} None", pool_index);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -391,18 +407,28 @@ impl ECStore {
|
||||
if let Some(meta) = rebalance_meta.as_mut() {
|
||||
if let Some(pool_stat) = meta.pool_stats.get_mut(pool_index) {
|
||||
warn!("bucket_rebalance_done: buckets {:?}", &pool_stat.buckets);
|
||||
if let Some(idx) = pool_stat.buckets.iter().position(|b| b.as_str() == bucket.as_str()) {
|
||||
warn!("bucket_rebalance_done: bucket {} rebalanced", &bucket);
|
||||
pool_stat.buckets.remove(idx);
|
||||
pool_stat.rebalanced_buckets.push(bucket);
|
||||
|
||||
// 使用 retain 来过滤掉要删除的 bucket
|
||||
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 // 删除这个元素
|
||||
} else {
|
||||
true // 保留这个元素
|
||||
}
|
||||
});
|
||||
|
||||
if found {
|
||||
warn!("bucket_rebalance_done: bucket {} rebalanced", &bucket);
|
||||
return Ok(());
|
||||
} else {
|
||||
warn!("bucket_rebalance_done: bucket {} not found", bucket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
warn!("bucket_rebalance_done: bucket {} not found", bucket);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -410,18 +436,28 @@ impl ECStore {
|
||||
let rebalance_meta = self.rebalance_meta.read().await;
|
||||
if let Some(ref meta) = *rebalance_meta {
|
||||
if meta.stopped_at.is_some() {
|
||||
warn!("is_rebalance_started: rebalance stopped");
|
||||
return false;
|
||||
}
|
||||
|
||||
meta.pool_stats.iter().enumerate().for_each(|(i, v)| {
|
||||
warn!(
|
||||
"is_rebalance_started: pool_index: {}, participating: {:?}, status: {:?}",
|
||||
i, v.participating, v.info.status
|
||||
);
|
||||
});
|
||||
|
||||
if meta
|
||||
.pool_stats
|
||||
.iter()
|
||||
.any(|v| v.participating && v.info.status != RebalStatus::Completed)
|
||||
{
|
||||
warn!("is_rebalance_started: rebalance started");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
warn!("is_rebalance_started: rebalance not started");
|
||||
false
|
||||
}
|
||||
|
||||
@@ -461,10 +497,11 @@ impl ECStore {
|
||||
|
||||
{
|
||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||
|
||||
if let Some(meta) = rebalance_meta.as_mut() {
|
||||
meta.cancel = Some(tx)
|
||||
} else {
|
||||
error!("start_rebalance: rebalance_meta is None exit");
|
||||
warn!("start_rebalance: rebalance_meta is None exit");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -473,19 +510,25 @@ impl ECStore {
|
||||
|
||||
let participants = {
|
||||
if let Some(ref meta) = *self.rebalance_meta.read().await {
|
||||
if meta.stopped_at.is_some() {
|
||||
warn!("start_rebalance: rebalance already stopped exit");
|
||||
return;
|
||||
}
|
||||
// if meta.stopped_at.is_some() {
|
||||
// warn!("start_rebalance: rebalance already stopped exit");
|
||||
// return;
|
||||
// }
|
||||
|
||||
let mut participants = vec![false; meta.pool_stats.len()];
|
||||
for (i, pool_stat) in meta.pool_stats.iter().enumerate() {
|
||||
if pool_stat.info.status == RebalStatus::Started {
|
||||
participants[i] = pool_stat.participating;
|
||||
warn!("start_rebalance: pool {} status: {:?}", i, pool_stat.info.status);
|
||||
if pool_stat.info.status != RebalStatus::Started {
|
||||
warn!("start_rebalance: pool {} not started, skipping", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
warn!("start_rebalance: pool {} participating: {:?}", i, pool_stat.participating);
|
||||
participants[i] = pool_stat.participating;
|
||||
}
|
||||
participants
|
||||
} else {
|
||||
warn!("start_rebalance:2 rebalance_meta is None exit");
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
@@ -496,11 +539,13 @@ impl ECStore {
|
||||
continue;
|
||||
}
|
||||
|
||||
if get_global_endpoints()
|
||||
.as_ref()
|
||||
.get(idx)
|
||||
.map_or(true, |v| v.endpoints.as_ref().first().map_or(true, |e| e.is_local))
|
||||
{
|
||||
if !get_global_endpoints().as_ref().get(idx).is_some_and(|v| {
|
||||
warn!("start_rebalance: pool {} endpoints: {:?}", idx, v.endpoints);
|
||||
v.endpoints.as_ref().first().is_some_and(|e| {
|
||||
warn!("start_rebalance: pool {} endpoint: {:?}, is_local: {}", idx, e, e.is_local);
|
||||
e.is_local
|
||||
})
|
||||
}) {
|
||||
warn!("start_rebalance: pool {} is not local, skipping", idx);
|
||||
continue;
|
||||
}
|
||||
@@ -521,13 +566,13 @@ impl ECStore {
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, rx))]
|
||||
async fn rebalance_buckets(self: &Arc<Self>, rx: B_Receiver<bool>, pool_index: usize) -> Result<()> {
|
||||
async fn rebalance_buckets(self: &Arc<Self>, mut rx: B_Receiver<bool>, pool_index: usize) -> Result<()> {
|
||||
let (done_tx, mut done_rx) = tokio::sync::mpsc::channel::<Result<()>>(1);
|
||||
|
||||
// Save rebalance metadata periodically
|
||||
let store = self.clone();
|
||||
let save_task = tokio::spawn(async move {
|
||||
let mut timer = tokio::time::interval_at(Instant::now() + Duration::from_secs(10), Duration::from_secs(10));
|
||||
let mut timer = tokio::time::interval_at(Instant::now() + Duration::from_secs(30), Duration::from_secs(10));
|
||||
let mut msg: String;
|
||||
let mut quit = false;
|
||||
|
||||
@@ -536,14 +581,15 @@ impl ECStore {
|
||||
// TODO: cancel rebalance
|
||||
Some(result) = done_rx.recv() => {
|
||||
quit = true;
|
||||
let now = SystemTime::now();
|
||||
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let state = match result {
|
||||
Ok(_) => {
|
||||
warn!("rebalance_buckets: completed");
|
||||
msg = format!("Rebalance completed at {:?}", now);
|
||||
RebalStatus::Completed},
|
||||
Err(err) => {
|
||||
warn!("rebalance_buckets: error: {:?}", err);
|
||||
// TODO: check stop
|
||||
if err.to_string().contains("canceled") {
|
||||
msg = format!("Rebalance stopped at {:?}", now);
|
||||
@@ -556,9 +602,11 @@ impl ECStore {
|
||||
};
|
||||
|
||||
{
|
||||
warn!("rebalance_buckets: save rebalance meta, pool_index: {}, state: {:?}", pool_index, state);
|
||||
let mut rebalance_meta = store.rebalance_meta.write().await;
|
||||
|
||||
if let Some(rbm) = rebalance_meta.as_mut() {
|
||||
warn!("rebalance_buckets: save rebalance meta2, pool_index: {}, state: {:?}", pool_index, state);
|
||||
rbm.pool_stats[pool_index].info.status = state;
|
||||
rbm.pool_stats[pool_index].info.end_time = Some(now);
|
||||
}
|
||||
@@ -567,7 +615,7 @@ impl ECStore {
|
||||
|
||||
}
|
||||
_ = timer.tick() => {
|
||||
let now = SystemTime::now();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
msg = format!("Saving rebalance metadata at {:?}", now);
|
||||
}
|
||||
}
|
||||
@@ -575,7 +623,7 @@ impl ECStore {
|
||||
if let Err(err) = store.save_rebalance_stats(pool_index, RebalSaveOpt::Stats).await {
|
||||
error!("{} err: {:?}", msg, err);
|
||||
} else {
|
||||
info!(msg);
|
||||
warn!(msg);
|
||||
}
|
||||
|
||||
if quit {
|
||||
@@ -587,30 +635,41 @@ impl ECStore {
|
||||
}
|
||||
});
|
||||
|
||||
warn!("Pool {} rebalancing is started", pool_index + 1);
|
||||
warn!("Pool {} rebalancing is started", pool_index);
|
||||
|
||||
while let Some(bucket) = self.next_rebal_bucket(pool_index).await? {
|
||||
warn!("Rebalancing bucket: start {}", bucket);
|
||||
|
||||
if let Err(err) = self.rebalance_bucket(rx.resubscribe(), bucket.clone(), pool_index).await {
|
||||
if err.to_string().contains("not initialized") {
|
||||
warn!("rebalance_bucket: rebalance not initialized, continue");
|
||||
continue;
|
||||
}
|
||||
error!("Error rebalancing bucket {}: {:?}", bucket, err);
|
||||
done_tx.send(Err(err)).await.ok();
|
||||
loop {
|
||||
if let Ok(true) = rx.try_recv() {
|
||||
warn!("Pool {} rebalancing is stopped", pool_index);
|
||||
done_tx.send(Err(Error::other("rebalance stopped canceled"))).await.ok();
|
||||
break;
|
||||
}
|
||||
|
||||
warn!("Rebalance bucket: done {} ", bucket);
|
||||
self.bucket_rebalance_done(pool_index, bucket).await?;
|
||||
if let Some(bucket) = self.next_rebal_bucket(pool_index).await? {
|
||||
warn!("Rebalancing bucket: start {}", bucket);
|
||||
|
||||
if let Err(err) = self.rebalance_bucket(rx.resubscribe(), bucket.clone(), pool_index).await {
|
||||
if err.to_string().contains("not initialized") {
|
||||
warn!("rebalance_bucket: rebalance not initialized, continue");
|
||||
continue;
|
||||
}
|
||||
error!("Error rebalancing bucket {}: {:?}", bucket, err);
|
||||
done_tx.send(Err(err)).await.ok();
|
||||
break;
|
||||
}
|
||||
|
||||
warn!("Rebalance bucket: done {} ", bucket);
|
||||
self.bucket_rebalance_done(pool_index, bucket).await?;
|
||||
} else {
|
||||
warn!("Rebalance bucket: no bucket to rebalance");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
warn!("Pool {} rebalancing is done", pool_index + 1);
|
||||
warn!("Pool {} rebalancing is done", pool_index);
|
||||
|
||||
done_tx.send(Ok(())).await.ok();
|
||||
save_task.await.ok();
|
||||
|
||||
warn!("Pool {} rebalancing is done2", pool_index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -621,6 +680,7 @@ impl ECStore {
|
||||
if let Some(pool_stat) = meta.pool_stats.get_mut(pool_index) {
|
||||
// Check if the pool's rebalance status is already completed
|
||||
if pool_stat.info.status == RebalStatus::Completed {
|
||||
warn!("check_if_rebalance_done: pool {} is already completed", pool_index);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -630,7 +690,8 @@ impl ECStore {
|
||||
// Mark pool rebalance as done if within 5% of the PercentFreeGoal
|
||||
if (pfi - meta.percent_free_goal).abs() <= 0.05 {
|
||||
pool_stat.info.status = RebalStatus::Completed;
|
||||
pool_stat.info.end_time = Some(SystemTime::now());
|
||||
pool_stat.info.end_time = Some(OffsetDateTime::now_utc());
|
||||
warn!("check_if_rebalance_done: pool {} is completed, pfi: {}", pool_index, pfi);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -640,24 +701,30 @@ impl ECStore {
|
||||
}
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
#[tracing::instrument(skip(self, wk, set))]
|
||||
#[tracing::instrument(skip(self, set))]
|
||||
async fn rebalance_entry(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
bucket: String,
|
||||
pool_index: usize,
|
||||
entry: MetaCacheEntry,
|
||||
set: Arc<SetDisks>,
|
||||
wk: Arc<Workers>,
|
||||
// wk: Arc<Workers>,
|
||||
) {
|
||||
defer!(|| async {
|
||||
wk.give().await;
|
||||
});
|
||||
warn!("rebalance_entry: start rebalance_entry");
|
||||
|
||||
// defer!(|| async {
|
||||
// warn!("rebalance_entry: defer give worker start");
|
||||
// wk.give().await;
|
||||
// warn!("rebalance_entry: defer give worker done");
|
||||
// });
|
||||
|
||||
if entry.is_dir() {
|
||||
warn!("rebalance_entry: entry is dir, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if self.check_if_rebalance_done(pool_index).await {
|
||||
warn!("rebalance_entry: rebalance done, skipping pool {}", pool_index);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -665,6 +732,7 @@ impl ECStore {
|
||||
Ok(fivs) => fivs,
|
||||
Err(err) => {
|
||||
error!("rebalance_entry Error getting file info versions: {}", err);
|
||||
warn!("rebalance_entry: Error getting file info versions, skipping");
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -675,7 +743,7 @@ impl ECStore {
|
||||
let expired: usize = 0;
|
||||
for version in fivs.versions.iter() {
|
||||
if version.is_remote() {
|
||||
info!("rebalance_entry Entry {} is remote, skipping", version.name);
|
||||
warn!("rebalance_entry Entry {} is remote, skipping", version.name);
|
||||
continue;
|
||||
}
|
||||
// TODO: filterLifecycle
|
||||
@@ -683,7 +751,7 @@ impl ECStore {
|
||||
let remaining_versions = fivs.versions.len() - expired;
|
||||
if version.deleted && remaining_versions == 1 {
|
||||
rebalanced += 1;
|
||||
info!("rebalance_entry Entry {} is deleted and last version, skipping", version.name);
|
||||
warn!("rebalance_entry Entry {} is deleted and last version, skipping", version.name);
|
||||
continue;
|
||||
}
|
||||
let version_id = version.version_id.map(|v| v.to_string());
|
||||
@@ -734,6 +802,7 @@ impl ECStore {
|
||||
}
|
||||
|
||||
for _i in 0..3 {
|
||||
warn!("rebalance_entry: get_object_reader, bucket: {}, version: {}", &bucket, &version.name);
|
||||
let rd = match set
|
||||
.get_object_reader(
|
||||
bucket.as_str(),
|
||||
@@ -752,6 +821,10 @@ impl ECStore {
|
||||
Err(err) => {
|
||||
if is_err_object_not_found(&err) || is_err_version_not_found(&err) {
|
||||
ignore = true;
|
||||
warn!(
|
||||
"rebalance_entry: get_object_reader, bucket: {}, version: {}, ignore",
|
||||
&bucket, &version.name
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -761,10 +834,10 @@ impl ECStore {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = self.rebalance_object(pool_index, bucket.clone(), rd).await {
|
||||
if let Err(err) = self.clone().rebalance_object(pool_index, bucket.clone(), rd).await {
|
||||
if is_err_object_not_found(&err) || is_err_version_not_found(&err) || is_err_data_movement_overwrite(&err) {
|
||||
ignore = true;
|
||||
info!("rebalance_entry {} Entry {} is already deleted, skipping", &bucket, version.name);
|
||||
warn!("rebalance_entry {} Entry {} is already deleted, skipping", &bucket, version.name);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -779,7 +852,7 @@ impl ECStore {
|
||||
}
|
||||
|
||||
if ignore {
|
||||
info!("rebalance_entry {} Entry {} is already deleted, skipping", &bucket, version.name);
|
||||
warn!("rebalance_entry {} Entry {} is already deleted, skipping", &bucket, version.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -811,13 +884,13 @@ impl ECStore {
|
||||
{
|
||||
error!("rebalance_entry: delete_object err {:?}", &err);
|
||||
} else {
|
||||
info!("rebalance_entry {} Entry {} deleted successfully", &bucket, &entry.name);
|
||||
warn!("rebalance_entry {} Entry {} deleted successfully", &bucket, &entry.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, rd))]
|
||||
async fn rebalance_object(&self, pool_idx: usize, bucket: String, rd: GetObjectReader) -> Result<()> {
|
||||
async fn rebalance_object(self: Arc<Self>, pool_idx: usize, bucket: String, rd: GetObjectReader) -> Result<()> {
|
||||
let object_info = rd.object_info.clone();
|
||||
|
||||
// TODO: check : use size or actual_size ?
|
||||
@@ -866,8 +939,7 @@ impl ECStore {
|
||||
reader.read_exact(&mut chunk).await?;
|
||||
|
||||
// 每次从 reader 中读取一个 part 上传
|
||||
let rd = Box::new(Cursor::new(chunk));
|
||||
let mut data = PutObjReader::new(rd, part.size);
|
||||
let mut data = PutObjReader::from_vec(chunk);
|
||||
|
||||
let pi = match self
|
||||
.put_object_part(
|
||||
@@ -877,7 +949,7 @@ impl ECStore {
|
||||
part.number,
|
||||
&mut data,
|
||||
&ObjectOptions {
|
||||
preserve_etag: part.e_tag.clone(),
|
||||
preserve_etag: Some(part.etag.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
@@ -892,11 +964,12 @@ impl ECStore {
|
||||
|
||||
parts[i] = CompletePart {
|
||||
part_num: pi.part_num,
|
||||
e_tag: pi.etag,
|
||||
etag: pi.etag,
|
||||
};
|
||||
}
|
||||
|
||||
if let Err(err) = self
|
||||
.clone()
|
||||
.complete_multipart_upload(
|
||||
&bucket,
|
||||
&object_info.name,
|
||||
@@ -917,7 +990,9 @@ impl ECStore {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut data = PutObjReader::new(rd.stream, object_info.size);
|
||||
let reader = BufReader::new(rd.stream);
|
||||
let hrd = HashReader::new(Box::new(WarpReader::new(reader)), object_info.size, object_info.size, None, false)?;
|
||||
let mut data = PutObjReader::new(hrd);
|
||||
|
||||
if let Err(err) = self
|
||||
.put_object(
|
||||
@@ -956,26 +1031,29 @@ impl ECStore {
|
||||
|
||||
let pool = self.pools[pool_index].clone();
|
||||
|
||||
let wk = Workers::new(pool.disk_set.len() * 2).map_err(|v| Error::from_string(v))?;
|
||||
let mut jobs = Vec::new();
|
||||
|
||||
// let wk = Workers::new(pool.disk_set.len() * 2).map_err(Error::other)?;
|
||||
// wk.clone().take().await;
|
||||
for (set_idx, set) in pool.disk_set.iter().enumerate() {
|
||||
wk.clone().take().await;
|
||||
|
||||
let rebalance_entry: ListCallback = Arc::new({
|
||||
let this = Arc::clone(self);
|
||||
let bucket = bucket.clone();
|
||||
let wk = wk.clone();
|
||||
// let wk = wk.clone();
|
||||
let set = set.clone();
|
||||
move |entry: MetaCacheEntry| {
|
||||
let this = this.clone();
|
||||
let bucket = bucket.clone();
|
||||
let wk = wk.clone();
|
||||
// let wk = wk.clone();
|
||||
let set = set.clone();
|
||||
Box::pin(async move {
|
||||
wk.take().await;
|
||||
tokio::spawn(async move {
|
||||
this.rebalance_entry(bucket, pool_index, entry, set, wk).await;
|
||||
});
|
||||
warn!("rebalance_entry: rebalance_entry spawn start");
|
||||
// wk.take().await;
|
||||
// tokio::spawn(async move {
|
||||
warn!("rebalance_entry: rebalance_entry spawn start2");
|
||||
this.rebalance_entry(bucket, pool_index, entry, set).await;
|
||||
warn!("rebalance_entry: rebalance_entry spawn done");
|
||||
// });
|
||||
})
|
||||
}
|
||||
});
|
||||
@@ -983,62 +1061,68 @@ impl ECStore {
|
||||
let set = set.clone();
|
||||
let rx = rx.resubscribe();
|
||||
let bucket = bucket.clone();
|
||||
let wk = wk.clone();
|
||||
tokio::spawn(async move {
|
||||
// let wk = wk.clone();
|
||||
|
||||
let job = tokio::spawn(async move {
|
||||
if let Err(err) = set.list_objects_to_rebalance(rx, bucket, rebalance_entry).await {
|
||||
error!("Rebalance worker {} error: {}", set_idx, err);
|
||||
} else {
|
||||
info!("Rebalance worker {} done", set_idx);
|
||||
}
|
||||
wk.clone().give().await;
|
||||
// wk.clone().give().await;
|
||||
});
|
||||
|
||||
jobs.push(job);
|
||||
}
|
||||
|
||||
wk.wait().await;
|
||||
// wk.wait().await;
|
||||
for job in jobs {
|
||||
job.await.unwrap();
|
||||
}
|
||||
warn!("rebalance_bucket: rebalance_bucket done");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn save_rebalance_stats(&self, pool_idx: usize, opt: RebalSaveOpt) -> Result<()> {
|
||||
// TODO: NSLOOK
|
||||
|
||||
// TODO: lock
|
||||
let mut meta = RebalanceMeta::new();
|
||||
meta.load_with_opts(
|
||||
self.pools[0].clone(),
|
||||
ObjectOptions {
|
||||
no_lock: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
if opt == RebalSaveOpt::StoppedAt {
|
||||
meta.stopped_at = Some(SystemTime::now());
|
||||
}
|
||||
|
||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||
|
||||
if let Some(rb) = rebalance_meta.as_mut() {
|
||||
if opt == RebalSaveOpt::Stats {
|
||||
meta.pool_stats[pool_idx] = rb.pool_stats[pool_idx].clone();
|
||||
if let Err(err) = meta.load(self.pools[0].clone()).await {
|
||||
if err != Error::ConfigNotFound {
|
||||
warn!("save_rebalance_stats: load err: {:?}", err);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
*rb = meta;
|
||||
} else {
|
||||
*rebalance_meta = Some(meta);
|
||||
}
|
||||
|
||||
if let Some(meta) = rebalance_meta.as_mut() {
|
||||
meta.save_with_opts(
|
||||
self.pools[0].clone(),
|
||||
ObjectOptions {
|
||||
no_lock: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
match opt {
|
||||
RebalSaveOpt::Stats => {
|
||||
{
|
||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||
if let Some(rbm) = rebalance_meta.as_mut() {
|
||||
meta.pool_stats[pool_idx] = rbm.pool_stats[pool_idx].clone();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pool_stat) = meta.pool_stats.get_mut(pool_idx) {
|
||||
pool_stat.info.end_time = Some(OffsetDateTime::now_utc());
|
||||
}
|
||||
}
|
||||
RebalSaveOpt::StoppedAt => {
|
||||
meta.stopped_at = Some(OffsetDateTime::now_utc());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut rebalance_meta = self.rebalance_meta.write().await;
|
||||
*rebalance_meta = Some(meta.clone());
|
||||
}
|
||||
|
||||
warn!(
|
||||
"save_rebalance_stats: save rebalance meta, pool_idx: {}, opt: {:?}, meta: {:?}",
|
||||
pool_idx, opt, meta
|
||||
);
|
||||
meta.save(self.pools[0].clone()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1051,12 +1135,15 @@ impl SetDisks {
|
||||
bucket: String,
|
||||
cb: ListCallback,
|
||||
) -> Result<()> {
|
||||
warn!("list_objects_to_rebalance: start list_objects_to_rebalance");
|
||||
// Placeholder for actual object listing logic
|
||||
let (disks, _) = self.get_online_disks_with_healing(false).await;
|
||||
if disks.is_empty() {
|
||||
return Err(Error::msg("errNoDiskAvailable"));
|
||||
warn!("list_objects_to_rebalance: no disk available");
|
||||
return Err(Error::other("errNoDiskAvailable"));
|
||||
}
|
||||
|
||||
warn!("list_objects_to_rebalance: get online disks with healing");
|
||||
let listing_quorum = self.set_drive_count.div_ceil(2);
|
||||
|
||||
let resolver = MetadataResolutionParams {
|
||||
@@ -1074,19 +1161,22 @@ impl SetDisks {
|
||||
bucket: bucket.clone(),
|
||||
recursice: true,
|
||||
min_disks: listing_quorum,
|
||||
agreed: Some(Box::new(move |entry: MetaCacheEntry| Box::pin(cb1(entry)))),
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
||||
agreed: Some(Box::new(move |entry: MetaCacheEntry| {
|
||||
warn!("list_objects_to_rebalance: agreed: {:?}", &entry.name);
|
||||
Box::pin(cb1(entry))
|
||||
})),
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<DiskError>]| {
|
||||
// let cb = cb.clone();
|
||||
let resolver = resolver.clone();
|
||||
let cb = cb.clone();
|
||||
|
||||
match entries.resolve(resolver) {
|
||||
Some(entry) => {
|
||||
warn!("rebalance: list_objects_to_decommission get {}", &entry.name);
|
||||
warn!("list_objects_to_rebalance: list_objects_to_decommission get {}", &entry.name);
|
||||
Box::pin(async move { cb(entry).await })
|
||||
}
|
||||
None => {
|
||||
warn!("rebalance: list_objects_to_decommission get none");
|
||||
warn!("list_objects_to_rebalance: list_objects_to_decommission get none");
|
||||
Box::pin(async {})
|
||||
}
|
||||
}
|
||||
@@ -1096,6 +1186,7 @@ impl SetDisks {
|
||||
)
|
||||
.await?;
|
||||
|
||||
warn!("list_objects_to_rebalance: list_objects_to_rebalance done");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
+879
-689
File diff suppressed because it is too large
Load Diff
+30
-29
@@ -1,16 +1,20 @@
|
||||
#![allow(clippy::map_entry)]
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::disk::error_reduce::count_errs;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{
|
||||
disk::{
|
||||
error::{is_unformatted_disk, DiskError},
|
||||
DiskAPI, DiskInfo, DiskOption, DiskStore,
|
||||
error::DiskError,
|
||||
format::{DistributionAlgoVersion, FormatV3},
|
||||
new_disk, DiskAPI, DiskInfo, DiskOption, DiskStore,
|
||||
new_disk,
|
||||
},
|
||||
endpoints::{Endpoints, PoolEndpoints},
|
||||
global::{is_dist_erasure, GLOBAL_LOCAL_DISK_SET_DRIVES},
|
||||
error::StorageError,
|
||||
global::{GLOBAL_LOCAL_DISK_SET_DRIVES, is_dist_erasure},
|
||||
heal::heal_commands::{
|
||||
HealOpts, DRIVE_STATE_CORRUPT, DRIVE_STATE_MISSING, DRIVE_STATE_OFFLINE, DRIVE_STATE_OK, HEAL_ITEM_METADATA,
|
||||
DRIVE_STATE_CORRUPT, DRIVE_STATE_MISSING, DRIVE_STATE_OFFLINE, DRIVE_STATE_OK, HEAL_ITEM_METADATA, HealOpts,
|
||||
},
|
||||
set_disk::SetDisks,
|
||||
store_api::{
|
||||
@@ -18,16 +22,14 @@ use crate::{
|
||||
ListMultipartsInfo, ListObjectVersionsInfo, ListObjectsV2Info, MakeBucketOptions, MultipartInfo, MultipartUploadResult,
|
||||
ObjectIO, ObjectInfo, ObjectOptions, ObjectToDelete, PartInfo, PutObjReader, StorageAPI,
|
||||
},
|
||||
store_err::StorageError,
|
||||
store_init::{check_format_erasure_values, get_format_erasure_in_quorum, load_format_erasure_all, save_format_file},
|
||||
utils::{hash, path::path_join_buf},
|
||||
};
|
||||
use common::error::{Error, Result};
|
||||
use common::globals::GLOBAL_Local_Node_Name;
|
||||
use futures::future::join_all;
|
||||
use http::HeaderMap;
|
||||
use lock::{namespace_lock::NsLockMap, new_lock_api, LockApi};
|
||||
use lock::{LockApi, namespace_lock::NsLockMap, new_lock_api};
|
||||
use madmin::heal_commands::{HealDriveInfo, HealResultItem};
|
||||
use rustfs_utils::{crc_hash, path::path_join_buf, sip_hash};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -122,7 +124,7 @@ impl Sets {
|
||||
}
|
||||
|
||||
let has_disk_id = disk.as_ref().unwrap().get_disk_id().await.unwrap_or_else(|err| {
|
||||
if is_unformatted_disk(&err) {
|
||||
if err == DiskError::UnformattedDisk {
|
||||
error!("get_disk_id err {:?}", err);
|
||||
} else {
|
||||
warn!("get_disk_id err {:?}", err);
|
||||
@@ -230,11 +232,9 @@ impl Sets {
|
||||
|
||||
fn get_hashed_set_index(&self, input: &str) -> usize {
|
||||
match self.distribution_algo {
|
||||
DistributionAlgoVersion::V1 => hash::crc_hash(input, self.disk_set.len()),
|
||||
DistributionAlgoVersion::V1 => crc_hash(input, self.disk_set.len()),
|
||||
|
||||
DistributionAlgoVersion::V2 | DistributionAlgoVersion::V3 => {
|
||||
hash::sip_hash(input, self.disk_set.len(), self.id.as_bytes())
|
||||
}
|
||||
DistributionAlgoVersion::V2 | DistributionAlgoVersion::V3 => sip_hash(input, self.disk_set.len(), self.id.as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,11 +452,11 @@ impl StorageAPI for Sets {
|
||||
return dst_set.put_object(dst_bucket, dst_object, put_object_reader, &put_opts).await;
|
||||
}
|
||||
|
||||
Err(Error::new(StorageError::InvalidArgument(
|
||||
Err(StorageError::InvalidArgument(
|
||||
src_bucket.to_owned(),
|
||||
src_object.to_owned(),
|
||||
"put_object_reader2 is none".to_owned(),
|
||||
)))
|
||||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -627,7 +627,7 @@ impl StorageAPI for Sets {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn complete_multipart_upload(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
bucket: &str,
|
||||
object: &str,
|
||||
upload_id: &str,
|
||||
@@ -705,9 +705,9 @@ impl StorageAPI for Sets {
|
||||
res.before.drives.push(v.clone());
|
||||
res.after.drives.push(v.clone());
|
||||
}
|
||||
if DiskError::UnformattedDisk.count_errs(&errs) == 0 {
|
||||
if count_errs(&errs, &DiskError::UnformattedDisk) == 0 {
|
||||
info!("disk formats success, NoHealRequired, errs: {:?}", errs);
|
||||
return Ok((res, Some(Error::new(DiskError::NoHealRequired))));
|
||||
return Ok((res, Some(StorageError::NoHealRequired)));
|
||||
}
|
||||
|
||||
// if !self.format.eq(&ref_format) {
|
||||
@@ -807,7 +807,7 @@ async fn _close_storage_disks(disks: &[Option<DiskStore>]) {
|
||||
async fn init_storage_disks_with_errors(
|
||||
endpoints: &Endpoints,
|
||||
opts: &DiskOption,
|
||||
) -> (Vec<Option<DiskStore>>, Vec<Option<Error>>) {
|
||||
) -> (Vec<Option<DiskStore>>, Vec<Option<DiskError>>) {
|
||||
// Bootstrap disks.
|
||||
// let disks = Arc::new(RwLock::new(vec![None; endpoints.as_ref().len()]));
|
||||
// let errs = Arc::new(RwLock::new(vec![None; endpoints.as_ref().len()]));
|
||||
@@ -856,20 +856,21 @@ async fn init_storage_disks_with_errors(
|
||||
(disks, errs)
|
||||
}
|
||||
|
||||
fn formats_to_drives_info(endpoints: &Endpoints, formats: &[Option<FormatV3>], errs: &[Option<Error>]) -> Vec<HealDriveInfo> {
|
||||
fn formats_to_drives_info(endpoints: &Endpoints, formats: &[Option<FormatV3>], errs: &[Option<DiskError>]) -> Vec<HealDriveInfo> {
|
||||
let mut before_drives = Vec::with_capacity(endpoints.as_ref().len());
|
||||
for (index, format) in formats.iter().enumerate() {
|
||||
let drive = endpoints.get_string(index);
|
||||
let state = if format.is_some() {
|
||||
DRIVE_STATE_OK
|
||||
} else {
|
||||
if let Some(Some(err)) = errs.get(index) {
|
||||
match err.downcast_ref::<DiskError>() {
|
||||
Some(DiskError::UnformattedDisk) => DRIVE_STATE_MISSING,
|
||||
Some(DiskError::DiskNotFound) => DRIVE_STATE_OFFLINE,
|
||||
_ => DRIVE_STATE_CORRUPT,
|
||||
};
|
||||
} else if let Some(Some(err)) = errs.get(index) {
|
||||
if *err == DiskError::UnformattedDisk {
|
||||
DRIVE_STATE_MISSING
|
||||
} else if *err == DiskError::DiskNotFound {
|
||||
DRIVE_STATE_OFFLINE
|
||||
} else {
|
||||
DRIVE_STATE_CORRUPT
|
||||
}
|
||||
} else {
|
||||
DRIVE_STATE_CORRUPT
|
||||
};
|
||||
|
||||
@@ -892,14 +893,14 @@ fn new_heal_format_sets(
|
||||
set_count: usize,
|
||||
set_drive_count: usize,
|
||||
formats: &[Option<FormatV3>],
|
||||
errs: &[Option<Error>],
|
||||
errs: &[Option<DiskError>],
|
||||
) -> (Vec<Vec<Option<FormatV3>>>, Vec<Vec<DiskInfo>>) {
|
||||
let mut new_formats = vec![vec![None; set_drive_count]; set_count];
|
||||
let mut current_disks_info = vec![vec![DiskInfo::default(); set_drive_count]; set_count];
|
||||
for (i, set) in ref_format.erasure.sets.iter().enumerate() {
|
||||
for j in 0..set.len() {
|
||||
if let Some(Some(err)) = errs.get(i * set_drive_count + j) {
|
||||
if let Some(DiskError::UnformattedDisk) = err.downcast_ref::<DiskError>() {
|
||||
if *err == DiskError::UnformattedDisk {
|
||||
let mut fm = FormatV3::new(set_count, set_drive_count);
|
||||
fm.id = ref_format.id;
|
||||
fm.format = ref_format.format.clone();
|
||||
|
||||
+138
-152
@@ -2,36 +2,32 @@
|
||||
|
||||
use crate::bucket::metadata_sys::{self, set_bucket_metadata};
|
||||
use crate::bucket::utils::{check_valid_bucket_name, check_valid_bucket_name_strict, is_meta_bucketname};
|
||||
use crate::config::storageclass;
|
||||
use crate::config::GLOBAL_StorageClass;
|
||||
use crate::config::storageclass;
|
||||
use crate::disk::endpoint::{Endpoint, EndpointType};
|
||||
use crate::disk::{DiskAPI, DiskInfo, DiskInfoOptions, MetaCacheEntry};
|
||||
use crate::error::clone_err;
|
||||
use crate::global::{
|
||||
get_global_endpoints, is_dist_erasure, is_erasure_sd, set_global_deployment_id, set_object_layer, DISK_ASSUME_UNKNOWN_SIZE,
|
||||
DISK_FILL_FRACTION, DISK_MIN_INODES, DISK_RESERVE_FRACTION, GLOBAL_BOOT_TIME, GLOBAL_LOCAL_DISK_MAP,
|
||||
GLOBAL_LOCAL_DISK_SET_DRIVES,
|
||||
use crate::disk::{DiskAPI, DiskInfo, DiskInfoOptions};
|
||||
use crate::error::{
|
||||
StorageError, is_err_bucket_exists, is_err_invalid_upload_id, is_err_object_not_found, is_err_read_quorum,
|
||||
is_err_version_not_found, to_object_err,
|
||||
};
|
||||
use crate::heal::data_usage::{DataUsageInfo, DATA_USAGE_ROOT};
|
||||
use crate::global::{
|
||||
DISK_ASSUME_UNKNOWN_SIZE, DISK_FILL_FRACTION, DISK_MIN_INODES, DISK_RESERVE_FRACTION, GLOBAL_BOOT_TIME,
|
||||
GLOBAL_LOCAL_DISK_MAP, GLOBAL_LOCAL_DISK_SET_DRIVES, get_global_endpoints, is_dist_erasure, is_erasure_sd,
|
||||
set_global_deployment_id, set_object_layer,
|
||||
};
|
||||
use crate::heal::data_usage::{DATA_USAGE_ROOT, DataUsageInfo};
|
||||
use crate::heal::data_usage_cache::{DataUsageCache, DataUsageCacheInfo};
|
||||
use crate::heal::heal_commands::{HealOpts, HealScanMode, HEAL_ITEM_METADATA};
|
||||
use crate::heal::heal_commands::{HEAL_ITEM_METADATA, HealOpts, HealScanMode};
|
||||
use crate::heal::heal_ops::{HealEntryFn, HealSequence};
|
||||
use crate::new_object_layer_fn;
|
||||
use crate::notification_sys::get_global_notification_sys;
|
||||
use crate::pools::PoolMeta;
|
||||
use crate::rebalance::RebalanceMeta;
|
||||
use crate::store_api::{ListMultipartsInfo, ListObjectVersionsInfo, MultipartInfo, ObjectIO};
|
||||
use crate::store_err::{
|
||||
is_err_bucket_exists, is_err_decommission_already_running, is_err_invalid_upload_id, is_err_object_not_found,
|
||||
is_err_read_quorum, is_err_version_not_found, to_object_err, StorageError,
|
||||
};
|
||||
use crate::store_init::ec_drives_no_config;
|
||||
use crate::utils::crypto::base64_decode;
|
||||
use crate::utils::path::{decode_dir_object, encode_dir_object, path_join_buf, SLASH_SEPARATOR};
|
||||
use crate::utils::xml;
|
||||
use crate::store_init::{check_disk_fatal_errs, ec_drives_no_config};
|
||||
use crate::{
|
||||
bucket::metadata::BucketMetadata,
|
||||
disk::{error::DiskError, new_disk, DiskOption, DiskStore, BUCKET_META_PREFIX, RUSTFS_META_BUCKET},
|
||||
disk::{BUCKET_META_PREFIX, DiskOption, DiskStore, RUSTFS_META_BUCKET, new_disk},
|
||||
endpoints::EndpointServerPools,
|
||||
peer::S3PeerSys,
|
||||
sets::Sets,
|
||||
@@ -42,15 +38,18 @@ use crate::{
|
||||
},
|
||||
store_init,
|
||||
};
|
||||
use rustfs_utils::crypto::base64_decode;
|
||||
use rustfs_utils::path::{SLASH_SEPARATOR, decode_dir_object, encode_dir_object, path_join_buf};
|
||||
|
||||
use common::error::{Error, Result};
|
||||
use crate::error::{Error, Result};
|
||||
use common::globals::{GLOBAL_Local_Node_Name, GLOBAL_Rustfs_Host, GLOBAL_Rustfs_Port};
|
||||
use futures::future::join_all;
|
||||
use glob::Pattern;
|
||||
use http::HeaderMap;
|
||||
use lazy_static::lazy_static;
|
||||
use madmin::heal_commands::HealResultItem;
|
||||
use rand::Rng;
|
||||
use rand::Rng as _;
|
||||
use rustfs_filemeta::MetaCacheEntry;
|
||||
use s3s::dto::{BucketVersioningStatus, ObjectLockConfiguration, ObjectLockEnabled, VersioningConfiguration};
|
||||
use std::cmp::Ordering;
|
||||
use std::net::SocketAddr;
|
||||
@@ -61,10 +60,10 @@ use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::select;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::sync::{broadcast, mpsc, RwLock};
|
||||
use tokio::sync::{RwLock, broadcast, mpsc};
|
||||
use tokio::time::{interval, sleep};
|
||||
use tracing::error;
|
||||
use tracing::{debug, info};
|
||||
use tracing::{error, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
const MAX_UPLOADS_LIST: usize = 10000;
|
||||
@@ -150,7 +149,7 @@ impl ECStore {
|
||||
)
|
||||
.await;
|
||||
|
||||
DiskError::check_disk_fatal_errs(&errs)?;
|
||||
check_disk_fatal_errs(&errs)?;
|
||||
|
||||
let fm = {
|
||||
let mut times = 0;
|
||||
@@ -172,7 +171,7 @@ impl ECStore {
|
||||
interval *= 2;
|
||||
}
|
||||
if times > 10 {
|
||||
return Err(Error::from_string("can not get formats"));
|
||||
return Err(Error::other("can not get formats"));
|
||||
}
|
||||
info!("retrying get formats after {:?}", interval);
|
||||
select! {
|
||||
@@ -191,7 +190,7 @@ impl ECStore {
|
||||
}
|
||||
|
||||
if deployment_id != Some(fm.id) {
|
||||
return Err(Error::msg("deployment_id not same in one pool"));
|
||||
return Err(Error::other("deployment_id not same in one pool"));
|
||||
}
|
||||
|
||||
if deployment_id.is_some() && deployment_id.unwrap().is_nil() {
|
||||
@@ -247,7 +246,7 @@ impl ECStore {
|
||||
sleep(Duration::from_secs(wait_sec)).await;
|
||||
|
||||
if exit_count > 10 {
|
||||
return Err(Error::msg("ec init faild"));
|
||||
return Err(Error::other("ec init failed"));
|
||||
}
|
||||
|
||||
exit_count += 1;
|
||||
@@ -297,7 +296,7 @@ impl ECStore {
|
||||
if let Some(idx) = endpoints.get_pool_idx(&p.cmd_line) {
|
||||
pool_indeces.push(idx);
|
||||
} else {
|
||||
return Err(Error::msg(format!(
|
||||
return Err(Error::other(format!(
|
||||
"unexpected state present for decommission status pool({}) not found",
|
||||
p.cmd_line
|
||||
)));
|
||||
@@ -316,7 +315,7 @@ impl ECStore {
|
||||
tokio::time::sleep(Duration::from_secs(60 * 3)).await;
|
||||
|
||||
if let Err(err) = store.decommission(rx.resubscribe(), pool_indeces.clone()).await {
|
||||
if is_err_decommission_already_running(&err) {
|
||||
if err == StorageError::DecommissionAlreadyRunning {
|
||||
for i in pool_indeces.iter() {
|
||||
store.do_decommission_in_routine(rx.resubscribe(), *i).await;
|
||||
}
|
||||
@@ -347,7 +346,7 @@ impl ECStore {
|
||||
// define in store_list_objects.rs
|
||||
// pub async fn list_path(&self, opts: &ListPathOptions, delimiter: &str) -> Result<ListObjectsInfo> {
|
||||
// // if opts.prefix.ends_with(SLASH_SEPARATOR) {
|
||||
// // return Err(Error::msg("eof"));
|
||||
// // return Err(Error::other("eof"));
|
||||
// // }
|
||||
|
||||
// let mut opts = opts.clone();
|
||||
@@ -620,7 +619,7 @@ impl ECStore {
|
||||
if let Some(hit_idx) = self.get_available_pool_idx(bucket, object, size).await {
|
||||
hit_idx
|
||||
} else {
|
||||
return Err(Error::new(DiskError::DiskFull));
|
||||
return Err(Error::DiskFull);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -639,7 +638,8 @@ impl ECStore {
|
||||
if let Some(idx) = self.get_available_pool_idx(bucket, object, size).await {
|
||||
idx
|
||||
} else {
|
||||
return Err(to_object_err(Error::new(DiskError::DiskFull), vec![bucket, object]));
|
||||
warn!("get_pool_idx_no_lock: disk full {}/{}", bucket, object);
|
||||
return Err(Error::DiskFull);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -737,7 +737,7 @@ impl ECStore {
|
||||
|
||||
let err = pinfo.err.as_ref().unwrap();
|
||||
|
||||
if is_err_read_quorum(err) && !opts.metadata_chg {
|
||||
if err == &Error::ErasureReadQuorum && !opts.metadata_chg {
|
||||
return Ok((pinfo.clone(), self.pools_with_object(&ress, opts).await));
|
||||
}
|
||||
|
||||
@@ -745,7 +745,7 @@ impl ECStore {
|
||||
has_def_pool = true;
|
||||
|
||||
if !is_err_object_not_found(err) && !is_err_version_not_found(err) {
|
||||
return Err(clone_err(err));
|
||||
return Err(err.clone());
|
||||
}
|
||||
|
||||
if pinfo.object_info.delete_marker && !pinfo.object_info.name.is_empty() {
|
||||
@@ -757,7 +757,7 @@ impl ECStore {
|
||||
return Ok((def_pool, Vec::new()));
|
||||
}
|
||||
|
||||
Err(to_object_err(Error::new(DiskError::FileNotFound), vec![bucket, object]))
|
||||
Err(Error::ObjectNotFound(bucket.to_owned(), object.to_owned()))
|
||||
}
|
||||
|
||||
async fn pools_with_object(&self, pools: &[PoolObjInfo], opts: &ObjectOptions) -> Vec<PoolErr> {
|
||||
@@ -773,10 +773,10 @@ impl ECStore {
|
||||
}
|
||||
|
||||
if let Some(err) = &pool.err {
|
||||
if is_err_read_quorum(err) {
|
||||
if err == &Error::ErasureReadQuorum {
|
||||
errs.push(PoolErr {
|
||||
index: Some(pool.index),
|
||||
err: Some(Error::new(StorageError::InsufficientReadQuorum)),
|
||||
err: Some(Error::ErasureReadQuorum),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -853,9 +853,26 @@ impl ECStore {
|
||||
let (update_closer_tx, mut update_close_rx) = mpsc::channel(10);
|
||||
let mut ctx_clone = cancel.subscribe();
|
||||
let all_buckets_clone = all_buckets.clone();
|
||||
// 新增:从环境变量读取interval,默认30秒
|
||||
let ns_scanner_interval_secs = std::env::var("RUSTFS_NS_SCANNER_INTERVAL")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.unwrap_or(30);
|
||||
|
||||
// 检查是否跳过后台任务
|
||||
let skip_background_task = std::env::var("RUSTFS_SKIP_BACKGROUND_TASK")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<bool>().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
if skip_background_task {
|
||||
info!("跳过后台任务执行: RUSTFS_SKIP_BACKGROUND_TASK=true");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let task = tokio::spawn(async move {
|
||||
let mut last_update: Option<SystemTime> = None;
|
||||
let mut interval = interval(Duration::from_secs(30));
|
||||
let mut interval = interval(Duration::from_secs(ns_scanner_interval_secs));
|
||||
let all_merged = Arc::new(RwLock::new(DataUsageCache::default()));
|
||||
loop {
|
||||
select! {
|
||||
@@ -884,7 +901,7 @@ impl ECStore {
|
||||
}
|
||||
let _ = task.await;
|
||||
if let Some(err) = first_err.read().await.as_ref() {
|
||||
return Err(clone_err(err));
|
||||
return Err(err.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -967,13 +984,13 @@ impl ECStore {
|
||||
let object = decode_dir_object(object);
|
||||
|
||||
if opts.version_id.is_none() {
|
||||
Err(Error::new(StorageError::ObjectNotFound(bucket.to_owned(), object.to_owned())))
|
||||
Err(StorageError::ObjectNotFound(bucket.to_owned(), object.to_owned()))
|
||||
} else {
|
||||
Err(Error::new(StorageError::VersionNotFound(
|
||||
Err(StorageError::VersionNotFound(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
opts.version_id.clone().unwrap_or_default(),
|
||||
)))
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -989,9 +1006,9 @@ impl ECStore {
|
||||
|
||||
for pe in errs.iter() {
|
||||
if let Some(err) = &pe.err {
|
||||
if is_err_read_quorum(err) {
|
||||
if err == &StorageError::ErasureWriteQuorum {
|
||||
objs.push(None);
|
||||
derrs.push(Some(Error::new(StorageError::InsufficientWriteQuorum)));
|
||||
derrs.push(Some(StorageError::ErasureWriteQuorum));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1012,7 +1029,7 @@ impl ECStore {
|
||||
}
|
||||
|
||||
if let Some(e) = &derrs[0] {
|
||||
return Err(clone_err(e));
|
||||
return Err(e.clone());
|
||||
}
|
||||
|
||||
Ok(objs[0].as_ref().unwrap().clone())
|
||||
@@ -1148,7 +1165,7 @@ impl Clone for PoolObjInfo {
|
||||
Self {
|
||||
index: self.index,
|
||||
object_info: self.object_info.clone(),
|
||||
err: self.err.as_ref().map(clone_err),
|
||||
err: self.err.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1222,14 +1239,14 @@ impl ObjectIO for ECStore {
|
||||
return self.pools[0].put_object(bucket, object.as_str(), data, opts).await;
|
||||
}
|
||||
|
||||
let idx = self.get_pool_idx(bucket, &object, data.content_length as i64).await?;
|
||||
let idx = self.get_pool_idx(bucket, &object, data.size()).await?;
|
||||
|
||||
if opts.data_movement && idx == opts.src_pool_idx {
|
||||
return Err(Error::new(StorageError::DataMovementOverwriteErr(
|
||||
return Err(StorageError::DataMovementOverwriteErr(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
opts.version_id.clone().unwrap_or_default(),
|
||||
)));
|
||||
));
|
||||
}
|
||||
|
||||
self.pools[idx].put_object(bucket, &object, data, opts).await
|
||||
@@ -1326,14 +1343,14 @@ impl StorageAPI for ECStore {
|
||||
async fn make_bucket(&self, bucket: &str, opts: &MakeBucketOptions) -> Result<()> {
|
||||
if !is_meta_bucketname(bucket) {
|
||||
if let Err(err) = check_valid_bucket_name_strict(bucket) {
|
||||
return Err(StorageError::BucketNameInvalid(err.to_string()).into());
|
||||
return Err(StorageError::BucketNameInvalid(err.to_string()));
|
||||
}
|
||||
|
||||
// TODO: nslock
|
||||
}
|
||||
|
||||
if let Err(err) = self.peer_sys.make_bucket(bucket, opts).await {
|
||||
if !is_err_bucket_exists(&err) {
|
||||
if !is_err_bucket_exists(&err.into()) {
|
||||
let _ = self
|
||||
.delete_bucket(
|
||||
bucket,
|
||||
@@ -1352,15 +1369,15 @@ impl StorageAPI for ECStore {
|
||||
meta.set_created(opts.created_at);
|
||||
|
||||
if opts.lock_enabled {
|
||||
meta.object_lock_config_xml = xml::serialize::<ObjectLockConfiguration>(&enableObjcetLockConfig)?;
|
||||
meta.versioning_config_xml = xml::serialize::<VersioningConfiguration>(&enableVersioningConfig)?;
|
||||
meta.object_lock_config_xml = crate::bucket::utils::serialize::<ObjectLockConfiguration>(&enableObjcetLockConfig)?;
|
||||
meta.versioning_config_xml = crate::bucket::utils::serialize::<VersioningConfiguration>(&enableVersioningConfig)?;
|
||||
}
|
||||
|
||||
if opts.versioning_enabled {
|
||||
meta.versioning_config_xml = xml::serialize::<VersioningConfiguration>(&enableVersioningConfig)?;
|
||||
meta.versioning_config_xml = crate::bucket::utils::serialize::<VersioningConfiguration>(&enableVersioningConfig)?;
|
||||
}
|
||||
|
||||
meta.save().await.map_err(|e| to_object_err(e, vec![bucket]))?;
|
||||
meta.save().await?;
|
||||
|
||||
set_bucket_metadata(bucket.to_string(), meta).await?;
|
||||
|
||||
@@ -1369,11 +1386,7 @@ impl StorageAPI for ECStore {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn get_bucket_info(&self, bucket: &str, opts: &BucketOptions) -> Result<BucketInfo> {
|
||||
let mut info = self
|
||||
.peer_sys
|
||||
.get_bucket_info(bucket, opts)
|
||||
.await
|
||||
.map_err(|e| to_object_err(e, vec![bucket]))?;
|
||||
let mut info = self.peer_sys.get_bucket_info(bucket, opts).await?;
|
||||
|
||||
if let Ok(sys) = metadata_sys::get(bucket).await {
|
||||
info.created = Some(sys.created);
|
||||
@@ -1401,11 +1414,11 @@ impl StorageAPI for ECStore {
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_bucket(&self, bucket: &str, opts: &DeleteBucketOptions) -> Result<()> {
|
||||
if is_meta_bucketname(bucket) {
|
||||
return Err(StorageError::BucketNameInvalid(bucket.to_string()).into());
|
||||
return Err(StorageError::BucketNameInvalid(bucket.to_string()));
|
||||
}
|
||||
|
||||
if let Err(err) = check_valid_bucket_name(bucket) {
|
||||
return Err(StorageError::BucketNameInvalid(err.to_string()).into());
|
||||
return Err(StorageError::BucketNameInvalid(err.to_string()));
|
||||
}
|
||||
|
||||
// TODO: nslock
|
||||
@@ -1419,7 +1432,7 @@ impl StorageAPI for ECStore {
|
||||
self.peer_sys
|
||||
.delete_bucket(bucket, &opts)
|
||||
.await
|
||||
.map_err(|e| to_object_err(e, vec![bucket]))?;
|
||||
.map_err(|e| to_object_err(e.into(), vec![bucket]))?;
|
||||
|
||||
// TODO: replication opts.srdelete_op
|
||||
|
||||
@@ -1501,9 +1514,7 @@ impl StorageAPI for ECStore {
|
||||
|
||||
// TODO: nslock
|
||||
|
||||
let pool_idx = self
|
||||
.get_pool_idx_no_lock(src_bucket, &src_object, src_info.size as i64)
|
||||
.await?;
|
||||
let pool_idx = self.get_pool_idx_no_lock(src_bucket, &src_object, src_info.size).await?;
|
||||
|
||||
if cp_src_dst_same {
|
||||
if let (Some(src_vid), Some(dst_vid)) = (&src_opts.version_id, &dst_opts.version_id) {
|
||||
@@ -1543,11 +1554,11 @@ impl StorageAPI for ECStore {
|
||||
.await;
|
||||
}
|
||||
|
||||
Err(Error::new(StorageError::InvalidArgument(
|
||||
Err(StorageError::InvalidArgument(
|
||||
src_bucket.to_owned(),
|
||||
src_object.to_owned(),
|
||||
"put_object_reader is none".to_owned(),
|
||||
)))
|
||||
))
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_object(&self, bucket: &str, object: &str, opts: ObjectOptions) -> Result<ObjectInfo> {
|
||||
@@ -1569,7 +1580,7 @@ impl StorageAPI for ECStore {
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if is_err_read_quorum(&e) {
|
||||
Error::new(StorageError::InsufficientWriteQuorum)
|
||||
StorageError::ErasureWriteQuorum
|
||||
} else {
|
||||
e
|
||||
}
|
||||
@@ -1581,11 +1592,11 @@ impl StorageAPI for ECStore {
|
||||
}
|
||||
|
||||
if opts.data_movement && opts.src_pool_idx == pinfo.index {
|
||||
return Err(Error::new(StorageError::DataMovementOverwriteErr(
|
||||
return Err(StorageError::DataMovementOverwriteErr(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
opts.version_id.unwrap_or_default(),
|
||||
)));
|
||||
));
|
||||
}
|
||||
|
||||
if opts.data_movement {
|
||||
@@ -1614,10 +1625,10 @@ impl StorageAPI for ECStore {
|
||||
}
|
||||
|
||||
if let Some(ver) = opts.version_id {
|
||||
return Err(Error::new(StorageError::VersionNotFound(bucket.to_owned(), object.to_owned(), ver)));
|
||||
return Err(StorageError::VersionNotFound(bucket.to_owned(), object.to_owned(), ver));
|
||||
}
|
||||
|
||||
Err(Error::new(StorageError::ObjectNotFound(bucket.to_owned(), object.to_owned())))
|
||||
Err(StorageError::ObjectNotFound(bucket.to_owned(), object.to_owned()))
|
||||
}
|
||||
// TODO: review
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -1849,11 +1860,11 @@ impl StorageAPI for ECStore {
|
||||
|
||||
let idx = self.get_pool_idx(bucket, object, -1).await?;
|
||||
if opts.data_movement && idx == opts.src_pool_idx {
|
||||
return Err(Error::new(StorageError::DataMovementOverwriteErr(
|
||||
return Err(StorageError::DataMovementOverwriteErr(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
"".to_owned(),
|
||||
)));
|
||||
));
|
||||
}
|
||||
|
||||
self.pools[idx].new_multipart_upload(bucket, object, opts).await
|
||||
@@ -1920,11 +1931,7 @@ impl StorageAPI for ECStore {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::new(StorageError::InvalidUploadID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
upload_id.to_owned(),
|
||||
)))
|
||||
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -1957,11 +1964,7 @@ impl StorageAPI for ECStore {
|
||||
};
|
||||
}
|
||||
|
||||
Err(Error::new(StorageError::InvalidUploadID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
upload_id.to_owned(),
|
||||
)))
|
||||
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn abort_multipart_upload(&self, bucket: &str, object: &str, upload_id: &str, opts: &ObjectOptions) -> Result<()> {
|
||||
@@ -1982,11 +1985,7 @@ impl StorageAPI for ECStore {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(err) => {
|
||||
//
|
||||
if is_err_invalid_upload_id(&err) {
|
||||
None
|
||||
} else {
|
||||
Some(err)
|
||||
}
|
||||
if is_err_invalid_upload_id(&err) { None } else { Some(err) }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1995,16 +1994,12 @@ impl StorageAPI for ECStore {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::new(StorageError::InvalidUploadID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
upload_id.to_owned(),
|
||||
)))
|
||||
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn complete_multipart_upload(
|
||||
&self,
|
||||
self: Arc<Self>,
|
||||
bucket: &str,
|
||||
object: &str,
|
||||
upload_id: &str,
|
||||
@@ -2015,6 +2010,7 @@ impl StorageAPI for ECStore {
|
||||
|
||||
if self.single_pool() {
|
||||
return self.pools[0]
|
||||
.clone()
|
||||
.complete_multipart_upload(bucket, object, upload_id, uploaded_parts, opts)
|
||||
.await;
|
||||
}
|
||||
@@ -2024,6 +2020,7 @@ impl StorageAPI for ECStore {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pool = pool.clone();
|
||||
let err = match pool
|
||||
.complete_multipart_upload(bucket, object, upload_id, uploaded_parts.clone(), opts)
|
||||
.await
|
||||
@@ -2031,11 +2028,7 @@ impl StorageAPI for ECStore {
|
||||
Ok(res) => return Ok(res),
|
||||
Err(err) => {
|
||||
//
|
||||
if is_err_invalid_upload_id(&err) {
|
||||
None
|
||||
} else {
|
||||
Some(err)
|
||||
}
|
||||
if is_err_invalid_upload_id(&err) { None } else { Some(err) }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2044,11 +2037,7 @@ impl StorageAPI for ECStore {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::new(StorageError::InvalidUploadID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
upload_id.to_owned(),
|
||||
)))
|
||||
Err(StorageError::InvalidUploadID(bucket.to_owned(), object.to_owned(), upload_id.to_owned()))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -2056,7 +2045,7 @@ impl StorageAPI for ECStore {
|
||||
if pool_idx < self.pools.len() && set_idx < self.pools[pool_idx].disk_set.len() {
|
||||
self.pools[pool_idx].disk_set[set_idx].get_disks(0, 0).await
|
||||
} else {
|
||||
Err(Error::msg(format!("pool idx {}, set idx {}, not found", pool_idx, set_idx)))
|
||||
Err(Error::other(format!("pool idx {}, set idx {}, not found", pool_idx, set_idx)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2135,8 +2124,8 @@ impl StorageAPI for ECStore {
|
||||
for pool in self.pools.iter() {
|
||||
let (mut result, err) = pool.heal_format(dry_run).await?;
|
||||
if let Some(err) = err {
|
||||
match err.downcast_ref::<DiskError>() {
|
||||
Some(DiskError::NoHealRequired) => {
|
||||
match err {
|
||||
StorageError::NoHealRequired => {
|
||||
count_no_heal += 1;
|
||||
}
|
||||
_ => {
|
||||
@@ -2151,7 +2140,7 @@ impl StorageAPI for ECStore {
|
||||
}
|
||||
if count_no_heal == self.pools.len() {
|
||||
info!("heal format success, NoHealRequired");
|
||||
return Ok((r, Some(Error::new(DiskError::NoHealRequired))));
|
||||
return Ok((r, Some(StorageError::NoHealRequired)));
|
||||
}
|
||||
info!("heal format success result: {:?}", r);
|
||||
Ok((r, None))
|
||||
@@ -2159,7 +2148,9 @@ impl StorageAPI for ECStore {
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn heal_bucket(&self, bucket: &str, opts: &HealOpts) -> Result<HealResultItem> {
|
||||
self.peer_sys.heal_bucket(bucket, opts).await
|
||||
let res = self.peer_sys.heal_bucket(bucket, opts).await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn heal_object(
|
||||
@@ -2218,10 +2209,12 @@ impl StorageAPI for ECStore {
|
||||
// No pool returned a nil error, return the first non 'not found' error
|
||||
for (index, err) in errs.iter().enumerate() {
|
||||
match err {
|
||||
Some(err) => match err.downcast_ref::<DiskError>() {
|
||||
Some(DiskError::FileNotFound) | Some(DiskError::FileVersionNotFound) => {}
|
||||
_ => return Ok((ress.remove(index), Some(clone_err(err)))),
|
||||
},
|
||||
Some(err) => {
|
||||
if is_err_object_not_found(err) || is_err_version_not_found(err) {
|
||||
continue;
|
||||
}
|
||||
return Ok((ress.remove(index), Some(err.clone())));
|
||||
}
|
||||
None => {
|
||||
return Ok((ress.remove(index), None));
|
||||
}
|
||||
@@ -2230,10 +2223,10 @@ impl StorageAPI for ECStore {
|
||||
|
||||
// At this stage, all errors are 'not found'
|
||||
if !version_id.is_empty() {
|
||||
return Ok((HealResultItem::default(), Some(Error::new(DiskError::FileVersionNotFound))));
|
||||
return Ok((HealResultItem::default(), Some(Error::FileVersionNotFound)));
|
||||
}
|
||||
|
||||
Ok((HealResultItem::default(), Some(Error::new(DiskError::FileNotFound))))
|
||||
Ok((HealResultItem::default(), Some(Error::FileNotFound)))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -2272,12 +2265,14 @@ impl StorageAPI for ECStore {
|
||||
HealSequence::heal_meta_object(hs_clone.clone(), &bucket, &entry.name, "", scan_mode).await
|
||||
} else {
|
||||
HealSequence::heal_object(hs_clone.clone(), &bucket, &entry.name, "", scan_mode).await
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if opts_clone.remove && !opts_clone.dry_run {
|
||||
let Some(store) = new_object_layer_fn() else { return Err(Error::msg("errServerNotInitialized")) };
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(Error::other("errServerNotInitialized"));
|
||||
};
|
||||
|
||||
if let Err(err) = store.check_abandoned_parts(&bucket, &entry.name, &opts_clone).await {
|
||||
info!("unable to check object {}/{} for abandoned data: {}", bucket, entry.name, err.to_string());
|
||||
@@ -2294,8 +2289,8 @@ impl StorageAPI for ECStore {
|
||||
)
|
||||
.await
|
||||
{
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::FileNotFound) | Some(DiskError::FileVersionNotFound) => {}
|
||||
match err {
|
||||
Error::FileNotFound | Error::FileVersionNotFound => {}
|
||||
_ => {
|
||||
return Err(err);
|
||||
}
|
||||
@@ -2310,8 +2305,8 @@ impl StorageAPI for ECStore {
|
||||
)
|
||||
.await
|
||||
{
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::FileNotFound) | Some(DiskError::FileVersionNotFound) => {}
|
||||
match err {
|
||||
Error::FileNotFound | Error::FileVersionNotFound => {}
|
||||
_ => {
|
||||
return Err(err);
|
||||
}
|
||||
@@ -2360,7 +2355,7 @@ impl StorageAPI for ECStore {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::new(DiskError::DiskNotFound))
|
||||
Err(Error::DiskNotFound)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
@@ -2379,7 +2374,7 @@ impl StorageAPI for ECStore {
|
||||
}
|
||||
|
||||
if !errs.is_empty() {
|
||||
return Err(clone_err(&errs[0]));
|
||||
return Err(errs[0].clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -2423,11 +2418,11 @@ fn is_valid_object_name(object: &str) -> bool {
|
||||
|
||||
fn check_object_name_for_length_and_slash(bucket: &str, object: &str) -> Result<()> {
|
||||
if object.len() > 1024 {
|
||||
return Err(Error::new(StorageError::ObjectNameTooLong(bucket.to_owned(), object.to_owned())));
|
||||
return Err(StorageError::ObjectNameTooLong(bucket.to_owned(), object.to_owned()));
|
||||
}
|
||||
|
||||
if object.starts_with(SLASH_SEPARATOR) {
|
||||
return Err(Error::new(StorageError::ObjectNamePrefixAsSlash(bucket.to_owned(), object.to_owned())));
|
||||
return Err(StorageError::ObjectNamePrefixAsSlash(bucket.to_owned(), object.to_owned()));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -2441,7 +2436,7 @@ fn check_object_name_for_length_and_slash(bucket: &str, object: &str) -> Result<
|
||||
|| object.contains('<')
|
||||
|| object.contains('>')
|
||||
{
|
||||
return Err(Error::new(StorageError::ObjectNameInvalid(bucket.to_owned(), object.to_owned())));
|
||||
return Err(StorageError::ObjectNameInvalid(bucket.to_owned(), object.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2462,19 +2457,19 @@ fn check_del_obj_args(bucket: &str, object: &str) -> Result<()> {
|
||||
|
||||
fn check_bucket_and_object_names(bucket: &str, object: &str) -> Result<()> {
|
||||
if !is_meta_bucketname(bucket) && check_valid_bucket_name_strict(bucket).is_err() {
|
||||
return Err(Error::new(StorageError::BucketNameInvalid(bucket.to_string())));
|
||||
return Err(StorageError::BucketNameInvalid(bucket.to_string()));
|
||||
}
|
||||
|
||||
if object.is_empty() {
|
||||
return Err(Error::new(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string())));
|
||||
return Err(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string()));
|
||||
}
|
||||
|
||||
if !is_valid_object_prefix(object) {
|
||||
return Err(Error::new(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string())));
|
||||
return Err(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string()));
|
||||
}
|
||||
|
||||
if cfg!(target_os = "windows") && object.contains('\\') {
|
||||
return Err(Error::new(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string())));
|
||||
return Err(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -2482,11 +2477,11 @@ fn check_bucket_and_object_names(bucket: &str, object: &str) -> Result<()> {
|
||||
|
||||
pub fn check_list_objs_args(bucket: &str, prefix: &str, _marker: &Option<String>) -> Result<()> {
|
||||
if !is_meta_bucketname(bucket) && check_valid_bucket_name_strict(bucket).is_err() {
|
||||
return Err(Error::new(StorageError::BucketNameInvalid(bucket.to_string())));
|
||||
return Err(StorageError::BucketNameInvalid(bucket.to_string()));
|
||||
}
|
||||
|
||||
if !is_valid_object_prefix(prefix) {
|
||||
return Err(Error::new(StorageError::ObjectNameInvalid(bucket.to_string(), prefix.to_string())));
|
||||
return Err(StorageError::ObjectNameInvalid(bucket.to_string(), prefix.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -2504,15 +2499,15 @@ fn check_list_multipart_args(
|
||||
if let Some(upload_id_marker) = upload_id_marker {
|
||||
if let Some(key_marker) = key_marker {
|
||||
if key_marker.ends_with('/') {
|
||||
return Err(Error::new(StorageError::InvalidUploadIDKeyCombination(
|
||||
return Err(StorageError::InvalidUploadIDKeyCombination(
|
||||
upload_id_marker.to_string(),
|
||||
key_marker.to_string(),
|
||||
)));
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(_e) = base64_decode(upload_id_marker.as_bytes()) {
|
||||
return Err(Error::new(StorageError::MalformedUploadID(upload_id_marker.to_owned())));
|
||||
return Err(StorageError::MalformedUploadID(upload_id_marker.to_owned()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2521,13 +2516,13 @@ fn check_list_multipart_args(
|
||||
|
||||
fn check_object_args(bucket: &str, object: &str) -> Result<()> {
|
||||
if !is_meta_bucketname(bucket) && check_valid_bucket_name_strict(bucket).is_err() {
|
||||
return Err(Error::new(StorageError::BucketNameInvalid(bucket.to_string())));
|
||||
return Err(StorageError::BucketNameInvalid(bucket.to_string()));
|
||||
}
|
||||
|
||||
check_object_name_for_length_and_slash(bucket, object)?;
|
||||
|
||||
if !is_valid_object_name(object) {
|
||||
return Err(Error::new(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string())));
|
||||
return Err(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -2539,10 +2534,7 @@ fn check_new_multipart_args(bucket: &str, object: &str) -> Result<()> {
|
||||
|
||||
fn check_multipart_object_args(bucket: &str, object: &str, upload_id: &str) -> Result<()> {
|
||||
if let Err(e) = base64_decode(upload_id.as_bytes()) {
|
||||
return Err(Error::new(StorageError::MalformedUploadID(format!(
|
||||
"{}/{}-{},err:{}",
|
||||
bucket, object, upload_id, e
|
||||
))));
|
||||
return Err(StorageError::MalformedUploadID(format!("{}/{}-{},err:{}", bucket, object, upload_id, e)));
|
||||
};
|
||||
check_object_args(bucket, object)
|
||||
}
|
||||
@@ -2566,13 +2558,13 @@ fn check_abort_multipart_args(bucket: &str, object: &str, upload_id: &str) -> Re
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn check_put_object_args(bucket: &str, object: &str) -> Result<()> {
|
||||
if !is_meta_bucketname(bucket) && check_valid_bucket_name_strict(bucket).is_err() {
|
||||
return Err(Error::new(StorageError::BucketNameInvalid(bucket.to_string())));
|
||||
return Err(StorageError::BucketNameInvalid(bucket.to_string()));
|
||||
}
|
||||
|
||||
check_object_name_for_length_and_slash(bucket, object)?;
|
||||
|
||||
if object.is_empty() || !is_valid_object_prefix(object) {
|
||||
return Err(Error::new(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string())));
|
||||
return Err(StorageError::ObjectNameInvalid(bucket.to_string(), object.to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -2646,13 +2638,7 @@ impl ServerPoolsAvailableSpace {
|
||||
}
|
||||
|
||||
pub async fn has_space_for(dis: &[Option<DiskInfo>], size: i64) -> Result<bool> {
|
||||
let size = {
|
||||
if size < 0 {
|
||||
DISK_ASSUME_UNKNOWN_SIZE
|
||||
} else {
|
||||
size as u64 * 2
|
||||
}
|
||||
};
|
||||
let size = { if size < 0 { DISK_ASSUME_UNKNOWN_SIZE } else { size as u64 * 2 } };
|
||||
|
||||
let mut available = 0;
|
||||
let mut total = 0;
|
||||
@@ -2665,7 +2651,7 @@ pub async fn has_space_for(dis: &[Option<DiskInfo>], size: i64) -> Result<bool>
|
||||
}
|
||||
|
||||
if disks_num < dis.len() / 2 || disks_num == 0 {
|
||||
return Err(Error::msg(format!(
|
||||
return Err(Error::other(format!(
|
||||
"not enough online disks to calculate the available space,need {}, found {}",
|
||||
(dis.len() / 2) + 1,
|
||||
disks_num,
|
||||
|
||||
+264
-1320
File diff suppressed because it is too large
Load Diff
@@ -1,322 +0,0 @@
|
||||
use crate::{
|
||||
disk::error::{is_err_file_not_found, DiskError},
|
||||
utils::path::decode_dir_object,
|
||||
};
|
||||
use common::error::Error;
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
|
||||
pub enum StorageError {
|
||||
#[error("not implemented")]
|
||||
NotImplemented,
|
||||
|
||||
#[error("Invalid arguments provided for {0}/{1}-{2}")]
|
||||
InvalidArgument(String, String, String),
|
||||
|
||||
#[error("method not allowed")]
|
||||
MethodNotAllowed,
|
||||
|
||||
#[error("Bucket not found: {0}")]
|
||||
BucketNotFound(String),
|
||||
|
||||
#[error("Bucket not empty: {0}")]
|
||||
BucketNotEmpty(String),
|
||||
|
||||
#[error("Bucket name invalid: {0}")]
|
||||
BucketNameInvalid(String),
|
||||
|
||||
#[error("Object name invalid: {0}/{1}")]
|
||||
ObjectNameInvalid(String, String),
|
||||
|
||||
#[error("Bucket exists: {0}")]
|
||||
BucketExists(String),
|
||||
#[error("Storage reached its minimum free drive threshold.")]
|
||||
StorageFull,
|
||||
#[error("Please reduce your request rate")]
|
||||
SlowDown,
|
||||
|
||||
#[error("Prefix access is denied:{0}/{1}")]
|
||||
PrefixAccessDenied(String, String),
|
||||
|
||||
#[error("Invalid UploadID KeyCombination: {0}/{1}")]
|
||||
InvalidUploadIDKeyCombination(String, String),
|
||||
|
||||
#[error("Malformed UploadID: {0}")]
|
||||
MalformedUploadID(String),
|
||||
|
||||
#[error("Object name too long: {0}/{1}")]
|
||||
ObjectNameTooLong(String, String),
|
||||
|
||||
#[error("Object name contains forward slash as prefix: {0}/{1}")]
|
||||
ObjectNamePrefixAsSlash(String, String),
|
||||
|
||||
#[error("Object not found: {0}/{1}")]
|
||||
ObjectNotFound(String, String),
|
||||
|
||||
#[error("volume not found: {0}")]
|
||||
VolumeNotFound(String),
|
||||
|
||||
#[error("Version not found: {0}/{1}-{2}")]
|
||||
VersionNotFound(String, String, String),
|
||||
|
||||
#[error("Invalid upload id: {0}/{1}-{2}")]
|
||||
InvalidUploadID(String, String, String),
|
||||
|
||||
#[error("Specified part could not be found. PartNumber {0}, Expected {1}, got {2}")]
|
||||
InvalidPart(usize, String, String),
|
||||
|
||||
#[error("Invalid version id: {0}/{1}-{2}")]
|
||||
InvalidVersionID(String, String, String),
|
||||
#[error("invalid data movement operation, source and destination pool are the same for : {0}/{1}-{2}")]
|
||||
DataMovementOverwriteErr(String, String, String),
|
||||
|
||||
#[error("Object exists on :{0} as directory {1}")]
|
||||
ObjectExistsAsDirectory(String, String),
|
||||
|
||||
#[error("Storage resources are insufficient for the read operation")]
|
||||
InsufficientReadQuorum,
|
||||
|
||||
#[error("Storage resources are insufficient for the write operation")]
|
||||
InsufficientWriteQuorum,
|
||||
|
||||
#[error("Decommission not started")]
|
||||
DecommissionNotStarted,
|
||||
#[error("Decommission already running")]
|
||||
DecommissionAlreadyRunning,
|
||||
|
||||
#[error("DoneForNow")]
|
||||
DoneForNow,
|
||||
}
|
||||
|
||||
impl StorageError {
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
match self {
|
||||
StorageError::NotImplemented => 0x01,
|
||||
StorageError::InvalidArgument(_, _, _) => 0x02,
|
||||
StorageError::MethodNotAllowed => 0x03,
|
||||
StorageError::BucketNotFound(_) => 0x04,
|
||||
StorageError::BucketNotEmpty(_) => 0x05,
|
||||
StorageError::BucketNameInvalid(_) => 0x06,
|
||||
StorageError::ObjectNameInvalid(_, _) => 0x07,
|
||||
StorageError::BucketExists(_) => 0x08,
|
||||
StorageError::StorageFull => 0x09,
|
||||
StorageError::SlowDown => 0x0A,
|
||||
StorageError::PrefixAccessDenied(_, _) => 0x0B,
|
||||
StorageError::InvalidUploadIDKeyCombination(_, _) => 0x0C,
|
||||
StorageError::MalformedUploadID(_) => 0x0D,
|
||||
StorageError::ObjectNameTooLong(_, _) => 0x0E,
|
||||
StorageError::ObjectNamePrefixAsSlash(_, _) => 0x0F,
|
||||
StorageError::ObjectNotFound(_, _) => 0x10,
|
||||
StorageError::VersionNotFound(_, _, _) => 0x11,
|
||||
StorageError::InvalidUploadID(_, _, _) => 0x12,
|
||||
StorageError::InvalidVersionID(_, _, _) => 0x13,
|
||||
StorageError::DataMovementOverwriteErr(_, _, _) => 0x14,
|
||||
StorageError::ObjectExistsAsDirectory(_, _) => 0x15,
|
||||
StorageError::InsufficientReadQuorum => 0x16,
|
||||
StorageError::InsufficientWriteQuorum => 0x17,
|
||||
StorageError::DecommissionNotStarted => 0x18,
|
||||
StorageError::InvalidPart(_, _, _) => 0x19,
|
||||
StorageError::VolumeNotFound(_) => 0x20,
|
||||
StorageError::DoneForNow => 0x21,
|
||||
StorageError::DecommissionAlreadyRunning => 0x22,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u32(error: u32) -> Option<Self> {
|
||||
match error {
|
||||
0x01 => Some(StorageError::NotImplemented),
|
||||
0x02 => Some(StorageError::InvalidArgument(Default::default(), Default::default(), Default::default())),
|
||||
0x03 => Some(StorageError::MethodNotAllowed),
|
||||
0x04 => Some(StorageError::BucketNotFound(Default::default())),
|
||||
0x05 => Some(StorageError::BucketNotEmpty(Default::default())),
|
||||
0x06 => Some(StorageError::BucketNameInvalid(Default::default())),
|
||||
0x07 => Some(StorageError::ObjectNameInvalid(Default::default(), Default::default())),
|
||||
0x08 => Some(StorageError::BucketExists(Default::default())),
|
||||
0x09 => Some(StorageError::StorageFull),
|
||||
0x0A => Some(StorageError::SlowDown),
|
||||
0x0B => Some(StorageError::PrefixAccessDenied(Default::default(), Default::default())),
|
||||
0x0C => Some(StorageError::InvalidUploadIDKeyCombination(Default::default(), Default::default())),
|
||||
0x0D => Some(StorageError::MalformedUploadID(Default::default())),
|
||||
0x0E => Some(StorageError::ObjectNameTooLong(Default::default(), Default::default())),
|
||||
0x0F => Some(StorageError::ObjectNamePrefixAsSlash(Default::default(), Default::default())),
|
||||
0x10 => Some(StorageError::ObjectNotFound(Default::default(), Default::default())),
|
||||
0x11 => Some(StorageError::VersionNotFound(Default::default(), Default::default(), Default::default())),
|
||||
0x12 => Some(StorageError::InvalidUploadID(Default::default(), Default::default(), Default::default())),
|
||||
0x13 => Some(StorageError::InvalidVersionID(Default::default(), Default::default(), Default::default())),
|
||||
0x14 => Some(StorageError::DataMovementOverwriteErr(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)),
|
||||
0x15 => Some(StorageError::ObjectExistsAsDirectory(Default::default(), Default::default())),
|
||||
0x16 => Some(StorageError::InsufficientReadQuorum),
|
||||
0x17 => Some(StorageError::InsufficientWriteQuorum),
|
||||
0x18 => Some(StorageError::DecommissionNotStarted),
|
||||
0x19 => Some(StorageError::InvalidPart(Default::default(), Default::default(), Default::default())),
|
||||
0x20 => Some(StorageError::VolumeNotFound(Default::default())),
|
||||
0x21 => Some(StorageError::DoneForNow),
|
||||
0x22 => Some(StorageError::DecommissionAlreadyRunning),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_object_err(err: Error, params: Vec<&str>) -> Error {
|
||||
if let Some(e) = err.downcast_ref::<DiskError>() {
|
||||
match e {
|
||||
DiskError::DiskFull => {
|
||||
return Error::new(StorageError::StorageFull);
|
||||
}
|
||||
|
||||
DiskError::FileNotFound => {
|
||||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||||
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
|
||||
|
||||
return Error::new(StorageError::ObjectNotFound(bucket, object));
|
||||
}
|
||||
DiskError::FileVersionNotFound => {
|
||||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||||
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
|
||||
let version = params.get(2).cloned().unwrap_or_default().to_owned();
|
||||
|
||||
return Error::new(StorageError::VersionNotFound(bucket, object, version));
|
||||
}
|
||||
DiskError::TooManyOpenFiles => {
|
||||
return Error::new(StorageError::SlowDown);
|
||||
}
|
||||
DiskError::FileNameTooLong => {
|
||||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||||
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
|
||||
|
||||
return Error::new(StorageError::ObjectNameInvalid(bucket, object));
|
||||
}
|
||||
DiskError::VolumeExists => {
|
||||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||||
return Error::new(StorageError::BucketExists(bucket));
|
||||
}
|
||||
DiskError::IsNotRegular => {
|
||||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||||
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
|
||||
|
||||
return Error::new(StorageError::ObjectExistsAsDirectory(bucket, object));
|
||||
}
|
||||
|
||||
DiskError::VolumeNotFound => {
|
||||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||||
return Error::new(StorageError::BucketNotFound(bucket));
|
||||
}
|
||||
DiskError::VolumeNotEmpty => {
|
||||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||||
return Error::new(StorageError::BucketNotEmpty(bucket));
|
||||
}
|
||||
|
||||
DiskError::FileAccessDenied => {
|
||||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||||
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
|
||||
|
||||
return Error::new(StorageError::PrefixAccessDenied(bucket, object));
|
||||
}
|
||||
// DiskError::MaxVersionsExceeded => todo!(),
|
||||
// DiskError::Unexpected => todo!(),
|
||||
// DiskError::CorruptedFormat => todo!(),
|
||||
// DiskError::CorruptedBackend => todo!(),
|
||||
// DiskError::UnformattedDisk => todo!(),
|
||||
// DiskError::InconsistentDisk => todo!(),
|
||||
// DiskError::UnsupportedDisk => todo!(),
|
||||
// DiskError::DiskNotDir => todo!(),
|
||||
// DiskError::DiskNotFound => todo!(),
|
||||
// DiskError::DiskOngoingReq => todo!(),
|
||||
// DiskError::DriveIsRoot => todo!(),
|
||||
// DiskError::FaultyRemoteDisk => todo!(),
|
||||
// DiskError::FaultyDisk => todo!(),
|
||||
// DiskError::DiskAccessDenied => todo!(),
|
||||
// DiskError::FileCorrupt => todo!(),
|
||||
// DiskError::BitrotHashAlgoInvalid => todo!(),
|
||||
// DiskError::CrossDeviceLink => todo!(),
|
||||
// DiskError::LessData => todo!(),
|
||||
// DiskError::MoreData => todo!(),
|
||||
// DiskError::OutdatedXLMeta => todo!(),
|
||||
// DiskError::PartMissingOrCorrupt => todo!(),
|
||||
// DiskError::PathNotFound => todo!(),
|
||||
// DiskError::VolumeAccessDenied => todo!(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
err
|
||||
}
|
||||
|
||||
pub fn is_err_decommission_already_running(err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
matches!(e, StorageError::DecommissionAlreadyRunning)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_data_movement_overwrite(err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
matches!(e, StorageError::DataMovementOverwriteErr(_, _, _))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_read_quorum(err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
matches!(e, StorageError::InsufficientReadQuorum)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_invalid_upload_id(err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
matches!(e, StorageError::InvalidUploadID(_, _, _))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_version_not_found(err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
matches!(e, StorageError::VersionNotFound(_, _, _))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_bucket_exists(err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
matches!(e, StorageError::BucketExists(_))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_bucket_not_found(err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
matches!(e, StorageError::VolumeNotFound(_)) || matches!(e, StorageError::BucketNotFound(_))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_object_not_found(err: &Error) -> bool {
|
||||
if is_err_file_not_found(err) {
|
||||
return true;
|
||||
}
|
||||
if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
matches!(e, StorageError::ObjectNotFound(_, _))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_error() {
|
||||
let e1 = Error::new(StorageError::BucketExists("ss".into()));
|
||||
let e2 = Error::new(StorageError::ObjectNotFound("ss".into(), "sdf".to_owned()));
|
||||
assert!(is_err_bucket_exists(&e1));
|
||||
assert!(!is_err_object_not_found(&e1));
|
||||
assert!(is_err_object_not_found(&e2));
|
||||
}
|
||||
+110
-90
@@ -1,25 +1,24 @@
|
||||
use crate::config::{storageclass, KVS};
|
||||
use crate::disk::DiskAPI;
|
||||
use crate::config::{KVS, storageclass};
|
||||
use crate::disk::error_reduce::{count_errs, reduce_write_quorum_errs};
|
||||
use crate::disk::{self, DiskAPI};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{
|
||||
disk::{
|
||||
DiskInfoOptions, DiskOption, DiskStore, FORMAT_CONFIG_FILE, RUSTFS_META_BUCKET,
|
||||
error::DiskError,
|
||||
format::{FormatErasureVersion, FormatMetaVersion, FormatV3},
|
||||
new_disk, DiskInfoOptions, DiskOption, DiskStore, FORMAT_CONFIG_FILE, RUSTFS_META_BUCKET,
|
||||
new_disk,
|
||||
},
|
||||
endpoints::Endpoints,
|
||||
heal::heal_commands::init_healing_tracker,
|
||||
};
|
||||
use common::error::{Error, Result};
|
||||
use futures::future::join_all;
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
fmt::Debug,
|
||||
};
|
||||
use std::collections::{HashMap, hash_map::Entry};
|
||||
|
||||
use tracing::{debug, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn init_disks(eps: &Endpoints, opt: &DiskOption) -> (Vec<Option<DiskStore>>, Vec<Option<Error>>) {
|
||||
pub async fn init_disks(eps: &Endpoints, opt: &DiskOption) -> (Vec<Option<DiskStore>>, Vec<Option<DiskError>>) {
|
||||
let mut futures = Vec::with_capacity(eps.as_ref().len());
|
||||
|
||||
for ep in eps.as_ref().iter() {
|
||||
@@ -52,29 +51,21 @@ pub async fn connect_load_init_formats(
|
||||
set_count: usize,
|
||||
set_drive_count: usize,
|
||||
deployment_id: Option<Uuid>,
|
||||
) -> Result<FormatV3, Error> {
|
||||
) -> Result<FormatV3> {
|
||||
warn!("connect_load_init_formats first_disk: {}", first_disk);
|
||||
let (formats, errs) = load_format_erasure_all(disks, false).await;
|
||||
|
||||
debug!("load_format_erasure_all errs {:?}", &errs);
|
||||
|
||||
DiskError::check_disk_fatal_errs(&errs)?;
|
||||
check_disk_fatal_errs(&errs)?;
|
||||
|
||||
check_format_erasure_values(&formats, set_drive_count)?;
|
||||
|
||||
if first_disk && DiskError::should_init_erasure_disks(&errs) {
|
||||
if first_disk && should_init_erasure_disks(&errs) {
|
||||
// UnformattedDisk, not format file create
|
||||
warn!("first_disk && should_init_erasure_disks");
|
||||
// new format and save
|
||||
let fms = init_format_erasure(disks, set_count, set_drive_count, deployment_id);
|
||||
|
||||
let errs = save_format_file_all(disks, &fms).await;
|
||||
|
||||
warn!("save_format_file_all errs {:?}", &errs);
|
||||
// TODO: check quorum
|
||||
// reduceWriteQuorumErrs(&errs)?;
|
||||
|
||||
let fm = get_format_erasure_in_quorum(&fms)?;
|
||||
let fm = init_format_erasure(disks, set_count, set_drive_count, deployment_id).await?;
|
||||
|
||||
return Ok(fm);
|
||||
}
|
||||
@@ -82,16 +73,16 @@ pub async fn connect_load_init_formats(
|
||||
warn!(
|
||||
"first_disk: {}, should_init_erasure_disks: {}",
|
||||
first_disk,
|
||||
DiskError::should_init_erasure_disks(&errs)
|
||||
should_init_erasure_disks(&errs)
|
||||
);
|
||||
|
||||
let unformatted = DiskError::quorum_unformatted_disks(&errs);
|
||||
let unformatted = quorum_unformatted_disks(&errs);
|
||||
if unformatted && !first_disk {
|
||||
return Err(Error::new(ErasureError::NotFirstDisk));
|
||||
return Err(Error::NotFirstDisk);
|
||||
}
|
||||
|
||||
if unformatted && first_disk {
|
||||
return Err(Error::new(ErasureError::FirstDiskWait));
|
||||
return Err(Error::FirstDiskWait);
|
||||
}
|
||||
|
||||
let fm = get_format_erasure_in_quorum(&formats)?;
|
||||
@@ -99,12 +90,36 @@ pub async fn connect_load_init_formats(
|
||||
Ok(fm)
|
||||
}
|
||||
|
||||
fn init_format_erasure(
|
||||
pub fn quorum_unformatted_disks(errs: &[Option<DiskError>]) -> bool {
|
||||
count_errs(errs, &DiskError::UnformattedDisk) > (errs.len() / 2)
|
||||
}
|
||||
|
||||
pub fn should_init_erasure_disks(errs: &[Option<DiskError>]) -> bool {
|
||||
count_errs(errs, &DiskError::UnformattedDisk) == errs.len()
|
||||
}
|
||||
|
||||
pub fn check_disk_fatal_errs(errs: &[Option<DiskError>]) -> disk::error::Result<()> {
|
||||
if count_errs(errs, &DiskError::UnsupportedDisk) == errs.len() {
|
||||
return Err(DiskError::UnsupportedDisk);
|
||||
}
|
||||
|
||||
if count_errs(errs, &DiskError::FileAccessDenied) == errs.len() {
|
||||
return Err(DiskError::FileAccessDenied);
|
||||
}
|
||||
|
||||
if count_errs(errs, &DiskError::DiskNotDir) == errs.len() {
|
||||
return Err(DiskError::DiskNotDir);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn init_format_erasure(
|
||||
disks: &[Option<DiskStore>],
|
||||
set_count: usize,
|
||||
set_drive_count: usize,
|
||||
deployment_id: Option<Uuid>,
|
||||
) -> Vec<Option<FormatV3>> {
|
||||
) -> Result<FormatV3> {
|
||||
let fm = FormatV3::new(set_count, set_drive_count);
|
||||
let mut fms = vec![None; disks.len()];
|
||||
for i in 0..set_count {
|
||||
@@ -120,7 +135,9 @@ fn init_format_erasure(
|
||||
}
|
||||
}
|
||||
|
||||
fms
|
||||
save_format_file_all(disks, &fms).await?;
|
||||
|
||||
get_format_erasure_in_quorum(&fms)
|
||||
}
|
||||
|
||||
pub fn get_format_erasure_in_quorum(formats: &[Option<FormatV3>]) -> Result<FormatV3> {
|
||||
@@ -143,13 +160,13 @@ pub fn get_format_erasure_in_quorum(formats: &[Option<FormatV3>]) -> Result<Form
|
||||
|
||||
if *max_drives == 0 || *max_count <= formats.len() / 2 {
|
||||
warn!("get_format_erasure_in_quorum fi: {:?}", &formats);
|
||||
return Err(Error::new(ErasureError::ErasureReadQuorum));
|
||||
return Err(Error::ErasureReadQuorum);
|
||||
}
|
||||
|
||||
let format = formats
|
||||
.iter()
|
||||
.find(|f| f.as_ref().is_some_and(|v| v.drives().eq(max_drives)))
|
||||
.ok_or(Error::new(ErasureError::ErasureReadQuorum))?;
|
||||
.ok_or(Error::ErasureReadQuorum)?;
|
||||
|
||||
let mut format = format.as_ref().unwrap().clone();
|
||||
format.erasure.this = Uuid::nil();
|
||||
@@ -172,28 +189,28 @@ pub fn check_format_erasure_values(
|
||||
check_format_erasure_value(f)?;
|
||||
|
||||
if formats.len() != f.erasure.sets.len() * f.erasure.sets[0].len() {
|
||||
return Err(Error::msg("formats length for erasure.sets not mtach"));
|
||||
return Err(Error::other("formats length for erasure.sets not mtach"));
|
||||
}
|
||||
|
||||
if f.erasure.sets[0].len() != set_drive_count {
|
||||
return Err(Error::msg("erasure set length not match set_drive_count"));
|
||||
return Err(Error::other("erasure set length not match set_drive_count"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn check_format_erasure_value(format: &FormatV3) -> Result<()> {
|
||||
if format.version != FormatMetaVersion::V1 {
|
||||
return Err(Error::msg("invalid FormatMetaVersion"));
|
||||
return Err(Error::other("invalid FormatMetaVersion"));
|
||||
}
|
||||
|
||||
if format.erasure.version != FormatErasureVersion::V3 {
|
||||
return Err(Error::msg("invalid FormatErasureVersion"));
|
||||
return Err(Error::other("invalid FormatErasureVersion"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// load_format_erasure_all 读取所有 foramt.json
|
||||
pub async fn load_format_erasure_all(disks: &[Option<DiskStore>], heal: bool) -> (Vec<Option<FormatV3>>, Vec<Option<Error>>) {
|
||||
pub async fn load_format_erasure_all(disks: &[Option<DiskStore>], heal: bool) -> (Vec<Option<FormatV3>>, Vec<Option<DiskError>>) {
|
||||
let mut futures = Vec::with_capacity(disks.len());
|
||||
let mut datas = Vec::with_capacity(disks.len());
|
||||
let mut errors = Vec::with_capacity(disks.len());
|
||||
@@ -203,7 +220,7 @@ pub async fn load_format_erasure_all(disks: &[Option<DiskStore>], heal: bool) ->
|
||||
if let Some(disk) = disk {
|
||||
load_format_erasure(disk, heal).await
|
||||
} else {
|
||||
Err(Error::new(DiskError::DiskNotFound))
|
||||
Err(DiskError::DiskNotFound)
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -229,18 +246,17 @@ pub async fn load_format_erasure_all(disks: &[Option<DiskStore>], heal: bool) ->
|
||||
(datas, errors)
|
||||
}
|
||||
|
||||
pub async fn load_format_erasure(disk: &DiskStore, heal: bool) -> Result<FormatV3, Error> {
|
||||
pub async fn load_format_erasure(disk: &DiskStore, heal: bool) -> disk::error::Result<FormatV3> {
|
||||
let data = disk
|
||||
.read_all(RUSTFS_META_BUCKET, FORMAT_CONFIG_FILE)
|
||||
.await
|
||||
.map_err(|e| match &e.downcast_ref::<DiskError>() {
|
||||
Some(DiskError::FileNotFound) => Error::new(DiskError::UnformattedDisk),
|
||||
Some(DiskError::DiskNotFound) => Error::new(DiskError::UnformattedDisk),
|
||||
Some(_) => e,
|
||||
None => e,
|
||||
.map_err(|e| match e {
|
||||
DiskError::FileNotFound => DiskError::UnformattedDisk,
|
||||
DiskError::DiskNotFound => DiskError::UnformattedDisk,
|
||||
_ => e,
|
||||
})?;
|
||||
|
||||
let mut fm = FormatV3::try_from(data.as_slice())?;
|
||||
let mut fm = FormatV3::try_from(data.as_ref())?;
|
||||
|
||||
if heal {
|
||||
let info = disk
|
||||
@@ -255,7 +271,7 @@ pub async fn load_format_erasure(disk: &DiskStore, heal: bool) -> Result<FormatV
|
||||
Ok(fm)
|
||||
}
|
||||
|
||||
async fn save_format_file_all(disks: &[Option<DiskStore>], formats: &[Option<FormatV3>]) -> Vec<Option<Error>> {
|
||||
async fn save_format_file_all(disks: &[Option<DiskStore>], formats: &[Option<FormatV3>]) -> disk::error::Result<()> {
|
||||
let mut futures = Vec::with_capacity(disks.len());
|
||||
|
||||
for (i, disk) in disks.iter().enumerate() {
|
||||
@@ -276,12 +292,16 @@ async fn save_format_file_all(disks: &[Option<DiskStore>], formats: &[Option<For
|
||||
}
|
||||
}
|
||||
|
||||
errors
|
||||
if let Some(e) = reduce_write_quorum_errs(&errors, &[], disks.len()) {
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn save_format_file(disk: &Option<DiskStore>, format: &Option<FormatV3>, heal_id: &str) -> Result<()> {
|
||||
pub async fn save_format_file(disk: &Option<DiskStore>, format: &Option<FormatV3>, heal_id: &str) -> disk::error::Result<()> {
|
||||
if disk.is_none() {
|
||||
return Err(Error::new(DiskError::DiskNotFound));
|
||||
return Err(DiskError::DiskNotFound);
|
||||
}
|
||||
|
||||
let format = format.as_ref().unwrap();
|
||||
@@ -291,7 +311,7 @@ pub async fn save_format_file(disk: &Option<DiskStore>, format: &Option<FormatV3
|
||||
let tmpfile = Uuid::new_v4().to_string();
|
||||
|
||||
let disk = disk.as_ref().unwrap();
|
||||
disk.write_all(RUSTFS_META_BUCKET, tmpfile.as_str(), json_data.into_bytes())
|
||||
disk.write_all(RUSTFS_META_BUCKET, tmpfile.as_str(), json_data.into_bytes().into())
|
||||
.await?;
|
||||
|
||||
disk.rename_file(RUSTFS_META_BUCKET, tmpfile.as_str(), RUSTFS_META_BUCKET, FORMAT_CONFIG_FILE)
|
||||
@@ -311,53 +331,53 @@ pub fn ec_drives_no_config(set_drive_count: usize) -> Result<usize> {
|
||||
Ok(sc.get_parity_for_sc(storageclass::STANDARD).unwrap_or_default())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub enum ErasureError {
|
||||
#[error("erasure read quorum")]
|
||||
ErasureReadQuorum,
|
||||
// #[derive(Debug, PartialEq, thiserror::Error)]
|
||||
// pub enum ErasureError {
|
||||
// #[error("erasure read quorum")]
|
||||
// ErasureReadQuorum,
|
||||
|
||||
#[error("erasure write quorum")]
|
||||
_ErasureWriteQuorum,
|
||||
// #[error("erasure write quorum")]
|
||||
// _ErasureWriteQuorum,
|
||||
|
||||
#[error("not first disk")]
|
||||
NotFirstDisk,
|
||||
// #[error("not first disk")]
|
||||
// NotFirstDisk,
|
||||
|
||||
#[error("first disk wiat")]
|
||||
FirstDiskWait,
|
||||
// #[error("first disk wait")]
|
||||
// FirstDiskWait,
|
||||
|
||||
#[error("invalid part id {0}")]
|
||||
InvalidPart(usize),
|
||||
}
|
||||
// #[error("invalid part id {0}")]
|
||||
// InvalidPart(usize),
|
||||
// }
|
||||
|
||||
impl ErasureError {
|
||||
pub fn is(&self, err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<ErasureError>() {
|
||||
return self == e;
|
||||
}
|
||||
// impl ErasureError {
|
||||
// pub fn is(&self, err: &Error) -> bool {
|
||||
// if let Some(e) = err.downcast_ref::<ErasureError>() {
|
||||
// return self == e;
|
||||
// }
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
// false
|
||||
// }
|
||||
// }
|
||||
|
||||
impl ErasureError {
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
match self {
|
||||
ErasureError::ErasureReadQuorum => 0x01,
|
||||
ErasureError::_ErasureWriteQuorum => 0x02,
|
||||
ErasureError::NotFirstDisk => 0x03,
|
||||
ErasureError::FirstDiskWait => 0x04,
|
||||
ErasureError::InvalidPart(_) => 0x05,
|
||||
}
|
||||
}
|
||||
// impl ErasureError {
|
||||
// pub fn to_u32(&self) -> u32 {
|
||||
// match self {
|
||||
// ErasureError::ErasureReadQuorum => 0x01,
|
||||
// ErasureError::_ErasureWriteQuorum => 0x02,
|
||||
// ErasureError::NotFirstDisk => 0x03,
|
||||
// ErasureError::FirstDiskWait => 0x04,
|
||||
// ErasureError::InvalidPart(_) => 0x05,
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn from_u32(error: u32) -> Option<Self> {
|
||||
match error {
|
||||
0x01 => Some(ErasureError::ErasureReadQuorum),
|
||||
0x02 => Some(ErasureError::_ErasureWriteQuorum),
|
||||
0x03 => Some(ErasureError::NotFirstDisk),
|
||||
0x04 => Some(ErasureError::FirstDiskWait),
|
||||
0x05 => Some(ErasureError::InvalidPart(Default::default())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
// pub fn from_u32(error: u32) -> Option<Self> {
|
||||
// match error {
|
||||
// 0x01 => Some(ErasureError::ErasureReadQuorum),
|
||||
// 0x02 => Some(ErasureError::_ErasureWriteQuorum),
|
||||
// 0x03 => Some(ErasureError::NotFirstDisk),
|
||||
// 0x04 => Some(ErasureError::FirstDiskWait),
|
||||
// 0x05 => Some(ErasureError::InvalidPart(Default::default())),
|
||||
// _ => None,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
use crate::bucket::metadata_sys::get_versioning_config;
|
||||
use crate::bucket::versioning::VersioningApi;
|
||||
use crate::cache_value::metacache_set::{list_path_raw, ListPathRawOptions};
|
||||
use crate::disk::error::{is_all_not_found, is_all_volume_not_found, is_err_eof, DiskError};
|
||||
use crate::disk::{
|
||||
DiskInfo, DiskStore, MetaCacheEntries, MetaCacheEntriesSorted, MetaCacheEntriesSortedResult, MetaCacheEntry,
|
||||
MetadataResolutionParams,
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::disk::{DiskInfo, DiskStore};
|
||||
use crate::error::{
|
||||
is_all_not_found, is_all_volume_not_found, is_err_bucket_not_found, to_object_err, Error, Result, StorageError,
|
||||
};
|
||||
use crate::error::clone_err;
|
||||
use crate::file_meta::merge_file_meta_versions;
|
||||
use crate::peer::is_reserved_or_invalid_bucket;
|
||||
use crate::set_disk::SetDisks;
|
||||
use crate::store::check_list_objs_args;
|
||||
use crate::store_api::{FileInfo, ListObjectVersionsInfo, ListObjectsInfo, ObjectInfo, ObjectOptions};
|
||||
use crate::store_err::{is_err_bucket_not_found, to_object_err, StorageError};
|
||||
use crate::utils::path::{self, base_dir_from_prefix, SLASH_SEPARATOR};
|
||||
use crate::store_api::{ListObjectVersionsInfo, ListObjectsInfo, ObjectInfo, ObjectOptions};
|
||||
use crate::StorageAPI;
|
||||
use crate::{store::ECStore, store_api::ListObjectsV2Info};
|
||||
use common::error::{Error, Result};
|
||||
use futures::future::join_all;
|
||||
use rand::seq::SliceRandom;
|
||||
use rustfs_filemeta::{
|
||||
merge_file_meta_versions, FileInfo, MetaCacheEntries, MetaCacheEntriesSorted, MetaCacheEntriesSortedResult, MetaCacheEntry,
|
||||
MetadataResolutionParams,
|
||||
};
|
||||
use rustfs_utils::path::{self, base_dir_from_prefix, SLASH_SEPARATOR};
|
||||
use std::collections::HashMap;
|
||||
use std::io::ErrorKind;
|
||||
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;
|
||||
use crate::disk::fs::SLASH_SEPARATOR;
|
||||
|
||||
const MAX_OBJECT_LIST: i32 = 1000;
|
||||
// const MAX_DELETE_LIST: i32 = 1000;
|
||||
@@ -280,13 +280,13 @@ impl ECStore {
|
||||
.list_path(&opts)
|
||||
.await
|
||||
.unwrap_or_else(|err| MetaCacheEntriesSortedResult {
|
||||
err: Some(err),
|
||||
err: Some(err.into()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
if let Some(err) = &list_result.err {
|
||||
if !is_err_eof(err) {
|
||||
return Err(to_object_err(list_result.err.unwrap(), vec![bucket, prefix]));
|
||||
if let Some(err) = list_result.err.clone() {
|
||||
if err != rustfs_filemeta::Error::Unexpected {
|
||||
return Err(to_object_err(err.into(), vec![bucket, prefix]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,11 +296,13 @@ impl ECStore {
|
||||
|
||||
// contextCanceled
|
||||
|
||||
let mut get_objects = list_result
|
||||
.entries
|
||||
.unwrap_or_default()
|
||||
.file_infos(bucket, prefix, delimiter.clone())
|
||||
.await;
|
||||
let mut get_objects = ObjectInfo::from_meta_cache_entries_sorted(
|
||||
&list_result.entries.unwrap_or_default(),
|
||||
bucket,
|
||||
prefix,
|
||||
delimiter.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let is_truncated = {
|
||||
if max_keys > 0 && get_objects.len() > max_keys as usize {
|
||||
@@ -363,7 +365,8 @@ impl ECStore {
|
||||
max_keys: i32,
|
||||
) -> Result<ListObjectVersionsInfo> {
|
||||
if marker.is_none() && version_marker.is_some() {
|
||||
return Err(Error::new(StorageError::NotImplemented));
|
||||
warn!("inner_list_object_versions: marker is none and version_marker is some");
|
||||
return Err(StorageError::NotImplemented);
|
||||
}
|
||||
|
||||
// if marker set, limit +1
|
||||
@@ -382,14 +385,14 @@ impl ECStore {
|
||||
let mut list_result = match self.list_path(&opts).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => MetaCacheEntriesSortedResult {
|
||||
err: Some(err),
|
||||
err: Some(err.into()),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
if let Some(err) = &list_result.err {
|
||||
if !is_err_eof(err) {
|
||||
return Err(to_object_err(list_result.err.unwrap(), vec![bucket, prefix]));
|
||||
if let Some(err) = list_result.err.clone() {
|
||||
if err != rustfs_filemeta::Error::Unexpected {
|
||||
return Err(to_object_err(err.into(), vec![bucket, prefix]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,11 +400,13 @@ impl ECStore {
|
||||
result.forward_past(opts.marker);
|
||||
}
|
||||
|
||||
let mut get_objects = list_result
|
||||
.entries
|
||||
.unwrap_or_default()
|
||||
.file_info_versions(bucket, prefix, delimiter.clone(), version_marker)
|
||||
.await;
|
||||
let mut get_objects = ObjectInfo::from_meta_cache_entries_sorted(
|
||||
&list_result.entries.unwrap_or_default(),
|
||||
bucket,
|
||||
prefix,
|
||||
delimiter.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let is_truncated = {
|
||||
if max_keys > 0 && get_objects.len() > max_keys as usize {
|
||||
@@ -471,16 +476,16 @@ impl ECStore {
|
||||
|
||||
if let Some(marker) = &o.marker {
|
||||
if !o.prefix.is_empty() && !marker.starts_with(&o.prefix) {
|
||||
return Err(Error::new(std::io::Error::from(ErrorKind::UnexpectedEof)));
|
||||
return Err(Error::Unexpected);
|
||||
}
|
||||
}
|
||||
|
||||
if o.limit == 0 {
|
||||
return Err(Error::new(std::io::Error::from(ErrorKind::UnexpectedEof)));
|
||||
return Err(Error::Unexpected);
|
||||
}
|
||||
|
||||
if o.prefix.starts_with(SLASH_SEPARATOR) {
|
||||
return Err(Error::new(std::io::Error::from(ErrorKind::UnexpectedEof)));
|
||||
return Err(Error::Unexpected);
|
||||
}
|
||||
|
||||
let slash_separator = Some(SLASH_SEPARATOR.to_owned());
|
||||
@@ -535,6 +540,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 = {
|
||||
@@ -545,12 +553,12 @@ impl ECStore {
|
||||
match res{
|
||||
Ok(o) => {
|
||||
error!("list_path err_rx.recv() ok {:?}", &o);
|
||||
MetaCacheEntriesSortedResult{ entries: None, err: Some(clone_err(o.as_ref())) }
|
||||
MetaCacheEntriesSortedResult{ entries: None, err: Some(o.as_ref().clone().into()) }
|
||||
},
|
||||
Err(err) => {
|
||||
error!("list_path err_rx.recv() err {:?}", &err);
|
||||
|
||||
MetaCacheEntriesSortedResult{ entries: None, err: Some(Error::new(err)) }
|
||||
MetaCacheEntriesSortedResult{ entries: None, err: Some(rustfs_filemeta::Error::other(err)) }
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -560,9 +568,6 @@ impl ECStore {
|
||||
}
|
||||
};
|
||||
|
||||
// cancel call exit spawns
|
||||
cancel_tx.send(true)?;
|
||||
|
||||
// wait spawns exit
|
||||
join_all(vec![job1, job2]).await;
|
||||
|
||||
@@ -583,7 +588,7 @@ impl ECStore {
|
||||
}
|
||||
|
||||
if !truncated {
|
||||
result.err = Some(Error::new(std::io::Error::from(ErrorKind::UnexpectedEof)));
|
||||
result.err = Some(Error::Unexpected.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,7 +622,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)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -643,7 +648,7 @@ impl ECStore {
|
||||
|
||||
if is_all_not_found(&errs) {
|
||||
if is_all_volume_not_found(&errs) {
|
||||
return Err(Error::new(DiskError::VolumeNotFound));
|
||||
return Err(StorageError::VolumeNotFound);
|
||||
}
|
||||
|
||||
return Ok(Vec::new());
|
||||
@@ -655,11 +660,11 @@ impl ECStore {
|
||||
|
||||
for err in errs.iter() {
|
||||
if let Some(err) = err {
|
||||
if is_err_eof(err) {
|
||||
if err == &Error::Unexpected {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(clone_err(err));
|
||||
return Err(err.clone());
|
||||
} else {
|
||||
all_at_eof = false;
|
||||
continue;
|
||||
@@ -772,7 +777,7 @@ impl ECStore {
|
||||
}
|
||||
})
|
||||
})),
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<DiskError>]| {
|
||||
Box::pin({
|
||||
let value = tx2.clone();
|
||||
let resolver = resolver.clone();
|
||||
@@ -813,7 +818,7 @@ impl ECStore {
|
||||
if !sent_err {
|
||||
let item = ObjectInfoOrErr {
|
||||
item: None,
|
||||
err: Some(err),
|
||||
err: Some(err.into()),
|
||||
};
|
||||
|
||||
if let Err(err) = result.send(item).await {
|
||||
@@ -832,7 +837,7 @@ impl ECStore {
|
||||
if let Some(fiter) = opts.filter {
|
||||
if fiter(&fi) {
|
||||
let item = ObjectInfoOrErr {
|
||||
item: Some(fi.to_object_info(&bucket, &fi.name, {
|
||||
item: Some(ObjectInfo::from_file_info(&fi, &bucket, &fi.name, {
|
||||
if let Some(v) = &vcf {
|
||||
v.versioned(&fi.name)
|
||||
} else {
|
||||
@@ -848,7 +853,7 @@ impl ECStore {
|
||||
}
|
||||
} else {
|
||||
let item = ObjectInfoOrErr {
|
||||
item: Some(fi.to_object_info(&bucket, &fi.name, {
|
||||
item: Some(ObjectInfo::from_file_info(&fi, &bucket, &fi.name, {
|
||||
if let Some(v) = &vcf {
|
||||
v.versioned(&fi.name)
|
||||
} else {
|
||||
@@ -870,7 +875,7 @@ impl ECStore {
|
||||
Err(err) => {
|
||||
let item = ObjectInfoOrErr {
|
||||
item: None,
|
||||
err: Some(err),
|
||||
err: Some(err.into()),
|
||||
};
|
||||
|
||||
if let Err(err) = result.send(item).await {
|
||||
@@ -888,7 +893,7 @@ impl ECStore {
|
||||
if let Some(fiter) = opts.filter {
|
||||
if fiter(fi) {
|
||||
let item = ObjectInfoOrErr {
|
||||
item: Some(fi.to_object_info(&bucket, &fi.name, {
|
||||
item: Some(ObjectInfo::from_file_info(fi, &bucket, &fi.name, {
|
||||
if let Some(v) = &vcf {
|
||||
v.versioned(&fi.name)
|
||||
} else {
|
||||
@@ -904,7 +909,7 @@ impl ECStore {
|
||||
}
|
||||
} else {
|
||||
let item = ObjectInfoOrErr {
|
||||
item: Some(fi.to_object_info(&bucket, &fi.name, {
|
||||
item: Some(ObjectInfo::from_file_info(fi, &bucket, &fi.name, {
|
||||
if let Some(v) = &vcf {
|
||||
v.versioned(&fi.name)
|
||||
} else {
|
||||
@@ -1012,7 +1017,8 @@ async fn gather_results(
|
||||
}),
|
||||
err: None,
|
||||
})
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Error::other)?;
|
||||
|
||||
returned = true;
|
||||
sender = None;
|
||||
@@ -1031,9 +1037,10 @@ async fn gather_results(
|
||||
o: MetaCacheEntries(entrys.clone()),
|
||||
..Default::default()
|
||||
}),
|
||||
err: Some(Error::new(std::io::Error::new(ErrorKind::UnexpectedEof, "Unexpected EOF"))),
|
||||
err: Some(Error::Unexpected.into()),
|
||||
})
|
||||
.await?;
|
||||
.await
|
||||
.map_err(Error::other)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -1072,12 +1079,15 @@ async fn merge_entry_channels(
|
||||
has_entry = in_channels[0].recv()=>{
|
||||
if let Some(entry) = has_entry{
|
||||
// warn!("merge_entry_channels entry {}", &entry.name);
|
||||
out_channel.send(entry).await?;
|
||||
out_channel.send(entry).await.map_err(Error::other)?;
|
||||
} else {
|
||||
return Ok(())
|
||||
}
|
||||
},
|
||||
_ = rx.recv()=>return Err(Error::msg("cancel")),
|
||||
_ = rx.recv()=>{
|
||||
warn!("merge_entry_channels rx.recv() cancel");
|
||||
return Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1207,7 +1217,7 @@ async fn merge_entry_channels(
|
||||
|
||||
if let Some(best_entry) = &best {
|
||||
if best_entry.name > last {
|
||||
out_channel.send(best_entry.clone()).await?;
|
||||
out_channel.send(best_entry.clone()).await.map_err(Error::other)?;
|
||||
last = best_entry.name.clone();
|
||||
}
|
||||
top[best_idx] = None; // Replace entry we just sent
|
||||
@@ -1290,7 +1300,7 @@ impl SetDisks {
|
||||
}
|
||||
})
|
||||
})),
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<Error>]| {
|
||||
partial: Some(Box::new(move |entries: MetaCacheEntries, _: &[Option<DiskError>]| {
|
||||
Box::pin({
|
||||
let value = tx2.clone();
|
||||
let resolver = resolver.clone();
|
||||
@@ -1308,6 +1318,7 @@ impl SetDisks {
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::config::storageclass::STANDARD;
|
||||
use crate::xhttp::AMZ_OBJECT_TAGGING;
|
||||
use crate::xhttp::AMZ_STORAGE_CLASS;
|
||||
use rustfs_filemeta::headers::AMZ_OBJECT_TAGGING;
|
||||
use rustfs_filemeta::headers::AMZ_STORAGE_CLASS;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn clean_metadata(metadata: &mut HashMap<String, String>) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
use common::error::{Error, Result};
|
||||
|
||||
pub fn parse_bool(str: &str) -> Result<bool> {
|
||||
match str {
|
||||
"1" | "t" | "T" | "true" | "TRUE" | "True" | "on" | "ON" | "On" | "enabled" => Ok(true),
|
||||
"0" | "f" | "F" | "false" | "FALSE" | "False" | "off" | "OFF" | "Off" | "disabled" => Ok(false),
|
||||
_ => Err(Error::from_string(format!("ParseBool: parsing {}", str))),
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
pub fn base64_encode(input: &[u8]) -> String {
|
||||
base64_simd::URL_SAFE_NO_PAD.encode_to_string(input)
|
||||
}
|
||||
|
||||
pub fn base64_decode(input: &[u8]) -> Result<Vec<u8>, base64_simd::Error> {
|
||||
base64_simd::URL_SAFE_NO_PAD.decode_to_vec(input)
|
||||
}
|
||||
|
||||
pub fn hex(data: impl AsRef<[u8]>) -> String {
|
||||
hex_simd::encode_to_string(data, hex_simd::AsciiCase::Lower)
|
||||
}
|
||||
|
||||
// #[cfg(windows)]
|
||||
// pub fn sha256(data: &[u8]) -> impl AsRef<[u8; 32]> {
|
||||
// use sha2::{Digest, Sha256};
|
||||
// <Sha256 as Digest>::digest(data)
|
||||
// }
|
||||
|
||||
// #[cfg(not(windows))]
|
||||
// pub fn sha256(data: &[u8]) -> impl AsRef<[u8]> {
|
||||
// use openssl::hash::{Hasher, MessageDigest};
|
||||
// let mut h = Hasher::new(MessageDigest::sha256()).unwrap();
|
||||
// h.update(data).unwrap();
|
||||
// h.finish().unwrap()
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_base64_encoding_decoding() {
|
||||
let original_uuid_timestamp = "c0194290-d911-45cb-8e12-79ec563f46a8x1735460504394878000";
|
||||
|
||||
let encoded_string = base64_encode(original_uuid_timestamp.as_bytes());
|
||||
|
||||
println!("Encoded: {}", &encoded_string);
|
||||
|
||||
let decoded_bytes = base64_decode(encoded_string.clone().as_bytes()).unwrap();
|
||||
let decoded_string = String::from_utf8(decoded_bytes).unwrap();
|
||||
|
||||
assert_eq!(decoded_string, original_uuid_timestamp)
|
||||
}
|
||||
@@ -1,561 +0,0 @@
|
||||
use common::error::{Error, Result};
|
||||
use lazy_static::*;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
static ref ELLIPSES_RE: Regex = Regex::new(r"(.*)(\{[0-9a-z]*\.\.\.[0-9a-z]*\})(.*)").unwrap();
|
||||
}
|
||||
|
||||
/// Ellipses constants
|
||||
const OPEN_BRACES: &str = "{";
|
||||
const CLOSE_BRACES: &str = "}";
|
||||
const ELLIPSES: &str = "...";
|
||||
|
||||
/// ellipses pattern, describes the range and also the
|
||||
/// associated prefix and suffixes.
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct Pattern {
|
||||
pub(crate) prefix: String,
|
||||
pub(crate) suffix: String,
|
||||
pub(crate) seq: Vec<String>,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
/// expands a ellipses pattern.
|
||||
pub fn expand(&self) -> Vec<String> {
|
||||
let mut ret = Vec::with_capacity(self.suffix.len());
|
||||
|
||||
for v in self.seq.iter() {
|
||||
match (self.prefix.is_empty(), self.suffix.is_empty()) {
|
||||
(false, true) => ret.push(format!("{}{}", self.prefix, v)),
|
||||
(true, false) => ret.push(format!("{}{}", v, self.suffix)),
|
||||
(true, true) => ret.push(v.to_string()),
|
||||
(false, false) => ret.push(format!("{}{}{}", self.prefix, v, self.suffix)),
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.seq.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.seq.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// contains a list of patterns provided in the input.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ArgPattern {
|
||||
inner: Vec<Pattern>,
|
||||
}
|
||||
|
||||
impl AsRef<Vec<Pattern>> for ArgPattern {
|
||||
fn as_ref(&self) -> &Vec<Pattern> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<Vec<Pattern>> for ArgPattern {
|
||||
fn as_mut(&mut self) -> &mut Vec<Pattern> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl ArgPattern {
|
||||
pub fn new(inner: Vec<Pattern>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// expands all the ellipses patterns in the given argument.
|
||||
pub fn expand(&self) -> Vec<Vec<String>> {
|
||||
let ret: Vec<Vec<String>> = self.inner.iter().map(|v| v.expand()).collect();
|
||||
|
||||
Self::arg_expander(&ret)
|
||||
}
|
||||
|
||||
/// recursively expands labels into its respective forms.
|
||||
fn arg_expander(lbs: &[Vec<String>]) -> Vec<Vec<String>> {
|
||||
if lbs.len() == 1 {
|
||||
return lbs[0].iter().map(|v| vec![v.to_string()]).collect();
|
||||
}
|
||||
|
||||
let mut ret = Vec::new();
|
||||
let (first, others) = lbs.split_at(1);
|
||||
|
||||
for bs in first[0].iter() {
|
||||
let ots = Self::arg_expander(others);
|
||||
for mut obs in ots {
|
||||
obs.push(bs.to_string());
|
||||
ret.push(obs);
|
||||
}
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// returns the total number of sizes in the given patterns.
|
||||
pub fn total_sizes(&self) -> usize {
|
||||
self.inner.iter().fold(1, |acc, v| acc * v.seq.len())
|
||||
}
|
||||
}
|
||||
|
||||
/// finds all ellipses patterns, recursively and parses the ranges numerically.
|
||||
pub fn find_ellipses_patterns(arg: &str) -> Result<ArgPattern> {
|
||||
let mut parts = match ELLIPSES_RE.captures(arg) {
|
||||
Some(caps) => caps,
|
||||
None => {
|
||||
return Err(Error::from_string(format!("Invalid ellipsis format in ({}), Ellipsis range must be provided in format {{N...M}} where N and M are positive integers, M must be greater than N, with an allowed minimum range of 4", arg)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut pattens = Vec::new();
|
||||
while let Some(prefix) = parts.get(1) {
|
||||
let seq = parse_ellipses_range(parts[2].into())?;
|
||||
|
||||
match ELLIPSES_RE.captures(prefix.into()) {
|
||||
Some(cs) => {
|
||||
pattens.push(Pattern {
|
||||
seq,
|
||||
prefix: String::new(),
|
||||
suffix: parts[3].into(),
|
||||
});
|
||||
parts = cs;
|
||||
}
|
||||
None => {
|
||||
pattens.push(Pattern {
|
||||
seq,
|
||||
prefix: prefix.as_str().to_owned(),
|
||||
suffix: parts[3].into(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Check if any of the prefix or suffixes now have flower braces
|
||||
// left over, in such a case we generally think that there is
|
||||
// perhaps a typo in users input and error out accordingly.
|
||||
for p in pattens.iter() {
|
||||
if p.prefix.contains(OPEN_BRACES)
|
||||
|| p.prefix.contains(CLOSE_BRACES)
|
||||
|| p.suffix.contains(OPEN_BRACES)
|
||||
|| p.suffix.contains(CLOSE_BRACES)
|
||||
{
|
||||
return Err(Error::from_string(format!("Invalid ellipsis format in ({}), Ellipsis range must be provided in format {{N...M}} where N and M are positive integers, M must be greater than N, with an allowed minimum range of 4", arg)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ArgPattern::new(pattens))
|
||||
}
|
||||
|
||||
/// returns true if input arg has ellipses type pattern.
|
||||
pub fn has_ellipses<T: AsRef<str>>(s: &[T]) -> bool {
|
||||
let pattern = [ELLIPSES, OPEN_BRACES, CLOSE_BRACES];
|
||||
|
||||
s.iter().any(|v| pattern.iter().any(|p| v.as_ref().contains(p)))
|
||||
}
|
||||
|
||||
/// Parses an ellipses range pattern of following style
|
||||
///
|
||||
/// example:
|
||||
/// {1...64}
|
||||
/// {33...64}
|
||||
pub fn parse_ellipses_range(pattern: &str) -> Result<Vec<String>> {
|
||||
if !pattern.contains(OPEN_BRACES) {
|
||||
return Err(Error::from_string("Invalid argument"));
|
||||
}
|
||||
if !pattern.contains(OPEN_BRACES) {
|
||||
return Err(Error::from_string("Invalid argument"));
|
||||
}
|
||||
|
||||
let ellipses_range: Vec<&str> = pattern
|
||||
.trim_start_matches(OPEN_BRACES)
|
||||
.trim_end_matches(CLOSE_BRACES)
|
||||
.split(ELLIPSES)
|
||||
.collect();
|
||||
|
||||
if ellipses_range.len() != 2 {
|
||||
return Err(Error::from_string("Invalid argument"));
|
||||
}
|
||||
|
||||
// TODO: Add support for hexadecimals.
|
||||
let start = ellipses_range[0].parse::<usize>()?;
|
||||
let end = ellipses_range[1].parse::<usize>()?;
|
||||
|
||||
if start > end {
|
||||
return Err(Error::from_string("Invalid argument:range start cannot be bigger than end"));
|
||||
}
|
||||
|
||||
let mut ret: Vec<String> = Vec::with_capacity(end - start + 1);
|
||||
for i in start..=end {
|
||||
if ellipses_range[0].starts_with('0') && ellipses_range[0].len() > 1 {
|
||||
ret.push(format!("{:0width$}", i, width = ellipses_range[1].len()));
|
||||
} else {
|
||||
ret.push(format!("{}", i));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_has_ellipses() {
|
||||
// Tests for all args without ellipses.
|
||||
let test_cases = [
|
||||
(1, vec!["64"], false),
|
||||
// Found flower braces, still attempt to parse and throw an error.
|
||||
(2, vec!["{1..64}"], true),
|
||||
(3, vec!["{1..2..}"], true),
|
||||
// Test for valid input.
|
||||
(4, vec!["1...64"], true),
|
||||
(5, vec!["{1...2O}"], true),
|
||||
(6, vec!["..."], true),
|
||||
(7, vec!["{-1...1}"], true),
|
||||
(8, vec!["{0...-1}"], true),
|
||||
(9, vec!["{1....4}"], true),
|
||||
(10, vec!["{1...64}"], true),
|
||||
(11, vec!["{...}"], true),
|
||||
(12, vec!["{1...64}", "{65...128}"], true),
|
||||
(13, vec!["http://rustfs{2...3}/export/set{1...64}"], true),
|
||||
(
|
||||
14,
|
||||
vec![
|
||||
"http://rustfs{2...3}/export/set{1...64}",
|
||||
"http://rustfs{2...3}/export/set{65...128}",
|
||||
],
|
||||
true,
|
||||
),
|
||||
(15, vec!["mydisk-{a...z}{1...20}"], true),
|
||||
(16, vec!["mydisk-{1...4}{1..2.}"], true),
|
||||
];
|
||||
|
||||
for (i, args, expected) in test_cases {
|
||||
let ret = has_ellipses(&args);
|
||||
assert_eq!(ret, expected, "Test{}: Expected {}, got {}", i, expected, ret);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_ellipses_patterns() {
|
||||
#[derive(Default)]
|
||||
struct TestCase<'a> {
|
||||
num: usize,
|
||||
pattern: &'a str,
|
||||
success: bool,
|
||||
want: Vec<Vec<&'a str>>,
|
||||
}
|
||||
|
||||
let test_cases = [
|
||||
TestCase {
|
||||
num: 1,
|
||||
pattern: "{1..64}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 2,
|
||||
pattern: "1...64",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 2,
|
||||
pattern: "...",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 3,
|
||||
pattern: "{1...",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 4,
|
||||
pattern: "...64}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 5,
|
||||
pattern: "{...}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 6,
|
||||
pattern: "{-1...1}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 7,
|
||||
pattern: "{0...-1}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 8,
|
||||
pattern: "{1...2O}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 9,
|
||||
pattern: "{64...1}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 10,
|
||||
pattern: "{1....4}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 11,
|
||||
pattern: "mydisk-{a...z}{1...20}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 12,
|
||||
pattern: "mydisk-{1...4}{1..2.}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 13,
|
||||
pattern: "{1..2.}-mydisk-{1...4}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 14,
|
||||
pattern: "{{1...4}}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 16,
|
||||
pattern: "{4...02}",
|
||||
..Default::default()
|
||||
},
|
||||
TestCase {
|
||||
num: 17,
|
||||
pattern: "{f...z}",
|
||||
..Default::default()
|
||||
},
|
||||
// Test for valid input.
|
||||
TestCase {
|
||||
num: 18,
|
||||
pattern: "{1...64}",
|
||||
success: true,
|
||||
want: vec![
|
||||
vec!["1"],
|
||||
vec!["2"],
|
||||
vec!["3"],
|
||||
vec!["4"],
|
||||
vec!["5"],
|
||||
vec!["6"],
|
||||
vec!["7"],
|
||||
vec!["8"],
|
||||
vec!["9"],
|
||||
vec!["10"],
|
||||
vec!["11"],
|
||||
vec!["12"],
|
||||
vec!["13"],
|
||||
vec!["14"],
|
||||
vec!["15"],
|
||||
vec!["16"],
|
||||
vec!["17"],
|
||||
vec!["18"],
|
||||
vec!["19"],
|
||||
vec!["20"],
|
||||
vec!["21"],
|
||||
vec!["22"],
|
||||
vec!["23"],
|
||||
vec!["24"],
|
||||
vec!["25"],
|
||||
vec!["26"],
|
||||
vec!["27"],
|
||||
vec!["28"],
|
||||
vec!["29"],
|
||||
vec!["30"],
|
||||
vec!["31"],
|
||||
vec!["32"],
|
||||
vec!["33"],
|
||||
vec!["34"],
|
||||
vec!["35"],
|
||||
vec!["36"],
|
||||
vec!["37"],
|
||||
vec!["38"],
|
||||
vec!["39"],
|
||||
vec!["40"],
|
||||
vec!["41"],
|
||||
vec!["42"],
|
||||
vec!["43"],
|
||||
vec!["44"],
|
||||
vec!["45"],
|
||||
vec!["46"],
|
||||
vec!["47"],
|
||||
vec!["48"],
|
||||
vec!["49"],
|
||||
vec!["50"],
|
||||
vec!["51"],
|
||||
vec!["52"],
|
||||
vec!["53"],
|
||||
vec!["54"],
|
||||
vec!["55"],
|
||||
vec!["56"],
|
||||
vec!["57"],
|
||||
vec!["58"],
|
||||
vec!["59"],
|
||||
vec!["60"],
|
||||
vec!["61"],
|
||||
vec!["62"],
|
||||
vec!["63"],
|
||||
vec!["64"],
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
num: 19,
|
||||
pattern: "{1...5} {65...70}",
|
||||
success: true,
|
||||
want: vec![
|
||||
vec!["1 ", "65"],
|
||||
vec!["2 ", "65"],
|
||||
vec!["3 ", "65"],
|
||||
vec!["4 ", "65"],
|
||||
vec!["5 ", "65"],
|
||||
vec!["1 ", "66"],
|
||||
vec!["2 ", "66"],
|
||||
vec!["3 ", "66"],
|
||||
vec!["4 ", "66"],
|
||||
vec!["5 ", "66"],
|
||||
vec!["1 ", "67"],
|
||||
vec!["2 ", "67"],
|
||||
vec!["3 ", "67"],
|
||||
vec!["4 ", "67"],
|
||||
vec!["5 ", "67"],
|
||||
vec!["1 ", "68"],
|
||||
vec!["2 ", "68"],
|
||||
vec!["3 ", "68"],
|
||||
vec!["4 ", "68"],
|
||||
vec!["5 ", "68"],
|
||||
vec!["1 ", "69"],
|
||||
vec!["2 ", "69"],
|
||||
vec!["3 ", "69"],
|
||||
vec!["4 ", "69"],
|
||||
vec!["5 ", "69"],
|
||||
vec!["1 ", "70"],
|
||||
vec!["2 ", "70"],
|
||||
vec!["3 ", "70"],
|
||||
vec!["4 ", "70"],
|
||||
vec!["5 ", "70"],
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
num: 20,
|
||||
pattern: "{01...036}",
|
||||
success: true,
|
||||
want: vec![
|
||||
vec!["001"],
|
||||
vec!["002"],
|
||||
vec!["003"],
|
||||
vec!["004"],
|
||||
vec!["005"],
|
||||
vec!["006"],
|
||||
vec!["007"],
|
||||
vec!["008"],
|
||||
vec!["009"],
|
||||
vec!["010"],
|
||||
vec!["011"],
|
||||
vec!["012"],
|
||||
vec!["013"],
|
||||
vec!["014"],
|
||||
vec!["015"],
|
||||
vec!["016"],
|
||||
vec!["017"],
|
||||
vec!["018"],
|
||||
vec!["019"],
|
||||
vec!["020"],
|
||||
vec!["021"],
|
||||
vec!["022"],
|
||||
vec!["023"],
|
||||
vec!["024"],
|
||||
vec!["025"],
|
||||
vec!["026"],
|
||||
vec!["027"],
|
||||
vec!["028"],
|
||||
vec!["029"],
|
||||
vec!["030"],
|
||||
vec!["031"],
|
||||
vec!["032"],
|
||||
vec!["033"],
|
||||
vec!["034"],
|
||||
vec!["035"],
|
||||
vec!["036"],
|
||||
],
|
||||
},
|
||||
TestCase {
|
||||
num: 21,
|
||||
pattern: "{001...036}",
|
||||
success: true,
|
||||
want: vec![
|
||||
vec!["001"],
|
||||
vec!["002"],
|
||||
vec!["003"],
|
||||
vec!["004"],
|
||||
vec!["005"],
|
||||
vec!["006"],
|
||||
vec!["007"],
|
||||
vec!["008"],
|
||||
vec!["009"],
|
||||
vec!["010"],
|
||||
vec!["011"],
|
||||
vec!["012"],
|
||||
vec!["013"],
|
||||
vec!["014"],
|
||||
vec!["015"],
|
||||
vec!["016"],
|
||||
vec!["017"],
|
||||
vec!["018"],
|
||||
vec!["019"],
|
||||
vec!["020"],
|
||||
vec!["021"],
|
||||
vec!["022"],
|
||||
vec!["023"],
|
||||
vec!["024"],
|
||||
vec!["025"],
|
||||
vec!["026"],
|
||||
vec!["027"],
|
||||
vec!["028"],
|
||||
vec!["029"],
|
||||
vec!["030"],
|
||||
vec!["031"],
|
||||
vec!["032"],
|
||||
vec!["033"],
|
||||
vec!["034"],
|
||||
vec!["035"],
|
||||
vec!["036"],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
let ret = find_ellipses_patterns(test_case.pattern);
|
||||
match ret {
|
||||
Ok(v) => {
|
||||
if !test_case.success {
|
||||
panic!("Test{}: Expected failure but passed instead", test_case.num);
|
||||
}
|
||||
|
||||
let got = v.expand();
|
||||
if got.len() != test_case.want.len() {
|
||||
panic!("Test{}: Expected {}, got {}", test_case.num, test_case.want.len(), got.len());
|
||||
}
|
||||
|
||||
assert_eq!(got, test_case.want, "Test{}: Expected {:?}, got {:?}", test_case.num, test_case.want, got);
|
||||
}
|
||||
Err(e) => {
|
||||
if test_case.success {
|
||||
panic!("Test{}: Expected success but failed instead {:?}", test_case.num, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
use std::{fs::Metadata, path::Path};
|
||||
|
||||
use tokio::{
|
||||
fs::{self, File},
|
||||
io,
|
||||
};
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
if f1.dev() != f2.dev() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.ino() != f2.ino() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.size() != f2.size() {
|
||||
return false;
|
||||
}
|
||||
if f1.permissions() != f2.permissions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.mtime() != f2.mtime() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn same_file(f1: &Metadata, f2: &Metadata) -> bool {
|
||||
if f1.permissions() != f2.permissions() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.file_type() != f2.file_type() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if f1.len() != f2.len() {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
type FileMode = usize;
|
||||
|
||||
pub const O_RDONLY: FileMode = 0x00000;
|
||||
pub const O_WRONLY: FileMode = 0x00001;
|
||||
pub const O_RDWR: FileMode = 0x00002;
|
||||
pub const O_CREATE: FileMode = 0x00040;
|
||||
// pub const O_EXCL: FileMode = 0x00080;
|
||||
// pub const O_NOCTTY: FileMode = 0x00100;
|
||||
pub const O_TRUNC: FileMode = 0x00200;
|
||||
// pub const O_NONBLOCK: FileMode = 0x00800;
|
||||
pub const O_APPEND: FileMode = 0x00400;
|
||||
// pub const O_SYNC: FileMode = 0x01000;
|
||||
// pub const O_ASYNC: FileMode = 0x02000;
|
||||
// pub const O_CLOEXEC: FileMode = 0x80000;
|
||||
|
||||
// read: bool,
|
||||
// write: bool,
|
||||
// append: bool,
|
||||
// truncate: bool,
|
||||
// create: bool,
|
||||
// create_new: bool,
|
||||
|
||||
pub async fn open_file(path: impl AsRef<Path>, mode: FileMode) -> io::Result<File> {
|
||||
let mut opts = fs::OpenOptions::new();
|
||||
|
||||
match mode & (O_RDONLY | O_WRONLY | O_RDWR) {
|
||||
O_RDONLY => {
|
||||
opts.read(true);
|
||||
}
|
||||
O_WRONLY => {
|
||||
opts.write(true);
|
||||
}
|
||||
O_RDWR => {
|
||||
opts.read(true);
|
||||
opts.write(true);
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
if mode & O_CREATE != 0 {
|
||||
opts.create(true);
|
||||
}
|
||||
|
||||
if mode & O_APPEND != 0 {
|
||||
opts.append(true);
|
||||
}
|
||||
|
||||
if mode & O_TRUNC != 0 {
|
||||
opts.truncate(true);
|
||||
}
|
||||
|
||||
opts.open(path.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn access(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::metadata(path).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn access_std(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
std::fs::metadata(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn lstat(path: impl AsRef<Path>) -> io::Result<Metadata> {
|
||||
fs::metadata(path).await
|
||||
}
|
||||
|
||||
pub fn lstat_std(path: impl AsRef<Path>) -> io::Result<Metadata> {
|
||||
std::fs::metadata(path)
|
||||
}
|
||||
|
||||
pub async fn make_dir_all(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::create_dir_all(path.as_ref()).await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn remove(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let meta = fs::metadata(path.as_ref()).await?;
|
||||
if meta.is_dir() {
|
||||
fs::remove_dir(path.as_ref()).await
|
||||
} else {
|
||||
fs::remove_file(path.as_ref()).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_all(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let meta = fs::metadata(path.as_ref()).await?;
|
||||
if meta.is_dir() {
|
||||
fs::remove_dir_all(path.as_ref()).await
|
||||
} else {
|
||||
fs::remove_file(path.as_ref()).await
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub fn remove_std(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let meta = std::fs::metadata(path.as_ref())?;
|
||||
if meta.is_dir() {
|
||||
std::fs::remove_dir(path.as_ref())
|
||||
} else {
|
||||
std::fs::remove_file(path.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_all_std(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
let meta = std::fs::metadata(path.as_ref())?;
|
||||
if meta.is_dir() {
|
||||
std::fs::remove_dir_all(path.as_ref())
|
||||
} else {
|
||||
std::fs::remove_file(path.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mkdir(path: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::create_dir(path.as_ref()).await
|
||||
}
|
||||
|
||||
pub async fn rename(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
|
||||
fs::rename(from, to).await
|
||||
}
|
||||
|
||||
pub fn rename_std(from: impl AsRef<Path>, to: impl AsRef<Path>) -> io::Result<()> {
|
||||
std::fs::rename(from, to)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn read_file(path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
|
||||
fs::read(path.as_ref()).await
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
use crc32fast::Hasher;
|
||||
use siphasher::sip::SipHasher;
|
||||
|
||||
pub fn sip_hash(key: &str, cardinality: usize, id: &[u8; 16]) -> usize {
|
||||
// 你的密钥,必须是 16 字节
|
||||
|
||||
// 计算字符串的 SipHash 值
|
||||
let result = SipHasher::new_with_key(id).hash(key.as_bytes());
|
||||
|
||||
result as usize % cardinality
|
||||
}
|
||||
|
||||
pub fn crc_hash(key: &str, cardinality: usize) -> usize {
|
||||
let mut hasher = Hasher::new(); // 创建一个新的哈希器
|
||||
|
||||
hasher.update(key.as_bytes()); // 更新哈希状态,添加数据
|
||||
|
||||
let checksum = hasher.finalize();
|
||||
|
||||
checksum as usize % cardinality
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
use crate::bucket::error::BucketMetadataError;
|
||||
use crate::config::error::ConfigError;
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::quorum::QuorumError;
|
||||
use crate::store_err::StorageError;
|
||||
use crate::store_init::ErasureError;
|
||||
use common::error::Error;
|
||||
use protos::proto_gen::node_service::Error as Proto_Error;
|
||||
|
||||
pub mod bool_flag;
|
||||
pub mod crypto;
|
||||
pub mod ellipses;
|
||||
pub mod fs;
|
||||
pub mod hash;
|
||||
pub mod net;
|
||||
pub mod os;
|
||||
pub mod path;
|
||||
pub mod wildcard;
|
||||
pub mod xml;
|
||||
|
||||
const ERROR_MODULE_MASK: u32 = 0xFF00;
|
||||
pub const ERROR_TYPE_MASK: u32 = 0x00FF;
|
||||
const DISK_ERROR_MASK: u32 = 0x0100;
|
||||
const STORAGE_ERROR_MASK: u32 = 0x0200;
|
||||
const BUCKET_METADATA_ERROR_MASK: u32 = 0x0300;
|
||||
const CONFIG_ERROR_MASK: u32 = 0x04000;
|
||||
const QUORUM_ERROR_MASK: u32 = 0x0500;
|
||||
const ERASURE_ERROR_MASK: u32 = 0x0600;
|
||||
|
||||
// error to u8
|
||||
pub fn error_to_u32(err: &Error) -> u32 {
|
||||
if let Some(e) = err.downcast_ref::<DiskError>() {
|
||||
DISK_ERROR_MASK | e.to_u32()
|
||||
} else if let Some(e) = err.downcast_ref::<StorageError>() {
|
||||
STORAGE_ERROR_MASK | e.to_u32()
|
||||
} else if let Some(e) = err.downcast_ref::<BucketMetadataError>() {
|
||||
BUCKET_METADATA_ERROR_MASK | e.to_u32()
|
||||
} else if let Some(e) = err.downcast_ref::<ConfigError>() {
|
||||
CONFIG_ERROR_MASK | e.to_u32()
|
||||
} else if let Some(e) = err.downcast_ref::<QuorumError>() {
|
||||
QUORUM_ERROR_MASK | e.to_u32()
|
||||
} else if let Some(e) = err.downcast_ref::<ErasureError>() {
|
||||
ERASURE_ERROR_MASK | e.to_u32()
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn u32_to_error(e: u32) -> Option<Error> {
|
||||
match e & ERROR_MODULE_MASK {
|
||||
DISK_ERROR_MASK => DiskError::from_u32(e & ERROR_TYPE_MASK).map(|e| Error::new(e)),
|
||||
STORAGE_ERROR_MASK => StorageError::from_u32(e & ERROR_TYPE_MASK).map(|e| Error::new(e)),
|
||||
BUCKET_METADATA_ERROR_MASK => BucketMetadataError::from_u32(e & ERROR_TYPE_MASK).map(|e| Error::new(e)),
|
||||
CONFIG_ERROR_MASK => ConfigError::from_u32(e & ERROR_TYPE_MASK).map(|e| Error::new(e)),
|
||||
QUORUM_ERROR_MASK => QuorumError::from_u32(e & ERROR_TYPE_MASK).map(|e| Error::new(e)),
|
||||
ERASURE_ERROR_MASK => ErasureError::from_u32(e & ERROR_TYPE_MASK).map(|e| Error::new(e)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn err_to_proto_err(err: &Error, msg: &str) -> Proto_Error {
|
||||
let num = error_to_u32(err);
|
||||
Proto_Error {
|
||||
code: num,
|
||||
error_info: msg.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proto_err_to_err(err: &Proto_Error) -> Error {
|
||||
if let Some(e) = u32_to_error(err.code) {
|
||||
e
|
||||
} else {
|
||||
Error::from_string(err.error_info.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_u32_to_error() {
|
||||
let error = Error::new(DiskError::FileCorrupt);
|
||||
let num = error_to_u32(&error);
|
||||
let new_error = u32_to_error(num);
|
||||
assert!(new_error.is_some());
|
||||
assert_eq!(new_error.unwrap().downcast_ref::<DiskError>(), Some(&DiskError::FileCorrupt));
|
||||
|
||||
let error = Error::new(StorageError::BucketNotEmpty(Default::default()));
|
||||
let num = error_to_u32(&error);
|
||||
let new_error = u32_to_error(num);
|
||||
assert!(new_error.is_some());
|
||||
assert_eq!(
|
||||
new_error.unwrap().downcast_ref::<StorageError>(),
|
||||
Some(&StorageError::BucketNotEmpty(Default::default()))
|
||||
);
|
||||
|
||||
let error = Error::new(BucketMetadataError::BucketObjectLockConfigNotFound);
|
||||
let num = error_to_u32(&error);
|
||||
let new_error = u32_to_error(num);
|
||||
assert!(new_error.is_some());
|
||||
assert_eq!(
|
||||
new_error.unwrap().downcast_ref::<BucketMetadataError>(),
|
||||
Some(&BucketMetadataError::BucketObjectLockConfigNotFound)
|
||||
);
|
||||
|
||||
let error = Error::new(ConfigError::NotFound);
|
||||
let num = error_to_u32(&error);
|
||||
let new_error = u32_to_error(num);
|
||||
assert!(new_error.is_some());
|
||||
assert_eq!(new_error.unwrap().downcast_ref::<ConfigError>(), Some(&ConfigError::NotFound));
|
||||
|
||||
let error = Error::new(QuorumError::Read);
|
||||
let num = error_to_u32(&error);
|
||||
let new_error = u32_to_error(num);
|
||||
assert!(new_error.is_some());
|
||||
assert_eq!(new_error.unwrap().downcast_ref::<QuorumError>(), Some(&QuorumError::Read));
|
||||
|
||||
let error = Error::new(ErasureError::ErasureReadQuorum);
|
||||
let num = error_to_u32(&error);
|
||||
let new_error = u32_to_error(num);
|
||||
assert!(new_error.is_some());
|
||||
assert_eq!(new_error.unwrap().downcast_ref::<ErasureError>(), Some(&ErasureError::ErasureReadQuorum));
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
use common::error::{Error, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt::Display,
|
||||
net::{IpAddr, Ipv6Addr, SocketAddr, TcpListener, ToSocketAddrs},
|
||||
};
|
||||
|
||||
use url::Host;
|
||||
|
||||
lazy_static! {
|
||||
static ref LOCAL_IPS: Vec<IpAddr> = must_get_local_ips().unwrap();
|
||||
}
|
||||
|
||||
/// helper for validating if the provided arg is an ip address.
|
||||
pub fn is_socket_addr(addr: &str) -> bool {
|
||||
// TODO IPv6 zone information?
|
||||
|
||||
addr.parse::<SocketAddr>().is_ok() || addr.parse::<IpAddr>().is_ok()
|
||||
}
|
||||
|
||||
/// checks if server_addr is valid and local host.
|
||||
pub fn check_local_server_addr(server_addr: &str) -> Result<SocketAddr> {
|
||||
let addr: Vec<SocketAddr> = match server_addr.to_socket_addrs() {
|
||||
Ok(addr) => addr.collect(),
|
||||
Err(err) => return Err(Error::new(Box::new(err))),
|
||||
};
|
||||
|
||||
// 0.0.0.0 is a wildcard address and refers to local network
|
||||
// addresses. I.e, 0.0.0.0:9000 like ":9000" refers to port
|
||||
// 9000 on localhost.
|
||||
for a in addr {
|
||||
if a.ip().is_unspecified() {
|
||||
return Ok(a);
|
||||
}
|
||||
|
||||
let host = match a {
|
||||
SocketAddr::V4(a) => Host::<&str>::Ipv4(*a.ip()),
|
||||
SocketAddr::V6(a) => Host::Ipv6(*a.ip()),
|
||||
};
|
||||
|
||||
if is_local_host(host, 0, 0)? {
|
||||
return Ok(a);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::from_string("host in server address should be this server"))
|
||||
}
|
||||
|
||||
/// checks if the given parameter correspond to one of
|
||||
/// the local IP of the current machine
|
||||
pub fn is_local_host(host: Host<&str>, port: u16, local_port: u16) -> Result<bool> {
|
||||
let local_set: HashSet<IpAddr> = LOCAL_IPS.iter().copied().collect();
|
||||
let is_local_host = match host {
|
||||
Host::Domain(domain) => {
|
||||
let ips = match (domain, 0).to_socket_addrs().map(|v| v.map(|v| v.ip()).collect::<Vec<_>>()) {
|
||||
Ok(ips) => ips,
|
||||
Err(err) => return Err(Error::new(Box::new(err))),
|
||||
};
|
||||
|
||||
ips.iter().any(|ip| local_set.contains(ip))
|
||||
}
|
||||
Host::Ipv4(ip) => local_set.contains(&IpAddr::V4(ip)),
|
||||
Host::Ipv6(ip) => local_set.contains(&IpAddr::V6(ip)),
|
||||
};
|
||||
|
||||
if port > 0 {
|
||||
return Ok(is_local_host && port == local_port);
|
||||
}
|
||||
|
||||
Ok(is_local_host)
|
||||
}
|
||||
|
||||
/// returns IP address of given host.
|
||||
pub fn get_host_ip(host: Host<&str>) -> Result<HashSet<IpAddr>> {
|
||||
match host {
|
||||
Host::Domain(domain) => match (domain, 0)
|
||||
.to_socket_addrs()
|
||||
.map(|v| v.map(|v| v.ip()).collect::<HashSet<_>>())
|
||||
{
|
||||
Ok(ips) => Ok(ips),
|
||||
Err(err) => Err(Error::new(Box::new(err))),
|
||||
},
|
||||
Host::Ipv4(ip) => {
|
||||
let mut set = HashSet::with_capacity(1);
|
||||
set.insert(IpAddr::V4(ip));
|
||||
Ok(set)
|
||||
}
|
||||
Host::Ipv6(ip) => {
|
||||
let mut set = HashSet::with_capacity(1);
|
||||
set.insert(IpAddr::V6(ip));
|
||||
Ok(set)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_available_port() -> u16 {
|
||||
TcpListener::bind("0.0.0.0:0").unwrap().local_addr().unwrap().port()
|
||||
}
|
||||
|
||||
/// returns IPs of local interface
|
||||
pub(crate) fn must_get_local_ips() -> Result<Vec<IpAddr>> {
|
||||
match netif::up() {
|
||||
Ok(up) => Ok(up.map(|x| x.address().to_owned()).collect()),
|
||||
Err(err) => Err(Error::from_string(format!("Unable to get IP addresses of this host: {}", err))),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct XHost {
|
||||
pub name: String,
|
||||
pub port: u16,
|
||||
pub is_port_set: bool,
|
||||
}
|
||||
|
||||
impl Display for XHost {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if !self.is_port_set {
|
||||
write!(f, "{}", self.name)
|
||||
} else if self.name.contains(':') {
|
||||
write!(f, "[{}]:{}", self.name, self.port)
|
||||
} else {
|
||||
write!(f, "{}:{}", self.name, self.port)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for XHost {
|
||||
type Error = std::io::Error;
|
||||
|
||||
fn try_from(value: String) -> std::result::Result<Self, Self::Error> {
|
||||
if let Some(addr) = value.to_socket_addrs()?.next() {
|
||||
Ok(Self {
|
||||
name: addr.ip().to_string(),
|
||||
port: addr.port(),
|
||||
is_port_set: addr.port() > 0,
|
||||
})
|
||||
} else {
|
||||
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "value invalid"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// parses the address string, process the ":port" format for double-stack binding,
|
||||
/// and resolve the host name or IP address. If the port is 0, an available port is assigned.
|
||||
pub fn parse_and_resolve_address(addr_str: &str) -> Result<SocketAddr> {
|
||||
let resolved_addr: SocketAddr = if let Some(port) = addr_str.strip_prefix(":") {
|
||||
// Process the ":port" format for double stack binding
|
||||
let port_str = port;
|
||||
let port: u16 = port_str
|
||||
.parse()
|
||||
.map_err(|e| Error::from_string(format!("Invalid port format: {}, err:{:?}", addr_str, e)))?;
|
||||
let final_port = if port == 0 {
|
||||
get_available_port() // assume get_available_port is available here
|
||||
} else {
|
||||
port
|
||||
};
|
||||
// Using IPv6 without address specified [::], it should handle both IPv4 and IPv6
|
||||
SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), final_port)
|
||||
} else {
|
||||
// Use existing logic to handle regular address formats
|
||||
let mut addr = check_local_server_addr(addr_str)?; // assume check_local_server_addr is available here
|
||||
if addr.port() == 0 {
|
||||
addr.set_port(get_available_port());
|
||||
}
|
||||
addr
|
||||
};
|
||||
Ok(resolved_addr)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_socket_addr() {
|
||||
let test_cases = [
|
||||
("localhost", false),
|
||||
("localhost:9000", false),
|
||||
("example.com", false),
|
||||
("http://192.168.1.0", false),
|
||||
("http://192.168.1.0:9000", false),
|
||||
("192.168.1.0", true),
|
||||
("[2001:db8::1]:9000", true),
|
||||
];
|
||||
|
||||
for (addr, expected) in test_cases {
|
||||
let ret = is_socket_addr(addr);
|
||||
assert_eq!(expected, ret, "addr: {}, expected: {}, got: {}", addr, expected, ret);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_check_local_server_addr() {
|
||||
let test_cases = [
|
||||
// (":54321", Ok(())),
|
||||
("localhost:54321", Ok(())),
|
||||
("0.0.0.0:9000", Ok(())),
|
||||
// (":0", Ok(())),
|
||||
("localhost", Err(Error::from_string("invalid socket address"))),
|
||||
("", Err(Error::from_string("invalid socket address"))),
|
||||
(
|
||||
"example.org:54321",
|
||||
Err(Error::from_string("host in server address should be this server")),
|
||||
),
|
||||
(":-10", Err(Error::from_string("invalid port value"))),
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
let ret = check_local_server_addr(test_case.0);
|
||||
if test_case.1.is_ok() && ret.is_err() {
|
||||
panic!("{}: error: expected = <nil>, got = {:?}", test_case.0, ret);
|
||||
}
|
||||
if test_case.1.is_err() && ret.is_ok() {
|
||||
panic!("{}: error: expected = {:?}, got = <nil>", test_case.0, test_case.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_must_get_local_ips() {
|
||||
let local_ips = must_get_local_ips().unwrap();
|
||||
let local_set: HashSet<IpAddr> = local_ips.into_iter().collect();
|
||||
|
||||
assert!(local_set.contains(&IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))));
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
use nix::sys::stat::{self, stat};
|
||||
use nix::sys::statfs::{self, statfs, FsType};
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufRead, Error, ErrorKind};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::disk::Info;
|
||||
use common::error::{Error as e_Error, Result};
|
||||
|
||||
use super::IOStats;
|
||||
|
||||
/// returns total and free bytes available in a directory, e.g. `/`.
|
||||
pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
let stat_fs = statfs(p.as_ref())?;
|
||||
|
||||
let bsize = stat_fs.block_size() as u64;
|
||||
let bfree = stat_fs.blocks_free() as u64;
|
||||
let bavail = stat_fs.blocks_available() as u64;
|
||||
let blocks = stat_fs.blocks() as u64;
|
||||
|
||||
let reserved = match bfree.checked_sub(bavail) {
|
||||
Some(reserved) => reserved,
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"detected f_bavail space ({}) > f_bfree space ({}), fs corruption at ({}). please run 'fsck'",
|
||||
bavail,
|
||||
bfree,
|
||||
p.as_ref().display()
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let total = match blocks.checked_sub(reserved) {
|
||||
Some(total) => total * bsize,
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"detected reserved space ({}) > blocks space ({}), fs corruption at ({}). please run 'fsck'",
|
||||
reserved,
|
||||
blocks,
|
||||
p.as_ref().display()
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let free = bavail * bsize;
|
||||
let used = match total.checked_sub(free) {
|
||||
Some(used) => used,
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"detected free space ({}) > total drive space ({}), fs corruption at ({}). please run 'fsck'",
|
||||
free,
|
||||
total,
|
||||
p.as_ref().display()
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let st = stat(p.as_ref())?;
|
||||
|
||||
Ok(Info {
|
||||
total,
|
||||
free,
|
||||
used,
|
||||
files: stat_fs.files(),
|
||||
ffree: stat_fs.files_free(),
|
||||
fstype: get_fs_type(stat_fs.filesystem_type()).to_string(),
|
||||
|
||||
major: stat::major(st.st_dev),
|
||||
minor: stat::minor(st.st_dev),
|
||||
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// returns the filesystem type of the underlying mounted filesystem
|
||||
///
|
||||
/// TODO The following mapping could not find the corresponding constant in `nix`:
|
||||
///
|
||||
/// "137d" => "EXT",
|
||||
/// "4244" => "HFS",
|
||||
/// "5346544e" => "NTFS",
|
||||
/// "61756673" => "AUFS",
|
||||
/// "ef51" => "EXT2OLD",
|
||||
/// "2fc12fc1" => "zfs",
|
||||
/// "ff534d42" => "cifs",
|
||||
/// "53464846" => "wslfs",
|
||||
fn get_fs_type(fs_type: FsType) -> &'static str {
|
||||
match fs_type {
|
||||
statfs::TMPFS_MAGIC => "TMPFS",
|
||||
statfs::MSDOS_SUPER_MAGIC => "MSDOS",
|
||||
// statfs::XFS_SUPER_MAGIC => "XFS",
|
||||
statfs::NFS_SUPER_MAGIC => "NFS",
|
||||
statfs::EXT4_SUPER_MAGIC => "EXT4",
|
||||
statfs::ECRYPTFS_SUPER_MAGIC => "ecryptfs",
|
||||
statfs::OVERLAYFS_SUPER_MAGIC => "overlayfs",
|
||||
statfs::REISERFS_SUPER_MAGIC => "REISERFS",
|
||||
|
||||
_ => "UNKNOWN",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn same_disk(disk1: &str, disk2: &str) -> Result<bool> {
|
||||
let stat1 = stat(disk1)?;
|
||||
let stat2 = stat(disk2)?;
|
||||
|
||||
Ok(stat1.st_dev == stat2.st_dev)
|
||||
}
|
||||
|
||||
pub fn get_drive_stats(major: u32, minor: u32) -> Result<IOStats> {
|
||||
read_drive_stats(&format!("/sys/dev/block/{}:{}/stat", major, minor))
|
||||
}
|
||||
|
||||
fn read_drive_stats(stats_file: &str) -> Result<IOStats> {
|
||||
let stats = read_stat(stats_file)?;
|
||||
if stats.len() < 11 {
|
||||
return Err(e_Error::from_string(format!("found invalid format while reading {}", stats_file)));
|
||||
}
|
||||
let mut io_stats = IOStats {
|
||||
read_ios: stats[0],
|
||||
read_merges: stats[1],
|
||||
read_sectors: stats[2],
|
||||
read_ticks: stats[3],
|
||||
write_ios: stats[4],
|
||||
write_merges: stats[5],
|
||||
write_sectors: stats[6],
|
||||
write_ticks: stats[7],
|
||||
current_ios: stats[8],
|
||||
total_ticks: stats[9],
|
||||
req_ticks: stats[10],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if stats.len() > 14 {
|
||||
io_stats.discard_ios = stats[11];
|
||||
io_stats.discard_merges = stats[12];
|
||||
io_stats.discard_sectors = stats[13];
|
||||
io_stats.discard_ticks = stats[14];
|
||||
}
|
||||
Ok(io_stats)
|
||||
}
|
||||
|
||||
fn read_stat(file_name: &str) -> Result<Vec<u64>> {
|
||||
// 打开文件
|
||||
let path = Path::new(file_name);
|
||||
let file = File::open(path)?;
|
||||
|
||||
// 创建一个 BufReader
|
||||
let reader = io::BufReader::new(file);
|
||||
|
||||
// 读取第一行
|
||||
let mut stats = Vec::new();
|
||||
if let Some(line) = reader.lines().next() {
|
||||
let line = line?;
|
||||
// 分割行并解析为 u64
|
||||
// https://rust-lang.github.io/rust-clippy/master/index.html#trim_split_whitespace
|
||||
for token in line.split_whitespace() {
|
||||
let ui64: u64 = token.parse()?;
|
||||
stats.push(ui64);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(stats)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::get_drive_stats;
|
||||
|
||||
#[ignore] // FIXME: failed in github actions
|
||||
#[test]
|
||||
fn test_stats() {
|
||||
let major = 7;
|
||||
let minor = 11;
|
||||
let s = get_drive_stats(major, minor).unwrap();
|
||||
println!("{:?}", s);
|
||||
}
|
||||
}
|
||||
@@ -1,338 +0,0 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
#[cfg(all(unix, not(target_os = "linux")))]
|
||||
mod unix;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod windows;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::{get_drive_stats, get_info, same_disk};
|
||||
// pub use linux::same_disk;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "linux")))]
|
||||
pub use unix::{get_drive_stats, get_info, same_disk};
|
||||
#[cfg(target_os = "windows")]
|
||||
pub use windows::{get_drive_stats, get_info, same_disk};
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct IOStats {
|
||||
pub read_ios: u64,
|
||||
pub read_merges: u64,
|
||||
pub read_sectors: u64,
|
||||
pub read_ticks: u64,
|
||||
pub write_ios: u64,
|
||||
pub write_merges: u64,
|
||||
pub write_sectors: u64,
|
||||
pub write_ticks: u64,
|
||||
pub current_ios: u64,
|
||||
pub total_ticks: u64,
|
||||
pub req_ticks: u64,
|
||||
pub discard_ios: u64,
|
||||
pub discard_merges: u64,
|
||||
pub discard_sectors: u64,
|
||||
pub discard_ticks: u64,
|
||||
pub flush_ios: u64,
|
||||
pub flush_ticks: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn test_get_info_valid_path() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let info = get_info(temp_dir.path()).unwrap();
|
||||
|
||||
println!("Disk Info: {:?}", info);
|
||||
|
||||
assert!(info.total > 0);
|
||||
assert!(info.free > 0);
|
||||
assert!(info.used > 0);
|
||||
assert!(info.files > 0);
|
||||
assert!(info.ffree > 0);
|
||||
assert!(!info.fstype.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_info_invalid_path() {
|
||||
let invalid_path = PathBuf::from("/invalid/path");
|
||||
let result = get_info(&invalid_path);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_same_disk_same_path() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let path = temp_dir.path().to_str().unwrap();
|
||||
|
||||
let result = same_disk(path, path).unwrap();
|
||||
assert!(result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_same_disk_different_paths() {
|
||||
let temp_dir1 = tempfile::tempdir().unwrap();
|
||||
let temp_dir2 = tempfile::tempdir().unwrap();
|
||||
|
||||
let path1 = temp_dir1.path().to_str().unwrap();
|
||||
let path2 = temp_dir2.path().to_str().unwrap();
|
||||
|
||||
let result = same_disk(path1, path2).unwrap();
|
||||
// Note: On many systems, temporary directories are on the same disk
|
||||
// This test mainly verifies the function works without error
|
||||
// The actual result depends on the system configuration
|
||||
println!("Same disk result for temp dirs: {}", result);
|
||||
|
||||
// The function returns a boolean value as expected
|
||||
let _: bool = result; // Type assertion to verify return type
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_drive_stats_default() {
|
||||
let stats = get_drive_stats(0, 0).unwrap();
|
||||
assert_eq!(stats, IOStats::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iostats_default_values() {
|
||||
// Test that IOStats default values are all zero
|
||||
let stats = IOStats::default();
|
||||
|
||||
assert_eq!(stats.read_ios, 0);
|
||||
assert_eq!(stats.read_merges, 0);
|
||||
assert_eq!(stats.read_sectors, 0);
|
||||
assert_eq!(stats.read_ticks, 0);
|
||||
assert_eq!(stats.write_ios, 0);
|
||||
assert_eq!(stats.write_merges, 0);
|
||||
assert_eq!(stats.write_sectors, 0);
|
||||
assert_eq!(stats.write_ticks, 0);
|
||||
assert_eq!(stats.current_ios, 0);
|
||||
assert_eq!(stats.total_ticks, 0);
|
||||
assert_eq!(stats.req_ticks, 0);
|
||||
assert_eq!(stats.discard_ios, 0);
|
||||
assert_eq!(stats.discard_merges, 0);
|
||||
assert_eq!(stats.discard_sectors, 0);
|
||||
assert_eq!(stats.discard_ticks, 0);
|
||||
assert_eq!(stats.flush_ios, 0);
|
||||
assert_eq!(stats.flush_ticks, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iostats_equality() {
|
||||
// Test IOStats equality comparison
|
||||
let stats1 = IOStats::default();
|
||||
let stats2 = IOStats::default();
|
||||
assert_eq!(stats1, stats2);
|
||||
|
||||
let stats3 = IOStats {
|
||||
read_ios: 100,
|
||||
write_ios: 50,
|
||||
..Default::default()
|
||||
};
|
||||
let stats4 = IOStats {
|
||||
read_ios: 100,
|
||||
write_ios: 50,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(stats3, stats4);
|
||||
|
||||
// Test inequality
|
||||
assert_ne!(stats1, stats3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iostats_debug_format() {
|
||||
// Test Debug trait implementation
|
||||
let stats = IOStats {
|
||||
read_ios: 123,
|
||||
write_ios: 456,
|
||||
total_ticks: 789,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let debug_str = format!("{:?}", stats);
|
||||
assert!(debug_str.contains("read_ios: 123"));
|
||||
assert!(debug_str.contains("write_ios: 456"));
|
||||
assert!(debug_str.contains("total_ticks: 789"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iostats_partial_eq() {
|
||||
// Test PartialEq trait implementation with various field combinations
|
||||
let base_stats = IOStats {
|
||||
read_ios: 10,
|
||||
write_ios: 20,
|
||||
read_sectors: 100,
|
||||
write_sectors: 200,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let same_stats = IOStats {
|
||||
read_ios: 10,
|
||||
write_ios: 20,
|
||||
read_sectors: 100,
|
||||
write_sectors: 200,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let different_read = IOStats {
|
||||
read_ios: 11, // Different
|
||||
write_ios: 20,
|
||||
read_sectors: 100,
|
||||
write_sectors: 200,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(base_stats, same_stats);
|
||||
assert_ne!(base_stats, different_read);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_info_path_edge_cases() {
|
||||
// Test with root directory (should work on most systems)
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let result = get_info(std::path::Path::new("/"));
|
||||
assert!(result.is_ok(), "Root directory should be accessible");
|
||||
|
||||
if let Ok(info) = result {
|
||||
assert!(info.total > 0, "Root filesystem should have non-zero total space");
|
||||
assert!(!info.fstype.is_empty(), "Root filesystem should have a type");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let result = get_info(std::path::Path::new("C:\\"));
|
||||
// On Windows, C:\ might not always exist, so we don't assert success
|
||||
if let Ok(info) = result {
|
||||
assert!(info.total > 0);
|
||||
assert!(!info.fstype.is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_info_nonexistent_path() {
|
||||
// Test with various types of invalid paths
|
||||
let invalid_paths = [
|
||||
"/this/path/definitely/does/not/exist/anywhere",
|
||||
"/dev/null/invalid", // /dev/null is a file, not a directory
|
||||
"", // Empty path
|
||||
];
|
||||
|
||||
for invalid_path in &invalid_paths {
|
||||
let result = get_info(std::path::Path::new(invalid_path));
|
||||
assert!(result.is_err(), "Invalid path should return error: {}", invalid_path);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_same_disk_edge_cases() {
|
||||
// Test with same path (should always be true)
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let path_str = temp_dir.path().to_str().unwrap();
|
||||
|
||||
let result = same_disk(path_str, path_str);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap(), "Same path should be on same disk");
|
||||
|
||||
// Test with parent and child directories (should be on same disk)
|
||||
let child_dir = temp_dir.path().join("child");
|
||||
std::fs::create_dir(&child_dir).unwrap();
|
||||
let child_path = child_dir.to_str().unwrap();
|
||||
|
||||
let result = same_disk(path_str, child_path);
|
||||
assert!(result.is_ok());
|
||||
assert!(result.unwrap(), "Parent and child should be on same disk");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_same_disk_invalid_paths() {
|
||||
// Test with invalid paths
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let valid_path = temp_dir.path().to_str().unwrap();
|
||||
let invalid_path = "/this/path/does/not/exist";
|
||||
|
||||
let result1 = same_disk(valid_path, invalid_path);
|
||||
assert!(result1.is_err(), "Should fail with one invalid path");
|
||||
|
||||
let result2 = same_disk(invalid_path, valid_path);
|
||||
assert!(result2.is_err(), "Should fail with one invalid path");
|
||||
|
||||
let result3 = same_disk(invalid_path, invalid_path);
|
||||
assert!(result3.is_err(), "Should fail with both invalid paths");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iostats_field_ranges() {
|
||||
// Test that IOStats can handle large values
|
||||
let large_stats = IOStats {
|
||||
read_ios: u64::MAX,
|
||||
write_ios: u64::MAX,
|
||||
read_sectors: u64::MAX,
|
||||
write_sectors: u64::MAX,
|
||||
total_ticks: u64::MAX,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Should be able to create and compare
|
||||
let another_large = IOStats {
|
||||
read_ios: u64::MAX,
|
||||
write_ios: u64::MAX,
|
||||
read_sectors: u64::MAX,
|
||||
write_sectors: u64::MAX,
|
||||
total_ticks: u64::MAX,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(large_stats, another_large);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_drive_stats_error_handling() {
|
||||
// Test with potentially invalid major/minor numbers
|
||||
// Note: This might succeed on some systems, so we just ensure it doesn't panic
|
||||
let result1 = get_drive_stats(999, 999);
|
||||
// Don't assert success/failure as it's platform-dependent
|
||||
let _ = result1;
|
||||
|
||||
let result2 = get_drive_stats(u32::MAX, u32::MAX);
|
||||
let _ = result2;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_unix_specific_paths() {
|
||||
// Test Unix-specific paths
|
||||
let unix_paths = ["/tmp", "/var", "/usr"];
|
||||
|
||||
for path in &unix_paths {
|
||||
if std::path::Path::new(path).exists() {
|
||||
let result = get_info(std::path::Path::new(path));
|
||||
if result.is_ok() {
|
||||
let info = result.unwrap();
|
||||
assert!(info.total > 0, "Path {} should have non-zero total space", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iostats_clone_and_copy() {
|
||||
// Test that IOStats implements Clone (if it does)
|
||||
let original = IOStats {
|
||||
read_ios: 42,
|
||||
write_ios: 84,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Test Debug formatting with non-default values
|
||||
let debug_output = format!("{:?}", original);
|
||||
assert!(debug_output.contains("42"));
|
||||
assert!(debug_output.contains("84"));
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use super::IOStats;
|
||||
use crate::disk::Info;
|
||||
use common::error::Result;
|
||||
use nix::sys::{stat::stat, statfs::statfs};
|
||||
use std::io::Error;
|
||||
use std::path::Path;
|
||||
|
||||
/// returns total and free bytes available in a directory, e.g. `/`.
|
||||
pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
let stat = statfs(p.as_ref())?;
|
||||
|
||||
let bsize = stat.block_size() as u64;
|
||||
let bfree = stat.blocks_free();
|
||||
let bavail = stat.blocks_available();
|
||||
let blocks = stat.blocks();
|
||||
|
||||
let reserved = match bfree.checked_sub(bavail) {
|
||||
Some(reserved) => reserved,
|
||||
None => {
|
||||
return Err(Error::other(format!(
|
||||
"detected f_bavail space ({}) > f_bfree space ({}), fs corruption at ({}). please run fsck",
|
||||
bavail,
|
||||
bfree,
|
||||
p.as_ref().display()
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let total = match blocks.checked_sub(reserved) {
|
||||
Some(total) => total * bsize,
|
||||
None => {
|
||||
return Err(Error::other(format!(
|
||||
"detected reserved space ({}) > blocks space ({}), fs corruption at ({}). please run fsck",
|
||||
reserved,
|
||||
blocks,
|
||||
p.as_ref().display()
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let free = bavail * bsize;
|
||||
let used = match total.checked_sub(free) {
|
||||
Some(used) => used,
|
||||
None => {
|
||||
return Err(Error::other(format!(
|
||||
"detected free space ({}) > total drive space ({}), fs corruption at ({}). please run fsck",
|
||||
free,
|
||||
total,
|
||||
p.as_ref().display()
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Info {
|
||||
total,
|
||||
free,
|
||||
used,
|
||||
files: stat.files(),
|
||||
ffree: stat.files_free(),
|
||||
fstype: stat.filesystem_type_name().to_string(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn same_disk(disk1: &str, disk2: &str) -> Result<bool> {
|
||||
let stat1 = stat(disk1)?;
|
||||
let stat2 = stat(disk2)?;
|
||||
|
||||
Ok(stat1.st_dev == stat2.st_dev)
|
||||
}
|
||||
|
||||
pub fn get_drive_stats(_major: u32, _minor: u32) -> Result<IOStats> {
|
||||
Ok(IOStats::default())
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
#![allow(unsafe_code)] // TODO: audit unsafe code
|
||||
|
||||
use super::IOStats;
|
||||
use crate::disk::Info;
|
||||
use common::error::Result;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::mem;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
use winapi::shared::minwindef::{DWORD, MAX_PATH};
|
||||
use winapi::shared::ntdef::ULARGE_INTEGER;
|
||||
use winapi::um::fileapi::{GetDiskFreeSpaceExW, GetDiskFreeSpaceW, GetVolumeInformationW, GetVolumePathNameW};
|
||||
use winapi::um::winnt::{LPCWSTR, WCHAR};
|
||||
|
||||
/// returns total and free bytes available in a directory, e.g. `C:\`.
|
||||
pub fn get_info(p: impl AsRef<Path>) -> Result<Info> {
|
||||
let path_wide: Vec<WCHAR> = p
|
||||
.as_ref()
|
||||
.canonicalize()?
|
||||
.into_os_string()
|
||||
.encode_wide()
|
||||
.chain(std::iter::once(0)) // Null-terminate the string
|
||||
.collect();
|
||||
|
||||
let mut lp_free_bytes_available: ULARGE_INTEGER = unsafe { mem::zeroed() };
|
||||
let mut lp_total_number_of_bytes: ULARGE_INTEGER = unsafe { mem::zeroed() };
|
||||
let mut lp_total_number_of_free_bytes: ULARGE_INTEGER = unsafe { mem::zeroed() };
|
||||
|
||||
let success = unsafe {
|
||||
GetDiskFreeSpaceExW(
|
||||
path_wide.as_ptr(),
|
||||
&mut lp_free_bytes_available,
|
||||
&mut lp_total_number_of_bytes,
|
||||
&mut lp_total_number_of_free_bytes,
|
||||
)
|
||||
};
|
||||
if success == 0 {
|
||||
return Err(Error::last_os_error().into());
|
||||
}
|
||||
|
||||
let total = unsafe { *lp_total_number_of_bytes.QuadPart() };
|
||||
let free = unsafe { *lp_total_number_of_free_bytes.QuadPart() };
|
||||
|
||||
if free > total {
|
||||
return Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"detected free space ({}) > total drive space ({}), fs corruption at ({}). please run 'fsck'",
|
||||
free,
|
||||
total,
|
||||
p.as_ref().display()
|
||||
),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut lp_sectors_per_cluster: DWORD = 0;
|
||||
let mut lp_bytes_per_sector: DWORD = 0;
|
||||
let mut lp_number_of_free_clusters: DWORD = 0;
|
||||
let mut lp_total_number_of_clusters: DWORD = 0;
|
||||
|
||||
let success = unsafe {
|
||||
GetDiskFreeSpaceW(
|
||||
path_wide.as_ptr(),
|
||||
&mut lp_sectors_per_cluster,
|
||||
&mut lp_bytes_per_sector,
|
||||
&mut lp_number_of_free_clusters,
|
||||
&mut lp_total_number_of_clusters,
|
||||
)
|
||||
};
|
||||
if success == 0 {
|
||||
return Err(Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(Info {
|
||||
total,
|
||||
free,
|
||||
used: total - free,
|
||||
files: lp_total_number_of_clusters as u64,
|
||||
ffree: lp_number_of_free_clusters as u64,
|
||||
fstype: get_fs_type(&path_wide)?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// returns leading volume name.
|
||||
fn get_volume_name(v: &[WCHAR]) -> Result<LPCWSTR> {
|
||||
let volume_name_size: DWORD = MAX_PATH as _;
|
||||
let mut lp_volume_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH];
|
||||
|
||||
let success = unsafe { GetVolumePathNameW(v.as_ptr(), lp_volume_name_buffer.as_mut_ptr(), volume_name_size) };
|
||||
|
||||
if success == 0 {
|
||||
return Err(Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(lp_volume_name_buffer.as_ptr())
|
||||
}
|
||||
|
||||
fn utf16_to_string(v: &[WCHAR]) -> String {
|
||||
let len = v.iter().position(|&x| x == 0).unwrap_or(v.len());
|
||||
String::from_utf16_lossy(&v[..len])
|
||||
}
|
||||
|
||||
/// returns the filesystem type of the underlying mounted filesystem
|
||||
fn get_fs_type(p: &[WCHAR]) -> Result<String> {
|
||||
let path = get_volume_name(p)?;
|
||||
|
||||
let volume_name_size: DWORD = MAX_PATH as _;
|
||||
let n_file_system_name_size: DWORD = MAX_PATH as _;
|
||||
|
||||
let mut lp_volume_serial_number: DWORD = 0;
|
||||
let mut lp_maximum_component_length: DWORD = 0;
|
||||
let mut lp_file_system_flags: DWORD = 0;
|
||||
|
||||
let mut lp_volume_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH];
|
||||
let mut lp_file_system_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH];
|
||||
|
||||
let success = unsafe {
|
||||
GetVolumeInformationW(
|
||||
path,
|
||||
lp_volume_name_buffer.as_mut_ptr(),
|
||||
volume_name_size,
|
||||
&mut lp_volume_serial_number,
|
||||
&mut lp_maximum_component_length,
|
||||
&mut lp_file_system_flags,
|
||||
lp_file_system_name_buffer.as_mut_ptr(),
|
||||
n_file_system_name_size,
|
||||
)
|
||||
};
|
||||
|
||||
if success == 0 {
|
||||
return Err(Error::last_os_error().into());
|
||||
}
|
||||
|
||||
Ok(utf16_to_string(&lp_file_system_name_buffer))
|
||||
}
|
||||
|
||||
pub fn same_disk(_add_extensiondisk1: &str, _disk2: &str) -> Result<bool> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn get_drive_stats(_major: u32, _minor: u32) -> Result<IOStats> {
|
||||
Ok(IOStats::default())
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub const GLOBAL_DIR_SUFFIX: &str = "__XLDIR__";
|
||||
|
||||
pub const SLASH_SEPARATOR: &str = "/";
|
||||
|
||||
pub const GLOBAL_DIR_SUFFIX_WITH_SLASH: &str = "__XLDIR__/";
|
||||
|
||||
pub fn has_suffix(s: &str, suffix: &str) -> bool {
|
||||
if cfg!(target_os = "windows") {
|
||||
s.to_lowercase().ends_with(&suffix.to_lowercase())
|
||||
} else {
|
||||
s.ends_with(suffix)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode_dir_object(object: &str) -> String {
|
||||
if has_suffix(object, SLASH_SEPARATOR) {
|
||||
format!("{}{}", object.trim_end_matches(SLASH_SEPARATOR), GLOBAL_DIR_SUFFIX)
|
||||
} else {
|
||||
object.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_dir_object(object: &str) -> bool {
|
||||
let obj = encode_dir_object(object);
|
||||
obj.ends_with(GLOBAL_DIR_SUFFIX)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn decode_dir_object(object: &str) -> String {
|
||||
if has_suffix(object, GLOBAL_DIR_SUFFIX) {
|
||||
format!("{}{}", object.trim_end_matches(GLOBAL_DIR_SUFFIX), SLASH_SEPARATOR)
|
||||
} else {
|
||||
object.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn retain_slash(s: &str) -> String {
|
||||
if s.is_empty() {
|
||||
return s.to_string();
|
||||
}
|
||||
if s.ends_with(SLASH_SEPARATOR) {
|
||||
s.to_string()
|
||||
} else {
|
||||
format!("{}{}", s, SLASH_SEPARATOR)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strings_has_prefix_fold(s: &str, prefix: &str) -> bool {
|
||||
s.len() >= prefix.len() && (s[..prefix.len()] == *prefix || s[..prefix.len()].eq_ignore_ascii_case(prefix))
|
||||
}
|
||||
|
||||
pub fn has_prefix(s: &str, prefix: &str) -> bool {
|
||||
if cfg!(target_os = "windows") {
|
||||
return strings_has_prefix_fold(s, prefix);
|
||||
}
|
||||
|
||||
s.starts_with(prefix)
|
||||
}
|
||||
|
||||
pub fn path_join(elem: &[PathBuf]) -> PathBuf {
|
||||
let mut joined_path = PathBuf::new();
|
||||
|
||||
for path in elem {
|
||||
joined_path.push(path);
|
||||
}
|
||||
|
||||
joined_path
|
||||
}
|
||||
|
||||
pub fn path_join_buf(elements: &[&str]) -> String {
|
||||
let trailing_slash = !elements.is_empty() && elements.last().unwrap().ends_with(SLASH_SEPARATOR);
|
||||
|
||||
let mut dst = String::new();
|
||||
let mut added = 0;
|
||||
|
||||
for e in elements {
|
||||
if added > 0 || !e.is_empty() {
|
||||
if added > 0 {
|
||||
dst.push_str(SLASH_SEPARATOR);
|
||||
}
|
||||
dst.push_str(e);
|
||||
added += e.len();
|
||||
}
|
||||
}
|
||||
|
||||
let result = dst.to_string();
|
||||
let cpath = Path::new(&result).components().collect::<PathBuf>();
|
||||
let clean_path = cpath.to_string_lossy();
|
||||
|
||||
if trailing_slash {
|
||||
return format!("{}{}", clean_path, SLASH_SEPARATOR);
|
||||
}
|
||||
clean_path.to_string()
|
||||
}
|
||||
|
||||
pub fn path_to_bucket_object_with_base_path(bash_path: &str, path: &str) -> (String, String) {
|
||||
let path = path.trim_start_matches(bash_path).trim_start_matches(SLASH_SEPARATOR);
|
||||
if let Some(m) = path.find(SLASH_SEPARATOR) {
|
||||
return (path[..m].to_string(), path[m + SLASH_SEPARATOR.len()..].to_string());
|
||||
}
|
||||
|
||||
(path.to_string(), "".to_string())
|
||||
}
|
||||
|
||||
pub fn path_to_bucket_object(s: &str) -> (String, String) {
|
||||
path_to_bucket_object_with_base_path("", s)
|
||||
}
|
||||
|
||||
pub fn base_dir_from_prefix(prefix: &str) -> String {
|
||||
let mut base_dir = dir(prefix).to_owned();
|
||||
if base_dir == "." || base_dir == "./" || base_dir == "/" {
|
||||
base_dir = "".to_owned();
|
||||
}
|
||||
if !prefix.contains('/') {
|
||||
base_dir = "".to_owned();
|
||||
}
|
||||
if !base_dir.is_empty() && !base_dir.ends_with(SLASH_SEPARATOR) {
|
||||
base_dir.push_str(SLASH_SEPARATOR);
|
||||
}
|
||||
base_dir
|
||||
}
|
||||
|
||||
pub struct LazyBuf {
|
||||
s: String,
|
||||
buf: Option<Vec<u8>>,
|
||||
w: usize,
|
||||
}
|
||||
|
||||
impl LazyBuf {
|
||||
pub fn new(s: String) -> Self {
|
||||
LazyBuf { s, buf: None, w: 0 }
|
||||
}
|
||||
|
||||
pub fn index(&self, i: usize) -> u8 {
|
||||
if let Some(ref buf) = self.buf {
|
||||
buf[i]
|
||||
} else {
|
||||
self.s.as_bytes()[i]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, c: u8) {
|
||||
if self.buf.is_none() {
|
||||
if self.w < self.s.len() && self.s.as_bytes()[self.w] == c {
|
||||
self.w += 1;
|
||||
return;
|
||||
}
|
||||
let mut new_buf = vec![0; self.s.len()];
|
||||
new_buf[..self.w].copy_from_slice(&self.s.as_bytes()[..self.w]);
|
||||
self.buf = Some(new_buf);
|
||||
}
|
||||
|
||||
if let Some(ref mut buf) = self.buf {
|
||||
buf[self.w] = c;
|
||||
self.w += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn string(&self) -> String {
|
||||
if let Some(ref buf) = self.buf {
|
||||
String::from_utf8(buf[..self.w].to_vec()).unwrap()
|
||||
} else {
|
||||
self.s[..self.w].to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean(path: &str) -> String {
|
||||
if path.is_empty() {
|
||||
return ".".to_string();
|
||||
}
|
||||
|
||||
let rooted = path.starts_with('/');
|
||||
let n = path.len();
|
||||
let mut out = LazyBuf::new(path.to_string());
|
||||
let mut r = 0;
|
||||
let mut dotdot = 0;
|
||||
|
||||
if rooted {
|
||||
out.append(b'/');
|
||||
r = 1;
|
||||
dotdot = 1;
|
||||
}
|
||||
|
||||
while r < n {
|
||||
match path.as_bytes()[r] {
|
||||
b'/' => {
|
||||
// Empty path element
|
||||
r += 1;
|
||||
}
|
||||
b'.' if r + 1 == n || path.as_bytes()[r + 1] == b'/' => {
|
||||
// . element
|
||||
r += 1;
|
||||
}
|
||||
b'.' if path.as_bytes()[r + 1] == b'.' && (r + 2 == n || path.as_bytes()[r + 2] == b'/') => {
|
||||
// .. element: remove to last /
|
||||
r += 2;
|
||||
|
||||
if out.w > dotdot {
|
||||
// Can backtrack
|
||||
out.w -= 1;
|
||||
while out.w > dotdot && out.index(out.w) != b'/' {
|
||||
out.w -= 1;
|
||||
}
|
||||
} else if !rooted {
|
||||
// Cannot backtrack but not rooted, so append .. element.
|
||||
if out.w > 0 {
|
||||
out.append(b'/');
|
||||
}
|
||||
out.append(b'.');
|
||||
out.append(b'.');
|
||||
dotdot = out.w;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Real path element.
|
||||
// Add slash if needed
|
||||
if (rooted && out.w != 1) || (!rooted && out.w != 0) {
|
||||
out.append(b'/');
|
||||
}
|
||||
|
||||
// Copy element
|
||||
while r < n && path.as_bytes()[r] != b'/' {
|
||||
out.append(path.as_bytes()[r]);
|
||||
r += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Turn empty string into "."
|
||||
if out.w == 0 {
|
||||
return ".".to_string();
|
||||
}
|
||||
|
||||
out.string()
|
||||
}
|
||||
|
||||
pub fn split(path: &str) -> (&str, &str) {
|
||||
// Find the last occurrence of the '/' character
|
||||
if let Some(i) = path.rfind('/') {
|
||||
// Return the directory (up to and including the last '/') and the file name
|
||||
return (&path[..i + 1], &path[i + 1..]);
|
||||
}
|
||||
// If no '/' is found, return an empty string for the directory and the whole path as the file name
|
||||
(path, "")
|
||||
}
|
||||
|
||||
pub fn dir(path: &str) -> String {
|
||||
let (a, _) = split(path);
|
||||
clean(a)
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_base_dir_from_prefix() {
|
||||
let a = "da/";
|
||||
println!("---- in {}", a);
|
||||
let a = base_dir_from_prefix(a);
|
||||
println!("---- out {}", a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clean() {
|
||||
assert_eq!(clean(""), ".");
|
||||
assert_eq!(clean("abc"), "abc");
|
||||
assert_eq!(clean("abc/def"), "abc/def");
|
||||
assert_eq!(clean("a/b/c"), "a/b/c");
|
||||
assert_eq!(clean("."), ".");
|
||||
assert_eq!(clean(".."), "..");
|
||||
assert_eq!(clean("../.."), "../..");
|
||||
assert_eq!(clean("../../abc"), "../../abc");
|
||||
assert_eq!(clean("/abc"), "/abc");
|
||||
assert_eq!(clean("/"), "/");
|
||||
assert_eq!(clean("abc/"), "abc");
|
||||
assert_eq!(clean("abc/def/"), "abc/def");
|
||||
assert_eq!(clean("a/b/c/"), "a/b/c");
|
||||
assert_eq!(clean("./"), ".");
|
||||
assert_eq!(clean("../"), "..");
|
||||
assert_eq!(clean("../../"), "../..");
|
||||
assert_eq!(clean("/abc/"), "/abc");
|
||||
assert_eq!(clean("abc//def//ghi"), "abc/def/ghi");
|
||||
assert_eq!(clean("//abc"), "/abc");
|
||||
assert_eq!(clean("///abc"), "/abc");
|
||||
assert_eq!(clean("//abc//"), "/abc");
|
||||
assert_eq!(clean("abc//"), "abc");
|
||||
assert_eq!(clean("abc/./def"), "abc/def");
|
||||
assert_eq!(clean("/./abc/def"), "/abc/def");
|
||||
assert_eq!(clean("abc/."), "abc");
|
||||
assert_eq!(clean("abc/./../def"), "def");
|
||||
assert_eq!(clean("abc//./../def"), "def");
|
||||
assert_eq!(clean("abc/../../././../def"), "../../def");
|
||||
|
||||
assert_eq!(clean("abc/def/ghi/../jkl"), "abc/def/jkl");
|
||||
assert_eq!(clean("abc/def/../ghi/../jkl"), "abc/jkl");
|
||||
assert_eq!(clean("abc/def/.."), "abc");
|
||||
assert_eq!(clean("abc/def/../.."), ".");
|
||||
assert_eq!(clean("/abc/def/../.."), "/");
|
||||
assert_eq!(clean("abc/def/../../.."), "..");
|
||||
assert_eq!(clean("/abc/def/../../.."), "/");
|
||||
assert_eq!(clean("abc/def/../../../ghi/jkl/../../../mno"), "../../mno");
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
use nix::sys::{
|
||||
stat::{major, minor, stat},
|
||||
statfs::{statfs, FsType},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
disk::Info,
|
||||
error::{Error, Result},
|
||||
};
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
|
||||
lazy_static! {
|
||||
static ref FS_TYPE_TO_STRING_MAP: HashMap<&'static str, &'static str> = {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("1021994", "TMPFS");
|
||||
m.insert("137d", "EXT");
|
||||
m.insert("4244", "HFS");
|
||||
m.insert("4d44", "MSDOS");
|
||||
m.insert("52654973", "REISERFS");
|
||||
m.insert("5346544e", "NTFS");
|
||||
m.insert("58465342", "XFS");
|
||||
m.insert("61756673", "AUFS");
|
||||
m.insert("6969", "NFS");
|
||||
m.insert("ef51", "EXT2OLD");
|
||||
m.insert("ef53", "EXT4");
|
||||
m.insert("f15f", "ecryptfs");
|
||||
m.insert("794c7630", "overlayfs");
|
||||
m.insert("2fc12fc1", "zfs");
|
||||
m.insert("ff534d42", "cifs");
|
||||
m.insert("53464846", "wslfs");
|
||||
m
|
||||
};
|
||||
}
|
||||
|
||||
fn get_fs_type(ftype: FsType) -> String {
|
||||
let binding = format!("{:?}", ftype);
|
||||
let fs_type_hex = binding.as_str();
|
||||
match FS_TYPE_TO_STRING_MAP.get(fs_type_hex) {
|
||||
Some(fs_type_string) => fs_type_string.to_string(),
|
||||
None => "UNKNOWN".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_info(path: &str) -> Result<Info> {
|
||||
let statfs = statfs(path)?;
|
||||
let reserved_blocks = statfs.blocks_free() - statfs.blocks_available();
|
||||
let mut info = Info {
|
||||
total: statfs.block_size() as u64 * (statfs.blocks() - reserved_blocks),
|
||||
free: statfs.blocks() as u64 * statfs.blocks_available(),
|
||||
files: statfs.files(),
|
||||
ffree: statfs.files_free(),
|
||||
fstype: get_fs_type(statfs.filesystem_type()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let stat = stat(path)?;
|
||||
let dev_id = stat.st_dev as u64;
|
||||
info.major = major(dev_id);
|
||||
info.minor = minor(dev_id);
|
||||
|
||||
if info.free > info.total {
|
||||
return Err(Error::from_string(format!(
|
||||
"detected free space {} > total drive space {}, fs corruption at {}. please run 'fsck'",
|
||||
info.free, info.total, path
|
||||
)));
|
||||
}
|
||||
|
||||
info.used = info.total - info.free;
|
||||
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
pub fn same_disk(disk1: &str, disk2: &str) -> Result<bool> {
|
||||
let stat1 = stat(disk1)?;
|
||||
let stat2 = stat(disk2)?;
|
||||
|
||||
Ok(stat1.st_dev == stat2.st_dev)
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
use crate::disk::RUSTFS_META_BUCKET;
|
||||
|
||||
pub fn match_simple(pattern: &str, name: &str) -> bool {
|
||||
if pattern.is_empty() {
|
||||
return name == pattern;
|
||||
}
|
||||
if pattern == "*" {
|
||||
return true;
|
||||
}
|
||||
// Do an extended wildcard '*' and '?' match.
|
||||
deep_match_rune(name.as_bytes(), pattern.as_bytes(), true)
|
||||
}
|
||||
|
||||
pub fn match_pattern(pattern: &str, name: &str) -> bool {
|
||||
if pattern.is_empty() {
|
||||
return name == pattern;
|
||||
}
|
||||
if pattern == "*" {
|
||||
return true;
|
||||
}
|
||||
// Do an extended wildcard '*' and '?' match.
|
||||
deep_match_rune(name.as_bytes(), pattern.as_bytes(), false)
|
||||
}
|
||||
|
||||
fn deep_match_rune(str_: &[u8], pattern: &[u8], simple: bool) -> bool {
|
||||
let (mut str_, mut pattern) = (str_, pattern);
|
||||
while !pattern.is_empty() {
|
||||
match pattern[0] as char {
|
||||
'*' => {
|
||||
return if pattern.len() == 1 {
|
||||
true
|
||||
} else {
|
||||
deep_match_rune(str_, &pattern[1..], simple)
|
||||
|| (!str_.is_empty() && deep_match_rune(&str_[1..], pattern, simple))
|
||||
}
|
||||
}
|
||||
'?' => {
|
||||
if str_.is_empty() {
|
||||
return simple;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if str_.is_empty() || str_[0] != pattern[0] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
str_ = &str_[1..];
|
||||
pattern = &pattern[1..];
|
||||
}
|
||||
str_.is_empty() && pattern.is_empty()
|
||||
}
|
||||
|
||||
pub fn match_as_pattern_prefix(pattern: &str, text: &str) -> bool {
|
||||
let mut i = 0;
|
||||
while i < text.len() && i < pattern.len() {
|
||||
match pattern.as_bytes()[i] as char {
|
||||
'*' => return true,
|
||||
'?' => i += 1,
|
||||
_ => {
|
||||
if pattern.as_bytes()[i] != text.as_bytes()[i] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
text.len() <= pattern.len()
|
||||
}
|
||||
|
||||
pub fn is_rustfs_meta_bucket_name(bucket: &str) -> bool {
|
||||
bucket.starts_with(RUSTFS_META_BUCKET)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use s3s::xml;
|
||||
|
||||
pub fn deserialize<T>(input: &[u8]) -> xml::DeResult<T>
|
||||
where
|
||||
T: for<'xml> xml::Deserialize<'xml>,
|
||||
{
|
||||
let mut d = xml::Deserializer::new(input);
|
||||
let ans = T::deserialize(&mut d)?;
|
||||
d.expect_eof()?;
|
||||
Ok(ans)
|
||||
}
|
||||
|
||||
pub fn serialize_content<T: xml::SerializeContent>(val: &T) -> xml::SerResult<String> {
|
||||
let mut buf = Vec::with_capacity(256);
|
||||
{
|
||||
let mut ser = xml::Serializer::new(&mut buf);
|
||||
val.serialize_content(&mut ser)?;
|
||||
}
|
||||
Ok(String::from_utf8(buf).unwrap())
|
||||
}
|
||||
|
||||
pub fn serialize<T: xml::Serialize>(val: &T) -> xml::SerResult<Vec<u8>> {
|
||||
let mut buf = Vec::with_capacity(256);
|
||||
{
|
||||
let mut ser = xml::Serializer::new(&mut buf);
|
||||
val.serialize(&mut ser)?;
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
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";
|
||||
Reference in New Issue
Block a user