mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
mc test ok
This commit is contained in:
Generated
+1
@@ -7710,6 +7710,7 @@ dependencies = [
|
||||
"rustfs-event-notifier",
|
||||
"rustfs-filemeta",
|
||||
"rustfs-obs",
|
||||
"rustfs-rio",
|
||||
"rustfs-utils",
|
||||
"rustfs-zip",
|
||||
"rustls 0.23.27",
|
||||
|
||||
@@ -11,6 +11,13 @@ pub struct Error {
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn other<E>(error: E) -> Self
|
||||
where
|
||||
E: std::fmt::Display + Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
Self::from_std_error(error.into())
|
||||
}
|
||||
|
||||
/// Create a new error from a `std::error::Error`.
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
[package]
|
||||
name = "rustfs-disk"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
url.workspace = true
|
||||
rustfs-filemeta.workspace = true
|
||||
rustfs-error.workspace = true
|
||||
rustfs-rio.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
uuid.workspace = true
|
||||
tracing.workspace = true
|
||||
tokio.workspace = true
|
||||
path-absolutize = "3.1.1"
|
||||
rustfs-utils = {workspace = true, features =["net"]}
|
||||
async-trait.workspace = true
|
||||
time.workspace = true
|
||||
rustfs-metacache.workspace = true
|
||||
futures.workspace = true
|
||||
madmin.workspace = true
|
||||
protos.workspace = true
|
||||
tonic.workspace = true
|
||||
urlencoding = "2.1.3"
|
||||
rmp-serde.workspace = true
|
||||
http.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,667 +0,0 @@
|
||||
use crate::{endpoint::Endpoint, local::LocalDisk, remote::RemoteDisk};
|
||||
use madmin::DiskMetrics;
|
||||
use rustfs_error::{Error, Result};
|
||||
use rustfs_filemeta::{FileInfo, FileInfoVersions, RawFileInfo};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Debug, path::PathBuf, sync::Arc};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub const RUSTFS_META_BUCKET: &str = ".rustfs.sys";
|
||||
pub const RUSTFS_META_MULTIPART_BUCKET: &str = ".rustfs.sys/multipart";
|
||||
pub const RUSTFS_META_TMP_BUCKET: &str = ".rustfs.sys/tmp";
|
||||
pub const RUSTFS_META_TMP_DELETED_BUCKET: &str = ".rustfs.sys/tmp/.trash";
|
||||
pub const BUCKET_META_PREFIX: &str = "buckets";
|
||||
pub const FORMAT_CONFIG_FILE: &str = "format.json";
|
||||
pub const STORAGE_FORMAT_FILE: &str = "xl.meta";
|
||||
pub const STORAGE_FORMAT_FILE_BACKUP: &str = "xl.meta.bkp";
|
||||
|
||||
pub type DiskStore = Arc<Disk>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Disk {
|
||||
Local(LocalDisk),
|
||||
Remote(RemoteDisk),
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl DiskAPI for Disk {
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.to_string(),
|
||||
Disk::Remote(remote_disk) => remote_disk.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn is_local(&self) -> bool {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.is_local(),
|
||||
Disk::Remote(remote_disk) => remote_disk.is_local(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn host_name(&self) -> String {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.host_name(),
|
||||
Disk::Remote(remote_disk) => remote_disk.host_name(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn is_online(&self) -> bool {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.is_online().await,
|
||||
Disk::Remote(remote_disk) => remote_disk.is_online().await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn endpoint(&self) -> Endpoint {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.endpoint(),
|
||||
Disk::Remote(remote_disk) => remote_disk.endpoint(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn close(&self) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.close().await,
|
||||
Disk::Remote(remote_disk) => remote_disk.close().await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn path(&self) -> PathBuf {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.path(),
|
||||
Disk::Remote(remote_disk) => remote_disk.path(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn get_disk_location(&self) -> DiskLocation {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.get_disk_location(),
|
||||
Disk::Remote(remote_disk) => remote_disk.get_disk_location(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn get_disk_id(&self) -> Result<Option<Uuid>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.get_disk_id().await,
|
||||
Disk::Remote(remote_disk) => remote_disk.get_disk_id().await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn set_disk_id(&self, id: Option<Uuid>) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.set_disk_id(id).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.set_disk_id(id).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_all(&self, volume: &str, path: &str) -> Result<Vec<u8>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.read_all(volume, path).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.read_all(volume, path).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn write_all(&self, volume: &str, path: &str, data: Vec<u8>) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.write_all(volume, path, data).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.write_all(volume, path, data).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete(&self, volume: &str, path: &str, opt: DeleteOptions) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.delete(volume, path, opt).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.delete(volume, path, opt).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn verify_file(&self, volume: &str, path: &str, fi: &FileInfo) -> Result<CheckPartsResp> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.verify_file(volume, path, fi).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.verify_file(volume, path, fi).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn check_parts(&self, volume: &str, path: &str, fi: &FileInfo) -> Result<CheckPartsResp> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.check_parts(volume, path, fi).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.check_parts(volume, path, fi).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn rename_part(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str, meta: Vec<u8>) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.rename_part(src_volume, src_path, dst_volume, dst_path, meta).await,
|
||||
Disk::Remote(remote_disk) => {
|
||||
remote_disk
|
||||
.rename_part(src_volume, src_path, dst_volume, dst_path, meta)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn rename_file(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.rename_file(src_volume, src_path, dst_volume, dst_path).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.rename_file(src_volume, src_path, dst_volume, dst_path).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn create_file(&self, _origvolume: &str, volume: &str, path: &str, _file_size: usize) -> Result<Box<dyn AsyncWrite>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.create_file(_origvolume, volume, path, _file_size).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.create_file(_origvolume, volume, path, _file_size).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn append_file(&self, volume: &str, path: &str) -> Result<Box<dyn AsyncWrite>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.append_file(volume, path).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.append_file(volume, path).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_file(&self, volume: &str, path: &str) -> Result<Box<dyn AsyncRead>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.read_file(volume, path).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.read_file(volume, path).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_file_stream(&self, volume: &str, path: &str, offset: usize, length: usize) -> Result<Box<dyn AsyncRead>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.read_file_stream(volume, path, offset, length).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.read_file_stream(volume, path, offset, length).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn list_dir(&self, _origvolume: &str, volume: &str, _dir_path: &str, _count: i32) -> Result<Vec<String>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.list_dir(_origvolume, volume, _dir_path, _count).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.list_dir(_origvolume, volume, _dir_path, _count).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, wr))]
|
||||
async fn walk_dir<W: AsyncWrite + Unpin + Send + Sync + 'static>(&self, opts: WalkDirOptions, wr: &mut W) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.walk_dir(opts, wr).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.walk_dir(opts, wr).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn rename_data(
|
||||
&self,
|
||||
src_volume: &str,
|
||||
src_path: &str,
|
||||
fi: FileInfo,
|
||||
dst_volume: &str,
|
||||
dst_path: &str,
|
||||
) -> Result<RenameDataResp> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.rename_data(src_volume, src_path, fi, dst_volume, dst_path).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.rename_data(src_volume, src_path, fi, dst_volume, dst_path).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn make_volumes(&self, volumes: Vec<&str>) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.make_volumes(volumes).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.make_volumes(volumes).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn make_volume(&self, volume: &str) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.make_volume(volume).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.make_volume(volume).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn list_volumes(&self) -> Result<Vec<VolumeInfo>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.list_volumes().await,
|
||||
Disk::Remote(remote_disk) => remote_disk.list_volumes().await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn stat_volume(&self, volume: &str) -> Result<VolumeInfo> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.stat_volume(volume).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.stat_volume(volume).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_paths(&self, volume: &str, paths: &[String]) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.delete_paths(volume, paths).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.delete_paths(volume, paths).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn update_metadata(&self, volume: &str, path: &str, fi: FileInfo, opts: &UpdateMetadataOpts) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.update_metadata(volume, path, fi, opts).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.update_metadata(volume, path, fi, opts).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn write_metadata(&self, _org_volume: &str, volume: &str, path: &str, fi: FileInfo) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.write_metadata(_org_volume, volume, path, fi).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.write_metadata(_org_volume, volume, path, fi).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn read_version(
|
||||
&self,
|
||||
_org_volume: &str,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
version_id: &str,
|
||||
opts: &ReadOptions,
|
||||
) -> Result<FileInfo> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.read_version(_org_volume, volume, path, version_id, opts).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.read_version(_org_volume, volume, path, version_id, opts).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_xl(&self, volume: &str, path: &str, read_data: bool) -> Result<RawFileInfo> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.read_xl(volume, path, read_data).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.read_xl(volume, path, read_data).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_version(
|
||||
&self,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
fi: FileInfo,
|
||||
force_del_marker: bool,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.delete_version(volume, path, fi, force_del_marker, opts).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.delete_version(volume, path, fi, force_del_marker, opts).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_versions(
|
||||
&self,
|
||||
volume: &str,
|
||||
versions: Vec<FileInfoVersions>,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<Vec<Option<Error>>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.delete_versions(volume, versions, opts).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.delete_versions(volume, versions, opts).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_multiple(&self, req: ReadMultipleReq) -> Result<Vec<ReadMultipleResp>> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.read_multiple(req).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.read_multiple(req).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_volume(&self, volume: &str) -> Result<()> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.delete_volume(volume).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.delete_volume(volume).await,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn disk_info(&self, opts: &DiskInfoOptions) -> Result<DiskInfo> {
|
||||
match self {
|
||||
Disk::Local(local_disk) => local_disk.disk_info(opts).await,
|
||||
Disk::Remote(remote_disk) => remote_disk.disk_info(opts).await,
|
||||
}
|
||||
}
|
||||
|
||||
// #[tracing::instrument(skip(self, cache, we_sleep, scan_mode))]
|
||||
// async fn ns_scanner(
|
||||
// &self,
|
||||
// cache: &DataUsageCache,
|
||||
// updates: Sender<DataUsageEntry>,
|
||||
// scan_mode: HealScanMode,
|
||||
// we_sleep: ShouldSleepFn,
|
||||
// ) -> Result<DataUsageCache> {
|
||||
// match self {
|
||||
// Disk::Local(local_disk) => local_disk.ns_scanner(cache, updates, scan_mode, we_sleep).await,
|
||||
// Disk::Remote(remote_disk) => remote_disk.ns_scanner(cache, updates, scan_mode, we_sleep).await,
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[tracing::instrument(skip(self))]
|
||||
// async fn healing(&self) -> Option<HealingTracker> {
|
||||
// match self {
|
||||
// Disk::Local(local_disk) => local_disk.healing().await,
|
||||
// Disk::Remote(remote_disk) => remote_disk.healing().await,
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
pub async fn new_disk(ep: &Endpoint, opt: &DiskOption) -> Result<DiskStore> {
|
||||
if ep.is_local {
|
||||
Ok(Arc::new(Disk::Local(LocalDisk::new(ep, opt.cleanup).await?)))
|
||||
} else {
|
||||
Ok(Arc::new(Disk::Remote(RemoteDisk::new(ep, opt).await?)))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait DiskAPI: Debug + Send + Sync + 'static {
|
||||
fn to_string(&self) -> String;
|
||||
async fn is_online(&self) -> bool;
|
||||
fn is_local(&self) -> bool;
|
||||
// LastConn
|
||||
fn host_name(&self) -> String;
|
||||
fn endpoint(&self) -> Endpoint;
|
||||
async fn close(&self) -> Result<()>;
|
||||
async fn get_disk_id(&self) -> Result<Option<Uuid>>;
|
||||
async fn set_disk_id(&self, id: Option<Uuid>) -> Result<()>;
|
||||
|
||||
fn path(&self) -> PathBuf;
|
||||
fn get_disk_location(&self) -> DiskLocation;
|
||||
|
||||
// Healing
|
||||
// DiskInfo
|
||||
// NSScanner
|
||||
|
||||
// Volume operations.
|
||||
async fn make_volume(&self, volume: &str) -> Result<()>;
|
||||
async fn make_volumes(&self, volume: Vec<&str>) -> Result<()>;
|
||||
async fn list_volumes(&self) -> Result<Vec<VolumeInfo>>;
|
||||
async fn stat_volume(&self, volume: &str) -> Result<VolumeInfo>;
|
||||
async fn delete_volume(&self, volume: &str) -> Result<()>;
|
||||
|
||||
// 并发边读边写 w <- MetaCacheEntry
|
||||
async fn walk_dir<W: AsyncWrite + Unpin + Send + Sync + 'static>(&self, opts: WalkDirOptions, wr: &mut W) -> Result<()>;
|
||||
|
||||
// Metadata operations
|
||||
async fn delete_version(
|
||||
&self,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
fi: FileInfo,
|
||||
force_del_marker: bool,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<()>;
|
||||
async fn delete_versions(
|
||||
&self,
|
||||
volume: &str,
|
||||
versions: Vec<FileInfoVersions>,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<Vec<Option<Error>>>;
|
||||
async fn delete_paths(&self, volume: &str, paths: &[String]) -> Result<()>;
|
||||
async fn write_metadata(&self, org_volume: &str, volume: &str, path: &str, fi: FileInfo) -> Result<()>;
|
||||
async fn update_metadata(&self, volume: &str, path: &str, fi: FileInfo, opts: &UpdateMetadataOpts) -> Result<()>;
|
||||
async fn read_version(
|
||||
&self,
|
||||
org_volume: &str,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
version_id: &str,
|
||||
opts: &ReadOptions,
|
||||
) -> Result<FileInfo>;
|
||||
async fn read_xl(&self, volume: &str, path: &str, read_data: bool) -> Result<RawFileInfo>;
|
||||
async fn rename_data(
|
||||
&self,
|
||||
src_volume: &str,
|
||||
src_path: &str,
|
||||
file_info: FileInfo,
|
||||
dst_volume: &str,
|
||||
dst_path: &str,
|
||||
) -> Result<RenameDataResp>;
|
||||
|
||||
// File operations.
|
||||
// 读目录下的所有文件、目录
|
||||
async fn list_dir(&self, origvolume: &str, volume: &str, dir_path: &str, count: i32) -> Result<Vec<String>>;
|
||||
async fn read_file(&self, volume: &str, path: &str) -> Result<Box<dyn AsyncRead>>;
|
||||
async fn read_file_stream(&self, volume: &str, path: &str, offset: usize, length: usize) -> Result<Box<dyn AsyncRead>>;
|
||||
async fn append_file(&self, volume: &str, path: &str) -> Result<Box<dyn AsyncWrite>>;
|
||||
async fn create_file(&self, origvolume: &str, volume: &str, path: &str, file_size: usize) -> Result<Box<dyn AsyncWrite>>;
|
||||
// ReadFileStream
|
||||
async fn rename_file(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str) -> Result<()>;
|
||||
async fn rename_part(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str, meta: Vec<u8>) -> Result<()>;
|
||||
async fn delete(&self, volume: &str, path: &str, opt: DeleteOptions) -> Result<()>;
|
||||
// VerifyFile
|
||||
async fn verify_file(&self, volume: &str, path: &str, fi: &FileInfo) -> Result<CheckPartsResp>;
|
||||
// CheckParts
|
||||
async fn check_parts(&self, volume: &str, path: &str, fi: &FileInfo) -> Result<CheckPartsResp>;
|
||||
// StatInfoFile
|
||||
// ReadParts
|
||||
async fn read_multiple(&self, req: ReadMultipleReq) -> Result<Vec<ReadMultipleResp>>;
|
||||
// CleanAbandonedData
|
||||
async fn write_all(&self, volume: &str, path: &str, data: Vec<u8>) -> Result<()>;
|
||||
async fn read_all(&self, volume: &str, path: &str) -> Result<Vec<u8>>;
|
||||
async fn disk_info(&self, opts: &DiskInfoOptions) -> Result<DiskInfo>;
|
||||
// async fn ns_scanner(
|
||||
// &self,
|
||||
// cache: &DataUsageCache,
|
||||
// updates: Sender<DataUsageEntry>,
|
||||
// scan_mode: HealScanMode,
|
||||
// we_sleep: ShouldSleepFn,
|
||||
// ) -> Result<DataUsageCache>;
|
||||
// async fn healing(&self) -> Option<HealingTracker>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct CheckPartsResp {
|
||||
pub results: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct UpdateMetadataOpts {
|
||||
pub no_persistence: bool,
|
||||
}
|
||||
|
||||
pub struct DiskLocation {
|
||||
pub pool_idx: Option<usize>,
|
||||
pub set_idx: Option<usize>,
|
||||
pub disk_idx: Option<usize>,
|
||||
}
|
||||
|
||||
impl DiskLocation {
|
||||
pub fn valid(&self) -> bool {
|
||||
self.pool_idx.is_some() && self.set_idx.is_some() && self.disk_idx.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct DiskInfoOptions {
|
||||
pub disk_id: String,
|
||||
pub metrics: bool,
|
||||
pub noop: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct DiskInfo {
|
||||
pub total: u64,
|
||||
pub free: u64,
|
||||
pub used: u64,
|
||||
pub used_inodes: u64,
|
||||
pub free_inodes: u64,
|
||||
pub major: u64,
|
||||
pub minor: u64,
|
||||
pub nr_requests: u64,
|
||||
pub fs_type: String,
|
||||
pub root_disk: bool,
|
||||
pub healing: bool,
|
||||
pub scanning: bool,
|
||||
pub endpoint: String,
|
||||
pub mount_path: String,
|
||||
pub id: String,
|
||||
pub rotational: bool,
|
||||
pub metrics: DiskMetrics,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Info {
|
||||
pub total: u64,
|
||||
pub free: u64,
|
||||
pub used: u64,
|
||||
pub files: u64,
|
||||
pub ffree: u64,
|
||||
pub fstype: String,
|
||||
pub major: u64,
|
||||
pub minor: u64,
|
||||
pub name: String,
|
||||
pub rotational: bool,
|
||||
pub nrrequests: u64,
|
||||
}
|
||||
|
||||
// #[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
// pub struct FileInfoVersions {
|
||||
// // Name of the volume.
|
||||
// pub volume: String,
|
||||
|
||||
// // Name of the file.
|
||||
// pub name: String,
|
||||
|
||||
// // Represents the latest mod time of the
|
||||
// // latest version.
|
||||
// pub latest_mod_time: Option<OffsetDateTime>,
|
||||
|
||||
// pub versions: Vec<FileInfo>,
|
||||
// pub free_versions: Vec<FileInfo>,
|
||||
// }
|
||||
|
||||
// impl FileInfoVersions {
|
||||
// pub fn find_version_index(&self, v: &str) -> Option<usize> {
|
||||
// if v.is_empty() {
|
||||
// return None;
|
||||
// }
|
||||
|
||||
// let vid = Uuid::parse_str(v).unwrap_or(Uuid::nil());
|
||||
|
||||
// self.versions.iter().position(|v| v.version_id == Some(vid))
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct WalkDirOptions {
|
||||
// Bucket to scanner
|
||||
pub bucket: String,
|
||||
// Directory inside the bucket.
|
||||
pub base_dir: String,
|
||||
// Do a full recursive scan.
|
||||
pub recursive: bool,
|
||||
|
||||
// ReportNotFound will return errFileNotFound if all disks reports the BaseDir cannot be found.
|
||||
pub report_notfound: bool,
|
||||
|
||||
// FilterPrefix will only return results with given prefix within folder.
|
||||
// Should never contain a slash.
|
||||
pub filter_prefix: Option<String>,
|
||||
|
||||
// ForwardTo will forward to the given object path.
|
||||
pub forward_to: Option<String>,
|
||||
|
||||
// Limit the number of returned objects if > 0.
|
||||
pub limit: i32,
|
||||
|
||||
// DiskID contains the disk ID of the disk.
|
||||
// Leave empty to not check disk ID.
|
||||
pub disk_id: String,
|
||||
}
|
||||
// move metacache to metacache.rs
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct DiskOption {
|
||||
pub cleanup: bool,
|
||||
pub health_check: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct RenameDataResp {
|
||||
pub old_data_dir: Option<Uuid>,
|
||||
pub sign: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct DeleteOptions {
|
||||
pub recursive: bool,
|
||||
pub immediate: bool,
|
||||
pub undo_write: bool,
|
||||
pub old_data_dir: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ReadMultipleReq {
|
||||
pub bucket: String,
|
||||
pub prefix: String,
|
||||
pub files: Vec<String>,
|
||||
pub max_size: usize,
|
||||
pub metadata_only: bool,
|
||||
pub abort404: bool,
|
||||
pub max_results: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ReadMultipleResp {
|
||||
pub bucket: String,
|
||||
pub prefix: String,
|
||||
pub file: String,
|
||||
pub exists: bool,
|
||||
pub error: String,
|
||||
pub data: Vec<u8>,
|
||||
pub mod_time: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct VolumeInfo {
|
||||
pub name: String,
|
||||
pub created: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
pub struct ReadOptions {
|
||||
pub incl_free_versions: bool,
|
||||
pub read_data: bool,
|
||||
pub healing: bool,
|
||||
}
|
||||
@@ -1,379 +0,0 @@
|
||||
use path_absolutize::Absolutize;
|
||||
use rustfs_utils::{is_local_host, is_socket_addr};
|
||||
use std::{fmt::Display, path::Path};
|
||||
use url::{ParseError, Url};
|
||||
|
||||
/// enum for endpoint type.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum EndpointType {
|
||||
/// path style endpoint type enum.
|
||||
Path,
|
||||
|
||||
/// URL style endpoint type enum.
|
||||
Url,
|
||||
}
|
||||
|
||||
/// any type of endpoint.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
||||
pub struct Endpoint {
|
||||
pub url: url::Url,
|
||||
pub is_local: bool,
|
||||
|
||||
pub pool_idx: i32,
|
||||
pub set_idx: i32,
|
||||
pub disk_idx: i32,
|
||||
}
|
||||
|
||||
impl Display for Endpoint {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.url.scheme() == "file" {
|
||||
write!(f, "{}", self.get_file_path())
|
||||
} else {
|
||||
write!(f, "{}", self.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Endpoint {
|
||||
/// The type returned in the event of a conversion error.
|
||||
type Error = std::io::Error;
|
||||
|
||||
/// Performs the conversion.
|
||||
fn try_from(value: &str) -> core::result::Result<Self, Self::Error> {
|
||||
// check whether given path is not empty.
|
||||
if ["", "/", "\\"].iter().any(|&v| v.eq(value)) {
|
||||
return Err(std::io::Error::other("empty or root endpoint is not supported"));
|
||||
}
|
||||
|
||||
let mut is_local = false;
|
||||
let url = match Url::parse(value) {
|
||||
#[allow(unused_mut)]
|
||||
Ok(mut url) if url.has_host() => {
|
||||
// URL style of endpoint.
|
||||
// Valid URL style endpoint is
|
||||
// - Scheme field must contain "http" or "https"
|
||||
// - All field should be empty except Host and Path.
|
||||
if !((url.scheme() == "http" || url.scheme() == "https")
|
||||
&& url.username().is_empty()
|
||||
&& url.fragment().is_none()
|
||||
&& url.query().is_none())
|
||||
{
|
||||
return Err(std::io::Error::other("invalid URL endpoint format"));
|
||||
}
|
||||
|
||||
let path = url.path().to_string();
|
||||
|
||||
#[cfg(not(windows))]
|
||||
let path = Path::new(&path).absolutize()?;
|
||||
|
||||
// On windows having a preceding SlashSeparator will cause problems, if the
|
||||
// command line already has C:/<export-folder/ in it. Final resulting
|
||||
// path on windows might become C:/C:/ this will cause problems
|
||||
// of starting rustfs server properly in distributed mode on windows.
|
||||
// As a special case make sure to trim the separator.
|
||||
#[cfg(windows)]
|
||||
let path = Path::new(&path[1..]).absolutize()?;
|
||||
|
||||
if path.parent().is_none() || Path::new("").eq(&path) {
|
||||
return Err(std::io::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(std::io::Error::other("invalid path")),
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
Ok(_) => {
|
||||
// like d:/foo
|
||||
is_local = true;
|
||||
url_parse_from_file_path(value)?
|
||||
}
|
||||
Err(e) => match e {
|
||||
ParseError::InvalidPort => {
|
||||
return Err(std::io::Error::other(
|
||||
"invalid URL endpoint format: port number must be between 1 to 65535",
|
||||
))
|
||||
}
|
||||
ParseError::EmptyHost => return Err(std::io::Error::other("invalid URL endpoint format: empty host name")),
|
||||
ParseError::RelativeUrlWithoutBase => {
|
||||
// like /foo
|
||||
is_local = true;
|
||||
url_parse_from_file_path(value)?
|
||||
}
|
||||
_ => return Err(std::io::Error::other(format!("invalid URL endpoint format: {}", e))),
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Endpoint {
|
||||
url,
|
||||
is_local,
|
||||
pool_idx: -1,
|
||||
set_idx: -1,
|
||||
disk_idx: -1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
/// returns type of endpoint.
|
||||
pub fn get_type(&self) -> EndpointType {
|
||||
if self.url.scheme() == "file" {
|
||||
EndpointType::Path
|
||||
} else {
|
||||
EndpointType::Url
|
||||
}
|
||||
}
|
||||
|
||||
/// sets a specific pool number to this node
|
||||
pub fn set_pool_index(&mut self, idx: usize) {
|
||||
self.pool_idx = idx as i32
|
||||
}
|
||||
|
||||
/// sets a specific set number to this node
|
||||
pub fn set_set_index(&mut self, idx: usize) {
|
||||
self.set_idx = idx as i32
|
||||
}
|
||||
|
||||
/// sets a specific disk number to this node
|
||||
pub fn set_disk_index(&mut self, idx: usize) {
|
||||
self.disk_idx = idx as i32
|
||||
}
|
||||
|
||||
/// resolves the host and updates if it is local or not.
|
||||
pub fn update_is_local(&mut self, local_port: u16) -> std::io::Result<()> {
|
||||
match (self.url.scheme(), self.url.host()) {
|
||||
(v, Some(host)) if v != "file" => {
|
||||
self.is_local = is_local_host(host, self.url.port().unwrap_or_default(), local_port)?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// returns the host to be used for grid connections.
|
||||
pub fn grid_host(&self) -> String {
|
||||
match (self.url.host(), self.url.port()) {
|
||||
(Some(host), Some(port)) => format!("{}://{}:{}", self.url.scheme(), host, port),
|
||||
(Some(host), None) => format!("{}://{}", self.url.scheme(), host),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host_port(&self) -> String {
|
||||
match (self.url.host(), self.url.port()) {
|
||||
(Some(host), Some(port)) => format!("{}:{}", host, port),
|
||||
(Some(host), None) => format!("{}", host),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_file_path(&self) -> &str {
|
||||
let path = self.url.path();
|
||||
|
||||
#[cfg(windows)]
|
||||
let path = &path[1..];
|
||||
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
/// parse a file path into an URL.
|
||||
fn url_parse_from_file_path(value: &str) -> std::io::Result<url::Url> {
|
||||
// Only check if the arg is an ip address and ask for scheme since its absent.
|
||||
// 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 is_socket_addr(addr[0]) {
|
||||
return Err(std::io::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(std::io::Error::other(format!("absolute path failed: {}", err))),
|
||||
};
|
||||
|
||||
match Url::from_file_path(file_path) {
|
||||
Ok(url) => Ok(url),
|
||||
Err(_) => Err(std::io::Error::other("Convert a file path into an URL failed")),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new_endpoint() {
|
||||
#[derive(Default)]
|
||||
struct TestCase<'a> {
|
||||
arg: &'a str,
|
||||
expected_endpoint: Option<Endpoint>,
|
||||
expected_type: Option<EndpointType>,
|
||||
expected_err: Option<std::io::Error>,
|
||||
}
|
||||
|
||||
let u2 = url::Url::parse("https://example.org/path").unwrap();
|
||||
let u4 = url::Url::parse("http://192.168.253.200/path").unwrap();
|
||||
let u6 = url::Url::parse("http://server:/path").unwrap();
|
||||
let root_slash_foo = url::Url::from_file_path("/foo").unwrap();
|
||||
|
||||
let test_cases = [
|
||||
TestCase {
|
||||
arg: "/foo",
|
||||
expected_endpoint: Some(Endpoint {
|
||||
url: root_slash_foo,
|
||||
is_local: true,
|
||||
pool_idx: -1,
|
||||
set_idx: -1,
|
||||
disk_idx: -1,
|
||||
}),
|
||||
expected_type: Some(EndpointType::Path),
|
||||
expected_err: None,
|
||||
},
|
||||
TestCase {
|
||||
arg: "https://example.org/path",
|
||||
expected_endpoint: Some(Endpoint {
|
||||
url: u2,
|
||||
is_local: false,
|
||||
pool_idx: -1,
|
||||
set_idx: -1,
|
||||
disk_idx: -1,
|
||||
}),
|
||||
expected_type: Some(EndpointType::Url),
|
||||
expected_err: None,
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://192.168.253.200/path",
|
||||
expected_endpoint: Some(Endpoint {
|
||||
url: u4,
|
||||
is_local: false,
|
||||
pool_idx: -1,
|
||||
set_idx: -1,
|
||||
disk_idx: -1,
|
||||
}),
|
||||
expected_type: Some(EndpointType::Url),
|
||||
expected_err: None,
|
||||
},
|
||||
TestCase {
|
||||
arg: "",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::Error::other("empty or root endpoint is not supported")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "/",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::Error::other("empty or root endpoint is not supported")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "\\",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::Error::other("empty or root endpoint is not supported")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "c://foo",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::Error::other("invalid URL endpoint format")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "ftp://foo",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::Error::other("invalid URL endpoint format")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://server/path?location",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::Error::other("invalid URL endpoint format")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://:/path",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::Error::other("invalid URL endpoint format: empty host name")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://:8080/path",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::Error::other("invalid URL endpoint format: empty host name")),
|
||||
},
|
||||
TestCase {
|
||||
arg: "http://server:/path",
|
||||
expected_endpoint: Some(Endpoint {
|
||||
url: u6,
|
||||
is_local: false,
|
||||
pool_idx: -1,
|
||||
set_idx: -1,
|
||||
disk_idx: -1,
|
||||
}),
|
||||
expected_type: Some(EndpointType::Url),
|
||||
expected_err: None,
|
||||
},
|
||||
TestCase {
|
||||
arg: "https://93.184.216.34:808080/path",
|
||||
expected_endpoint: None,
|
||||
expected_type: None,
|
||||
expected_err: Some(std::io::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(std::io::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(std::io::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(std::io::Error::other("invalid URL endpoint format: missing scheme http or https")),
|
||||
},
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
let ret = Endpoint::try_from(test_case.arg);
|
||||
if test_case.expected_err.is_none() && ret.is_err() {
|
||||
panic!("{}: error: expected = <nil>, got = {:?}", test_case.arg, ret);
|
||||
}
|
||||
if test_case.expected_err.is_some() && ret.is_ok() {
|
||||
panic!("{}: error: expected = {:?}, got = <nil>", test_case.arg, test_case.expected_err);
|
||||
}
|
||||
match (test_case.expected_err, ret) {
|
||||
(None, Err(e)) => panic!("{}: error: expected = <nil>, got = {}", test_case.arg, e),
|
||||
(None, Ok(mut ep)) => {
|
||||
let _ = ep.update_is_local(9000);
|
||||
if test_case.expected_type != Some(ep.get_type()) {
|
||||
panic!(
|
||||
"{}: type: expected = {:?}, got = {:?}",
|
||||
test_case.arg,
|
||||
test_case.expected_type,
|
||||
ep.get_type()
|
||||
);
|
||||
}
|
||||
|
||||
assert_eq!(test_case.expected_endpoint, Some(ep), "{}: endpoint", test_case.arg);
|
||||
}
|
||||
(Some(e), Ok(_)) => panic!("{}: error: expected = {}, got = <nil>", test_case.arg, e),
|
||||
(Some(e), Err(e2)) => {
|
||||
assert_eq!(e.to_string(), e2.to_string(), "{}: error: expected = {}, got = {}", test_case.arg, e, e2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,594 +0,0 @@
|
||||
use std::io::{self, ErrorKind};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tracing::error;
|
||||
|
||||
use crate::quorum::CheckErrorFn;
|
||||
use crate::utils::ERROR_TYPE_MASK;
|
||||
use common::error::{Error, Result};
|
||||
|
||||
// DiskError == StorageErr
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum DiskError {
|
||||
#[error("maximum versions exceeded, please delete few versions to proceed")]
|
||||
MaxVersionsExceeded,
|
||||
|
||||
#[error("unexpected error")]
|
||||
Unexpected,
|
||||
|
||||
#[error("corrupted format")]
|
||||
CorruptedFormat,
|
||||
|
||||
#[error("corrupted backend")]
|
||||
CorruptedBackend,
|
||||
|
||||
#[error("unformatted disk error")]
|
||||
UnformattedDisk,
|
||||
|
||||
#[error("inconsistent drive found")]
|
||||
InconsistentDisk,
|
||||
|
||||
#[error("drive does not support O_DIRECT")]
|
||||
UnsupportedDisk,
|
||||
|
||||
#[error("drive path full")]
|
||||
DiskFull,
|
||||
|
||||
#[error("disk not a dir")]
|
||||
DiskNotDir,
|
||||
|
||||
#[error("disk not found")]
|
||||
DiskNotFound,
|
||||
|
||||
#[error("drive still did not complete the request")]
|
||||
DiskOngoingReq,
|
||||
|
||||
#[error("drive is part of root drive, will not be used")]
|
||||
DriveIsRoot,
|
||||
|
||||
#[error("remote drive is faulty")]
|
||||
FaultyRemoteDisk,
|
||||
|
||||
#[error("drive is faulty")]
|
||||
FaultyDisk,
|
||||
|
||||
#[error("drive access denied")]
|
||||
DiskAccessDenied,
|
||||
|
||||
#[error("file not found")]
|
||||
FileNotFound,
|
||||
|
||||
#[error("file version not found")]
|
||||
FileVersionNotFound,
|
||||
|
||||
#[error("too many open files, please increase 'ulimit -n'")]
|
||||
TooManyOpenFiles,
|
||||
|
||||
#[error("file name too long")]
|
||||
FileNameTooLong,
|
||||
|
||||
#[error("volume already exists")]
|
||||
VolumeExists,
|
||||
|
||||
#[error("not of regular file type")]
|
||||
IsNotRegular,
|
||||
|
||||
#[error("path not found")]
|
||||
PathNotFound,
|
||||
|
||||
#[error("volume not found")]
|
||||
VolumeNotFound,
|
||||
|
||||
#[error("volume is not empty")]
|
||||
VolumeNotEmpty,
|
||||
|
||||
#[error("volume access denied")]
|
||||
VolumeAccessDenied,
|
||||
|
||||
#[error("disk access denied")]
|
||||
FileAccessDenied,
|
||||
|
||||
#[error("file is corrupted")]
|
||||
FileCorrupt,
|
||||
|
||||
#[error("bit-rot hash algorithm is invalid")]
|
||||
BitrotHashAlgoInvalid,
|
||||
|
||||
#[error("Rename across devices not allowed, please fix your backend configuration")]
|
||||
CrossDeviceLink,
|
||||
|
||||
#[error("less data available than what was requested")]
|
||||
LessData,
|
||||
|
||||
#[error("more data was sent than what was advertised")]
|
||||
MoreData,
|
||||
|
||||
#[error("outdated XL meta")]
|
||||
OutdatedXLMeta,
|
||||
|
||||
#[error("part missing or corrupt")]
|
||||
PartMissingOrCorrupt,
|
||||
|
||||
#[error("No healing is required")]
|
||||
NoHealRequired,
|
||||
}
|
||||
|
||||
impl DiskError {
|
||||
/// Checks if the given array of errors contains fatal disk errors.
|
||||
/// If all errors are of the same fatal disk error type, returns the corresponding error.
|
||||
/// Otherwise, returns Ok.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `errs`: A slice of optional errors.
|
||||
///
|
||||
/// # Returns
|
||||
/// If all errors are of the same fatal disk error type, returns the corresponding error.
|
||||
/// Otherwise, returns Ok.
|
||||
pub fn check_disk_fatal_errs(errs: &[Option<Error>]) -> Result<()> {
|
||||
if DiskError::UnsupportedDisk.count_errs(errs) == errs.len() {
|
||||
return Err(DiskError::UnsupportedDisk.into());
|
||||
}
|
||||
|
||||
if DiskError::FileAccessDenied.count_errs(errs) == errs.len() {
|
||||
return Err(DiskError::FileAccessDenied.into());
|
||||
}
|
||||
|
||||
if DiskError::DiskNotDir.count_errs(errs) == errs.len() {
|
||||
return Err(DiskError::DiskNotDir.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn count_errs(&self, errs: &[Option<Error>]) -> usize {
|
||||
errs.iter()
|
||||
.filter(|&err| match err {
|
||||
None => false,
|
||||
Some(e) => self.is(e),
|
||||
})
|
||||
.count()
|
||||
}
|
||||
|
||||
pub fn quorum_unformatted_disks(errs: &[Option<Error>]) -> bool {
|
||||
DiskError::UnformattedDisk.count_errs(errs) > (errs.len() / 2)
|
||||
}
|
||||
|
||||
pub fn should_init_erasure_disks(errs: &[Option<Error>]) -> bool {
|
||||
DiskError::UnformattedDisk.count_errs(errs) == errs.len()
|
||||
}
|
||||
|
||||
/// Check if the error is a disk error
|
||||
pub fn is(&self, err: &Error) -> bool {
|
||||
if let Some(e) = err.downcast_ref::<DiskError>() {
|
||||
e == self
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DiskError {
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
match self {
|
||||
DiskError::MaxVersionsExceeded => 0x01,
|
||||
DiskError::Unexpected => 0x02,
|
||||
DiskError::CorruptedFormat => 0x03,
|
||||
DiskError::CorruptedBackend => 0x04,
|
||||
DiskError::UnformattedDisk => 0x05,
|
||||
DiskError::InconsistentDisk => 0x06,
|
||||
DiskError::UnsupportedDisk => 0x07,
|
||||
DiskError::DiskFull => 0x08,
|
||||
DiskError::DiskNotDir => 0x09,
|
||||
DiskError::DiskNotFound => 0x0A,
|
||||
DiskError::DiskOngoingReq => 0x0B,
|
||||
DiskError::DriveIsRoot => 0x0C,
|
||||
DiskError::FaultyRemoteDisk => 0x0D,
|
||||
DiskError::FaultyDisk => 0x0E,
|
||||
DiskError::DiskAccessDenied => 0x0F,
|
||||
DiskError::FileNotFound => 0x10,
|
||||
DiskError::FileVersionNotFound => 0x11,
|
||||
DiskError::TooManyOpenFiles => 0x12,
|
||||
DiskError::FileNameTooLong => 0x13,
|
||||
DiskError::VolumeExists => 0x14,
|
||||
DiskError::IsNotRegular => 0x15,
|
||||
DiskError::PathNotFound => 0x16,
|
||||
DiskError::VolumeNotFound => 0x17,
|
||||
DiskError::VolumeNotEmpty => 0x18,
|
||||
DiskError::VolumeAccessDenied => 0x19,
|
||||
DiskError::FileAccessDenied => 0x1A,
|
||||
DiskError::FileCorrupt => 0x1B,
|
||||
DiskError::BitrotHashAlgoInvalid => 0x1C,
|
||||
DiskError::CrossDeviceLink => 0x1D,
|
||||
DiskError::LessData => 0x1E,
|
||||
DiskError::MoreData => 0x1F,
|
||||
DiskError::OutdatedXLMeta => 0x20,
|
||||
DiskError::PartMissingOrCorrupt => 0x21,
|
||||
DiskError::NoHealRequired => 0x22,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u32(error: u32) -> Option<Self> {
|
||||
match error & ERROR_TYPE_MASK {
|
||||
0x01 => Some(DiskError::MaxVersionsExceeded),
|
||||
0x02 => Some(DiskError::Unexpected),
|
||||
0x03 => Some(DiskError::CorruptedFormat),
|
||||
0x04 => Some(DiskError::CorruptedBackend),
|
||||
0x05 => Some(DiskError::UnformattedDisk),
|
||||
0x06 => Some(DiskError::InconsistentDisk),
|
||||
0x07 => Some(DiskError::UnsupportedDisk),
|
||||
0x08 => Some(DiskError::DiskFull),
|
||||
0x09 => Some(DiskError::DiskNotDir),
|
||||
0x0A => Some(DiskError::DiskNotFound),
|
||||
0x0B => Some(DiskError::DiskOngoingReq),
|
||||
0x0C => Some(DiskError::DriveIsRoot),
|
||||
0x0D => Some(DiskError::FaultyRemoteDisk),
|
||||
0x0E => Some(DiskError::FaultyDisk),
|
||||
0x0F => Some(DiskError::DiskAccessDenied),
|
||||
0x10 => Some(DiskError::FileNotFound),
|
||||
0x11 => Some(DiskError::FileVersionNotFound),
|
||||
0x12 => Some(DiskError::TooManyOpenFiles),
|
||||
0x13 => Some(DiskError::FileNameTooLong),
|
||||
0x14 => Some(DiskError::VolumeExists),
|
||||
0x15 => Some(DiskError::IsNotRegular),
|
||||
0x16 => Some(DiskError::PathNotFound),
|
||||
0x17 => Some(DiskError::VolumeNotFound),
|
||||
0x18 => Some(DiskError::VolumeNotEmpty),
|
||||
0x19 => Some(DiskError::VolumeAccessDenied),
|
||||
0x1A => Some(DiskError::FileAccessDenied),
|
||||
0x1B => Some(DiskError::FileCorrupt),
|
||||
0x1C => Some(DiskError::BitrotHashAlgoInvalid),
|
||||
0x1D => Some(DiskError::CrossDeviceLink),
|
||||
0x1E => Some(DiskError::LessData),
|
||||
0x1F => Some(DiskError::MoreData),
|
||||
0x20 => Some(DiskError::OutdatedXLMeta),
|
||||
0x21 => Some(DiskError::PartMissingOrCorrupt),
|
||||
0x22 => Some(DiskError::NoHealRequired),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for DiskError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
core::mem::discriminant(self) == core::mem::discriminant(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckErrorFn for DiskError {
|
||||
fn is(&self, e: &Error) -> bool {
|
||||
self.is(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_disk_err(e: &DiskError) -> Error {
|
||||
match e {
|
||||
DiskError::MaxVersionsExceeded => Error::new(DiskError::MaxVersionsExceeded),
|
||||
DiskError::Unexpected => Error::new(DiskError::Unexpected),
|
||||
DiskError::CorruptedFormat => Error::new(DiskError::CorruptedFormat),
|
||||
DiskError::CorruptedBackend => Error::new(DiskError::CorruptedBackend),
|
||||
DiskError::UnformattedDisk => Error::new(DiskError::UnformattedDisk),
|
||||
DiskError::InconsistentDisk => Error::new(DiskError::InconsistentDisk),
|
||||
DiskError::UnsupportedDisk => Error::new(DiskError::UnsupportedDisk),
|
||||
DiskError::DiskFull => Error::new(DiskError::DiskFull),
|
||||
DiskError::DiskNotDir => Error::new(DiskError::DiskNotDir),
|
||||
DiskError::DiskNotFound => Error::new(DiskError::DiskNotFound),
|
||||
DiskError::DiskOngoingReq => Error::new(DiskError::DiskOngoingReq),
|
||||
DiskError::DriveIsRoot => Error::new(DiskError::DriveIsRoot),
|
||||
DiskError::FaultyRemoteDisk => Error::new(DiskError::FaultyRemoteDisk),
|
||||
DiskError::FaultyDisk => Error::new(DiskError::FaultyDisk),
|
||||
DiskError::DiskAccessDenied => Error::new(DiskError::DiskAccessDenied),
|
||||
DiskError::FileNotFound => Error::new(DiskError::FileNotFound),
|
||||
DiskError::FileVersionNotFound => Error::new(DiskError::FileVersionNotFound),
|
||||
DiskError::TooManyOpenFiles => Error::new(DiskError::TooManyOpenFiles),
|
||||
DiskError::FileNameTooLong => Error::new(DiskError::FileNameTooLong),
|
||||
DiskError::VolumeExists => Error::new(DiskError::VolumeExists),
|
||||
DiskError::IsNotRegular => Error::new(DiskError::IsNotRegular),
|
||||
DiskError::PathNotFound => Error::new(DiskError::PathNotFound),
|
||||
DiskError::VolumeNotFound => Error::new(DiskError::VolumeNotFound),
|
||||
DiskError::VolumeNotEmpty => Error::new(DiskError::VolumeNotEmpty),
|
||||
DiskError::VolumeAccessDenied => Error::new(DiskError::VolumeAccessDenied),
|
||||
DiskError::FileAccessDenied => Error::new(DiskError::FileAccessDenied),
|
||||
DiskError::FileCorrupt => Error::new(DiskError::FileCorrupt),
|
||||
DiskError::BitrotHashAlgoInvalid => Error::new(DiskError::BitrotHashAlgoInvalid),
|
||||
DiskError::CrossDeviceLink => Error::new(DiskError::CrossDeviceLink),
|
||||
DiskError::LessData => Error::new(DiskError::LessData),
|
||||
DiskError::MoreData => Error::new(DiskError::MoreData),
|
||||
DiskError::OutdatedXLMeta => Error::new(DiskError::OutdatedXLMeta),
|
||||
DiskError::PartMissingOrCorrupt => Error::new(DiskError::PartMissingOrCorrupt),
|
||||
DiskError::NoHealRequired => Error::new(DiskError::NoHealRequired),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn os_err_to_file_err(e: io::Error) -> Error {
|
||||
match e.kind() {
|
||||
io::ErrorKind::NotFound => Error::new(DiskError::FileNotFound),
|
||||
io::ErrorKind::PermissionDenied => Error::new(DiskError::FileAccessDenied),
|
||||
// io::ErrorKind::ConnectionRefused => todo!(),
|
||||
// io::ErrorKind::ConnectionReset => todo!(),
|
||||
// io::ErrorKind::HostUnreachable => todo!(),
|
||||
// io::ErrorKind::NetworkUnreachable => todo!(),
|
||||
// io::ErrorKind::ConnectionAborted => todo!(),
|
||||
// io::ErrorKind::NotConnected => todo!(),
|
||||
// io::ErrorKind::AddrInUse => todo!(),
|
||||
// io::ErrorKind::AddrNotAvailable => todo!(),
|
||||
// io::ErrorKind::NetworkDown => todo!(),
|
||||
// io::ErrorKind::BrokenPipe => todo!(),
|
||||
// io::ErrorKind::AlreadyExists => todo!(),
|
||||
// io::ErrorKind::WouldBlock => todo!(),
|
||||
// io::ErrorKind::NotADirectory => DiskError::FileNotFound,
|
||||
// io::ErrorKind::IsADirectory => DiskError::FileNotFound,
|
||||
// io::ErrorKind::DirectoryNotEmpty => DiskError::VolumeNotEmpty,
|
||||
// io::ErrorKind::ReadOnlyFilesystem => todo!(),
|
||||
// io::ErrorKind::FilesystemLoop => todo!(),
|
||||
// io::ErrorKind::StaleNetworkFileHandle => todo!(),
|
||||
// io::ErrorKind::InvalidInput => todo!(),
|
||||
// io::ErrorKind::InvalidData => todo!(),
|
||||
// io::ErrorKind::TimedOut => todo!(),
|
||||
// io::ErrorKind::WriteZero => todo!(),
|
||||
// io::ErrorKind::StorageFull => DiskError::DiskFull,
|
||||
// io::ErrorKind::NotSeekable => todo!(),
|
||||
// io::ErrorKind::FilesystemQuotaExceeded => todo!(),
|
||||
// io::ErrorKind::FileTooLarge => todo!(),
|
||||
// io::ErrorKind::ResourceBusy => todo!(),
|
||||
// io::ErrorKind::ExecutableFileBusy => todo!(),
|
||||
// io::ErrorKind::Deadlock => todo!(),
|
||||
// io::ErrorKind::CrossesDevices => todo!(),
|
||||
// io::ErrorKind::TooManyLinks =>DiskError::TooManyOpenFiles,
|
||||
// io::ErrorKind::InvalidFilename => todo!(),
|
||||
// io::ErrorKind::ArgumentListTooLong => todo!(),
|
||||
// io::ErrorKind::Interrupted => todo!(),
|
||||
// io::ErrorKind::Unsupported => todo!(),
|
||||
// io::ErrorKind::UnexpectedEof => todo!(),
|
||||
// io::ErrorKind::OutOfMemory => todo!(),
|
||||
// io::ErrorKind::Other => todo!(),
|
||||
// TODO: 把不支持的king用字符串处理
|
||||
_ => Error::new(e),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub struct FileAccessDeniedWithContext {
|
||||
pub path: PathBuf,
|
||||
#[source]
|
||||
pub source: std::io::Error,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FileAccessDeniedWithContext {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "访问文件 '{}' 被拒绝: {}", self.path.display(), self.source)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_unformatted_disk(err: &Error) -> bool {
|
||||
matches!(err.downcast_ref::<DiskError>(), Some(DiskError::UnformattedDisk))
|
||||
}
|
||||
|
||||
pub fn is_err_file_not_found(err: &Error) -> bool {
|
||||
if let Some(ioerr) = err.downcast_ref::<io::Error>() {
|
||||
return ioerr.kind() == ErrorKind::NotFound;
|
||||
}
|
||||
|
||||
matches!(err.downcast_ref::<DiskError>(), Some(DiskError::FileNotFound))
|
||||
}
|
||||
|
||||
pub fn is_err_file_version_not_found(err: &Error) -> bool {
|
||||
matches!(err.downcast_ref::<DiskError>(), Some(DiskError::FileVersionNotFound))
|
||||
}
|
||||
|
||||
pub fn is_err_volume_not_found(err: &Error) -> bool {
|
||||
matches!(err.downcast_ref::<DiskError>(), Some(DiskError::VolumeNotFound))
|
||||
}
|
||||
|
||||
pub fn is_err_eof(err: &Error) -> bool {
|
||||
if let Some(ioerr) = err.downcast_ref::<io::Error>() {
|
||||
return ioerr.kind() == ErrorKind::UnexpectedEof;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_no_space(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 28;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_invalid_arg(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 22;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// TODO: ??
|
||||
pub fn is_sys_err_io(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 5;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_is_dir(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 21;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_not_dir(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 20;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_too_long(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 63;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_too_many_symlinks(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 62;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_not_empty(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
if no == 66 {
|
||||
return true;
|
||||
}
|
||||
|
||||
if cfg!(target_os = "solaris") && no == 17 {
|
||||
return true;
|
||||
}
|
||||
|
||||
if cfg!(target_os = "windows") && no == 145 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_path_not_found(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
if cfg!(target_os = "windows") {
|
||||
if no == 3 {
|
||||
return true;
|
||||
}
|
||||
} else if no == 2 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_handle_invalid(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
if cfg!(target_os = "windows") {
|
||||
if no == 6 {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_cross_device(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 18;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_sys_err_too_many_files(e: &io::Error) -> bool {
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
return no == 23 || no == 24;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
// pub fn os_is_not_exist(e: &io::Error) -> bool {
|
||||
// e.kind() == ErrorKind::NotFound
|
||||
// }
|
||||
|
||||
pub fn os_is_permission(e: &io::Error) -> bool {
|
||||
if e.kind() == ErrorKind::PermissionDenied {
|
||||
return true;
|
||||
}
|
||||
if let Some(no) = e.raw_os_error() {
|
||||
if no == 30 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// pub fn os_is_exist(e: &io::Error) -> bool {
|
||||
// e.kind() == ErrorKind::AlreadyExists
|
||||
// }
|
||||
|
||||
// // map_err_not_exists
|
||||
// pub fn map_err_not_exists(e: io::Error) -> Error {
|
||||
// if os_is_not_exist(&e) {
|
||||
// return Error::new(DiskError::VolumeNotEmpty);
|
||||
// } else if is_sys_err_io(&e) {
|
||||
// return Error::new(DiskError::FaultyDisk);
|
||||
// }
|
||||
|
||||
// Error::new(e)
|
||||
// }
|
||||
|
||||
// pub fn convert_access_error(e: io::Error, per_err: DiskError) -> Error {
|
||||
// if os_is_not_exist(&e) {
|
||||
// return Error::new(DiskError::VolumeNotEmpty);
|
||||
// } else if is_sys_err_io(&e) {
|
||||
// return Error::new(DiskError::FaultyDisk);
|
||||
// } else if os_is_permission(&e) {
|
||||
// return Error::new(per_err);
|
||||
// }
|
||||
|
||||
// Error::new(e)
|
||||
// }
|
||||
|
||||
pub fn is_all_not_found(errs: &[Option<Error>]) -> bool {
|
||||
for err in errs.iter() {
|
||||
if let Some(err) = err {
|
||||
if let Some(err) = err.downcast_ref::<DiskError>() {
|
||||
match err {
|
||||
DiskError::FileNotFound | DiskError::VolumeNotFound | &DiskError::FileVersionNotFound => {
|
||||
continue;
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
!errs.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_all_volume_not_found(errs: &[Option<Error>]) -> bool {
|
||||
DiskError::VolumeNotFound.count_errs(errs) == errs.len()
|
||||
}
|
||||
|
||||
pub fn is_all_buckets_not_found(errs: &[Option<Error>]) -> bool {
|
||||
if errs.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let mut not_found_count = 0;
|
||||
for err in errs.iter().flatten() {
|
||||
match err.downcast_ref() {
|
||||
Some(DiskError::VolumeNotFound) | Some(DiskError::DiskNotFound) => {
|
||||
not_found_count += 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
errs.len() == not_found_count
|
||||
}
|
||||
|
||||
pub fn is_err_os_not_exist(err: &Error) -> bool {
|
||||
if let Some(os_err) = err.downcast_ref::<io::Error>() {
|
||||
os_err.kind() == ErrorKind::NotFound
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_err_os_disk_full(err: &Error) -> bool {
|
||||
if let Some(os_err) = err.downcast_ref::<io::Error>() {
|
||||
is_sys_err_no_space(os_err)
|
||||
} else if let Some(e) = err.downcast_ref::<DiskError>() {
|
||||
e == &DiskError::DiskFull
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
// use super::{error::DiskError, DiskInfo};
|
||||
use rustfs_error::{Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Error as JsonError;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::api::DiskInfo;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum FormatMetaVersion {
|
||||
#[serde(rename = "1")]
|
||||
V1,
|
||||
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum FormatBackend {
|
||||
#[serde(rename = "xl")]
|
||||
Erasure,
|
||||
#[serde(rename = "xl-single")]
|
||||
ErasureSingle,
|
||||
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Represents the V3 backend disk structure version
|
||||
/// under `.rustfs.sys` and actual data namespace.
|
||||
///
|
||||
/// FormatErasureV3 - structure holds format config version '3'.
|
||||
///
|
||||
/// The V3 format to support "large bucket" support where a bucket
|
||||
/// can span multiple erasure sets.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct FormatErasureV3 {
|
||||
/// Version of 'xl' format.
|
||||
pub version: FormatErasureVersion,
|
||||
|
||||
/// This field carries assigned disk uuid.
|
||||
pub this: Uuid,
|
||||
|
||||
/// Sets field carries the input disk order generated the first
|
||||
/// time when fresh disks were supplied, it is a two dimensional
|
||||
/// array second dimension represents list of disks used per set.
|
||||
pub sets: Vec<Vec<Uuid>>,
|
||||
|
||||
/// Distribution algorithm represents the hashing algorithm
|
||||
/// to pick the right set index for an object.
|
||||
#[serde(rename = "distributionAlgo")]
|
||||
pub distribution_algo: DistributionAlgoVersion,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum FormatErasureVersion {
|
||||
#[serde(rename = "1")]
|
||||
V1,
|
||||
#[serde(rename = "2")]
|
||||
V2,
|
||||
#[serde(rename = "3")]
|
||||
V3,
|
||||
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub enum DistributionAlgoVersion {
|
||||
#[serde(rename = "CRCMOD")]
|
||||
V1,
|
||||
#[serde(rename = "SIPMOD")]
|
||||
V2,
|
||||
#[serde(rename = "SIPMOD+PARITY")]
|
||||
V3,
|
||||
}
|
||||
|
||||
/// format.json currently has the format:
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "version": "1",
|
||||
/// "format": "XXXXX",
|
||||
/// "id": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
|
||||
/// "XXXXX": {
|
||||
//
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Ideally we will never have a situation where we will have to change the
|
||||
/// fields of this struct and deal with related migration.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct FormatV3 {
|
||||
/// Version of the format config.
|
||||
pub version: FormatMetaVersion,
|
||||
|
||||
/// Format indicates the backend format type, supports two values 'xl' and 'xl-single'.
|
||||
pub format: FormatBackend,
|
||||
|
||||
/// ID is the identifier for the rustfs deployment
|
||||
pub id: Uuid,
|
||||
|
||||
#[serde(rename = "xl")]
|
||||
pub erasure: FormatErasureV3,
|
||||
// /// DiskInfo is an extended type which returns current
|
||||
// /// disk usage per path.
|
||||
#[serde(skip)]
|
||||
pub disk_info: Option<DiskInfo>,
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for FormatV3 {
|
||||
type Error = JsonError;
|
||||
|
||||
fn try_from(data: &[u8]) -> core::result::Result<Self, JsonError> {
|
||||
serde_json::from_slice(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for FormatV3 {
|
||||
type Error = JsonError;
|
||||
|
||||
fn try_from(data: &str) -> core::result::Result<Self, JsonError> {
|
||||
serde_json::from_str(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatV3 {
|
||||
/// Create a new format config with the given number of sets and set length.
|
||||
pub fn new(num_sets: usize, set_len: usize) -> Self {
|
||||
let format = if set_len == 1 {
|
||||
FormatBackend::ErasureSingle
|
||||
} else {
|
||||
FormatBackend::Erasure
|
||||
};
|
||||
|
||||
let erasure = FormatErasureV3 {
|
||||
version: FormatErasureVersion::V3,
|
||||
this: Uuid::nil(),
|
||||
sets: (0..num_sets)
|
||||
.map(|_| (0..set_len).map(|_| Uuid::new_v4()).collect())
|
||||
.collect(),
|
||||
distribution_algo: DistributionAlgoVersion::V3,
|
||||
};
|
||||
|
||||
Self {
|
||||
version: FormatMetaVersion::V1,
|
||||
format,
|
||||
id: Uuid::new_v4(),
|
||||
erasure,
|
||||
disk_info: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of drives in the erasure set.
|
||||
pub fn drives(&self) -> usize {
|
||||
self.erasure.sets.iter().map(|v| v.len()).sum()
|
||||
}
|
||||
|
||||
pub fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(self)?)
|
||||
}
|
||||
|
||||
/// returns the i,j'th position of the input `diskID` against the reference
|
||||
///
|
||||
/// format, after successful validation.
|
||||
/// - i'th position is the set index
|
||||
/// - j'th position is the disk index in the current set
|
||||
pub fn find_disk_index_by_disk_id(&self, disk_id: Uuid) -> Result<(usize, usize)> {
|
||||
if disk_id == Uuid::nil() {
|
||||
return Err(Error::DiskNotFound);
|
||||
}
|
||||
if disk_id == Uuid::max() {
|
||||
return Err(Error::msg("disk offline"));
|
||||
}
|
||||
|
||||
for (i, set) in self.erasure.sets.iter().enumerate() {
|
||||
for (j, d) in set.iter().enumerate() {
|
||||
if disk_id.eq(d) {
|
||||
return Ok((i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::msg(format!("disk id not found {}", disk_id)))
|
||||
}
|
||||
|
||||
pub fn check_other(&self, other: &FormatV3) -> Result<()> {
|
||||
let mut tmp = other.clone();
|
||||
let this = tmp.erasure.this;
|
||||
tmp.erasure.this = Uuid::nil();
|
||||
|
||||
if self.erasure.sets.len() != other.erasure.sets.len() {
|
||||
return Err(Error::msg(format!(
|
||||
"Expected number of sets {}, got {}",
|
||||
self.erasure.sets.len(),
|
||||
other.erasure.sets.len()
|
||||
)));
|
||||
}
|
||||
|
||||
for i in 0..self.erasure.sets.len() {
|
||||
if self.erasure.sets[i].len() != other.erasure.sets[i].len() {
|
||||
return Err(Error::msg(format!(
|
||||
"Each set should be of same size, expected {}, got {}",
|
||||
self.erasure.sets[i].len(),
|
||||
other.erasure.sets[i].len()
|
||||
)));
|
||||
}
|
||||
|
||||
for j in 0..self.erasure.sets[i].len() {
|
||||
if self.erasure.sets[i][j] != other.erasure.sets[i][j] {
|
||||
return Err(Error::msg(format!(
|
||||
"UUID on positions {}:{} do not match with, expected {:?} got {:?}: (%w)",
|
||||
i,
|
||||
j,
|
||||
self.erasure.sets[i][j].to_string(),
|
||||
other.erasure.sets[i][j].to_string(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..tmp.erasure.sets.len() {
|
||||
for j in 0..tmp.erasure.sets[i].len() {
|
||||
if this == tmp.erasure.sets[i][j] {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::msg(format!(
|
||||
"DriveID {:?} not found in any drive sets {:?}",
|
||||
this, other.erasure.sets
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_format_v1() {
|
||||
let format = FormatV3::new(1, 4);
|
||||
|
||||
let str = serde_json::to_string(&format);
|
||||
println!("{:?}", str);
|
||||
|
||||
let data = r#"
|
||||
{
|
||||
"version": "1",
|
||||
"format": "xl",
|
||||
"id": "321b3874-987d-4c15-8fa5-757c956b1243",
|
||||
"xl": {
|
||||
"version": "1",
|
||||
"this": null,
|
||||
"sets": [
|
||||
[
|
||||
"8ab9a908-f869-4f1f-8e42-eb067ffa7eb5",
|
||||
"c26315da-05cf-4778-a9ea-b44ea09f58c5",
|
||||
"fb87a891-18d3-44cf-a46f-bcc15093a038",
|
||||
"356a925c-57b9-4313-88b3-053edf1104dc"
|
||||
]
|
||||
],
|
||||
"distributionAlgo": "CRCMOD"
|
||||
}
|
||||
}"#;
|
||||
|
||||
let p = FormatV3::try_from(data);
|
||||
|
||||
println!("{:?}", p);
|
||||
}
|
||||
}
|
||||
@@ -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,12 +0,0 @@
|
||||
pub mod endpoint;
|
||||
// pub mod error;
|
||||
pub mod format;
|
||||
pub mod fs;
|
||||
pub mod local;
|
||||
// pub mod metacache;
|
||||
pub mod api;
|
||||
pub mod local_list;
|
||||
pub mod os;
|
||||
pub mod path;
|
||||
pub mod remote;
|
||||
pub mod utils;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,535 +0,0 @@
|
||||
use crate::{
|
||||
api::{DiskAPI, DiskStore, WalkDirOptions, STORAGE_FORMAT_FILE},
|
||||
local::LocalDisk,
|
||||
os::is_empty_dir,
|
||||
path::{self, decode_dir_object, GLOBAL_DIR_SUFFIX_WITH_SLASH, SLASH_SEPARATOR},
|
||||
};
|
||||
use futures::future::join_all;
|
||||
use rustfs_error::{Error, Result};
|
||||
use rustfs_metacache::{MetaCacheEntries, MetaCacheEntry, MetacacheReader, MetacacheWriter};
|
||||
use std::{collections::HashSet, future::Future, pin::Pin, sync::Arc};
|
||||
use tokio::{io::AsyncWrite, spawn, sync::broadcast::Receiver as B_Receiver};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
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>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ListPathRawOptions {
|
||||
pub disks: Vec<Option<DiskStore>>,
|
||||
pub fallback_disks: Vec<Option<DiskStore>>,
|
||||
pub bucket: String,
|
||||
pub path: String,
|
||||
pub recursive: bool,
|
||||
pub filter_prefix: Option<String>,
|
||||
pub forward_to: Option<String>,
|
||||
pub min_disks: usize,
|
||||
pub report_not_found: bool,
|
||||
pub per_disk_limit: i32,
|
||||
pub agreed: Option<AgreedFn>,
|
||||
pub partial: Option<PartialFn>,
|
||||
pub finished: Option<FinishedFn>,
|
||||
}
|
||||
|
||||
impl Clone for ListPathRawOptions {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
disks: self.disks.clone(),
|
||||
fallback_disks: self.fallback_disks.clone(),
|
||||
bucket: self.bucket.clone(),
|
||||
path: self.path.clone(),
|
||||
recursive: self.recursive,
|
||||
filter_prefix: self.filter_prefix.clone(),
|
||||
forward_to: self.forward_to.clone(),
|
||||
min_disks: self.min_disks,
|
||||
report_not_found: self.report_not_found,
|
||||
per_disk_limit: self.per_disk_limit,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_path_raw(mut rx: B_Receiver<bool>, opts: ListPathRawOptions) -> Result<()> {
|
||||
if opts.disks.is_empty() {
|
||||
return Err(Error::msg("list_path_raw: 0 drives provided"));
|
||||
}
|
||||
|
||||
let mut jobs: Vec<tokio::task::JoinHandle<std::result::Result<(), Error>>> = Vec::new();
|
||||
let mut readers = Vec::with_capacity(opts.disks.len());
|
||||
let fds = Arc::new(opts.fallback_disks.clone());
|
||||
|
||||
for disk in opts.disks.iter() {
|
||||
let opdisk = disk.clone();
|
||||
let opts_clone = opts.clone();
|
||||
let fds_clone = fds.clone();
|
||||
let (rd, mut wr) = tokio::io::duplex(64);
|
||||
readers.push(MetacacheReader::new(rd));
|
||||
|
||||
jobs.push(spawn(async move {
|
||||
let walk_opts = WalkDirOptions {
|
||||
bucket: opts_clone.bucket.clone(),
|
||||
base_dir: opts_clone.path.clone(),
|
||||
recursive: opts_clone.recursive,
|
||||
report_notfound: opts_clone.report_not_found,
|
||||
filter_prefix: opts_clone.filter_prefix.clone(),
|
||||
forward_to: opts_clone.forward_to.clone(),
|
||||
limit: opts_clone.per_disk_limit,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut need_fallback = false;
|
||||
if let Some(disk) = opdisk {
|
||||
match disk.walk_dir(walk_opts, &mut wr).await {
|
||||
Ok(_res) => {}
|
||||
Err(err) => {
|
||||
error!("walk dir err {:?}", &err);
|
||||
need_fallback = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
need_fallback = true;
|
||||
}
|
||||
|
||||
while need_fallback {
|
||||
let disk = match fds_clone.iter().find(|d| d.is_some()) {
|
||||
Some(d) => {
|
||||
if let Some(disk) = d.clone() {
|
||||
disk
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
};
|
||||
|
||||
match disk
|
||||
.walk_dir(
|
||||
WalkDirOptions {
|
||||
bucket: opts_clone.bucket.clone(),
|
||||
base_dir: opts_clone.path.clone(),
|
||||
recursive: opts_clone.recursive,
|
||||
report_notfound: opts_clone.report_not_found,
|
||||
filter_prefix: opts_clone.filter_prefix.clone(),
|
||||
forward_to: opts_clone.forward_to.clone(),
|
||||
limit: opts_clone.per_disk_limit,
|
||||
..Default::default()
|
||||
},
|
||||
&mut wr,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_r) => {
|
||||
need_fallback = false;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("walk dir2 err {:?}", &err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
let revjob = spawn(async move {
|
||||
let mut errs: Vec<Option<Error>> = Vec::with_capacity(readers.len());
|
||||
for _ in 0..readers.len() {
|
||||
errs.push(None);
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut current = MetaCacheEntry::default();
|
||||
|
||||
if rx.try_recv().is_ok() {
|
||||
return Err(Error::msg("canceled"));
|
||||
}
|
||||
let mut top_entries: Vec<Option<MetaCacheEntry>> = vec![None; readers.len()];
|
||||
|
||||
let mut at_eof = 0;
|
||||
let mut fnf = 0;
|
||||
let mut vnf = 0;
|
||||
let mut has_err = 0;
|
||||
let mut agree = 0;
|
||||
|
||||
for (i, r) in readers.iter_mut().enumerate() {
|
||||
if errs[i].is_some() {
|
||||
has_err += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = match r.peek().await {
|
||||
Ok(res) => {
|
||||
if let Some(entry) = res {
|
||||
entry
|
||||
} else {
|
||||
at_eof += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if err == Error::FaultyDisk {
|
||||
at_eof += 1;
|
||||
continue;
|
||||
} else if err == Error::FileNotFound {
|
||||
at_eof += 1;
|
||||
fnf += 1;
|
||||
continue;
|
||||
} else if err == Error::VolumeNotFound {
|
||||
at_eof += 1;
|
||||
fnf += 1;
|
||||
vnf += 1;
|
||||
continue;
|
||||
} else {
|
||||
has_err += 1;
|
||||
errs[i] = Some(err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If no current, add it.
|
||||
if current.name.is_empty() {
|
||||
top_entries[i] = Some(entry.clone());
|
||||
current = entry;
|
||||
agree += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If exact match, we agree.
|
||||
if let (_, true) = current.matches(Some(&entry), true) {
|
||||
top_entries[i] = Some(entry);
|
||||
agree += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If only the name matches we didn't agree, but add it for resolution.
|
||||
if entry.name == current.name {
|
||||
top_entries[i] = Some(entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We got different entries
|
||||
if entry.name > current.name {
|
||||
continue;
|
||||
}
|
||||
|
||||
for item in top_entries.iter_mut().take(i) {
|
||||
*item = None;
|
||||
}
|
||||
|
||||
agree = 1;
|
||||
top_entries[i] = Some(entry.clone());
|
||||
current = entry;
|
||||
}
|
||||
|
||||
if vnf > 0 && vnf >= (readers.len() - opts.min_disks) {
|
||||
return Err(Error::VolumeNotFound);
|
||||
}
|
||||
|
||||
if fnf > 0 && fnf >= (readers.len() - opts.min_disks) {
|
||||
return Err(Error::FileNotFound);
|
||||
}
|
||||
|
||||
if has_err > 0 && has_err > opts.disks.len() - opts.min_disks {
|
||||
if let Some(finished_fn) = opts.finished.as_ref() {
|
||||
finished_fn(&errs).await;
|
||||
}
|
||||
let mut combined_err = Vec::new();
|
||||
errs.iter().zip(opts.disks.iter()).for_each(|(err, disk)| match (err, disk) {
|
||||
(Some(err), Some(disk)) => {
|
||||
combined_err.push(format!("drive {} returned: {}", disk.to_string(), err));
|
||||
}
|
||||
(Some(err), None) => {
|
||||
combined_err.push(err.to_string());
|
||||
}
|
||||
_ => {}
|
||||
});
|
||||
|
||||
return Err(Error::msg(combined_err.join(", ")));
|
||||
}
|
||||
|
||||
// Break if all at EOF or error.
|
||||
if at_eof + has_err == readers.len() {
|
||||
if has_err > 0 {
|
||||
if let Some(finished_fn) = opts.finished.as_ref() {
|
||||
finished_fn(&errs).await;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if agree == readers.len() {
|
||||
for r in readers.iter_mut() {
|
||||
let _ = r.skip(1).await;
|
||||
}
|
||||
|
||||
if let Some(agreed_fn) = opts.agreed.as_ref() {
|
||||
agreed_fn(current).await;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i, r) in readers.iter_mut().enumerate() {
|
||||
if top_entries[i].is_some() {
|
||||
let _ = r.skip(1).await;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(partial_fn) = opts.partial.as_ref() {
|
||||
partial_fn(MetaCacheEntries(top_entries), &errs).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
jobs.push(revjob);
|
||||
|
||||
let results = join_all(jobs).await;
|
||||
for result in results {
|
||||
if let Err(err) = result {
|
||||
error!("list_path_raw err {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl LocalDisk {
|
||||
pub(crate) async fn scan_dir<W: AsyncWrite + Unpin>(
|
||||
&self,
|
||||
current: &mut String,
|
||||
opts: &WalkDirOptions,
|
||||
out: &mut MetacacheWriter<W>,
|
||||
objs_returned: &mut i32,
|
||||
) -> Result<()> {
|
||||
let forward = {
|
||||
opts.forward_to.as_ref().filter(|v| v.starts_with(&*current)).map(|v| {
|
||||
let forward = v.trim_start_matches(&*current);
|
||||
if let Some(idx) = forward.find('/') {
|
||||
forward[..idx].to_owned()
|
||||
} else {
|
||||
forward.to_owned()
|
||||
}
|
||||
})
|
||||
// if let Some(forward_to) = &opts.forward_to {
|
||||
|
||||
// } else {
|
||||
// None
|
||||
// }
|
||||
// if !opts.forward_to.is_empty() && opts.forward_to.starts_with(&*current) {
|
||||
// let forward = opts.forward_to.trim_start_matches(&*current);
|
||||
// if let Some(idx) = forward.find('/') {
|
||||
// &forward[..idx]
|
||||
// } else {
|
||||
// forward
|
||||
// }
|
||||
// } else {
|
||||
// ""
|
||||
// }
|
||||
};
|
||||
|
||||
if opts.limit > 0 && *objs_returned >= opts.limit {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut entries = match self.list_dir("", &opts.bucket, current, -1).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if e != Error::VolumeNotFound && e != Error::FileNotFound {
|
||||
info!("scan list_dir {}, err {:?}", ¤t, &e);
|
||||
}
|
||||
|
||||
if opts.report_notfound && (e == Error::VolumeNotFound || e == Error::FileNotFound) && current == &opts.base_dir {
|
||||
return Err(Error::FileNotFound);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if entries.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let s = SLASH_SEPARATOR.chars().next().unwrap_or_default();
|
||||
*current = current.trim_matches(s).to_owned();
|
||||
|
||||
let bucket = opts.bucket.as_str();
|
||||
|
||||
let mut dir_objes = HashSet::new();
|
||||
|
||||
// 第一层过滤
|
||||
for item in entries.iter_mut() {
|
||||
let entry = item.clone();
|
||||
// check limit
|
||||
if opts.limit > 0 && *objs_returned >= opts.limit {
|
||||
return Ok(());
|
||||
}
|
||||
// check prefix
|
||||
if let Some(filter_prefix) = &opts.filter_prefix {
|
||||
if !entry.starts_with(filter_prefix) {
|
||||
*item = "".to_owned();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(forward) = &forward {
|
||||
if &entry < forward {
|
||||
*item = "".to_owned();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if entry.ends_with(SLASH_SEPARATOR) {
|
||||
if entry.ends_with(GLOBAL_DIR_SUFFIX_WITH_SLASH) {
|
||||
let entry = format!("{}{}", entry.as_str().trim_end_matches(GLOBAL_DIR_SUFFIX_WITH_SLASH), SLASH_SEPARATOR);
|
||||
dir_objes.insert(entry.clone());
|
||||
*item = entry;
|
||||
continue;
|
||||
}
|
||||
|
||||
*item = entry.trim_end_matches(SLASH_SEPARATOR).to_owned();
|
||||
continue;
|
||||
}
|
||||
|
||||
*item = "".to_owned();
|
||||
|
||||
if entry.ends_with(STORAGE_FORMAT_FILE) {
|
||||
//
|
||||
let metadata = self
|
||||
.read_metadata(self.get_object_path(bucket, format!("{}/{}", ¤t, &entry).as_str())?)
|
||||
.await?;
|
||||
|
||||
// 用 strip_suffix 只删除一次
|
||||
let entry = entry.strip_suffix(STORAGE_FORMAT_FILE).unwrap_or_default().to_owned();
|
||||
let name = entry.trim_end_matches(SLASH_SEPARATOR);
|
||||
let name = decode_dir_object(format!("{}/{}", ¤t, &name).as_str());
|
||||
|
||||
out.write_obj(&MetaCacheEntry {
|
||||
name,
|
||||
metadata,
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
*objs_returned += 1;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
entries.sort();
|
||||
|
||||
let mut entries = entries.as_slice();
|
||||
if let Some(forward) = &forward {
|
||||
for (i, entry) in entries.iter().enumerate() {
|
||||
if entry >= forward || forward.starts_with(entry.as_str()) {
|
||||
entries = &entries[i..];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut dir_stack: Vec<String> = Vec::with_capacity(5);
|
||||
|
||||
for entry in entries.iter() {
|
||||
if opts.limit > 0 && *objs_returned >= opts.limit {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if entry.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = path::path_join_buf(&[current, entry]);
|
||||
|
||||
if !dir_stack.is_empty() {
|
||||
if let Some(pop) = dir_stack.pop() {
|
||||
if pop < name {
|
||||
//
|
||||
out.write_obj(&MetaCacheEntry {
|
||||
name: pop.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
|
||||
if opts.recursive {
|
||||
let mut opts = opts.clone();
|
||||
opts.filter_prefix = None;
|
||||
if let Err(er) = Box::pin(self.scan_dir(&mut pop.clone(), &opts, out, objs_returned)).await {
|
||||
error!("scan_dir err {:?}", er);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut meta = MetaCacheEntry {
|
||||
name,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut is_dir_obj = false;
|
||||
|
||||
if let Some(_dir) = dir_objes.get(entry) {
|
||||
is_dir_obj = true;
|
||||
meta.name
|
||||
.truncate(meta.name.len() - meta.name.chars().last().unwrap().len_utf8());
|
||||
meta.name.push_str(GLOBAL_DIR_SUFFIX_WITH_SLASH);
|
||||
}
|
||||
|
||||
let fname = format!("{}/{}", &meta.name, STORAGE_FORMAT_FILE);
|
||||
|
||||
match self.read_metadata(self.get_object_path(&opts.bucket, fname.as_str())?).await {
|
||||
Ok(res) => {
|
||||
if is_dir_obj {
|
||||
meta.name = meta.name.trim_end_matches(GLOBAL_DIR_SUFFIX_WITH_SLASH).to_owned();
|
||||
meta.name.push_str(SLASH_SEPARATOR);
|
||||
}
|
||||
|
||||
meta.metadata = res;
|
||||
|
||||
out.write_obj(&meta).await?;
|
||||
*objs_returned += 1;
|
||||
}
|
||||
Err(err) => {
|
||||
if err == Error::FileNotFound || err == Error::IsNotRegular {
|
||||
// NOT an object, append to stack (with slash)
|
||||
// If dirObject, but no metadata (which is unexpected) we skip it.
|
||||
if !is_dir_obj && !is_empty_dir(self.get_object_path(&opts.bucket, &meta.name)?).await {
|
||||
meta.name.push_str(SLASH_SEPARATOR);
|
||||
dir_stack.push(meta.name);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
while let Some(dir) = dir_stack.pop() {
|
||||
if opts.limit > 0 && *objs_returned >= opts.limit {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
out.write_obj(&MetaCacheEntry {
|
||||
name: dir.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
*objs_returned += 1;
|
||||
|
||||
if opts.recursive {
|
||||
let mut dir = dir;
|
||||
let mut opts = opts.clone();
|
||||
opts.filter_prefix = None;
|
||||
if let Err(er) = Box::pin(self.scan_dir(&mut dir, &opts, out, objs_returned)).await {
|
||||
warn!("scan_dir err {:?}", &er);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,608 +0,0 @@
|
||||
use crate::bucket::metadata_sys::get_versioning_config;
|
||||
use crate::bucket::versioning::VersioningApi;
|
||||
use crate::store_api::ObjectInfo;
|
||||
use crate::utils::path::SLASH_SEPARATOR;
|
||||
use rustfs_error::{Error, Result};
|
||||
use rustfs_filemeta::merge_file_meta_versions;
|
||||
use rustfs_filemeta::FileInfo;
|
||||
use rustfs_filemeta::FileInfoVersions;
|
||||
use rustfs_filemeta::FileMeta;
|
||||
use rustfs_filemeta::FileMetaShallowVersion;
|
||||
use rustfs_filemeta::VersionType;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::cmp::Ordering;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::warn;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MetadataResolutionParams {
|
||||
pub dir_quorum: usize,
|
||||
pub obj_quorum: usize,
|
||||
pub requested_versions: usize,
|
||||
pub bucket: String,
|
||||
pub strict: bool,
|
||||
pub candidates: Vec<Vec<FileMetaShallowVersion>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct MetaCacheEntry {
|
||||
// name is the full name of the object including prefixes
|
||||
pub name: String,
|
||||
// Metadata. If none is present it is not an object but only a prefix.
|
||||
// Entries without metadata will only be present in non-recursive scans.
|
||||
pub metadata: Vec<u8>,
|
||||
|
||||
// cached contains the metadata if decoded.
|
||||
pub cached: Option<FileMeta>,
|
||||
|
||||
// Indicates the entry can be reused and only one reference to metadata is expected.
|
||||
pub reusable: bool,
|
||||
}
|
||||
|
||||
impl MetaCacheEntry {
|
||||
pub fn marshal_msg(&self) -> Result<Vec<u8>> {
|
||||
let mut wr = Vec::new();
|
||||
rmp::encode::write_bool(&mut wr, true)?;
|
||||
|
||||
rmp::encode::write_str(&mut wr, &self.name)?;
|
||||
|
||||
rmp::encode::write_bin(&mut wr, &self.metadata)?;
|
||||
|
||||
Ok(wr)
|
||||
}
|
||||
|
||||
pub fn is_dir(&self) -> bool {
|
||||
self.metadata.is_empty() && self.name.ends_with('/')
|
||||
}
|
||||
pub fn is_in_dir(&self, dir: &str, separator: &str) -> bool {
|
||||
if dir.is_empty() {
|
||||
let idx = self.name.find(separator);
|
||||
return idx.is_none() || idx.unwrap() == self.name.len() - separator.len();
|
||||
}
|
||||
|
||||
let ext = self.name.trim_start_matches(dir);
|
||||
|
||||
if ext.len() != self.name.len() {
|
||||
let idx = ext.find(separator);
|
||||
return idx.is_none() || idx.unwrap() == ext.len() - separator.len();
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
pub fn is_object(&self) -> bool {
|
||||
!self.metadata.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_object_dir(&self) -> bool {
|
||||
!self.metadata.is_empty() && self.name.ends_with(SLASH_SEPARATOR)
|
||||
}
|
||||
|
||||
pub fn is_latest_delete_marker(&mut self) -> bool {
|
||||
if let Some(cached) = &self.cached {
|
||||
if cached.versions.is_empty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
return cached.versions[0].header.version_type == VersionType::Delete;
|
||||
}
|
||||
|
||||
if !FileMeta::is_xl2_v1_format(&self.metadata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match FileMeta::check_xl2_v1(&self.metadata) {
|
||||
Ok((meta, _, _)) => {
|
||||
if !meta.is_empty() {
|
||||
return FileMeta::is_latest_delete_marker(meta);
|
||||
}
|
||||
}
|
||||
Err(_) => return true,
|
||||
}
|
||||
|
||||
match self.xl_meta() {
|
||||
Ok(res) => {
|
||||
if res.versions.is_empty() {
|
||||
return true;
|
||||
}
|
||||
res.versions[0].header.version_type == VersionType::Delete
|
||||
}
|
||||
Err(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
pub fn to_fileinfo(&self, bucket: &str) -> Result<FileInfo> {
|
||||
if self.is_dir() {
|
||||
return Ok(FileInfo {
|
||||
volume: bucket.to_owned(),
|
||||
name: self.name.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
if self.cached.is_some() {
|
||||
let fm = self.cached.as_ref().unwrap();
|
||||
if fm.versions.is_empty() {
|
||||
return Ok(FileInfo {
|
||||
volume: bucket.to_owned(),
|
||||
name: self.name.clone(),
|
||||
deleted: true,
|
||||
is_latest: true,
|
||||
mod_time: Some(OffsetDateTime::UNIX_EPOCH),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
let fi = fm.into_fileinfo(bucket, self.name.as_str(), "", false, false)?;
|
||||
|
||||
return Ok(fi);
|
||||
}
|
||||
|
||||
let mut fm = FileMeta::new();
|
||||
fm.unmarshal_msg(&self.metadata)?;
|
||||
|
||||
let fi = fm.into_fileinfo(bucket, self.name.as_str(), "", false, false)?;
|
||||
|
||||
return Ok(fi);
|
||||
}
|
||||
|
||||
pub fn file_info_versions(&self, bucket: &str) -> Result<FileInfoVersions> {
|
||||
if self.is_dir() {
|
||||
return Ok(FileInfoVersions {
|
||||
volume: bucket.to_string(),
|
||||
name: self.name.clone(),
|
||||
versions: vec![FileInfo {
|
||||
volume: bucket.to_string(),
|
||||
name: self.name.clone(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
let mut fm = FileMeta::new();
|
||||
fm.unmarshal_msg(&self.metadata)?;
|
||||
|
||||
fm.into_file_info_versions(bucket, self.name.as_str(), false)
|
||||
}
|
||||
|
||||
pub fn matches(&self, other: Option<&MetaCacheEntry>, strict: bool) -> (Option<MetaCacheEntry>, bool) {
|
||||
if other.is_none() {
|
||||
return (None, false);
|
||||
}
|
||||
|
||||
let other = other.unwrap();
|
||||
|
||||
let mut prefer = None;
|
||||
if self.name != other.name {
|
||||
if self.name < other.name {
|
||||
return (Some(self.clone()), false);
|
||||
}
|
||||
return (Some(other.clone()), false);
|
||||
}
|
||||
|
||||
if other.is_dir() || self.is_dir() {
|
||||
if self.is_dir() {
|
||||
return (Some(self.clone()), other.is_dir() == self.is_dir());
|
||||
}
|
||||
|
||||
return (Some(other.clone()), other.is_dir() == self.is_dir());
|
||||
}
|
||||
let self_vers = match &self.cached {
|
||||
Some(file_meta) => file_meta.clone(),
|
||||
None => match FileMeta::load(&self.metadata) {
|
||||
Ok(meta) => meta,
|
||||
Err(_) => {
|
||||
return (None, false);
|
||||
}
|
||||
},
|
||||
};
|
||||
let other_vers = match &other.cached {
|
||||
Some(file_meta) => file_meta.clone(),
|
||||
None => match FileMeta::load(&other.metadata) {
|
||||
Ok(meta) => meta,
|
||||
Err(_) => {
|
||||
return (None, false);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if self_vers.versions.len() != other_vers.versions.len() {
|
||||
match self_vers.lastest_mod_time().cmp(&other_vers.lastest_mod_time()) {
|
||||
Ordering::Greater => {
|
||||
return (Some(self.clone()), false);
|
||||
}
|
||||
Ordering::Less => {
|
||||
return (Some(other.clone()), false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if self_vers.versions.len() > other_vers.versions.len() {
|
||||
return (Some(self.clone()), false);
|
||||
}
|
||||
return (Some(other.clone()), false);
|
||||
}
|
||||
|
||||
for (s_version, o_version) in self_vers.versions.iter().zip(other_vers.versions.iter()) {
|
||||
if s_version.header != o_version.header {
|
||||
if s_version.header.has_ec() != o_version.header.has_ec() {
|
||||
// One version has EC and the other doesn't - may have been written later.
|
||||
// Compare without considering EC.
|
||||
let (mut a, mut b) = (s_version.header.clone(), o_version.header.clone());
|
||||
(a.ec_n, a.ec_m, b.ec_n, b.ec_m) = (0, 0, 0, 0);
|
||||
if a == b {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if !strict && s_version.header.matches_not_strict(&o_version.header) {
|
||||
if prefer.is_none() {
|
||||
if s_version.header.sorts_before(&o_version.header) {
|
||||
prefer = Some(self.clone());
|
||||
} else {
|
||||
prefer = Some(other.clone());
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if prefer.is_some() {
|
||||
return (prefer, false);
|
||||
}
|
||||
|
||||
if s_version.header.sorts_before(&o_version.header) {
|
||||
return (Some(self.clone()), false);
|
||||
}
|
||||
|
||||
return (Some(other.clone()), false);
|
||||
}
|
||||
}
|
||||
|
||||
if prefer.is_none() {
|
||||
prefer = Some(self.clone());
|
||||
}
|
||||
|
||||
(prefer, true)
|
||||
}
|
||||
|
||||
pub fn xl_meta(&mut self) -> Result<FileMeta> {
|
||||
if self.is_dir() {
|
||||
return Err(Error::FileNotFound);
|
||||
}
|
||||
|
||||
if let Some(meta) = &self.cached {
|
||||
Ok(meta.clone())
|
||||
} else {
|
||||
if self.metadata.is_empty() {
|
||||
return Err(Error::FileNotFound);
|
||||
}
|
||||
|
||||
let meta = FileMeta::load(&self.metadata)?;
|
||||
|
||||
self.cached = Some(meta.clone());
|
||||
|
||||
Ok(meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MetaCacheEntries(pub Vec<Option<MetaCacheEntry>>);
|
||||
|
||||
impl MetaCacheEntries {
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn as_ref(&self) -> &[Option<MetaCacheEntry>] {
|
||||
&self.0
|
||||
}
|
||||
pub fn resolve(&self, mut params: MetadataResolutionParams) -> Option<MetaCacheEntry> {
|
||||
if self.0.is_empty() {
|
||||
warn!("decommission_pool: entries resolve empty");
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut dir_exists = 0;
|
||||
let mut selected = None;
|
||||
|
||||
params.candidates.clear();
|
||||
let mut objs_agree = 0;
|
||||
let mut objs_valid = 0;
|
||||
|
||||
for entry in self.0.iter().flatten() {
|
||||
let mut entry = entry.clone();
|
||||
|
||||
warn!("decommission_pool: entries resolve entry {:?}", entry.name);
|
||||
if entry.name.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if entry.is_dir() {
|
||||
dir_exists += 1;
|
||||
selected = Some(entry.clone());
|
||||
warn!("decommission_pool: entries resolve entry dir {:?}", entry.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
let xl = match entry.xl_meta() {
|
||||
Ok(xl) => xl,
|
||||
Err(e) => {
|
||||
warn!("decommission_pool: entries resolve entry xl_meta {:?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
objs_valid += 1;
|
||||
|
||||
params.candidates.push(xl.versions.clone());
|
||||
|
||||
if selected.is_none() {
|
||||
selected = Some(entry.clone());
|
||||
objs_agree = 1;
|
||||
warn!("decommission_pool: entries resolve entry selected {:?}", entry.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if let (prefer, true) = entry.matches(selected.as_ref(), params.strict) {
|
||||
selected = prefer;
|
||||
objs_agree += 1;
|
||||
warn!("decommission_pool: entries resolve entry prefer {:?}", entry.name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(selected) = selected else {
|
||||
warn!("decommission_pool: entries resolve entry no selected");
|
||||
return None;
|
||||
};
|
||||
|
||||
if selected.is_dir() && dir_exists >= params.dir_quorum {
|
||||
warn!("decommission_pool: entries resolve entry dir selected {:?}", selected.name);
|
||||
return Some(selected);
|
||||
}
|
||||
|
||||
// If we would never be able to reach read quorum.
|
||||
if objs_valid < params.obj_quorum {
|
||||
warn!(
|
||||
"decommission_pool: entries resolve entry not enough objects {} < {}",
|
||||
objs_valid, params.obj_quorum
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
if objs_agree == objs_valid {
|
||||
warn!("decommission_pool: entries resolve entry all agree {} == {}", objs_agree, objs_valid);
|
||||
return Some(selected);
|
||||
}
|
||||
|
||||
let Some(cached) = selected.cached else {
|
||||
warn!("decommission_pool: entries resolve entry no cached");
|
||||
return None;
|
||||
};
|
||||
|
||||
let versions = merge_file_meta_versions(params.obj_quorum, params.strict, params.requested_versions, ¶ms.candidates);
|
||||
if versions.is_empty() {
|
||||
warn!("decommission_pool: entries resolve entry no versions");
|
||||
return None;
|
||||
}
|
||||
|
||||
let metadata = match cached.marshal_msg() {
|
||||
Ok(meta) => meta,
|
||||
Err(e) => {
|
||||
warn!("decommission_pool: entries resolve entry marshal_msg {:?}", e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Merge if we have disagreement.
|
||||
// Create a new merged result.
|
||||
let new_selected = MetaCacheEntry {
|
||||
name: selected.name.clone(),
|
||||
cached: Some(FileMeta {
|
||||
meta_ver: cached.meta_ver,
|
||||
versions,
|
||||
..Default::default()
|
||||
}),
|
||||
reusable: true,
|
||||
metadata,
|
||||
};
|
||||
|
||||
warn!("decommission_pool: entries resolve entry selected {:?}", new_selected.name);
|
||||
Some(new_selected)
|
||||
}
|
||||
|
||||
pub fn first_found(&self) -> (Option<MetaCacheEntry>, usize) {
|
||||
(self.0.iter().find(|x| x.is_some()).cloned().unwrap_or_default(), self.0.len())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MetaCacheEntriesSortedResult {
|
||||
pub entries: Option<MetaCacheEntriesSorted>,
|
||||
pub err: Option<Error>,
|
||||
}
|
||||
|
||||
// impl MetaCacheEntriesSortedResult {
|
||||
// pub fn entriy_list(&self) -> Vec<&MetaCacheEntry> {
|
||||
// if let Some(entries) = &self.entries {
|
||||
// entries.entries()
|
||||
// } else {
|
||||
// Vec::new()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MetaCacheEntriesSorted {
|
||||
pub o: MetaCacheEntries,
|
||||
pub list_id: Option<String>,
|
||||
pub reuse: bool,
|
||||
pub last_skipped_entry: Option<String>,
|
||||
}
|
||||
|
||||
impl MetaCacheEntriesSorted {
|
||||
pub fn entries(&self) -> Vec<&MetaCacheEntry> {
|
||||
let entries: Vec<&MetaCacheEntry> = self.o.0.iter().flatten().collect();
|
||||
entries
|
||||
}
|
||||
pub fn forward_past(&mut self, marker: Option<String>) {
|
||||
if let Some(val) = marker {
|
||||
// TODO: reuse
|
||||
if let Some(idx) = self.o.0.iter().flatten().position(|v| v.name > val) {
|
||||
self.o.0 = self.o.0.split_off(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn file_infos(&self, bucket: &str, prefix: &str, delimiter: Option<String>) -> Vec<ObjectInfo> {
|
||||
let vcfg = get_versioning_config(bucket).await.ok();
|
||||
let mut objects = Vec::with_capacity(self.o.as_ref().len());
|
||||
let mut prev_prefix = "";
|
||||
for entry in self.o.as_ref().iter().flatten() {
|
||||
if entry.is_object() {
|
||||
if let Some(delimiter) = &delimiter {
|
||||
if let Some(idx) = entry.name.trim_start_matches(prefix).find(delimiter) {
|
||||
let idx = prefix.len() + idx + delimiter.len();
|
||||
if let Some(curr_prefix) = entry.name.get(0..idx) {
|
||||
if curr_prefix == prev_prefix {
|
||||
continue;
|
||||
}
|
||||
|
||||
prev_prefix = curr_prefix;
|
||||
|
||||
objects.push(ObjectInfo {
|
||||
is_dir: true,
|
||||
bucket: bucket.to_owned(),
|
||||
name: curr_prefix.to_owned(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(fi) = entry.to_fileinfo(bucket) {
|
||||
// TODO:VersionPurgeStatus
|
||||
let versioned = vcfg.clone().map(|v| v.0.versioned(&entry.name)).unwrap_or_default();
|
||||
objects.push(ObjectInfo::from_file_info(&fi, bucket, &entry.name, versioned));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if entry.is_dir() {
|
||||
if let Some(delimiter) = &delimiter {
|
||||
if let Some(idx) = entry.name.trim_start_matches(prefix).find(delimiter) {
|
||||
let idx = prefix.len() + idx + delimiter.len();
|
||||
if let Some(curr_prefix) = entry.name.get(0..idx) {
|
||||
if curr_prefix == prev_prefix {
|
||||
continue;
|
||||
}
|
||||
|
||||
prev_prefix = curr_prefix;
|
||||
|
||||
objects.push(ObjectInfo {
|
||||
is_dir: true,
|
||||
bucket: bucket.to_owned(),
|
||||
name: curr_prefix.to_owned(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objects
|
||||
}
|
||||
|
||||
pub async fn file_info_versions(
|
||||
&self,
|
||||
bucket: &str,
|
||||
prefix: &str,
|
||||
delimiter: Option<String>,
|
||||
after_v: Option<String>,
|
||||
) -> Vec<ObjectInfo> {
|
||||
let vcfg = get_versioning_config(bucket).await.ok();
|
||||
let mut objects = Vec::with_capacity(self.o.as_ref().len());
|
||||
let mut prev_prefix = "";
|
||||
let mut after_v = after_v;
|
||||
for entry in self.o.as_ref().iter().flatten() {
|
||||
if entry.is_object() {
|
||||
if let Some(delimiter) = &delimiter {
|
||||
if let Some(idx) = entry.name.trim_start_matches(prefix).find(delimiter) {
|
||||
let idx = prefix.len() + idx + delimiter.len();
|
||||
if let Some(curr_prefix) = entry.name.get(0..idx) {
|
||||
if curr_prefix == prev_prefix {
|
||||
continue;
|
||||
}
|
||||
|
||||
prev_prefix = curr_prefix;
|
||||
|
||||
objects.push(ObjectInfo {
|
||||
is_dir: true,
|
||||
bucket: bucket.to_owned(),
|
||||
name: curr_prefix.to_owned(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let mut fiv = match entry.file_info_versions(bucket) {
|
||||
Ok(res) => res,
|
||||
Err(_err) => {
|
||||
//
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let fi_versions = 'c: {
|
||||
if let Some(after_val) = &after_v {
|
||||
if let Some(idx) = fiv.find_version_index(after_val) {
|
||||
after_v = None;
|
||||
break 'c fiv.versions.split_off(idx + 1);
|
||||
}
|
||||
|
||||
after_v = None;
|
||||
break 'c fiv.versions;
|
||||
} else {
|
||||
break 'c fiv.versions;
|
||||
}
|
||||
};
|
||||
|
||||
for fi in fi_versions.into_iter() {
|
||||
// VersionPurgeStatus
|
||||
|
||||
let versioned = vcfg.clone().map(|v| v.0.versioned(&entry.name)).unwrap_or_default();
|
||||
objects.push(ObjectInfo::from_file_info(&fi, bucket, &entry.name, versioned));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if entry.is_dir() {
|
||||
if let Some(delimiter) = &delimiter {
|
||||
if let Some(idx) = entry.name.trim_start_matches(prefix).find(delimiter) {
|
||||
let idx = prefix.len() + idx + delimiter.len();
|
||||
if let Some(curr_prefix) = entry.name.get(0..idx) {
|
||||
if curr_prefix == prev_prefix {
|
||||
continue;
|
||||
}
|
||||
|
||||
prev_prefix = curr_prefix;
|
||||
|
||||
objects.push(ObjectInfo {
|
||||
is_dir: true,
|
||||
bucket: bucket.to_owned(),
|
||||
name: curr_prefix.to_owned(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objects
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
use super::fs;
|
||||
use rustfs_error::{to_access_error, Error, Result};
|
||||
use rustfs_utils::os::same_disk;
|
||||
use std::{
|
||||
io,
|
||||
path::{Component, Path},
|
||||
};
|
||||
|
||||
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::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::FileNameTooLong);
|
||||
}
|
||||
|
||||
// On Unix we reject paths if they are just '.', '..' or '/'
|
||||
let invalid_paths = [".", "..", "/"];
|
||||
if invalid_paths.contains(&path_name) {
|
||||
return Err(Error::FileAccessDenied);
|
||||
}
|
||||
|
||||
// Check each path segment length is > 255 on all Unix
|
||||
// platforms, look for this value as NAME_MAX in
|
||||
// /usr/include/linux/limits.h
|
||||
let mut count = 0usize;
|
||||
for c in path_name.chars() {
|
||||
match c {
|
||||
'/' | '\\' if cfg!(target_os = "windows") => count = 0, // Reset
|
||||
_ => {
|
||||
count += 1;
|
||||
if count > 255 {
|
||||
return Err(Error::FileNameTooLong);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Success.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_root_disk(disk_path: &str, root_disk: &str) -> Result<bool> {
|
||||
if cfg!(target_os = "windows") {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(same_disk(disk_path, root_disk)?)
|
||||
}
|
||||
|
||||
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 {
|
||||
return Err(to_access_error(e, Error::FileAccessDenied).into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn is_empty_dir(path: impl AsRef<Path>) -> bool {
|
||||
read_dir(path.as_ref(), 1).await.is_ok_and(|v| v.is_empty())
|
||||
}
|
||||
|
||||
// read_dir count read limit. when count == 0 unlimit.
|
||||
pub async fn read_dir(path: impl AsRef<Path>, count: i32) -> std::io::Result<Vec<String>> {
|
||||
let mut entries = tokio::fs::read_dir(path.as_ref()).await?;
|
||||
|
||||
let mut volumes = Vec::new();
|
||||
|
||||
let mut count = count;
|
||||
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
|
||||
if name.is_empty() || name == "." || name == ".." {
|
||||
continue;
|
||||
}
|
||||
|
||||
let file_type = entry.file_type().await?;
|
||||
|
||||
if file_type.is_file() {
|
||||
volumes.push(name);
|
||||
} else if file_type.is_dir() {
|
||||
volumes.push(format!("{}{}", name, super::path::SLASH_SEPARATOR));
|
||||
}
|
||||
count -= 1;
|
||||
if count == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(volumes)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn rename_all(
|
||||
src_file_path: impl AsRef<Path>,
|
||||
dst_file_path: impl AsRef<Path>,
|
||||
base_dir: impl AsRef<Path>,
|
||||
) -> Result<()> {
|
||||
reliable_rename(src_file_path, dst_file_path.as_ref(), base_dir)
|
||||
.await
|
||||
.map_err(|e| to_access_error(e, Error::FileAccessDenied))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reliable_rename(
|
||||
src_file_path: impl AsRef<Path>,
|
||||
dst_file_path: impl AsRef<Path>,
|
||||
base_dir: impl AsRef<Path>,
|
||||
) -> io::Result<()> {
|
||||
if let Some(parent) = dst_file_path.as_ref().parent() {
|
||||
if !file_exists(parent) {
|
||||
// info!("reliable_rename reliable_mkdir_all parent: {:?}", parent);
|
||||
reliable_mkdir_all(parent, base_dir.as_ref()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
loop {
|
||||
if let Err(e) = fs::rename_std(src_file_path.as_ref(), dst_file_path.as_ref()) {
|
||||
if e.kind() == std::io::ErrorKind::NotFound && i == 0 {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
// info!(
|
||||
// "reliable_rename failed. src_file_path: {:?}, dst_file_path: {:?}, base_dir: {:?}, err: {:?}",
|
||||
// src_file_path.as_ref(),
|
||||
// dst_file_path.as_ref(),
|
||||
// base_dir.as_ref(),
|
||||
// e
|
||||
// );
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reliable_mkdir_all(path: impl AsRef<Path>, base_dir: impl AsRef<Path>) -> io::Result<()> {
|
||||
let mut i = 0;
|
||||
|
||||
let mut base_dir = base_dir.as_ref();
|
||||
loop {
|
||||
if let Err(e) = os_mkdir_all(path.as_ref(), base_dir).await {
|
||||
if e.kind() == std::io::ErrorKind::NotFound && i == 0 {
|
||||
i += 1;
|
||||
|
||||
if let Some(base_parent) = base_dir.parent() {
|
||||
if let Some(c) = base_parent.components().next() {
|
||||
if c != Component::RootDir {
|
||||
base_dir = base_parent
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn os_mkdir_all(dir_path: impl AsRef<Path>, base_dir: impl AsRef<Path>) -> io::Result<()> {
|
||||
if !base_dir.as_ref().to_string_lossy().is_empty() && base_dir.as_ref().starts_with(dir_path.as_ref()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(parent) = dir_path.as_ref().parent() {
|
||||
// 不支持递归,直接create_dir_all了
|
||||
if let Err(e) = fs::make_dir_all(&parent).await {
|
||||
if e.kind() == std::io::ErrorKind::AlreadyExists {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(e);
|
||||
}
|
||||
// Box::pin(os_mkdir_all(&parent, &base_dir)).await?;
|
||||
}
|
||||
|
||||
if let Err(e) = fs::mkdir(dir_path.as_ref()).await {
|
||||
if e.kind() == std::io::ErrorKind::AlreadyExists {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Err(e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn file_exists(path: impl AsRef<Path>) -> bool {
|
||||
std::fs::metadata(path.as_ref()).map(|_| true).unwrap_or(false)
|
||||
}
|
||||
@@ -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,908 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::api::CheckPartsResp;
|
||||
use crate::api::DeleteOptions;
|
||||
use crate::api::DiskAPI;
|
||||
use crate::api::DiskInfo;
|
||||
use crate::api::DiskInfoOptions;
|
||||
use crate::api::DiskLocation;
|
||||
use crate::api::DiskOption;
|
||||
use crate::api::ReadMultipleReq;
|
||||
use crate::api::ReadMultipleResp;
|
||||
use crate::api::ReadOptions;
|
||||
use crate::api::RenameDataResp;
|
||||
use crate::api::UpdateMetadataOpts;
|
||||
use crate::api::VolumeInfo;
|
||||
use crate::api::WalkDirOptions;
|
||||
use crate::endpoint::Endpoint;
|
||||
use futures::StreamExt as _;
|
||||
use http::HeaderMap;
|
||||
use http::Method;
|
||||
use protos::node_service_time_out_client;
|
||||
use protos::proto_gen::node_service::CheckPartsRequest;
|
||||
use protos::proto_gen::node_service::DeletePathsRequest;
|
||||
use protos::proto_gen::node_service::DeleteRequest;
|
||||
use protos::proto_gen::node_service::DeleteVersionRequest;
|
||||
use protos::proto_gen::node_service::DeleteVersionsRequest;
|
||||
use protos::proto_gen::node_service::DeleteVolumeRequest;
|
||||
use protos::proto_gen::node_service::DiskInfoRequest;
|
||||
use protos::proto_gen::node_service::ListDirRequest;
|
||||
use protos::proto_gen::node_service::ListVolumesRequest;
|
||||
use protos::proto_gen::node_service::MakeVolumeRequest;
|
||||
use protos::proto_gen::node_service::MakeVolumesRequest;
|
||||
use protos::proto_gen::node_service::ReadAllRequest;
|
||||
use protos::proto_gen::node_service::ReadMultipleRequest;
|
||||
use protos::proto_gen::node_service::ReadVersionRequest;
|
||||
use protos::proto_gen::node_service::ReadXlRequest;
|
||||
use protos::proto_gen::node_service::RenameDataRequest;
|
||||
use protos::proto_gen::node_service::RenameFileRequst;
|
||||
use protos::proto_gen::node_service::RenamePartRequst;
|
||||
use protos::proto_gen::node_service::StatVolumeRequest;
|
||||
use protos::proto_gen::node_service::UpdateMetadataRequest;
|
||||
use protos::proto_gen::node_service::VerifyFileRequest;
|
||||
use protos::proto_gen::node_service::WalkDirRequest;
|
||||
use protos::proto_gen::node_service::WriteAllRequest;
|
||||
use protos::proto_gen::node_service::WriteMetadataRequest;
|
||||
use rmp_serde::Serializer;
|
||||
use rustfs_error::Error;
|
||||
use rustfs_error::Result;
|
||||
use rustfs_filemeta::FileInfo;
|
||||
use rustfs_filemeta::FileInfoVersions;
|
||||
use rustfs_filemeta::RawFileInfo;
|
||||
use rustfs_metacache::MetaCacheEntry;
|
||||
use rustfs_metacache::MetacacheWriter;
|
||||
use rustfs_rio::HttpReader;
|
||||
use rustfs_rio::HttpWriter;
|
||||
use serde::Serialize as _;
|
||||
use tokio::io::AsyncRead;
|
||||
use tokio::io::AsyncWrite;
|
||||
use tokio::sync::Mutex;
|
||||
use tonic::Request;
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RemoteDisk {
|
||||
pub id: Mutex<Option<Uuid>>,
|
||||
pub addr: String,
|
||||
pub url: url::Url,
|
||||
pub root: PathBuf,
|
||||
endpoint: Endpoint,
|
||||
}
|
||||
|
||||
impl RemoteDisk {
|
||||
pub async fn new(ep: &Endpoint, _opt: &DiskOption) -> Result<Self> {
|
||||
// let root = fs::canonicalize(ep.url.path()).await?;
|
||||
let root = PathBuf::from(ep.get_file_path());
|
||||
let addr = format!("{}://{}:{}", ep.url.scheme(), ep.url.host_str().unwrap(), ep.url.port().unwrap());
|
||||
Ok(Self {
|
||||
id: Mutex::new(None),
|
||||
addr,
|
||||
url: ep.url.clone(),
|
||||
root,
|
||||
endpoint: ep.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: all api need to handle errors
|
||||
#[async_trait::async_trait]
|
||||
impl DiskAPI for RemoteDisk {
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn to_string(&self) -> String {
|
||||
self.endpoint.to_string()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn is_local(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn host_name(&self) -> String {
|
||||
self.endpoint.host_port()
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn is_online(&self) -> bool {
|
||||
// TODO: 连接状态
|
||||
if (node_service_time_out_client(&self.addr).await).is_ok() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn endpoint(&self) -> Endpoint {
|
||||
self.endpoint.clone()
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn close(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn path(&self) -> PathBuf {
|
||||
self.root.clone()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn get_disk_location(&self) -> DiskLocation {
|
||||
DiskLocation {
|
||||
pool_idx: {
|
||||
if self.endpoint.pool_idx < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.endpoint.pool_idx as usize)
|
||||
}
|
||||
},
|
||||
set_idx: {
|
||||
if self.endpoint.set_idx < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.endpoint.set_idx as usize)
|
||||
}
|
||||
},
|
||||
disk_idx: {
|
||||
if self.endpoint.disk_idx < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.endpoint.disk_idx as usize)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn get_disk_id(&self) -> Result<Option<Uuid>> {
|
||||
Ok(*self.id.lock().await)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn set_disk_id(&self, id: Option<Uuid>) -> Result<()> {
|
||||
let mut lock = self.id.lock().await;
|
||||
*lock = id;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_all(&self, volume: &str, path: &str) -> Result<Vec<u8>> {
|
||||
info!("read_all {}/{}", volume, path);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ReadAllRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
});
|
||||
|
||||
let response = client.read_all(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(Error::FileNotFound);
|
||||
}
|
||||
|
||||
Ok(response.data)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn write_all(&self, volume: &str, path: &str, data: Vec<u8>) -> Result<()> {
|
||||
info!("write_all");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(WriteAllRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
data,
|
||||
});
|
||||
|
||||
let response = client.write_all(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete(&self, volume: &str, path: &str, opt: DeleteOptions) -> Result<()> {
|
||||
info!("delete {}/{}/{}", self.endpoint.to_string(), volume, path);
|
||||
let options = serde_json::to_string(&opt)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeleteRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
options,
|
||||
});
|
||||
|
||||
let response = client.delete(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn verify_file(&self, volume: &str, path: &str, fi: &FileInfo) -> Result<CheckPartsResp> {
|
||||
info!("verify_file");
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(VerifyFileRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
});
|
||||
|
||||
let response = client.verify_file(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let check_parts_resp = serde_json::from_str::<CheckPartsResp>(&response.check_parts_resp)?;
|
||||
|
||||
Ok(check_parts_resp)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn check_parts(&self, volume: &str, path: &str, fi: &FileInfo) -> Result<CheckPartsResp> {
|
||||
info!("check_parts");
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(CheckPartsRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
});
|
||||
|
||||
let response = client.check_parts(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let check_parts_resp = serde_json::from_str::<CheckPartsResp>(&response.check_parts_resp)?;
|
||||
|
||||
Ok(check_parts_resp)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn rename_part(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str, meta: Vec<u8>) -> Result<()> {
|
||||
info!("rename_part {}/{}", src_volume, src_path);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(RenamePartRequst {
|
||||
disk: self.endpoint.to_string(),
|
||||
src_volume: src_volume.to_string(),
|
||||
src_path: src_path.to_string(),
|
||||
dst_volume: dst_volume.to_string(),
|
||||
dst_path: dst_path.to_string(),
|
||||
meta,
|
||||
});
|
||||
|
||||
let response = client.rename_part(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn rename_file(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str) -> Result<()> {
|
||||
info!("rename_file");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(RenameFileRequst {
|
||||
disk: self.endpoint.to_string(),
|
||||
src_volume: src_volume.to_string(),
|
||||
src_path: src_path.to_string(),
|
||||
dst_volume: dst_volume.to_string(),
|
||||
dst_path: dst_path.to_string(),
|
||||
});
|
||||
|
||||
let response = client.rename_file(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn create_file(&self, _origvolume: &str, volume: &str, path: &str, file_size: usize) -> Result<Box<dyn AsyncWrite>> {
|
||||
info!("create_file {}/{}/{}", self.endpoint.to_string(), volume, path);
|
||||
|
||||
let url = format!(
|
||||
"{}/rustfs/rpc/put_file_stream?disk={}&volume={}&path={}&append={}&size={}",
|
||||
self.endpoint.grid_host(),
|
||||
urlencoding::encode(&self.endpoint.to_string()),
|
||||
urlencoding::encode(volume),
|
||||
urlencoding::encode(path),
|
||||
false,
|
||||
file_size
|
||||
);
|
||||
|
||||
let wd = HttpWriter::new(url, Method::PUT, HeaderMap::new()).await?;
|
||||
|
||||
Ok(Box::new(wd))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn append_file(&self, volume: &str, path: &str) -> Result<Box<dyn AsyncWrite>> {
|
||||
info!("append_file {}/{}", volume, path);
|
||||
let url = format!(
|
||||
"{}/rustfs/rpc/put_file_stream?disk={}&volume={}&path={}&append={}&size={}",
|
||||
self.endpoint.grid_host(),
|
||||
urlencoding::encode(&self.endpoint.to_string()),
|
||||
urlencoding::encode(volume),
|
||||
urlencoding::encode(path),
|
||||
true,
|
||||
0
|
||||
);
|
||||
|
||||
let wd = HttpWriter::new(url, Method::PUT, HeaderMap::new()).await?;
|
||||
|
||||
Ok(Box::new(wd))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn read_file(&self, volume: &str, path: &str) -> Result<Box<dyn AsyncRead>> {
|
||||
info!("read_file {}/{}", volume, path);
|
||||
let url = format!(
|
||||
"{}/rustfs/rpc/read_file_stream?disk={}&volume={}&path={}&offset={}&length={}",
|
||||
self.endpoint.grid_host(),
|
||||
urlencoding::encode(&self.endpoint.to_string()),
|
||||
urlencoding::encode(volume),
|
||||
urlencoding::encode(path),
|
||||
0,
|
||||
0
|
||||
);
|
||||
|
||||
let rd = HttpReader::new(url, Method::GET, HeaderMap::new()).await?;
|
||||
|
||||
Ok(Box::new(rd))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn read_file_stream(&self, volume: &str, path: &str, offset: usize, length: usize) -> Result<Box<dyn AsyncRead>> {
|
||||
info!("read_file_stream {}/{}/{}", self.endpoint.to_string(), volume, path);
|
||||
let url = format!(
|
||||
"{}/rustfs/rpc/read_file_stream?disk={}&volume={}&path={}&offset={}&length={}",
|
||||
self.endpoint.grid_host(),
|
||||
urlencoding::encode(&self.endpoint.to_string()),
|
||||
urlencoding::encode(volume),
|
||||
urlencoding::encode(path),
|
||||
offset,
|
||||
length
|
||||
);
|
||||
let rd = HttpReader::new(url, Method::GET, HeaderMap::new()).await?;
|
||||
|
||||
Ok(Box::new(rd))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn list_dir(&self, _origvolume: &str, volume: &str, _dir_path: &str, _count: i32) -> Result<Vec<String>> {
|
||||
info!("list_dir {}/{}", volume, _dir_path);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ListDirRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
});
|
||||
|
||||
let response = client.list_dir(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(response.volumes)
|
||||
}
|
||||
|
||||
// FIXME: TODO: use writer
|
||||
#[tracing::instrument(skip(self, wr))]
|
||||
async fn walk_dir<W: AsyncWrite + Unpin + Send>(&self, opts: WalkDirOptions, wr: &mut W) -> Result<()> {
|
||||
let now = std::time::SystemTime::now();
|
||||
info!("walk_dir {}/{}/{:?}", self.endpoint.to_string(), opts.bucket, opts.filter_prefix);
|
||||
let mut wr = wr;
|
||||
let mut out = MetacacheWriter::new(&mut wr);
|
||||
let mut buf = Vec::new();
|
||||
opts.serialize(&mut Serializer::new(&mut buf))?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(WalkDirRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
walk_dir_options: buf,
|
||||
});
|
||||
let mut response = client.walk_dir(request).await?.into_inner();
|
||||
|
||||
loop {
|
||||
match response.next().await {
|
||||
Some(Ok(resp)) => {
|
||||
if !resp.success {
|
||||
return Err(Error::msg(resp.error_info.unwrap_or("".to_string())));
|
||||
}
|
||||
let entry = serde_json::from_str::<MetaCacheEntry>(&resp.meta_cache_entry)
|
||||
.map_err(|_| Error::msg(format!("Unexpected response: {:?}", response)))?;
|
||||
out.write_obj(&entry).await?;
|
||||
}
|
||||
None => break,
|
||||
_ => return Err(Error::msg(format!("Unexpected response: {:?}", response))),
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"walk_dir {}/{:?} done {:?}",
|
||||
opts.bucket,
|
||||
opts.filter_prefix,
|
||||
now.elapsed().unwrap_or_default()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn rename_data(
|
||||
&self,
|
||||
src_volume: &str,
|
||||
src_path: &str,
|
||||
fi: FileInfo,
|
||||
dst_volume: &str,
|
||||
dst_path: &str,
|
||||
) -> Result<RenameDataResp> {
|
||||
info!("rename_data {}/{}/{}/{}", self.addr, self.endpoint.to_string(), dst_volume, dst_path);
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(RenameDataRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
src_volume: src_volume.to_string(),
|
||||
src_path: src_path.to_string(),
|
||||
file_info,
|
||||
dst_volume: dst_volume.to_string(),
|
||||
dst_path: dst_path.to_string(),
|
||||
});
|
||||
|
||||
let response = client.rename_data(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let rename_data_resp = serde_json::from_str::<RenameDataResp>(&response.rename_data_resp)?;
|
||||
|
||||
Ok(rename_data_resp)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn make_volumes(&self, volumes: Vec<&str>) -> Result<()> {
|
||||
info!("make_volumes");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(MakeVolumesRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volumes: volumes.iter().map(|s| (*s).to_string()).collect(),
|
||||
});
|
||||
|
||||
let response = client.make_volumes(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn make_volume(&self, volume: &str) -> Result<()> {
|
||||
info!("make_volume");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(MakeVolumeRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
});
|
||||
|
||||
let response = client.make_volume(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn list_volumes(&self) -> Result<Vec<VolumeInfo>> {
|
||||
info!("list_volumes");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ListVolumesRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
});
|
||||
|
||||
let response = client.list_volumes(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let infos = response
|
||||
.volume_infos
|
||||
.into_iter()
|
||||
.filter_map(|json_str| serde_json::from_str::<VolumeInfo>(&json_str).ok())
|
||||
.collect();
|
||||
|
||||
Ok(infos)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn stat_volume(&self, volume: &str) -> Result<VolumeInfo> {
|
||||
info!("stat_volume");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(StatVolumeRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
});
|
||||
|
||||
let response = client.stat_volume(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let volume_info = serde_json::from_str::<VolumeInfo>(&response.volume_info)?;
|
||||
|
||||
Ok(volume_info)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_paths(&self, volume: &str, paths: &[String]) -> Result<()> {
|
||||
info!("delete_paths");
|
||||
let paths = paths.to_owned();
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeletePathsRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
paths,
|
||||
});
|
||||
|
||||
let response = client.delete_paths(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn update_metadata(&self, volume: &str, path: &str, fi: FileInfo, opts: &UpdateMetadataOpts) -> Result<()> {
|
||||
info!("update_metadata");
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let opts = serde_json::to_string(&opts)?;
|
||||
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(UpdateMetadataRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.update_metadata(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn write_metadata(&self, _org_volume: &str, volume: &str, path: &str, fi: FileInfo) -> Result<()> {
|
||||
info!("write_metadata {}/{}", volume, path);
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(WriteMetadataRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
});
|
||||
|
||||
let response = client.write_metadata(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_version(
|
||||
&self,
|
||||
_org_volume: &str,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
version_id: &str,
|
||||
opts: &ReadOptions,
|
||||
) -> Result<FileInfo> {
|
||||
info!("read_version");
|
||||
let opts = serde_json::to_string(opts)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ReadVersionRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
version_id: version_id.to_string(),
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.read_version(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let file_info = serde_json::from_str::<FileInfo>(&response.file_info)?;
|
||||
|
||||
Ok(file_info)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn read_xl(&self, volume: &str, path: &str, read_data: bool) -> Result<RawFileInfo> {
|
||||
info!("read_xl {}/{}/{}", self.endpoint.to_string(), volume, path);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ReadXlRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
read_data,
|
||||
});
|
||||
|
||||
let response = client.read_xl(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let raw_file_info = serde_json::from_str::<RawFileInfo>(&response.raw_file_info)?;
|
||||
|
||||
Ok(raw_file_info)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_version(
|
||||
&self,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
fi: FileInfo,
|
||||
force_del_marker: bool,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<()> {
|
||||
info!("delete_version");
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let opts = serde_json::to_string(&opts)?;
|
||||
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeleteVersionRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
force_del_marker,
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.delete_version(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
// let raw_file_info = serde_json::from_str::<RawFileInfo>(&response.raw_file_info)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_versions(
|
||||
&self,
|
||||
volume: &str,
|
||||
versions: Vec<FileInfoVersions>,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<Vec<Option<Error>>> {
|
||||
info!("delete_versions");
|
||||
let opts = serde_json::to_string(&opts)?;
|
||||
let mut versions_str = Vec::with_capacity(versions.len());
|
||||
for file_info_versions in versions.iter() {
|
||||
versions_str.push(serde_json::to_string(file_info_versions)?);
|
||||
}
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeleteVersionsRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
versions: versions_str,
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.delete_versions(request).await?.into_inner();
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
let errors = response
|
||||
.errors
|
||||
.iter()
|
||||
.map(|error| {
|
||||
if error.is_empty() {
|
||||
None
|
||||
} else {
|
||||
use std::str::FromStr;
|
||||
Some(Error::from_str(error).unwrap_or(Error::msg(error)))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(errors)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_multiple(&self, req: ReadMultipleReq) -> Result<Vec<ReadMultipleResp>> {
|
||||
info!("read_multiple {}/{}/{}", self.endpoint.to_string(), req.bucket, req.prefix);
|
||||
let read_multiple_req = serde_json::to_string(&req)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ReadMultipleRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
read_multiple_req,
|
||||
});
|
||||
|
||||
let response = client.read_multiple(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let read_multiple_resps = response
|
||||
.read_multiple_resps
|
||||
.into_iter()
|
||||
.filter_map(|json_str| serde_json::from_str::<ReadMultipleResp>(&json_str).ok())
|
||||
.collect();
|
||||
|
||||
Ok(read_multiple_resps)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_volume(&self, volume: &str) -> Result<()> {
|
||||
info!("delete_volume {}/{}", self.endpoint.to_string(), volume);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeleteVolumeRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
});
|
||||
|
||||
let response = client.delete_volume(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn disk_info(&self, opts: &DiskInfoOptions) -> Result<DiskInfo> {
|
||||
let opts = serde_json::to_string(&opts)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DiskInfoRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.disk_info(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let disk_info = serde_json::from_str::<DiskInfo>(&response.disk_info)?;
|
||||
|
||||
Ok(disk_info)
|
||||
}
|
||||
|
||||
// #[tracing::instrument(skip(self, cache, scan_mode, _we_sleep))]
|
||||
// async fn ns_scanner(
|
||||
// &self,
|
||||
// cache: &DataUsageCache,
|
||||
// updates: Sender<DataUsageEntry>,
|
||||
// scan_mode: HealScanMode,
|
||||
// _we_sleep: ShouldSleepFn,
|
||||
// ) -> Result<DataUsageCache> {
|
||||
// info!("ns_scanner");
|
||||
// let cache = serde_json::to_string(cache)?;
|
||||
// let mut client = node_service_time_out_client(&self.addr)
|
||||
// .await
|
||||
// .map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
|
||||
// let (tx, rx) = mpsc::channel(10);
|
||||
// let in_stream = ReceiverStream::new(rx);
|
||||
// let mut response = client.ns_scanner(in_stream).await?.into_inner();
|
||||
// let request = NsScannerRequest {
|
||||
// disk: self.endpoint.to_string(),
|
||||
// cache,
|
||||
// scan_mode: scan_mode as u64,
|
||||
// };
|
||||
// tx.send(request)
|
||||
// .await
|
||||
// .map_err(|err| Error::msg(format!("can not send request, err: {}", err)))?;
|
||||
|
||||
// loop {
|
||||
// match response.next().await {
|
||||
// Some(Ok(resp)) => {
|
||||
// if !resp.update.is_empty() {
|
||||
// let data_usage_cache = serde_json::from_str::<DataUsageEntry>(&resp.update)?;
|
||||
// let _ = updates.send(data_usage_cache).await;
|
||||
// } else if !resp.data_usage_cache.is_empty() {
|
||||
// let data_usage_cache = serde_json::from_str::<DataUsageCache>(&resp.data_usage_cache)?;
|
||||
// return Ok(data_usage_cache);
|
||||
// } else {
|
||||
// return Err(Error::msg("scan was interrupted"));
|
||||
// }
|
||||
// }
|
||||
// _ => return Err(Error::msg("scan was interrupted")),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[tracing::instrument(skip(self))]
|
||||
// async fn healing(&self) -> Option<HealingTracker> {
|
||||
// None
|
||||
// }
|
||||
}
|
||||
@@ -1,862 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{
|
||||
endpoint::Endpoint, CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo, DiskInfoOptions, DiskLocation, DiskOption,
|
||||
FileInfoVersions, ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp, UpdateMetadataOpts, VolumeInfo,
|
||||
WalkDirOptions,
|
||||
};
|
||||
use crate::heal::{
|
||||
data_scanner::ShouldSleepFn,
|
||||
data_usage_cache::{DataUsageCache, DataUsageEntry},
|
||||
heal_commands::{HealScanMode, HealingTracker},
|
||||
};
|
||||
use crate::io::{FileReader, FileWriter, HttpFileReader, HttpFileWriter};
|
||||
use crate::{disk::metacache::MetaCacheEntry, metacache::writer::MetacacheWriter};
|
||||
use futures::lock::Mutex;
|
||||
use protos::proto_gen::node_service::RenamePartRequst;
|
||||
use protos::{
|
||||
node_service_time_out_client,
|
||||
proto_gen::node_service::{
|
||||
CheckPartsRequest, DeletePathsRequest, DeleteRequest, DeleteVersionRequest, DeleteVersionsRequest, DeleteVolumeRequest,
|
||||
DiskInfoRequest, ListDirRequest, ListVolumesRequest, MakeVolumeRequest, MakeVolumesRequest, NsScannerRequest,
|
||||
ReadAllRequest, ReadMultipleRequest, ReadVersionRequest, ReadXlRequest, RenameDataRequest, RenameFileRequst,
|
||||
StatVolumeRequest, UpdateMetadataRequest, VerifyFileRequest, WalkDirRequest, WriteAllRequest, WriteMetadataRequest,
|
||||
},
|
||||
};
|
||||
use rmp_serde::Serializer;
|
||||
use rustfs_error::{Error, Result};
|
||||
use rustfs_filemeta::{FileInfo, RawFileInfo};
|
||||
use serde::Serialize;
|
||||
use tokio::{
|
||||
io::AsyncWrite,
|
||||
sync::mpsc::{self, Sender},
|
||||
};
|
||||
use tokio_stream::{wrappers::ReceiverStream, StreamExt};
|
||||
use tonic::Request;
|
||||
use tracing::info;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RemoteDisk {
|
||||
pub id: Mutex<Option<Uuid>>,
|
||||
pub addr: String,
|
||||
pub url: url::Url,
|
||||
pub root: PathBuf,
|
||||
endpoint: Endpoint,
|
||||
}
|
||||
|
||||
impl RemoteDisk {
|
||||
pub async fn new(ep: &Endpoint, _opt: &DiskOption) -> Result<Self> {
|
||||
// let root = fs::canonicalize(ep.url.path()).await?;
|
||||
let root = PathBuf::from(ep.get_file_path());
|
||||
let addr = format!("{}://{}:{}", ep.url.scheme(), ep.url.host_str().unwrap(), ep.url.port().unwrap());
|
||||
Ok(Self {
|
||||
id: Mutex::new(None),
|
||||
addr,
|
||||
url: ep.url.clone(),
|
||||
root,
|
||||
endpoint: ep.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: all api need to handle errors
|
||||
#[async_trait::async_trait]
|
||||
impl DiskAPI for RemoteDisk {
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn to_string(&self) -> String {
|
||||
self.endpoint.to_string()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn is_local(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn host_name(&self) -> String {
|
||||
self.endpoint.host_port()
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn is_online(&self) -> bool {
|
||||
// TODO: 连接状态
|
||||
if (node_service_time_out_client(&self.addr).await).is_ok() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn endpoint(&self) -> Endpoint {
|
||||
self.endpoint.clone()
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn close(&self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn path(&self) -> PathBuf {
|
||||
self.root.clone()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn get_disk_location(&self) -> DiskLocation {
|
||||
DiskLocation {
|
||||
pool_idx: {
|
||||
if self.endpoint.pool_idx < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.endpoint.pool_idx as usize)
|
||||
}
|
||||
},
|
||||
set_idx: {
|
||||
if self.endpoint.set_idx < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.endpoint.set_idx as usize)
|
||||
}
|
||||
},
|
||||
disk_idx: {
|
||||
if self.endpoint.disk_idx < 0 {
|
||||
None
|
||||
} else {
|
||||
Some(self.endpoint.disk_idx as usize)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn get_disk_id(&self) -> Result<Option<Uuid>> {
|
||||
Ok(*self.id.lock().await)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn set_disk_id(&self, id: Option<Uuid>) -> Result<()> {
|
||||
let mut lock = self.id.lock().await;
|
||||
*lock = id;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_all(&self, volume: &str, path: &str) -> Result<Vec<u8>> {
|
||||
info!("read_all {}/{}", volume, path);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ReadAllRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
});
|
||||
|
||||
let response = client.read_all(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(Error::FileNotFound);
|
||||
}
|
||||
|
||||
Ok(response.data)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn write_all(&self, volume: &str, path: &str, data: Vec<u8>) -> Result<()> {
|
||||
info!("write_all");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(WriteAllRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
data,
|
||||
});
|
||||
|
||||
let response = client.write_all(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete(&self, volume: &str, path: &str, opt: DeleteOptions) -> Result<()> {
|
||||
info!("delete {}/{}/{}", self.endpoint.to_string(), volume, path);
|
||||
let options = serde_json::to_string(&opt)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeleteRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
options,
|
||||
});
|
||||
|
||||
let response = client.delete(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn verify_file(&self, volume: &str, path: &str, fi: &FileInfo) -> Result<CheckPartsResp> {
|
||||
info!("verify_file");
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(VerifyFileRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
});
|
||||
|
||||
let response = client.verify_file(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let check_parts_resp = serde_json::from_str::<CheckPartsResp>(&response.check_parts_resp)?;
|
||||
|
||||
Ok(check_parts_resp)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn check_parts(&self, volume: &str, path: &str, fi: &FileInfo) -> Result<CheckPartsResp> {
|
||||
info!("check_parts");
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(CheckPartsRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
});
|
||||
|
||||
let response = client.check_parts(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let check_parts_resp = serde_json::from_str::<CheckPartsResp>(&response.check_parts_resp)?;
|
||||
|
||||
Ok(check_parts_resp)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn rename_part(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str, meta: Vec<u8>) -> Result<()> {
|
||||
info!("rename_part {}/{}", src_volume, src_path);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(RenamePartRequst {
|
||||
disk: self.endpoint.to_string(),
|
||||
src_volume: src_volume.to_string(),
|
||||
src_path: src_path.to_string(),
|
||||
dst_volume: dst_volume.to_string(),
|
||||
dst_path: dst_path.to_string(),
|
||||
meta,
|
||||
});
|
||||
|
||||
let response = client.rename_part(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn rename_file(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str) -> Result<()> {
|
||||
info!("rename_file");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(RenameFileRequst {
|
||||
disk: self.endpoint.to_string(),
|
||||
src_volume: src_volume.to_string(),
|
||||
src_path: src_path.to_string(),
|
||||
dst_volume: dst_volume.to_string(),
|
||||
dst_path: dst_path.to_string(),
|
||||
});
|
||||
|
||||
let response = client.rename_file(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn create_file(&self, _origvolume: &str, volume: &str, path: &str, file_size: usize) -> Result<FileWriter> {
|
||||
info!("create_file {}/{}/{}", self.endpoint.to_string(), volume, path);
|
||||
Ok(Box::new(HttpFileWriter::new(
|
||||
self.endpoint.grid_host().as_str(),
|
||||
self.endpoint.to_string().as_str(),
|
||||
volume,
|
||||
path,
|
||||
file_size,
|
||||
false,
|
||||
)?))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn append_file(&self, volume: &str, path: &str) -> Result<FileWriter> {
|
||||
info!("append_file {}/{}", volume, path);
|
||||
Ok(Box::new(HttpFileWriter::new(
|
||||
self.endpoint.grid_host().as_str(),
|
||||
self.endpoint.to_string().as_str(),
|
||||
volume,
|
||||
path,
|
||||
0,
|
||||
true,
|
||||
)?))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn read_file(&self, volume: &str, path: &str) -> Result<FileReader> {
|
||||
info!("read_file {}/{}", volume, path);
|
||||
Ok(Box::new(
|
||||
HttpFileReader::new(self.endpoint.grid_host().as_str(), self.endpoint.to_string().as_str(), volume, path, 0, 0)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn read_file_stream(&self, volume: &str, path: &str, offset: usize, length: usize) -> Result<FileReader> {
|
||||
info!("read_file_stream {}/{}/{}", self.endpoint.to_string(), volume, path);
|
||||
Ok(Box::new(
|
||||
HttpFileReader::new(
|
||||
self.endpoint.grid_host().as_str(),
|
||||
self.endpoint.to_string().as_str(),
|
||||
volume,
|
||||
path,
|
||||
offset,
|
||||
length,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn list_dir(&self, _origvolume: &str, volume: &str, _dir_path: &str, _count: i32) -> Result<Vec<String>> {
|
||||
info!("list_dir {}/{}", volume, _dir_path);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ListDirRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
});
|
||||
|
||||
let response = client.list_dir(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(response.volumes)
|
||||
}
|
||||
|
||||
// FIXME: TODO: use writer
|
||||
#[tracing::instrument(skip(self, wr))]
|
||||
async fn walk_dir<W: AsyncWrite + Unpin + Send>(&self, opts: WalkDirOptions, wr: &mut W) -> Result<()> {
|
||||
let now = std::time::SystemTime::now();
|
||||
info!("walk_dir {}/{}/{:?}", self.endpoint.to_string(), opts.bucket, opts.filter_prefix);
|
||||
let mut wr = wr;
|
||||
let mut out = MetacacheWriter::new(&mut wr);
|
||||
let mut buf = Vec::new();
|
||||
opts.serialize(&mut Serializer::new(&mut buf))?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(WalkDirRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
walk_dir_options: buf,
|
||||
});
|
||||
let mut response = client.walk_dir(request).await?.into_inner();
|
||||
|
||||
loop {
|
||||
match response.next().await {
|
||||
Some(Ok(resp)) => {
|
||||
if !resp.success {
|
||||
return Err(Error::msg(resp.error_info.unwrap_or("".to_string())));
|
||||
}
|
||||
let entry = serde_json::from_str::<MetaCacheEntry>(&resp.meta_cache_entry)
|
||||
.map_err(|_| Error::msg(format!("Unexpected response: {:?}", response)))?;
|
||||
out.write_obj(&entry).await?;
|
||||
}
|
||||
None => break,
|
||||
_ => return Err(Error::msg(format!("Unexpected response: {:?}", response))),
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"walk_dir {}/{:?} done {:?}",
|
||||
opts.bucket,
|
||||
opts.filter_prefix,
|
||||
now.elapsed().unwrap_or_default()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn rename_data(
|
||||
&self,
|
||||
src_volume: &str,
|
||||
src_path: &str,
|
||||
fi: FileInfo,
|
||||
dst_volume: &str,
|
||||
dst_path: &str,
|
||||
) -> Result<RenameDataResp> {
|
||||
info!("rename_data {}/{}/{}/{}", self.addr, self.endpoint.to_string(), dst_volume, dst_path);
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(RenameDataRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
src_volume: src_volume.to_string(),
|
||||
src_path: src_path.to_string(),
|
||||
file_info,
|
||||
dst_volume: dst_volume.to_string(),
|
||||
dst_path: dst_path.to_string(),
|
||||
});
|
||||
|
||||
let response = client.rename_data(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let rename_data_resp = serde_json::from_str::<RenameDataResp>(&response.rename_data_resp)?;
|
||||
|
||||
Ok(rename_data_resp)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn make_volumes(&self, volumes: Vec<&str>) -> Result<()> {
|
||||
info!("make_volumes");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(MakeVolumesRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volumes: volumes.iter().map(|s| (*s).to_string()).collect(),
|
||||
});
|
||||
|
||||
let response = client.make_volumes(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn make_volume(&self, volume: &str) -> Result<()> {
|
||||
info!("make_volume");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(MakeVolumeRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
});
|
||||
|
||||
let response = client.make_volume(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn list_volumes(&self) -> Result<Vec<VolumeInfo>> {
|
||||
info!("list_volumes");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ListVolumesRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
});
|
||||
|
||||
let response = client.list_volumes(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let infos = response
|
||||
.volume_infos
|
||||
.into_iter()
|
||||
.filter_map(|json_str| serde_json::from_str::<VolumeInfo>(&json_str).ok())
|
||||
.collect();
|
||||
|
||||
Ok(infos)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn stat_volume(&self, volume: &str) -> Result<VolumeInfo> {
|
||||
info!("stat_volume");
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(StatVolumeRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
});
|
||||
|
||||
let response = client.stat_volume(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let volume_info = serde_json::from_str::<VolumeInfo>(&response.volume_info)?;
|
||||
|
||||
Ok(volume_info)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_paths(&self, volume: &str, paths: &[String]) -> Result<()> {
|
||||
info!("delete_paths");
|
||||
let paths = paths.to_owned();
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeletePathsRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
paths,
|
||||
});
|
||||
|
||||
let response = client.delete_paths(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn update_metadata(&self, volume: &str, path: &str, fi: FileInfo, opts: &UpdateMetadataOpts) -> Result<()> {
|
||||
info!("update_metadata");
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let opts = serde_json::to_string(&opts)?;
|
||||
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(UpdateMetadataRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.update_metadata(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn write_metadata(&self, _org_volume: &str, volume: &str, path: &str, fi: FileInfo) -> Result<()> {
|
||||
info!("write_metadata {}/{}", volume, path);
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(WriteMetadataRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
});
|
||||
|
||||
let response = client.write_metadata(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_version(
|
||||
&self,
|
||||
_org_volume: &str,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
version_id: &str,
|
||||
opts: &ReadOptions,
|
||||
) -> Result<FileInfo> {
|
||||
info!("read_version");
|
||||
let opts = serde_json::to_string(opts)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ReadVersionRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
version_id: version_id.to_string(),
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.read_version(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let file_info = serde_json::from_str::<FileInfo>(&response.file_info)?;
|
||||
|
||||
Ok(file_info)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
async fn read_xl(&self, volume: &str, path: &str, read_data: bool) -> Result<RawFileInfo> {
|
||||
info!("read_xl {}/{}/{}", self.endpoint.to_string(), volume, path);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ReadXlRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
read_data,
|
||||
});
|
||||
|
||||
let response = client.read_xl(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let raw_file_info = serde_json::from_str::<RawFileInfo>(&response.raw_file_info)?;
|
||||
|
||||
Ok(raw_file_info)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_version(
|
||||
&self,
|
||||
volume: &str,
|
||||
path: &str,
|
||||
fi: FileInfo,
|
||||
force_del_marker: bool,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<()> {
|
||||
info!("delete_version");
|
||||
let file_info = serde_json::to_string(&fi)?;
|
||||
let opts = serde_json::to_string(&opts)?;
|
||||
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeleteVersionRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
path: path.to_string(),
|
||||
file_info,
|
||||
force_del_marker,
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.delete_version(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
// let raw_file_info = serde_json::from_str::<RawFileInfo>(&response.raw_file_info)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_versions(
|
||||
&self,
|
||||
volume: &str,
|
||||
versions: Vec<FileInfoVersions>,
|
||||
opts: DeleteOptions,
|
||||
) -> Result<Vec<Option<Error>>> {
|
||||
info!("delete_versions");
|
||||
let opts = serde_json::to_string(&opts)?;
|
||||
let mut versions_str = Vec::with_capacity(versions.len());
|
||||
for file_info_versions in versions.iter() {
|
||||
versions_str.push(serde_json::to_string(file_info_versions)?);
|
||||
}
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeleteVersionsRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
versions: versions_str,
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.delete_versions(request).await?.into_inner();
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
let errors = response
|
||||
.errors
|
||||
.iter()
|
||||
.map(|error| {
|
||||
if error.is_empty() {
|
||||
None
|
||||
} else {
|
||||
use std::str::FromStr;
|
||||
Some(Error::from_str(error).unwrap_or(Error::msg(error)))
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(errors)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn read_multiple(&self, req: ReadMultipleReq) -> Result<Vec<ReadMultipleResp>> {
|
||||
info!("read_multiple {}/{}/{}", self.endpoint.to_string(), req.bucket, req.prefix);
|
||||
let read_multiple_req = serde_json::to_string(&req)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(ReadMultipleRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
read_multiple_req,
|
||||
});
|
||||
|
||||
let response = client.read_multiple(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let read_multiple_resps = response
|
||||
.read_multiple_resps
|
||||
.into_iter()
|
||||
.filter_map(|json_str| serde_json::from_str::<ReadMultipleResp>(&json_str).ok())
|
||||
.collect();
|
||||
|
||||
Ok(read_multiple_resps)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn delete_volume(&self, volume: &str) -> Result<()> {
|
||||
info!("delete_volume {}/{}", self.endpoint.to_string(), volume);
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DeleteVolumeRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
volume: volume.to_string(),
|
||||
});
|
||||
|
||||
let response = client.delete_volume(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn disk_info(&self, opts: &DiskInfoOptions) -> Result<DiskInfo> {
|
||||
let opts = serde_json::to_string(&opts)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
let request = Request::new(DiskInfoRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
opts,
|
||||
});
|
||||
|
||||
let response = client.disk_info(request).await?.into_inner();
|
||||
|
||||
if !response.success {
|
||||
return Err(response.error.unwrap_or_default().into());
|
||||
}
|
||||
|
||||
let disk_info = serde_json::from_str::<DiskInfo>(&response.disk_info)?;
|
||||
|
||||
Ok(disk_info)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self, cache, scan_mode, _we_sleep))]
|
||||
async fn ns_scanner(
|
||||
&self,
|
||||
cache: &DataUsageCache,
|
||||
updates: Sender<DataUsageEntry>,
|
||||
scan_mode: HealScanMode,
|
||||
_we_sleep: ShouldSleepFn,
|
||||
) -> Result<DataUsageCache> {
|
||||
info!("ns_scanner");
|
||||
let cache = serde_json::to_string(cache)?;
|
||||
let mut client = node_service_time_out_client(&self.addr)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not get client, err: {}", err)))?;
|
||||
|
||||
let (tx, rx) = mpsc::channel(10);
|
||||
let in_stream = ReceiverStream::new(rx);
|
||||
let mut response = client.ns_scanner(in_stream).await?.into_inner();
|
||||
let request = NsScannerRequest {
|
||||
disk: self.endpoint.to_string(),
|
||||
cache,
|
||||
scan_mode: scan_mode as u64,
|
||||
};
|
||||
tx.send(request)
|
||||
.await
|
||||
.map_err(|err| Error::msg(format!("can not send request, err: {}", err)))?;
|
||||
|
||||
loop {
|
||||
match response.next().await {
|
||||
Some(Ok(resp)) => {
|
||||
if !resp.update.is_empty() {
|
||||
let data_usage_cache = serde_json::from_str::<DataUsageEntry>(&resp.update)?;
|
||||
let _ = updates.send(data_usage_cache).await;
|
||||
} else if !resp.data_usage_cache.is_empty() {
|
||||
let data_usage_cache = serde_json::from_str::<DataUsageCache>(&resp.data_usage_cache)?;
|
||||
return Ok(data_usage_cache);
|
||||
} else {
|
||||
return Err(Error::msg("scan was interrupted"));
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::msg("scan was interrupted")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn healing(&self) -> Option<HealingTracker> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use std::{fs::Metadata, path::Path};
|
||||
|
||||
use rustfs_error::{to_file_error, Error, Result};
|
||||
|
||||
pub async fn read_file_exists(path: impl AsRef<Path>) -> Result<(Vec<u8>, Option<Metadata>)> {
|
||||
let p = path.as_ref();
|
||||
let (data, meta) = match read_file_all(&p).await {
|
||||
Ok((data, meta)) => (data, Some(meta)),
|
||||
Err(e) => {
|
||||
if e == Error::FileNotFound {
|
||||
(Vec::new(), None)
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok((data, meta))
|
||||
}
|
||||
|
||||
pub async fn read_file_all(path: impl AsRef<Path>) -> Result<(Vec<u8>, Metadata)> {
|
||||
let p = path.as_ref();
|
||||
let meta = read_file_metadata(&path).await?;
|
||||
|
||||
let data = read_all(&p).await?;
|
||||
|
||||
Ok((data, meta))
|
||||
}
|
||||
|
||||
pub async fn read_file_metadata(p: impl AsRef<Path>) -> Result<Metadata> {
|
||||
Ok(tokio::fs::metadata(&p).await.map_err(to_file_error)?)
|
||||
}
|
||||
pub async fn read_all(p: impl AsRef<Path>) -> Result<Vec<u8>> {
|
||||
tokio::fs::read(&p).await.map_err(|e| to_file_error(e).into())
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
[package]
|
||||
name = "rustfs-error"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
|
||||
[dependencies]
|
||||
protos.workspace = true
|
||||
rmp.workspace = true
|
||||
rmp-serde.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
time.workspace = true
|
||||
tonic.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -1,27 +0,0 @@
|
||||
use crate::Error;
|
||||
|
||||
pub const CHECK_PART_UNKNOWN: usize = 0;
|
||||
pub const CHECK_PART_SUCCESS: usize = 1;
|
||||
pub const CHECK_PART_DISK_NOT_FOUND: usize = 2;
|
||||
pub const CHECK_PART_VOLUME_NOT_FOUND: usize = 3;
|
||||
pub const CHECK_PART_FILE_NOT_FOUND: usize = 4;
|
||||
pub const CHECK_PART_FILE_CORRUPT: usize = 5;
|
||||
|
||||
pub fn conv_part_err_to_int(err: &Option<Error>) -> usize {
|
||||
if let Some(err) = err {
|
||||
match err {
|
||||
Error::FileNotFound | Error::FileVersionNotFound => CHECK_PART_FILE_NOT_FOUND,
|
||||
Error::FileCorrupt => CHECK_PART_FILE_CORRUPT,
|
||||
Error::VolumeNotFound => CHECK_PART_VOLUME_NOT_FOUND,
|
||||
Error::DiskNotFound => CHECK_PART_DISK_NOT_FOUND,
|
||||
Error::Nil => CHECK_PART_SUCCESS,
|
||||
_ => CHECK_PART_UNKNOWN,
|
||||
}
|
||||
} else {
|
||||
CHECK_PART_SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_part_err(part_errs: &[usize]) -> bool {
|
||||
part_errs.iter().any(|err| *err != CHECK_PART_SUCCESS)
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
use crate::Error;
|
||||
|
||||
pub fn to_file_error(io_err: std::io::Error) -> std::io::Error {
|
||||
match io_err.kind() {
|
||||
std::io::ErrorKind::NotFound => Error::FileNotFound.into(),
|
||||
std::io::ErrorKind::PermissionDenied => Error::FileAccessDenied.into(),
|
||||
std::io::ErrorKind::IsADirectory => Error::IsNotRegular.into(),
|
||||
std::io::ErrorKind::NotADirectory => Error::FileAccessDenied.into(),
|
||||
std::io::ErrorKind::DirectoryNotEmpty => Error::FileAccessDenied.into(),
|
||||
std::io::ErrorKind::UnexpectedEof => Error::FaultyDisk.into(),
|
||||
std::io::ErrorKind::TooManyLinks => Error::TooManyOpenFiles.into(),
|
||||
std::io::ErrorKind::InvalidInput => Error::FileNotFound.into(),
|
||||
std::io::ErrorKind::InvalidData => Error::FileCorrupt.into(),
|
||||
std::io::ErrorKind::StorageFull => Error::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 => Error::VolumeNotFound.into(),
|
||||
std::io::ErrorKind::PermissionDenied => Error::DiskAccessDenied.into(),
|
||||
std::io::ErrorKind::DirectoryNotEmpty => Error::VolumeNotEmpty.into(),
|
||||
std::io::ErrorKind::NotADirectory => Error::IsNotRegular.into(),
|
||||
std::io::ErrorKind::Other => {
|
||||
let err = Error::from(io_err.to_string());
|
||||
match err {
|
||||
Error::FileNotFound => Error::VolumeNotFound.into(),
|
||||
Error::FileAccessDenied => Error::DiskAccessDenied.into(),
|
||||
_ => to_file_error(io_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 => Error::DiskNotFound.into(),
|
||||
std::io::ErrorKind::PermissionDenied => Error::DiskAccessDenied.into(),
|
||||
std::io::ErrorKind::Other => {
|
||||
let err = Error::from(io_err.to_string());
|
||||
match err {
|
||||
Error::FileNotFound => Error::DiskNotFound.into(),
|
||||
Error::VolumeNotFound => Error::DiskNotFound.into(),
|
||||
Error::FileAccessDenied => Error::DiskAccessDenied.into(),
|
||||
Error::VolumeAccessDenied => Error::DiskAccessDenied.into(),
|
||||
_ => to_volume_error(io_err),
|
||||
}
|
||||
}
|
||||
_ => to_volume_error(io_err),
|
||||
}
|
||||
}
|
||||
|
||||
// only errors from FileSystem operations
|
||||
pub fn to_access_error(io_err: std::io::Error, per_err: Error) -> 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 => Error::VolumeNotFound.into(),
|
||||
std::io::ErrorKind::UnexpectedEof => Error::FaultyDisk.into(),
|
||||
std::io::ErrorKind::Other => {
|
||||
let err = Error::from(io_err.to_string());
|
||||
match err {
|
||||
Error::DiskAccessDenied => per_err.into(),
|
||||
Error::FileAccessDenied => per_err.into(),
|
||||
Error::FileNotFound => Error::VolumeNotFound.into(),
|
||||
_ => to_volume_error(io_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 => Error::UnformattedDisk.into(),
|
||||
std::io::ErrorKind::PermissionDenied => Error::DiskAccessDenied.into(),
|
||||
std::io::ErrorKind::Other => {
|
||||
let err = Error::from(io_err.to_string());
|
||||
match err {
|
||||
Error::FileNotFound => Error::UnformattedDisk.into(),
|
||||
Error::DiskNotFound => Error::UnformattedDisk.into(),
|
||||
Error::VolumeNotFound => Error::UnformattedDisk.into(),
|
||||
Error::FileAccessDenied => Error::DiskAccessDenied.into(),
|
||||
Error::DiskAccessDenied => Error::DiskAccessDenied.into(),
|
||||
_ => Error::CorruptedBackend.into(),
|
||||
}
|
||||
}
|
||||
_ => Error::CorruptedBackend.into(),
|
||||
}
|
||||
}
|
||||
@@ -1,586 +0,0 @@
|
||||
use std::hash::Hash;
|
||||
use std::str::FromStr;
|
||||
|
||||
const ERROR_PREFIX: &str = "[RUSTFS error] ";
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
|
||||
#[derive(thiserror::Error, Default, Debug)]
|
||||
pub enum Error {
|
||||
#[default]
|
||||
#[error("[RUSTFS error] Nil")]
|
||||
Nil,
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(std::io::Error),
|
||||
#[error("[RUSTFS error] Erasure Read quorum not met")]
|
||||
ErasureReadQuorum,
|
||||
#[error("[RUSTFS error] Erasure Write quorum not met")]
|
||||
ErasureWriteQuorum,
|
||||
|
||||
#[error("[RUSTFS error] Disk not found")]
|
||||
DiskNotFound,
|
||||
#[error("[RUSTFS error] Faulty disk")]
|
||||
FaultyDisk,
|
||||
#[error("[RUSTFS error] Faulty remote disk")]
|
||||
FaultyRemoteDisk,
|
||||
#[error("[RUSTFS error] Unsupported disk")]
|
||||
UnsupportedDisk,
|
||||
#[error("[RUSTFS error] Unformatted disk")]
|
||||
UnformattedDisk,
|
||||
#[error("[RUSTFS error] Corrupted backend")]
|
||||
CorruptedBackend,
|
||||
|
||||
#[error("[RUSTFS error] Disk access denied")]
|
||||
DiskAccessDenied,
|
||||
#[error("[RUSTFS error] Disk ongoing request")]
|
||||
DiskOngoingReq,
|
||||
#[error("[RUSTFS error] Disk full")]
|
||||
DiskFull,
|
||||
|
||||
#[error("[RUSTFS error] Volume not found")]
|
||||
VolumeNotFound,
|
||||
#[error("[RUSTFS error] Volume not empty")]
|
||||
VolumeNotEmpty,
|
||||
#[error("[RUSTFS error] Volume access denied")]
|
||||
VolumeAccessDenied,
|
||||
|
||||
#[error("[RUSTFS error] Volume exists")]
|
||||
VolumeExists,
|
||||
|
||||
#[error("[RUSTFS error] Disk not a directory")]
|
||||
DiskNotDir,
|
||||
|
||||
#[error("[RUSTFS error] File not found")]
|
||||
FileNotFound,
|
||||
#[error("[RUSTFS error] File corrupt")]
|
||||
FileCorrupt,
|
||||
#[error("[RUSTFS error] File access denied")]
|
||||
FileAccessDenied,
|
||||
#[error("[RUSTFS error] Too many open files")]
|
||||
TooManyOpenFiles,
|
||||
#[error("[RUSTFS error] Is not a regular file")]
|
||||
IsNotRegular,
|
||||
|
||||
#[error("[RUSTFS error] File version not found")]
|
||||
FileVersionNotFound,
|
||||
|
||||
#[error("[RUSTFS error] Less data than expected")]
|
||||
LessData,
|
||||
#[error("[RUSTFS error] Short write")]
|
||||
ShortWrite,
|
||||
|
||||
#[error("[RUSTFS error] Done for now")]
|
||||
DoneForNow,
|
||||
|
||||
#[error("[RUSTFS error] Method not allowed")]
|
||||
MethodNotAllowed,
|
||||
|
||||
#[error("[RUSTFS error] Inconsistent disk")]
|
||||
InconsistentDisk,
|
||||
|
||||
#[error("[RUSTFS error] File name too long")]
|
||||
FileNameTooLong,
|
||||
|
||||
#[error("[RUSTFS error] Scan ignore file contribution")]
|
||||
ScanIgnoreFileContrib,
|
||||
#[error("[RUSTFS error] Scan skip file")]
|
||||
ScanSkipFile,
|
||||
#[error("[RUSTFS error] Scan heal stop signaled")]
|
||||
ScanHealStopSignal,
|
||||
#[error("[RUSTFS error] Scan heal idle timeout")]
|
||||
ScanHealIdleTimeout,
|
||||
#[error("[RUSTFS error] Scan retry healing")]
|
||||
ScanRetryHealing,
|
||||
|
||||
#[error("[RUSTFS error] {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
// Generic From implementation removed to avoid conflicts with std::convert::From<T> for T
|
||||
|
||||
impl FromStr for Error {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
|
||||
// Only strip prefix for non-IoError
|
||||
let s = if s.starts_with("I/O error: ") {
|
||||
s
|
||||
} else {
|
||||
s.strip_prefix(ERROR_PREFIX).unwrap_or(s)
|
||||
};
|
||||
|
||||
match s {
|
||||
"Nil" => Ok(Error::Nil),
|
||||
"ErasureReadQuorum" => Ok(Error::ErasureReadQuorum),
|
||||
"ErasureWriteQuorum" => Ok(Error::ErasureWriteQuorum),
|
||||
"DiskNotFound" | "Disk not found" => Ok(Error::DiskNotFound),
|
||||
"FaultyDisk" | "Faulty disk" => Ok(Error::FaultyDisk),
|
||||
"FaultyRemoteDisk" | "Faulty remote disk" => Ok(Error::FaultyRemoteDisk),
|
||||
"UnformattedDisk" | "Unformatted disk" => Ok(Error::UnformattedDisk),
|
||||
"DiskAccessDenied" | "Disk access denied" => Ok(Error::DiskAccessDenied),
|
||||
"DiskOngoingReq" | "Disk ongoing request" => Ok(Error::DiskOngoingReq),
|
||||
"FileNotFound" | "File not found" => Ok(Error::FileNotFound),
|
||||
"FileCorrupt" | "File corrupt" => Ok(Error::FileCorrupt),
|
||||
"FileVersionNotFound" | "File version not found" => Ok(Error::FileVersionNotFound),
|
||||
"LessData" | "Less data than expected" => Ok(Error::LessData),
|
||||
"ShortWrite" | "Short write" => Ok(Error::ShortWrite),
|
||||
"VolumeNotFound" | "Volume not found" => Ok(Error::VolumeNotFound),
|
||||
"VolumeNotEmpty" | "Volume not empty" => Ok(Error::VolumeNotEmpty),
|
||||
"VolumeExists" | "Volume exists" => Ok(Error::VolumeExists),
|
||||
"VolumeAccessDenied" | "Volume access denied" => Ok(Error::VolumeAccessDenied),
|
||||
"DiskNotDir" | "Disk not a directory" => Ok(Error::DiskNotDir),
|
||||
"FileAccessDenied" | "File access denied" => Ok(Error::FileAccessDenied),
|
||||
"TooManyOpenFiles" | "Too many open files" => Ok(Error::TooManyOpenFiles),
|
||||
"IsNotRegular" | "Is not a regular file" => Ok(Error::IsNotRegular),
|
||||
"CorruptedBackend" | "Corrupted backend" => Ok(Error::CorruptedBackend),
|
||||
"UnsupportedDisk" | "Unsupported disk" => Ok(Error::UnsupportedDisk),
|
||||
"InconsistentDisk" | "Inconsistent disk" => Ok(Error::InconsistentDisk),
|
||||
"DiskFull" | "Disk full" => Ok(Error::DiskFull),
|
||||
"FileNameTooLong" | "File name too long" => Ok(Error::FileNameTooLong),
|
||||
"ScanIgnoreFileContrib" | "Scan ignore file contribution" => Ok(Error::ScanIgnoreFileContrib),
|
||||
"ScanSkipFile" | "Scan skip file" => Ok(Error::ScanSkipFile),
|
||||
"ScanHealStopSignal" | "Scan heal stop signaled" => Ok(Error::ScanHealStopSignal),
|
||||
"ScanHealIdleTimeout" | "Scan heal idle timeout" => Ok(Error::ScanHealIdleTimeout),
|
||||
"ScanRetryHealing" | "Scan retry healing" => Ok(Error::ScanRetryHealing),
|
||||
s if s.starts_with("I/O error: ") => {
|
||||
Ok(Error::IoError(std::io::Error::other(s.strip_prefix("I/O error: ").unwrap_or(""))))
|
||||
}
|
||||
"DoneForNow" | "Done for now" => Ok(Error::DoneForNow),
|
||||
"MethodNotAllowed" | "Method not allowed" => Ok(Error::MethodNotAllowed),
|
||||
str => Err(Error::IoError(std::io::Error::other(str.to_string()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for std::io::Error {
|
||||
fn from(err: Error) -> Self {
|
||||
match err {
|
||||
Error::IoError(e) => e,
|
||||
e => std::io::Error::other(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
match e.kind() {
|
||||
// convert Error from string to Error
|
||||
std::io::ErrorKind::Other => Error::from(e.to_string()),
|
||||
_ => Error::IoError(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Error {
|
||||
fn from(s: String) -> Self {
|
||||
Error::from_str(&s).unwrap_or(Error::IoError(std::io::Error::other(s)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Error {
|
||||
fn from(s: &str) -> Self {
|
||||
Error::from_str(s).unwrap_or(Error::IoError(std::io::Error::other(s)))
|
||||
}
|
||||
}
|
||||
|
||||
// Common error type conversions for ? operator
|
||||
impl From<std::num::ParseIntError> for Error {
|
||||
fn from(e: std::num::ParseIntError) -> Self {
|
||||
Error::Other(format!("Parse int error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::ParseFloatError> for Error {
|
||||
fn from(e: std::num::ParseFloatError) -> Self {
|
||||
Error::Other(format!("Parse float error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::str::Utf8Error> for Error {
|
||||
fn from(e: std::str::Utf8Error) -> Self {
|
||||
Error::Other(format!("UTF-8 error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(e: std::string::FromUtf8Error) -> Self {
|
||||
Error::Other(format!("UTF-8 conversion error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::fmt::Error> for Error {
|
||||
fn from(e: std::fmt::Error) -> Self {
|
||||
Error::Other(format!("Format error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn std::error::Error + Send + Sync>> for Error {
|
||||
fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
|
||||
Error::Other(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<time::error::ComponentRange> for Error {
|
||||
fn from(e: time::error::ComponentRange) -> Self {
|
||||
Error::Other(format!("Time component range error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmp::decode::NumValueReadError<std::io::Error>> for Error {
|
||||
fn from(e: rmp::decode::NumValueReadError<std::io::Error>) -> Self {
|
||||
Error::Other(format!("NumValueReadError: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmp::encode::ValueWriteError> for Error {
|
||||
fn from(e: rmp::encode::ValueWriteError) -> Self {
|
||||
Error::Other(format!("ValueWriteError: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmp::decode::ValueReadError> for Error {
|
||||
fn from(e: rmp::decode::ValueReadError) -> Self {
|
||||
Error::Other(format!("ValueReadError: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<uuid::Error> for Error {
|
||||
fn from(e: uuid::Error) -> Self {
|
||||
Error::Other(format!("UUID error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmp_serde::decode::Error> for Error {
|
||||
fn from(e: rmp_serde::decode::Error) -> Self {
|
||||
Error::Other(format!("rmp_serde::decode::Error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<rmp_serde::encode::Error> for Error {
|
||||
fn from(e: rmp_serde::encode::Error) -> Self {
|
||||
Error::Other(format!("rmp_serde::encode::Error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde::de::value::Error> for Error {
|
||||
fn from(e: serde::de::value::Error) -> Self {
|
||||
Error::Other(format!("serde::de::value::Error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(e: serde_json::Error) -> Self {
|
||||
Error::Other(format!("serde_json::Error: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::collections::TryReserveError> for Error {
|
||||
fn from(e: std::collections::TryReserveError) -> Self {
|
||||
Error::Other(format!("TryReserveError: {}", e))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tonic::Status> for Error {
|
||||
fn from(e: tonic::Status) -> Self {
|
||||
Error::Other(format!("tonic::Status: {}", e.message()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<protos::proto_gen::node_service::Error> for Error {
|
||||
fn from(e: protos::proto_gen::node_service::Error) -> Self {
|
||||
Error::from_str(&e.error_info).unwrap_or(Error::Other(format!("Proto_Error: {}", e.error_info)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for protos::proto_gen::node_service::Error {
|
||||
fn from(val: Error) -> Self {
|
||||
protos::proto_gen::node_service::Error {
|
||||
code: 0,
|
||||
error_info: val.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Error {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Error::IoError(err) => {
|
||||
err.kind().hash(state);
|
||||
err.to_string().hash(state);
|
||||
}
|
||||
e => e.to_string().hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Error {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Error::IoError(err) => Error::IoError(std::io::Error::new(err.kind(), err.to_string())),
|
||||
Error::ErasureReadQuorum => Error::ErasureReadQuorum,
|
||||
Error::ErasureWriteQuorum => Error::ErasureWriteQuorum,
|
||||
Error::DiskNotFound => Error::DiskNotFound,
|
||||
Error::FaultyDisk => Error::FaultyDisk,
|
||||
Error::FaultyRemoteDisk => Error::FaultyRemoteDisk,
|
||||
Error::UnformattedDisk => Error::UnformattedDisk,
|
||||
Error::DiskAccessDenied => Error::DiskAccessDenied,
|
||||
Error::DiskOngoingReq => Error::DiskOngoingReq,
|
||||
Error::FileNotFound => Error::FileNotFound,
|
||||
Error::FileCorrupt => Error::FileCorrupt,
|
||||
Error::FileVersionNotFound => Error::FileVersionNotFound,
|
||||
Error::LessData => Error::LessData,
|
||||
Error::ShortWrite => Error::ShortWrite,
|
||||
Error::VolumeNotFound => Error::VolumeNotFound,
|
||||
Error::VolumeNotEmpty => Error::VolumeNotEmpty,
|
||||
Error::VolumeAccessDenied => Error::VolumeAccessDenied,
|
||||
Error::VolumeExists => Error::VolumeExists,
|
||||
Error::DiskNotDir => Error::DiskNotDir,
|
||||
Error::FileAccessDenied => Error::FileAccessDenied,
|
||||
Error::TooManyOpenFiles => Error::TooManyOpenFiles,
|
||||
Error::IsNotRegular => Error::IsNotRegular,
|
||||
Error::CorruptedBackend => Error::CorruptedBackend,
|
||||
Error::UnsupportedDisk => Error::UnsupportedDisk,
|
||||
Error::DiskFull => Error::DiskFull,
|
||||
Error::Nil => Error::Nil,
|
||||
Error::DoneForNow => Error::DoneForNow,
|
||||
Error::MethodNotAllowed => Error::MethodNotAllowed,
|
||||
Error::InconsistentDisk => Error::InconsistentDisk,
|
||||
Error::FileNameTooLong => Error::FileNameTooLong,
|
||||
Error::ScanIgnoreFileContrib => Error::ScanIgnoreFileContrib,
|
||||
Error::ScanSkipFile => Error::ScanSkipFile,
|
||||
Error::ScanHealStopSignal => Error::ScanHealStopSignal,
|
||||
Error::ScanHealIdleTimeout => Error::ScanHealIdleTimeout,
|
||||
Error::ScanRetryHealing => Error::ScanRetryHealing,
|
||||
Error::Other(msg) => Error::Other(msg.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Error {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Error::IoError(e1), Error::IoError(e2)) => e1.kind() == e2.kind() && e1.to_string() == e2.to_string(),
|
||||
(Error::ErasureReadQuorum, Error::ErasureReadQuorum) => true,
|
||||
(Error::ErasureWriteQuorum, Error::ErasureWriteQuorum) => true,
|
||||
(Error::DiskNotFound, Error::DiskNotFound) => true,
|
||||
(Error::FaultyDisk, Error::FaultyDisk) => true,
|
||||
(Error::FaultyRemoteDisk, Error::FaultyRemoteDisk) => true,
|
||||
(Error::UnformattedDisk, Error::UnformattedDisk) => true,
|
||||
(Error::DiskAccessDenied, Error::DiskAccessDenied) => true,
|
||||
(Error::DiskOngoingReq, Error::DiskOngoingReq) => true,
|
||||
(Error::FileNotFound, Error::FileNotFound) => true,
|
||||
(Error::FileCorrupt, Error::FileCorrupt) => true,
|
||||
(Error::FileVersionNotFound, Error::FileVersionNotFound) => true,
|
||||
(Error::LessData, Error::LessData) => true,
|
||||
(Error::ShortWrite, Error::ShortWrite) => true,
|
||||
(Error::VolumeNotFound, Error::VolumeNotFound) => true,
|
||||
(Error::VolumeNotEmpty, Error::VolumeNotEmpty) => true,
|
||||
(Error::VolumeAccessDenied, Error::VolumeAccessDenied) => true,
|
||||
(Error::VolumeExists, Error::VolumeExists) => true,
|
||||
(Error::DiskNotDir, Error::DiskNotDir) => true,
|
||||
(Error::FileAccessDenied, Error::FileAccessDenied) => true,
|
||||
(Error::TooManyOpenFiles, Error::TooManyOpenFiles) => true,
|
||||
(Error::IsNotRegular, Error::IsNotRegular) => true,
|
||||
(Error::CorruptedBackend, Error::CorruptedBackend) => true,
|
||||
(Error::UnsupportedDisk, Error::UnsupportedDisk) => true,
|
||||
(Error::DiskFull, Error::DiskFull) => true,
|
||||
(Error::Nil, Error::Nil) => true,
|
||||
(Error::DoneForNow, Error::DoneForNow) => true,
|
||||
(Error::MethodNotAllowed, Error::MethodNotAllowed) => true,
|
||||
(Error::InconsistentDisk, Error::InconsistentDisk) => true,
|
||||
(Error::FileNameTooLong, Error::FileNameTooLong) => true,
|
||||
(Error::ScanIgnoreFileContrib, Error::ScanIgnoreFileContrib) => true,
|
||||
(Error::ScanSkipFile, Error::ScanSkipFile) => true,
|
||||
(Error::ScanHealStopSignal, Error::ScanHealStopSignal) => true,
|
||||
(Error::ScanHealIdleTimeout, Error::ScanHealIdleTimeout) => true,
|
||||
(Error::ScanRetryHealing, Error::ScanRetryHealing) => true,
|
||||
(Error::Other(s1), Error::Other(s2)) => s1 == s2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Error {}
|
||||
|
||||
impl Error {
|
||||
/// Create an error from a message string (for backward compatibility)
|
||||
pub fn msg<S: Into<String>>(message: S) -> Self {
|
||||
Error::Other(message.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io;
|
||||
|
||||
#[test]
|
||||
fn test_display_and_debug() {
|
||||
let e = Error::DiskNotFound;
|
||||
assert_eq!(format!("{}", e), format!("{ERROR_PREFIX}Disk not found"));
|
||||
assert_eq!(format!("{:?}", e), "DiskNotFound");
|
||||
let io_err = Error::IoError(io::Error::other("fail"));
|
||||
assert_eq!(format!("{}", io_err), "I/O error: fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_eq_and_eq() {
|
||||
assert_eq!(Error::DiskNotFound, Error::DiskNotFound);
|
||||
assert_ne!(Error::DiskNotFound, Error::FaultyDisk);
|
||||
let e1 = Error::IoError(io::Error::other("fail"));
|
||||
let e2 = Error::IoError(io::Error::other("fail"));
|
||||
assert_eq!(e1, e2);
|
||||
let e3 = Error::IoError(io::Error::new(io::ErrorKind::NotFound, "fail"));
|
||||
assert_ne!(e1, e3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone() {
|
||||
let e = Error::DiskAccessDenied;
|
||||
let cloned = e.clone();
|
||||
assert_eq!(e, cloned);
|
||||
let io_err = Error::IoError(io::Error::other("fail"));
|
||||
let cloned_io = io_err.clone();
|
||||
assert_eq!(io_err, cloned_io);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash() {
|
||||
let e1 = Error::DiskNotFound;
|
||||
let e2 = Error::DiskNotFound;
|
||||
let mut h1 = DefaultHasher::new();
|
||||
let mut h2 = DefaultHasher::new();
|
||||
e1.hash(&mut h1);
|
||||
e2.hash(&mut h2);
|
||||
assert_eq!(h1.finish(), h2.finish());
|
||||
let io_err1 = Error::IoError(io::Error::other("fail"));
|
||||
let io_err2 = Error::IoError(io::Error::other("fail"));
|
||||
let mut h3 = DefaultHasher::new();
|
||||
let mut h4 = DefaultHasher::new();
|
||||
io_err1.hash(&mut h3);
|
||||
io_err2.hash(&mut h4);
|
||||
assert_eq!(h3.finish(), h4.finish());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_error_for_io_error() {
|
||||
let e = Error::DiskNotFound;
|
||||
let io_err: io::Error = e.into();
|
||||
assert_eq!(io_err.kind(), io::ErrorKind::Other);
|
||||
assert_eq!(io_err.to_string(), format!("{ERROR_PREFIX}Disk not found"));
|
||||
|
||||
assert_eq!(Error::from(io_err.to_string()), Error::DiskNotFound);
|
||||
|
||||
let orig = io::Error::other("fail");
|
||||
let e2 = Error::IoError(orig.kind().into());
|
||||
let io_err2: io::Error = e2.into();
|
||||
assert_eq!(io_err2.kind(), io::ErrorKind::Other);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_io_error_for_error() {
|
||||
let orig = io::Error::other("fail");
|
||||
let e: Error = orig.into();
|
||||
match e {
|
||||
Error::IoError(ioe) => {
|
||||
assert_eq!(ioe.kind(), io::ErrorKind::Other);
|
||||
assert_eq!(ioe.to_string(), "fail");
|
||||
}
|
||||
_ => panic!("Expected IoError variant"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
let e = Error::default();
|
||||
assert_eq!(e, Error::Nil);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
use std::str::FromStr;
|
||||
assert_eq!(Error::from_str("Nil"), Ok(Error::Nil));
|
||||
assert_eq!(Error::from_str("DiskNotFound"), Ok(Error::DiskNotFound));
|
||||
assert_eq!(Error::from_str("ErasureReadQuorum"), Ok(Error::ErasureReadQuorum));
|
||||
assert_eq!(Error::from_str("I/O error: fail"), Ok(Error::IoError(io::Error::other("fail"))));
|
||||
assert_eq!(Error::from_str(&format!("{ERROR_PREFIX}Disk not found")), Ok(Error::DiskNotFound));
|
||||
assert_eq!(
|
||||
Error::from_str("UnknownError"),
|
||||
Err(Error::IoError(std::io::Error::other("UnknownError")))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_string() {
|
||||
let e: Error = format!("{ERROR_PREFIX}Disk not found").parse().unwrap();
|
||||
assert_eq!(e, Error::DiskNotFound);
|
||||
let e2: Error = "I/O error: fail".to_string().parse().unwrap();
|
||||
assert_eq!(e2, Error::IoError(std::io::Error::other("fail")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_io_error() {
|
||||
let e = Error::IoError(io::Error::other("fail"));
|
||||
let io_err: io::Error = e.clone().into();
|
||||
assert_eq!(io_err.to_string(), "fail");
|
||||
|
||||
let e2: Error = io::Error::other("fail").into();
|
||||
assert_eq!(e2, Error::IoError(io::Error::other("fail")));
|
||||
|
||||
let result = Error::from(io::Error::other("fail"));
|
||||
assert_eq!(result, Error::IoError(io::Error::other("fail")));
|
||||
|
||||
let io_err2: std::io::Error = Error::CorruptedBackend.into();
|
||||
assert_eq!(io_err2.to_string(), "[RUSTFS error] Corrupted backend");
|
||||
|
||||
assert_eq!(Error::from(io_err2), Error::CorruptedBackend);
|
||||
|
||||
let io_err3: std::io::Error = Error::DiskNotFound.into();
|
||||
assert_eq!(io_err3.to_string(), "[RUSTFS error] Disk not found");
|
||||
|
||||
assert_eq!(Error::from(io_err3), Error::DiskNotFound);
|
||||
|
||||
let io_err4: std::io::Error = Error::DiskAccessDenied.into();
|
||||
assert_eq!(io_err4.to_string(), "[RUSTFS error] Disk access denied");
|
||||
|
||||
assert_eq!(Error::from(io_err4), Error::DiskAccessDenied);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_question_mark_operator() {
|
||||
fn parse_number(s: &str) -> Result<i32> {
|
||||
let num = s.parse::<i32>()?; // ParseIntError automatically converts to Error
|
||||
Ok(num)
|
||||
}
|
||||
|
||||
fn format_string() -> Result<String> {
|
||||
use std::fmt::Write;
|
||||
let mut s = String::new();
|
||||
write!(&mut s, "test")?; // fmt::Error automatically converts to Error
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn utf8_conversion() -> Result<String> {
|
||||
let bytes = vec![0xFF, 0xFE]; // Invalid UTF-8
|
||||
let s = String::from_utf8(bytes)?; // FromUtf8Error automatically converts to Error
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
// Test successful case
|
||||
assert_eq!(parse_number("42").unwrap(), 42);
|
||||
|
||||
// Test error conversion
|
||||
let err = parse_number("not_a_number").unwrap_err();
|
||||
assert!(matches!(err, Error::Other(_)));
|
||||
assert!(err.to_string().contains("Parse int error"));
|
||||
|
||||
// Test format error conversion
|
||||
assert_eq!(format_string().unwrap(), "test");
|
||||
|
||||
// Test UTF-8 error conversion
|
||||
let err = utf8_conversion().unwrap_err();
|
||||
assert!(matches!(err, Error::Other(_)));
|
||||
assert!(err.to_string().contains("UTF-8 conversion error"));
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
use crate::Error;
|
||||
pub static OBJECT_OP_IGNORED_ERRS: &[Error] = &[
|
||||
Error::DiskNotFound,
|
||||
Error::FaultyDisk,
|
||||
Error::FaultyRemoteDisk,
|
||||
Error::DiskAccessDenied,
|
||||
Error::DiskOngoingReq,
|
||||
Error::UnformattedDisk,
|
||||
];
|
||||
|
||||
pub static BASE_IGNORED_ERRS: &[Error] = &[Error::DiskNotFound, Error::FaultyDisk, Error::FaultyRemoteDisk];
|
||||
@@ -1,14 +0,0 @@
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
mod reduce;
|
||||
pub use reduce::*;
|
||||
|
||||
mod ignored;
|
||||
pub use ignored::*;
|
||||
|
||||
mod convert;
|
||||
pub use convert::*;
|
||||
|
||||
mod bitrot;
|
||||
pub use bitrot::*;
|
||||
@@ -1,138 +0,0 @@
|
||||
use crate::error::Error;
|
||||
|
||||
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 err_counts =
|
||||
errors
|
||||
.iter()
|
||||
.map(|e| e.as_ref().unwrap_or(&Error::Nil))
|
||||
.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
|
||||
});
|
||||
|
||||
let (err, max_count) = err_counts
|
||||
.into_iter()
|
||||
.max_by(|(e1, c1), (e2, c2)| {
|
||||
// Prefer Error::Nil if present in a tie
|
||||
let count_cmp = c1.cmp(c2);
|
||||
if count_cmp == std::cmp::Ordering::Equal {
|
||||
match (e1, e2) {
|
||||
(Error::Nil, _) => std::cmp::Ordering::Greater,
|
||||
(_, Error::Nil) => std::cmp::Ordering::Less,
|
||||
_ => format!("{e1:?}").cmp(&format!("{e2:?}")),
|
||||
}
|
||||
} else {
|
||||
count_cmp
|
||||
}
|
||||
})
|
||||
.unwrap_or((Error::Nil, 0));
|
||||
|
||||
(max_count, if err == Error::Nil { None } else { Some(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()
|
||||
.map(|e| if e.is_none() { &Error::Nil } else { e.as_ref().unwrap() })
|
||||
.filter(|&e| e == &err)
|
||||
.count()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn err_io(msg: &str) -> Error {
|
||||
Error::IoError(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![];
|
||||
// quorum = 2, should return e1
|
||||
let res = reduce_quorum_errs(&errors, &ignored, 2, Error::ErasureReadQuorum);
|
||||
assert_eq!(res, Some(e1));
|
||||
// quorum = 3, should return quorum error
|
||||
let res = reduce_quorum_errs(&errors, &ignored, 3, Error::ErasureReadQuorum);
|
||||
assert_eq!(res, Some(Error::ErasureReadQuorum));
|
||||
}
|
||||
|
||||
#[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.clone()), 2);
|
||||
assert_eq!(count_errs(&errors, e2.clone()), 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 e2 = err_io("b");
|
||||
let errors = vec![Some(e1.clone()), Some(e2.clone()), None, Some(e1.clone()), None]; // e1:1, Nil:1
|
||||
let ignored = vec![];
|
||||
let (count, err) = reduce_errs(&errors, &ignored);
|
||||
assert_eq!(count, 2);
|
||||
assert_eq!(err, None); // None means Error::Nil is preferred
|
||||
}
|
||||
}
|
||||
+719
-719
File diff suppressed because it is too large
Load Diff
@@ -34,13 +34,17 @@ impl BucketMetadataError {
|
||||
|
||||
impl From<BucketMetadataError> for Error {
|
||||
fn from(e: BucketMetadataError) -> Self {
|
||||
Error::other(e)
|
||||
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),
|
||||
}
|
||||
|
||||
+17
-17
@@ -1,29 +1,29 @@
|
||||
use super::error::{Error, Result};
|
||||
use super::os::{is_root_disk, rename_all};
|
||||
use super::{endpoint::Endpoint, error::DiskError, format::FormatV3};
|
||||
use super::{
|
||||
os, CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo, DiskInfoOptions, DiskLocation, DiskMetrics, FileInfoVersions, Info,
|
||||
ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp, UpdateMetadataOpts, VolumeInfo, WalkDirOptions,
|
||||
BUCKET_META_PREFIX, RUSTFS_META_BUCKET, STORAGE_FORMAT_FILE_BACKUP,
|
||||
BUCKET_META_PREFIX, CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo, DiskInfoOptions, DiskLocation, DiskMetrics,
|
||||
FileInfoVersions, Info, RUSTFS_META_BUCKET, ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp,
|
||||
STORAGE_FORMAT_FILE_BACKUP, UpdateMetadataOpts, VolumeInfo, WalkDirOptions, os,
|
||||
};
|
||||
use super::{endpoint::Endpoint, error::DiskError, format::FormatV3};
|
||||
|
||||
use crate::bucket::metadata_sys::{self};
|
||||
use crate::bucket::versioning::VersioningApi;
|
||||
use crate::bucket::versioning_sys::BucketVersioningSys;
|
||||
use crate::disk::STORAGE_FORMAT_FILE;
|
||||
use crate::disk::error::FileAccessDeniedWithContext;
|
||||
use crate::disk::error_conv::{to_access_error, to_file_error, to_unformatted_disk_error, to_volume_error};
|
||||
use crate::disk::fs::{
|
||||
access, lstat, lstat_std, remove, remove_all_std, remove_std, rename, O_APPEND, O_CREATE, O_RDONLY, O_TRUNC, O_WRONLY,
|
||||
O_APPEND, O_CREATE, O_RDONLY, O_TRUNC, O_WRONLY, access, lstat, lstat_std, remove, remove_all_std, remove_std, rename,
|
||||
};
|
||||
use crate::disk::os::{check_path_length, is_empty_dir};
|
||||
use crate::disk::STORAGE_FORMAT_FILE;
|
||||
use crate::disk::{
|
||||
conv_part_err_to_int, CHECK_PART_FILE_CORRUPT, CHECK_PART_FILE_NOT_FOUND, CHECK_PART_SUCCESS, CHECK_PART_UNKNOWN,
|
||||
CHECK_PART_VOLUME_NOT_FOUND,
|
||||
CHECK_PART_FILE_CORRUPT, CHECK_PART_FILE_NOT_FOUND, CHECK_PART_SUCCESS, CHECK_PART_UNKNOWN, CHECK_PART_VOLUME_NOT_FOUND,
|
||||
conv_part_err_to_int,
|
||||
};
|
||||
use crate::global::{GLOBAL_IsErasureSD, GLOBAL_RootDiskThreshold};
|
||||
use crate::heal::data_scanner::{
|
||||
lc_has_active_rules, rep_has_active_rules, scan_data_folder, ScannerItem, ShouldSleepFn, SizeSummary,
|
||||
ScannerItem, ShouldSleepFn, SizeSummary, lc_has_active_rules, rep_has_active_rules, scan_data_folder,
|
||||
};
|
||||
use crate::heal::data_scanner_metric::{ScannerMetric, ScannerMetrics};
|
||||
use crate::heal::data_usage_cache::{DataUsageCache, DataUsageEntry};
|
||||
@@ -35,23 +35,23 @@ use crate::new_object_layer_fn;
|
||||
use crate::store_api::{ObjectInfo, StorageAPI};
|
||||
use crate::utils::os::get_info;
|
||||
use crate::utils::path::{
|
||||
clean, decode_dir_object, encode_dir_object, has_suffix, path_join, path_join_buf, GLOBAL_DIR_SUFFIX,
|
||||
GLOBAL_DIR_SUFFIX_WITH_SLASH, SLASH_SEPARATOR,
|
||||
GLOBAL_DIR_SUFFIX, GLOBAL_DIR_SUFFIX_WITH_SLASH, SLASH_SEPARATOR, clean, decode_dir_object, encode_dir_object, has_suffix,
|
||||
path_join, path_join_buf,
|
||||
};
|
||||
|
||||
use common::defer;
|
||||
use path_absolutize::Absolutize;
|
||||
use rustfs_filemeta::{
|
||||
get_file_info, read_xl_meta_no_data, Cache, FileInfo, FileInfoOpts, FileMeta, MetaCacheEntry, MetacacheWriter, Opts,
|
||||
RawFileInfo, UpdateFn,
|
||||
Cache, FileInfo, FileInfoOpts, FileMeta, MetaCacheEntry, MetacacheWriter, Opts, RawFileInfo, UpdateFn, get_file_info,
|
||||
read_xl_meta_no_data,
|
||||
};
|
||||
use rustfs_rio::{bitrot_verify, Reader};
|
||||
use rustfs_rio::{Reader, bitrot_verify};
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::io::SeekFrom;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::{
|
||||
fs::Metadata,
|
||||
@@ -60,8 +60,8 @@ use std::{
|
||||
use time::OffsetDateTime;
|
||||
use tokio::fs::{self, File};
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWrite, AsyncWriteExt, ErrorKind};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -1550,7 +1550,7 @@ impl DiskAPI for LocalDisk {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
#[tracing::instrument(level = "debug", skip(self, fi))]
|
||||
async fn rename_data(
|
||||
&self,
|
||||
src_volume: &str,
|
||||
|
||||
+13
-13
@@ -1,8 +1,8 @@
|
||||
use crate::utils::ellipses::*;
|
||||
use common::error::{Error, Result};
|
||||
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
|
||||
|
||||
+28
-31
@@ -6,9 +6,9 @@ use crate::{
|
||||
global::global_rustfs_port,
|
||||
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 +76,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 +84,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,7 +156,7 @@ 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)?;
|
||||
@@ -167,7 +167,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 +201,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 +227,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)))?
|
||||
net::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 +256,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 +284,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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +452,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 +489,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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,8 +663,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 +672,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': 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 +692,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,7 +702,7 @@ mod test {
|
||||
"192.168.1.210:9000/tmp/dir2",
|
||||
"192.168.110:9000/tmp/dir3",
|
||||
],
|
||||
Some(Error::from_string(
|
||||
Some(Error::other(
|
||||
"'192.168.1.210:9000/tmp/dir0': invalid URL endpoint format: missing scheme http or https",
|
||||
)),
|
||||
13,
|
||||
@@ -811,7 +810,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 +818,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 +858,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 +952,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 +964,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 +976,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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::bitrot::{BitrotReader, BitrotWriter};
|
||||
use crate::disk::error::{Error, Result};
|
||||
use crate::disk::error_reduce::{reduce_write_quorum_errs, OBJECT_OP_IGNORED_ERRS};
|
||||
use crate::disk::error_reduce::{OBJECT_OP_IGNORED_ERRS, reduce_write_quorum_errs};
|
||||
use crate::io::Etag;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::future::join_all;
|
||||
@@ -72,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 {
|
||||
|
||||
@@ -164,6 +164,9 @@ pub enum StorageError {
|
||||
#[error("first disk wiat")]
|
||||
FirstDiskWait,
|
||||
|
||||
#[error("Bucket policy not found")]
|
||||
BucketPolicyNotFound,
|
||||
|
||||
#[error("Io error: {0}")]
|
||||
Io(std::io::Error),
|
||||
}
|
||||
@@ -376,6 +379,7 @@ impl Clone for StorageError {
|
||||
StorageError::FirstDiskWait => StorageError::FirstDiskWait,
|
||||
StorageError::TooManyOpenFiles => StorageError::TooManyOpenFiles,
|
||||
StorageError::NoHealRequired => StorageError::NoHealRequired,
|
||||
StorageError::BucketPolicyNotFound => StorageError::BucketPolicyNotFound,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -438,6 +442,7 @@ impl StorageError {
|
||||
StorageError::ConfigNotFound => 0x35,
|
||||
StorageError::TooManyOpenFiles => 0x36,
|
||||
StorageError::NoHealRequired => 0x37,
|
||||
StorageError::BucketPolicyNotFound => 0x38,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,6 +507,7 @@ impl StorageError {
|
||||
0x35 => Some(StorageError::ConfigNotFound),
|
||||
0x36 => Some(StorageError::TooManyOpenFiles),
|
||||
0x37 => Some(StorageError::NoHealRequired),
|
||||
0x38 => Some(StorageError::BucketPolicyNotFound),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
+3399
-3399
File diff suppressed because it is too large
Load Diff
@@ -27,11 +27,7 @@ impl InlineData {
|
||||
}
|
||||
|
||||
pub fn after_version(&self) -> &[u8] {
|
||||
if self.0.is_empty() {
|
||||
&self.0
|
||||
} else {
|
||||
&self.0[1..]
|
||||
}
|
||||
if self.0.is_empty() { &self.0 } else { &self.0[1..] }
|
||||
}
|
||||
|
||||
pub fn find(&self, key: &str) -> Result<Option<Vec<u8>>> {
|
||||
|
||||
+56
-70
@@ -1,7 +1,7 @@
|
||||
use crate::disk::error_reduce::{reduce_read_quorum_errs, reduce_write_quorum_errs, OBJECT_OP_IGNORED_ERRS};
|
||||
use crate::disk::error_reduce::{OBJECT_OP_IGNORED_ERRS, reduce_read_quorum_errs, reduce_write_quorum_errs};
|
||||
use crate::disk::{
|
||||
self, conv_part_err_to_int, has_part_err, CHECK_PART_DISK_NOT_FOUND, CHECK_PART_FILE_CORRUPT, CHECK_PART_FILE_NOT_FOUND,
|
||||
CHECK_PART_SUCCESS,
|
||||
self, CHECK_PART_DISK_NOT_FOUND, CHECK_PART_FILE_CORRUPT, CHECK_PART_FILE_NOT_FOUND, CHECK_PART_SUCCESS,
|
||||
conv_part_err_to_int, has_part_err,
|
||||
};
|
||||
use crate::erasure_coding;
|
||||
use crate::error::{Error, Result};
|
||||
@@ -9,24 +9,24 @@ use crate::global::GLOBAL_MRFState;
|
||||
use crate::heal::data_usage_cache::DataUsageCache;
|
||||
use crate::store_api::ObjectToDelete;
|
||||
use crate::{
|
||||
cache_value::metacache_set::{list_path_raw, ListPathRawOptions},
|
||||
config::{storageclass, GLOBAL_StorageClass},
|
||||
cache_value::metacache_set::{ListPathRawOptions, list_path_raw},
|
||||
config::{GLOBAL_StorageClass, storageclass},
|
||||
disk::{
|
||||
endpoint::Endpoint, error::DiskError, format::FormatV3, new_disk, CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo,
|
||||
DiskInfoOptions, DiskOption, DiskStore, FileInfoVersions, ReadMultipleReq, ReadMultipleResp, ReadOptions,
|
||||
UpdateMetadataOpts, RUSTFS_META_BUCKET, RUSTFS_META_MULTIPART_BUCKET, RUSTFS_META_TMP_BUCKET,
|
||||
CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo, DiskInfoOptions, DiskOption, DiskStore, FileInfoVersions,
|
||||
RUSTFS_META_BUCKET, RUSTFS_META_MULTIPART_BUCKET, RUSTFS_META_TMP_BUCKET, ReadMultipleReq, ReadMultipleResp, ReadOptions,
|
||||
UpdateMetadataOpts, endpoint::Endpoint, error::DiskError, format::FormatV3, new_disk,
|
||||
},
|
||||
error::{to_object_err, StorageError},
|
||||
error::{StorageError, to_object_err},
|
||||
global::{
|
||||
get_global_deployment_id, is_dist_erasure, GLOBAL_BackgroundHealState, GLOBAL_LOCAL_DISK_MAP,
|
||||
GLOBAL_LOCAL_DISK_SET_DRIVES,
|
||||
GLOBAL_BackgroundHealState, GLOBAL_LOCAL_DISK_MAP, GLOBAL_LOCAL_DISK_SET_DRIVES, get_global_deployment_id,
|
||||
is_dist_erasure,
|
||||
},
|
||||
heal::{
|
||||
data_usage::{DATA_USAGE_CACHE_NAME, DATA_USAGE_ROOT},
|
||||
data_usage_cache::{DataUsageCacheInfo, DataUsageEntry, DataUsageEntryInfo},
|
||||
heal_commands::{
|
||||
HealOpts, HealScanMode, HealingTracker, DRIVE_STATE_CORRUPT, DRIVE_STATE_MISSING, DRIVE_STATE_OFFLINE,
|
||||
DRIVE_STATE_OK, HEAL_DEEP_SCAN, HEAL_ITEM_OBJECT, HEAL_NORMAL_SCAN,
|
||||
DRIVE_STATE_CORRUPT, DRIVE_STATE_MISSING, DRIVE_STATE_OFFLINE, DRIVE_STATE_OK, HEAL_DEEP_SCAN, HEAL_ITEM_OBJECT,
|
||||
HEAL_NORMAL_SCAN, HealOpts, HealScanMode, HealingTracker,
|
||||
},
|
||||
heal_ops::BG_HEALING_UUID,
|
||||
},
|
||||
@@ -39,13 +39,13 @@ use crate::{
|
||||
store_init::load_format_erasure,
|
||||
utils::{
|
||||
crypto::{base64_decode, base64_encode, hex},
|
||||
path::{encode_dir_object, has_suffix, SLASH_SEPARATOR},
|
||||
path::{SLASH_SEPARATOR, encode_dir_object, has_suffix},
|
||||
},
|
||||
xhttp,
|
||||
};
|
||||
use crate::{disk::STORAGE_FORMAT_FILE, heal::mrf::PartialOperation};
|
||||
use crate::{
|
||||
heal::data_scanner::{globalHealConfig, HEAL_DELETE_DANGLING},
|
||||
heal::data_scanner::{HEAL_DELETE_DANGLING, globalHealConfig},
|
||||
store_api::ListObjectVersionsInfo,
|
||||
};
|
||||
use crate::{
|
||||
@@ -57,18 +57,18 @@ use chrono::Utc;
|
||||
use futures::future::join_all;
|
||||
use glob::Pattern;
|
||||
use http::HeaderMap;
|
||||
use lock::{namespace_lock::NsLockMap, LockApi};
|
||||
use lock::{LockApi, namespace_lock::NsLockMap};
|
||||
use madmin::heal_commands::{HealDriveInfo, HealResultItem};
|
||||
use md5::{Digest as Md5Digest, Md5};
|
||||
use rand::{
|
||||
thread_rng,
|
||||
{seq::SliceRandom, Rng},
|
||||
{Rng, seq::SliceRandom},
|
||||
};
|
||||
use rustfs_filemeta::{
|
||||
file_info_from_raw, merge_file_meta_versions, FileInfo, FileMeta, FileMetaShallowVersion, MetaCacheEntries, MetaCacheEntry,
|
||||
MetadataResolutionParams, ObjectPartInfo, RawFileInfo,
|
||||
FileInfo, FileMeta, FileMetaShallowVersion, MetaCacheEntries, MetaCacheEntry, MetadataResolutionParams, ObjectPartInfo,
|
||||
RawFileInfo, file_info_from_raw, merge_file_meta_versions,
|
||||
};
|
||||
use rustfs_rio::{bitrot_verify, BitrotReader, BitrotWriter, EtagResolvable, HashReader, Writer};
|
||||
use rustfs_rio::{BitrotReader, BitrotWriter, EtagResolvable, HashReader, Writer, bitrot_verify};
|
||||
use rustfs_utils::HashAlgorithm;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::hash::Hash;
|
||||
@@ -84,8 +84,8 @@ use std::{
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::{
|
||||
io::{empty, AsyncWrite},
|
||||
sync::{broadcast, RwLock},
|
||||
io::{AsyncWrite, empty},
|
||||
sync::{RwLock, broadcast},
|
||||
};
|
||||
use tokio::{
|
||||
select,
|
||||
@@ -406,11 +406,7 @@ impl SetDisks {
|
||||
}
|
||||
}
|
||||
|
||||
if max >= write_quorum {
|
||||
data_dir
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if max >= write_quorum { data_dir } else { None }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -741,11 +737,7 @@ impl SetDisks {
|
||||
|
||||
fn common_time(times: &[Option<OffsetDateTime>], quorum: usize) -> Option<OffsetDateTime> {
|
||||
let (time, count) = Self::common_time_and_occurrence(times);
|
||||
if count >= quorum {
|
||||
time
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if count >= quorum { time } else { None }
|
||||
}
|
||||
|
||||
fn common_time_and_occurrence(times: &[Option<OffsetDateTime>]) -> (Option<OffsetDateTime>, usize) {
|
||||
@@ -786,11 +778,7 @@ impl SetDisks {
|
||||
|
||||
fn common_etag(etags: &[Option<String>], quorum: usize) -> Option<String> {
|
||||
let (etag, count) = Self::common_etags(etags);
|
||||
if count >= quorum {
|
||||
etag
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if count >= quorum { etag } else { None }
|
||||
}
|
||||
|
||||
fn common_etags(etags: &[Option<String>]) -> (Option<String>, usize) {
|
||||
@@ -1837,13 +1825,7 @@ impl SetDisks {
|
||||
|
||||
let total_size = fi.size;
|
||||
|
||||
let length = {
|
||||
if length == 0 {
|
||||
total_size - offset
|
||||
} else {
|
||||
length
|
||||
}
|
||||
};
|
||||
let length = { if length == 0 { total_size - offset } else { length } };
|
||||
|
||||
if offset > total_size || offset + length > total_size {
|
||||
return Err(Error::other("offset out of range"));
|
||||
@@ -1896,12 +1878,16 @@ impl SetDisks {
|
||||
readers.push(Some(reader));
|
||||
errors.push(None);
|
||||
} else if let Some(disk) = disk_op {
|
||||
// Calculate ceiling division of till_offset by shard_size
|
||||
let till_offset =
|
||||
till_offset.div_ceil(erasure.shard_size()) * HashAlgorithm::HighwayHash256.size() + till_offset;
|
||||
|
||||
let rd = disk
|
||||
.read_file_stream(
|
||||
bucket,
|
||||
&format!("{}/{}/part.{}", object, files[idx].data_dir.unwrap_or(Uuid::nil()), part_number),
|
||||
part_offset,
|
||||
till_offset,
|
||||
part_length,
|
||||
)
|
||||
.await?;
|
||||
let reader = BitrotReader::new(rd, erasure.shard_size(), HashAlgorithm::HighwayHash256);
|
||||
@@ -2403,8 +2389,10 @@ impl SetDisks {
|
||||
}
|
||||
|
||||
if !lastest_meta.deleted && lastest_meta.erasure.distribution.len() != available_disks.len() {
|
||||
let err_str = format!("unexpected file distribution ({:?}) from available disks ({:?}), looks like backend disks have been manually modified refusing to heal {}/{}({})",
|
||||
lastest_meta.erasure.distribution, available_disks, bucket, object, version_id);
|
||||
let err_str = format!(
|
||||
"unexpected file distribution ({:?}) from available disks ({:?}), looks like backend disks have been manually modified refusing to heal {}/{}({})",
|
||||
lastest_meta.erasure.distribution, available_disks, bucket, object, version_id
|
||||
);
|
||||
warn!(err_str);
|
||||
let err = DiskError::other(err_str);
|
||||
return Ok((
|
||||
@@ -2416,8 +2404,10 @@ impl SetDisks {
|
||||
|
||||
let latest_disks = Self::shuffle_disks(&available_disks, &lastest_meta.erasure.distribution);
|
||||
if !lastest_meta.deleted && lastest_meta.erasure.distribution.len() != outdate_disks.len() {
|
||||
let err_str = format!("unexpected file distribution ({:?}) from outdated disks ({:?}), looks like backend disks have been manually modified refusing to heal {}/{}({})",
|
||||
lastest_meta.erasure.distribution, outdate_disks, bucket, object, version_id);
|
||||
let err_str = format!(
|
||||
"unexpected file distribution ({:?}) from outdated disks ({:?}), looks like backend disks have been manually modified refusing to heal {}/{}({})",
|
||||
lastest_meta.erasure.distribution, outdate_disks, bucket, object, version_id
|
||||
);
|
||||
warn!(err_str);
|
||||
let err = DiskError::other(err_str);
|
||||
return Ok((
|
||||
@@ -2428,8 +2418,14 @@ impl SetDisks {
|
||||
}
|
||||
|
||||
if !lastest_meta.deleted && lastest_meta.erasure.distribution.len() != parts_metadata.len() {
|
||||
let err_str = format!("unexpected file distribution ({:?}) from metadata entries ({:?}), looks like backend disks have been manually modified refusing to heal {}/{}({})",
|
||||
lastest_meta.erasure.distribution, parts_metadata.len(), bucket, object, version_id);
|
||||
let err_str = format!(
|
||||
"unexpected file distribution ({:?}) from metadata entries ({:?}), looks like backend disks have been manually modified refusing to heal {}/{}({})",
|
||||
lastest_meta.erasure.distribution,
|
||||
parts_metadata.len(),
|
||||
bucket,
|
||||
object,
|
||||
version_id
|
||||
);
|
||||
warn!(err_str);
|
||||
let err = DiskError::other(err_str);
|
||||
return Ok((
|
||||
@@ -3907,6 +3903,7 @@ impl ObjectIO for SetDisks {
|
||||
};
|
||||
|
||||
writers.push(Some(writer));
|
||||
errors.push(None);
|
||||
} else {
|
||||
errors.push(Some(DiskError::DiskNotFound));
|
||||
writers.push(None);
|
||||
@@ -3915,6 +3912,7 @@ impl ObjectIO for SetDisks {
|
||||
|
||||
let nil_count = errors.iter().filter(|&e| e.is_none()).count();
|
||||
if nil_count < write_quorum {
|
||||
error!("not enough disks to write: {:?}", errors);
|
||||
if let Some(write_err) = reduce_write_quorum_errs(&errors, OBJECT_OP_IGNORED_ERRS, write_quorum) {
|
||||
return Err(to_object_err(write_err.into(), vec![bucket, object]));
|
||||
}
|
||||
@@ -3926,7 +3924,7 @@ impl ObjectIO for SetDisks {
|
||||
|
||||
let (reader, w_size) = Arc::new(erasure).encode(stream, &mut writers, write_quorum).await?; // TODO: 出错,删除临时目录
|
||||
|
||||
mem::replace(&mut data.stream, reader);
|
||||
let _ = mem::replace(&mut data.stream, reader);
|
||||
// if let Err(err) = close_bitrot_writers(&mut writers).await {
|
||||
// error!("close_bitrot_writers err {:?}", err);
|
||||
// }
|
||||
@@ -4549,25 +4547,11 @@ impl StorageAPI for SetDisks {
|
||||
|
||||
let erasure = erasure_coding::Erasure::new(fi.erasure.data_blocks, fi.erasure.parity_blocks, fi.erasure.block_size);
|
||||
|
||||
let is_inline_buffer = {
|
||||
if let Some(sc) = GLOBAL_StorageClass.get() {
|
||||
sc.should_inline(erasure.shard_file_size(data.content_length), opts.versioned)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let mut writers = Vec::with_capacity(shuffle_disks.len());
|
||||
let mut errors = Vec::with_capacity(shuffle_disks.len());
|
||||
for disk_op in shuffle_disks.iter() {
|
||||
if let Some(disk) = disk_op {
|
||||
let writer = if is_inline_buffer {
|
||||
BitrotWriter::new(
|
||||
Writer::from_cursor(Cursor::new(Vec::new())),
|
||||
erasure.shard_size(),
|
||||
HashAlgorithm::HighwayHash256,
|
||||
)
|
||||
} else {
|
||||
let writer = {
|
||||
let f = match disk
|
||||
.create_file("", RUSTFS_META_TMP_BUCKET, &tmp_part_path, erasure.shard_file_size(data.content_length))
|
||||
.await
|
||||
@@ -4584,6 +4568,7 @@ impl StorageAPI for SetDisks {
|
||||
};
|
||||
|
||||
writers.push(Some(writer));
|
||||
errors.push(None);
|
||||
} else {
|
||||
errors.push(Some(DiskError::DiskNotFound));
|
||||
writers.push(None);
|
||||
@@ -4601,8 +4586,9 @@ impl StorageAPI for SetDisks {
|
||||
|
||||
let stream = mem::replace(&mut data.stream, HashReader::new(Box::new(Cursor::new(Vec::new())), 0, 0, None, false)?);
|
||||
|
||||
let (mut reader, w_size) = Arc::new(erasure).encode(stream, &mut writers, write_quorum).await?; // TODO: 出错,删除临时目录
|
||||
mem::replace(&mut data.stream, reader);
|
||||
let (reader, w_size) = Arc::new(erasure).encode(stream, &mut writers, write_quorum).await?; // TODO: 出错,删除临时目录
|
||||
|
||||
let _ = mem::replace(&mut data.stream, reader);
|
||||
|
||||
let mut etag = data.stream.try_resolve_etag().unwrap_or_default();
|
||||
|
||||
@@ -5755,9 +5741,9 @@ fn get_complete_multipart_md5(parts: &[CompletePart]) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::disk::CHECK_PART_UNKNOWN;
|
||||
use crate::disk::CHECK_PART_VOLUME_NOT_FOUND;
|
||||
use crate::disk::error::DiskError;
|
||||
use crate::store_api::CompletePart;
|
||||
use rustfs_filemeta::ErasureInfo;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use common::error::{Error, Result};
|
||||
use std::io::{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))),
|
||||
_ => Err(Error::other(format!("ParseBool: parsing {}", str))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use common::error::{Error, Result};
|
||||
use lazy_static::*;
|
||||
use regex::Regex;
|
||||
use std::io::{Error, Result};
|
||||
|
||||
lazy_static! {
|
||||
static ref ELLIPSES_RE: Regex = Regex::new(r"(.*)(\{[0-9a-z]*\.\.\.[0-9a-z]*\})(.*)").unwrap();
|
||||
@@ -107,7 +107,10 @@ 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)));
|
||||
return Err(Error::other(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
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -144,7 +147,10 @@ pub fn find_ellipses_patterns(arg: &str) -> Result<ArgPattern> {
|
||||
|| 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)));
|
||||
return Err(Error::other(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
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,10 +171,10 @@ pub fn has_ellipses<T: AsRef<str>>(s: &[T]) -> bool {
|
||||
/// {33...64}
|
||||
pub fn parse_ellipses_range(pattern: &str) -> Result<Vec<String>> {
|
||||
if !pattern.contains(OPEN_BRACES) {
|
||||
return Err(Error::from_string("Invalid argument"));
|
||||
return Err(Error::other("Invalid argument"));
|
||||
}
|
||||
if !pattern.contains(OPEN_BRACES) {
|
||||
return Err(Error::from_string("Invalid argument"));
|
||||
return Err(Error::other("Invalid argument"));
|
||||
}
|
||||
|
||||
let ellipses_range: Vec<&str> = pattern
|
||||
@@ -178,15 +184,15 @@ pub fn parse_ellipses_range(pattern: &str) -> Result<Vec<String>> {
|
||||
.collect();
|
||||
|
||||
if ellipses_range.len() != 2 {
|
||||
return Err(Error::from_string("Invalid argument"));
|
||||
return Err(Error::other("Invalid argument"));
|
||||
}
|
||||
|
||||
// TODO: Add support for hexadecimals.
|
||||
let start = ellipses_range[0].parse::<usize>()?;
|
||||
let end = ellipses_range[1].parse::<usize>()?;
|
||||
let start = ellipses_range[0].parse::<usize>().map_err(|e| Error::other(e))?;
|
||||
let end = ellipses_range[1].parse::<usize>().map_err(|e| Error::other(e))?;
|
||||
|
||||
if start > end {
|
||||
return Err(Error::from_string("Invalid argument:range start cannot be bigger than end"));
|
||||
return Err(Error::other("Invalid argument:range start cannot be bigger than end"));
|
||||
}
|
||||
|
||||
let mut ret: Vec<String> = Vec::with_capacity(end - start + 1);
|
||||
|
||||
+11
-14
@@ -1,5 +1,5 @@
|
||||
use common::error::{Error, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use std::io::{Error, Result};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt::Display,
|
||||
@@ -23,7 +23,7 @@ pub fn is_socket_addr(addr: &str) -> bool {
|
||||
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))),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
// 0.0.0.0 is a wildcard address and refers to local network
|
||||
@@ -44,7 +44,7 @@ pub fn check_local_server_addr(server_addr: &str) -> Result<SocketAddr> {
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::from_string("host in server address should be this server"))
|
||||
Err(Error::other("host in server address should be this server"))
|
||||
}
|
||||
|
||||
/// checks if the given parameter correspond to one of
|
||||
@@ -55,7 +55,7 @@ pub fn is_local_host(host: Host<&str>, port: u16, local_port: u16) -> Result<boo
|
||||
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))),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
ips.iter().any(|ip| local_set.contains(ip))
|
||||
@@ -79,7 +79,7 @@ pub fn get_host_ip(host: Host<&str>) -> Result<HashSet<IpAddr>> {
|
||||
.map(|v| v.map(|v| v.ip()).collect::<HashSet<_>>())
|
||||
{
|
||||
Ok(ips) => Ok(ips),
|
||||
Err(err) => Err(Error::new(Box::new(err))),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
Host::Ipv4(ip) => {
|
||||
let mut set = HashSet::with_capacity(1);
|
||||
@@ -102,7 +102,7 @@ pub fn get_available_port() -> u16 {
|
||||
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))),
|
||||
Err(err) => Err(Error::other(format!("Unable to get IP addresses of this host: {}", err))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ pub fn parse_and_resolve_address(addr_str: &str) -> Result<SocketAddr> {
|
||||
let port_str = port;
|
||||
let port: u16 = port_str
|
||||
.parse()
|
||||
.map_err(|e| Error::from_string(format!("Invalid port format: {}, err:{:?}", addr_str, e)))?;
|
||||
.map_err(|e| Error::other(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 {
|
||||
@@ -199,13 +199,10 @@ mod test {
|
||||
("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"))),
|
||||
("localhost", Err(Error::other("invalid socket address"))),
|
||||
("", Err(Error::other("invalid socket address"))),
|
||||
("example.org:54321", Err(Error::other("host in server address should be this server"))),
|
||||
(":-10", Err(Error::other("invalid port value"))),
|
||||
];
|
||||
|
||||
for test_case in test_cases {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use nix::sys::stat::{self, stat};
|
||||
use nix::sys::statfs::{self, statfs, FsType};
|
||||
use nix::sys::statfs::{self, FsType, statfs};
|
||||
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 std::io::{Error, Result};
|
||||
|
||||
use super::IOStats;
|
||||
|
||||
@@ -29,7 +29,7 @@ pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
bfree,
|
||||
p.as_ref().display()
|
||||
),
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
blocks,
|
||||
p.as_ref().display()
|
||||
),
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
total,
|
||||
p.as_ref().display()
|
||||
),
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,7 +122,7 @@ pub fn get_drive_stats(major: u32, minor: u32) -> Result<IOStats> {
|
||||
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)));
|
||||
return Err(Error::new(ErrorKind::Other, format!("found invalid format while reading {}", stats_file)));
|
||||
}
|
||||
let mut io_stats = IOStats {
|
||||
read_ios: stats[0],
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use super::IOStats;
|
||||
use crate::disk::Info;
|
||||
use common::error::Result;
|
||||
use nix::sys::{stat::stat, statfs::statfs};
|
||||
use std::io::Error;
|
||||
use std::io::{Error, Result};
|
||||
use std::path::Path;
|
||||
|
||||
/// returns total and free bytes available in a directory, e.g. `/`.
|
||||
@@ -22,7 +21,7 @@ pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
bavail,
|
||||
bfree,
|
||||
p.as_ref().display()
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,7 +33,7 @@ pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
reserved,
|
||||
blocks,
|
||||
p.as_ref().display()
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,7 +46,7 @@ pub fn get_info(p: impl AsRef<Path>) -> std::io::Result<Info> {
|
||||
free,
|
||||
total,
|
||||
p.as_ref().display()
|
||||
)))
|
||||
)));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
use super::IOStats;
|
||||
use crate::disk::Info;
|
||||
use common::error::Result;
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::io::{Error, ErrorKind, Result};
|
||||
use std::mem;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
|
||||
@@ -115,6 +115,15 @@ impl From<ecstore::error::StorageError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for ecstore::error::StorageError {
|
||||
fn from(e: Error) -> Self {
|
||||
match e {
|
||||
Error::ConfigNotFound => ecstore::error::StorageError::ConfigNotFound,
|
||||
_ => ecstore::error::StorageError::other(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<policy::error::Error> for Error {
|
||||
fn from(e: policy::error::Error) -> Self {
|
||||
match e {
|
||||
@@ -152,6 +161,12 @@ impl From<policy::error::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for std::io::Error {
|
||||
fn from(e: Error) -> Self {
|
||||
std::io::Error::other(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(e: serde_json::Error) -> Self {
|
||||
Error::other(e)
|
||||
|
||||
@@ -88,6 +88,7 @@ tower-http = { workspace = true, features = [
|
||||
uuid = { workspace = true }
|
||||
rustfs-filemeta.workspace = true
|
||||
thiserror.workspace = true
|
||||
rustfs-rio.workspace = true
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
libsystemd.workspace = true
|
||||
|
||||
@@ -7,7 +7,7 @@ use serde_urlencoded::from_bytes;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{admin::router::Operation, storage::error::to_s3_error};
|
||||
use crate::{admin::router::Operation, error::ApiError};
|
||||
|
||||
pub struct ListPools {}
|
||||
|
||||
@@ -33,7 +33,7 @@ impl Operation for ListPools {
|
||||
let mut pools_status = Vec::new();
|
||||
|
||||
for (idx, _) in endpoints.as_ref().iter().enumerate() {
|
||||
let state = store.status(idx).await.map_err(to_s3_error)?;
|
||||
let state = store.status(idx).await.map_err(ApiError::from)?;
|
||||
|
||||
pools_status.push(state);
|
||||
}
|
||||
@@ -103,7 +103,7 @@ impl Operation for StatusPool {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
};
|
||||
|
||||
let pools_status = store.status(idx).await.map_err(to_s3_error)?;
|
||||
let pools_status = store.status(idx).await.map_err(ApiError::from)?;
|
||||
|
||||
let data = serde_json::to_vec(&pools_status)
|
||||
.map_err(|_e| S3Error::with_message(S3ErrorCode::InternalError, "parse accountInfo failed"))?;
|
||||
@@ -191,7 +191,7 @@ impl Operation for StartDecommission {
|
||||
}
|
||||
|
||||
if !pools_indices.is_empty() {
|
||||
store.decommission(ctx_rx, pools_indices).await.map_err(to_s3_error)?;
|
||||
store.decommission(ctx_rx, pools_indices).await.map_err(ApiError::from)?;
|
||||
}
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, Body::default())))
|
||||
@@ -245,7 +245,7 @@ impl Operation for CancelDecommission {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
};
|
||||
|
||||
store.decommission_cancel(idx).await.map_err(to_s3_error)?;
|
||||
store.decommission_cancel(idx).await.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, Body::default())))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ pub mod router;
|
||||
mod rpc;
|
||||
pub mod utils;
|
||||
|
||||
use common::error::Result;
|
||||
// use ecstore::global::{is_dist_erasure, is_erasure};
|
||||
use handlers::{
|
||||
group, policys, pools, rebalance,
|
||||
@@ -18,7 +17,7 @@ use s3s::route::S3Route;
|
||||
|
||||
const ADMIN_PREFIX: &str = "/rustfs/admin";
|
||||
|
||||
pub fn make_admin_route() -> Result<impl S3Route> {
|
||||
pub fn make_admin_route() -> std::io::Result<impl S3Route> {
|
||||
let mut r: S3Router<AdminOperation> = S3Router::new();
|
||||
|
||||
// 1
|
||||
@@ -124,7 +123,7 @@ pub fn make_admin_route() -> Result<impl S3Route> {
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn register_user_route(r: &mut S3Router<AdminOperation>) -> Result<()> {
|
||||
fn register_user_route(r: &mut S3Router<AdminOperation>) -> std::io::Result<()> {
|
||||
// 1
|
||||
r.insert(
|
||||
Method::GET,
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
use common::error::Result;
|
||||
use hyper::http::Extensions;
|
||||
use hyper::HeaderMap;
|
||||
use hyper::Method;
|
||||
use hyper::StatusCode;
|
||||
use hyper::Uri;
|
||||
use hyper::http::Extensions;
|
||||
use matchit::Params;
|
||||
use matchit::Router;
|
||||
use s3s::header;
|
||||
use s3s::route::S3Route;
|
||||
use s3s::s3_error;
|
||||
use s3s::Body;
|
||||
use s3s::S3Request;
|
||||
use s3s::S3Response;
|
||||
use s3s::S3Result;
|
||||
use s3s::header;
|
||||
use s3s::route::S3Route;
|
||||
use s3s::s3_error;
|
||||
|
||||
use super::rpc::RPC_PREFIX;
|
||||
use super::ADMIN_PREFIX;
|
||||
use super::rpc::RPC_PREFIX;
|
||||
|
||||
pub struct S3Router<T> {
|
||||
router: Router<T>,
|
||||
@@ -28,12 +27,12 @@ impl<T: Operation> S3Router<T> {
|
||||
Self { router }
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, method: Method, path: &str, operation: T) -> Result<()> {
|
||||
pub fn insert(&mut self, method: Method, path: &str, operation: T) -> std::io::Result<()> {
|
||||
let path = Self::make_route_str(method, path);
|
||||
|
||||
// warn!("set uri {}", &path);
|
||||
|
||||
self.router.insert(path, operation)?;
|
||||
self.router.insert(path, operation).map_err(|e| std::io::Error::other(e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ use super::router::AdminOperation;
|
||||
use super::router::Operation;
|
||||
use super::router::S3Router;
|
||||
use crate::storage::ecfs::bytes_stream;
|
||||
use common::error::Result;
|
||||
use ecstore::disk::DiskAPI;
|
||||
use ecstore::io::READ_BUFFER_SIZE;
|
||||
use ecstore::store::find_local_disk;
|
||||
@@ -10,19 +9,19 @@ use futures::TryStreamExt;
|
||||
use http::StatusCode;
|
||||
use hyper::Method;
|
||||
use matchit::Params;
|
||||
use s3s::dto::StreamingBlob;
|
||||
use s3s::s3_error;
|
||||
use s3s::Body;
|
||||
use s3s::S3Request;
|
||||
use s3s::S3Response;
|
||||
use s3s::S3Result;
|
||||
use s3s::dto::StreamingBlob;
|
||||
use s3s::s3_error;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use tokio_util::io::ReaderStream;
|
||||
use tokio_util::io::StreamReader;
|
||||
|
||||
pub const RPC_PREFIX: &str = "/rustfs/rpc";
|
||||
|
||||
pub fn regist_rpc_route(r: &mut S3Router<AdminOperation>) -> Result<()> {
|
||||
pub fn regist_rpc_route(r: &mut S3Router<AdminOperation>) -> std::io::Result<()> {
|
||||
r.insert(
|
||||
Method::GET,
|
||||
format!("{}{}", RPC_PREFIX, "/read_file_stream").as_str(),
|
||||
|
||||
+87
-1675
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,6 +1,6 @@
|
||||
use std::{collections::HashMap, io::Cursor, pin::Pin};
|
||||
|
||||
use common::error::Error as EcsError;
|
||||
// use common::error::Error as EcsError;
|
||||
use ecstore::{
|
||||
admin_server_info::get_local_server_property,
|
||||
bucket::{metadata::load_bucket_metadata, metadata_sys},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use appauth::token::Token;
|
||||
use common::error::{Error, Result};
|
||||
use std::io::{Error, Result};
|
||||
use std::sync::OnceLock;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
@@ -37,7 +37,7 @@ pub fn license_check() -> Result<()> {
|
||||
let invalid_license = LICENSE.get().map(|token| {
|
||||
if token.expired < SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() {
|
||||
error!("License expired");
|
||||
return Err(Error::from_string("Incorrect license, please contact RustFS.".to_string()));
|
||||
return Err(Error::other("Incorrect license, please contact RustFS."));
|
||||
}
|
||||
info!("License is valid ! expired at {}", token.expired);
|
||||
Ok(())
|
||||
@@ -46,12 +46,12 @@ pub fn license_check() -> Result<()> {
|
||||
// let invalid_license = config::get_config().license.as_ref().map(|license| {
|
||||
// if license.is_empty() {
|
||||
// error!("License is empty");
|
||||
// return Err(Error::from_string("Incorrect license, please contact RustFS.".to_string()));
|
||||
// return Err(Error::other("Incorrect license, please contact RustFS.".to_string()));
|
||||
// }
|
||||
// let token = appauth::token::parse_license(license)?;
|
||||
// if token.expired < SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() {
|
||||
// error!("License expired");
|
||||
// return Err(Error::from_string("Incorrect license, please contact RustFS.".to_string()));
|
||||
// return Err(Error::other("Incorrect license, please contact RustFS.".to_string()));
|
||||
// }
|
||||
|
||||
// info!("License is valid ! expired at {}", token.expired);
|
||||
@@ -59,7 +59,7 @@ pub fn license_check() -> Result<()> {
|
||||
// });
|
||||
|
||||
if invalid_license.is_none() || invalid_license.is_some_and(|v| v.is_err()) {
|
||||
return Err(Error::from_string("Incorrect license, please contact RustFS.".to_string()));
|
||||
return Err(Error::other("Incorrect license, please contact RustFS."));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+8
-8
@@ -73,7 +73,7 @@ const MI_B: usize = 1024 * 1024;
|
||||
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn check_auth(req: Request<()>) -> Result<Request<()>, Status> {
|
||||
fn check_auth(req: Request<()>) -> std::result::Result<Request<()>, Status> {
|
||||
let token: MetadataValue<_> = "rustfs rpc".parse().unwrap();
|
||||
|
||||
match req.metadata().get("authorization") {
|
||||
@@ -120,7 +120,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
// Initialize event notifier
|
||||
event::init_event_notifier(opt.event_config).await;
|
||||
|
||||
let server_addr = net::parse_and_resolve_address(opt.address.as_str())?;
|
||||
let server_addr = net::parse_and_resolve_address(opt.address.as_str()).map_err(|err| Error::other(err))?;
|
||||
let server_port = server_addr.port();
|
||||
let server_address = server_addr.to_string();
|
||||
|
||||
@@ -140,7 +140,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
|
||||
// For RPC
|
||||
let (endpoint_pools, setup_type) = EndpointServerPools::from_volumes(server_address.clone().as_str(), opt.volumes.clone())
|
||||
.map_err(|err| Error::from_string(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err.to_string()))?;
|
||||
|
||||
// Print RustFS-style logging for pool formatting
|
||||
for (i, eps) in endpoint_pools.as_ref().iter().enumerate() {
|
||||
@@ -189,7 +189,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
// Initialize the local disk
|
||||
init_local_disks(endpoint_pools.clone())
|
||||
.await
|
||||
.map_err(|err| Error::from_string(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err))?;
|
||||
|
||||
// Setup S3 service
|
||||
// This project uses the S3S library to implement S3 services
|
||||
@@ -518,7 +518,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.map_err(|err| Error::from_string(err.to_string()))?;
|
||||
.map_err(|err| Error::other(err))?;
|
||||
|
||||
let buckets = buckets_list.into_iter().map(|v| v.name).collect();
|
||||
|
||||
@@ -528,7 +528,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
|
||||
new_global_notification_sys(endpoint_pools.clone()).await.map_err(|err| {
|
||||
error!("new_global_notification_sys failed {:?}", &err);
|
||||
Error::from_string(err.to_string())
|
||||
Error::other(err)
|
||||
})?;
|
||||
|
||||
// init scanner
|
||||
@@ -549,7 +549,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
|
||||
if console_address.is_empty() {
|
||||
error!("console_address is empty");
|
||||
return Err(Error::from_string("console_address is empty".to_string()));
|
||||
return Err(Error::other("console_address is empty".to_string()));
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
@@ -571,7 +571,7 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
// stop event notifier
|
||||
rustfs_event_notifier::shutdown().await.map_err(|err| {
|
||||
error!("Failed to shut down the notification system: {}", err);
|
||||
Error::from_string(err.to_string())
|
||||
Error::other(err)
|
||||
})?;
|
||||
}
|
||||
|
||||
|
||||
+111
-105
@@ -3,8 +3,9 @@ use super::options::del_opts;
|
||||
use super::options::extract_metadata;
|
||||
use super::options::put_opts;
|
||||
use crate::auth::get_condition_values;
|
||||
use crate::error::ApiError;
|
||||
use crate::error::Result;
|
||||
use crate::storage::access::ReqInfo;
|
||||
use crate::storage::error::to_s3_error;
|
||||
use crate::storage::options::copy_dst_opts;
|
||||
use crate::storage::options::copy_src_opts;
|
||||
use crate::storage::options::{extract_metadata_from_mime, get_opts};
|
||||
@@ -12,11 +13,9 @@ use api::query::Context;
|
||||
use api::query::Query;
|
||||
use api::server::dbms::DatabaseManagerSystem;
|
||||
use bytes::Bytes;
|
||||
use common::error::Result;
|
||||
use datafusion::arrow::csv::WriterBuilder as CsvWriterBuilder;
|
||||
use datafusion::arrow::json::writer::JsonArray;
|
||||
use datafusion::arrow::json::WriterBuilder as JsonWriterBuilder;
|
||||
use ecstore::bucket::error::BucketMetadataError;
|
||||
use datafusion::arrow::json::writer::JsonArray;
|
||||
use ecstore::bucket::metadata::BUCKET_LIFECYCLE_CONFIG;
|
||||
use ecstore::bucket::metadata::BUCKET_NOTIFICATION_CONFIG;
|
||||
use ecstore::bucket::metadata::BUCKET_POLICY_CONFIG;
|
||||
@@ -30,6 +29,7 @@ use ecstore::bucket::policy_sys::PolicySys;
|
||||
use ecstore::bucket::tagging::decode_tags;
|
||||
use ecstore::bucket::tagging::encode_tags;
|
||||
use ecstore::bucket::versioning_sys::BucketVersioningSys;
|
||||
use ecstore::error::StorageError;
|
||||
use ecstore::io::READ_BUFFER_SIZE;
|
||||
use ecstore::new_object_layer_fn;
|
||||
use ecstore::store_api::BucketOptions;
|
||||
@@ -42,8 +42,8 @@ use ecstore::store_api::ObjectIO;
|
||||
use ecstore::store_api::ObjectOptions;
|
||||
use ecstore::store_api::ObjectToDelete;
|
||||
use ecstore::store_api::PutObjReader;
|
||||
use ecstore::store_api::StorageAPI;
|
||||
use ecstore::store_api::RESERVED_METADATA_PREFIX_LOWER;
|
||||
use ecstore::store_api::StorageAPI;
|
||||
use ecstore::utils::path::path_join_buf;
|
||||
use ecstore::utils::xml;
|
||||
use ecstore::xhttp;
|
||||
@@ -52,27 +52,28 @@ use futures::{Stream, StreamExt};
|
||||
use http::HeaderMap;
|
||||
use lazy_static::lazy_static;
|
||||
use policy::auth;
|
||||
use policy::policy::action::Action;
|
||||
use policy::policy::action::S3Action;
|
||||
use policy::policy::BucketPolicy;
|
||||
use policy::policy::BucketPolicyArgs;
|
||||
use policy::policy::Validator;
|
||||
use policy::policy::action::Action;
|
||||
use policy::policy::action::S3Action;
|
||||
use query::instance::make_rustfsms;
|
||||
use rustfs_rio::HashReader;
|
||||
use rustfs_zip::CompressionFormat;
|
||||
use s3s::dto::*;
|
||||
use s3s::s3_error;
|
||||
use s3s::S3;
|
||||
use s3s::S3Error;
|
||||
use s3s::S3ErrorCode;
|
||||
use s3s::S3Result;
|
||||
use s3s::S3;
|
||||
use s3s::dto::*;
|
||||
use s3s::s3_error;
|
||||
use s3s::{S3Request, S3Response};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use time::OffsetDateTime;
|
||||
use time::format_description::well_known::Rfc3339;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_tar::Archive;
|
||||
@@ -175,12 +176,15 @@ impl FS {
|
||||
|
||||
println!("Extracted: {}, size {}", fpath, size);
|
||||
|
||||
let mut reader = PutObjReader::new(Box::new(f), size);
|
||||
// Wrap the tar entry with BufReader to make it compatible with Reader trait
|
||||
let reader = Box::new(tokio::io::BufReader::new(f));
|
||||
let hrd = HashReader::new(reader, size as i64, size as i64, None, false).map_err(ApiError::from)?;
|
||||
let mut reader = PutObjReader::new(hrd, size);
|
||||
|
||||
let _obj_info = store
|
||||
.put_object(&bucket, &fpath, &mut reader, &ObjectOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// let e_tag = obj_info.etag;
|
||||
|
||||
@@ -244,7 +248,7 @@ impl S3 for FS {
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let output = CreateBucketOutput::default();
|
||||
Ok(S3Response::new(output))
|
||||
@@ -270,7 +274,7 @@ impl S3 for FS {
|
||||
|
||||
// warn!("copy_object {}/{}, to {}/{}", &src_bucket, &src_key, &bucket, &key);
|
||||
|
||||
let mut src_opts = copy_src_opts(&src_bucket, &src_key, &req.headers).map_err(to_s3_error)?;
|
||||
let mut src_opts = copy_src_opts(&src_bucket, &src_key, &req.headers).map_err(ApiError::from)?;
|
||||
|
||||
src_opts.version_id = version_id.clone();
|
||||
|
||||
@@ -283,7 +287,7 @@ impl S3 for FS {
|
||||
|
||||
let dst_opts = copy_dst_opts(&bucket, &key, version_id, &req.headers, None)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let cp_src_dst_same = path_join_buf(&[&src_bucket, &src_key]) == path_join_buf(&[&bucket, &key]);
|
||||
|
||||
@@ -300,7 +304,7 @@ impl S3 for FS {
|
||||
let gr = store
|
||||
.get_object_reader(&src_bucket, &src_key, None, h, &get_opts)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let mut src_info = gr.object_info.clone();
|
||||
|
||||
@@ -308,8 +312,11 @@ impl S3 for FS {
|
||||
src_info.metadata_only = true;
|
||||
}
|
||||
|
||||
let hrd = HashReader::new(gr.stream, gr.object_info.size as i64, gr.object_info.size as i64, None, false)
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
src_info.put_object_reader = Some(PutObjReader {
|
||||
stream: gr.stream,
|
||||
stream: hrd,
|
||||
content_length: gr.object_info.size as usize,
|
||||
});
|
||||
|
||||
@@ -320,7 +327,7 @@ impl S3 for FS {
|
||||
let oi = store
|
||||
.copy_object(&src_bucket, &src_key, &bucket, &key, &mut src_info, &src_opts, &dst_opts)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// warn!("copy_object oi {:?}", &oi);
|
||||
|
||||
@@ -355,7 +362,7 @@ impl S3 for FS {
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(DeleteBucketOutput {}))
|
||||
}
|
||||
@@ -371,7 +378,7 @@ impl S3 for FS {
|
||||
|
||||
let opts: ObjectOptions = del_opts(&bucket, &key, version_id, &req.headers, Some(metadata))
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let version_id = opts.version_id.as_ref().map(|v| Uuid::parse_str(v).ok()).unwrap_or_default();
|
||||
let dobj = ObjectToDelete {
|
||||
@@ -384,7 +391,7 @@ impl S3 for FS {
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
};
|
||||
let (dobjs, _errs) = store.delete_objects(&bucket, objects, opts).await.map_err(to_s3_error)?;
|
||||
let (dobjs, _errs) = store.delete_objects(&bucket, objects, opts).await.map_err(ApiError::from)?;
|
||||
|
||||
// TODO: let errors;
|
||||
|
||||
@@ -392,13 +399,7 @@ impl S3 for FS {
|
||||
if let Some((a, b)) = dobjs
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let delete_marker = {
|
||||
if v.delete_marker {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let delete_marker = { if v.delete_marker { Some(true) } else { None } };
|
||||
|
||||
let version_id = v.version_id.clone();
|
||||
|
||||
@@ -447,20 +448,14 @@ impl S3 for FS {
|
||||
|
||||
let opts: ObjectOptions = del_opts(&bucket, "", None, &req.headers, Some(metadata))
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let (dobjs, errs) = store.delete_objects(&bucket, objects, opts).await.map_err(to_s3_error)?;
|
||||
let (dobjs, errs) = store.delete_objects(&bucket, objects, opts).await.map_err(ApiError::from)?;
|
||||
|
||||
let deleted = dobjs
|
||||
.iter()
|
||||
.map(|v| DeletedObject {
|
||||
delete_marker: {
|
||||
if v.delete_marker {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
delete_marker: { if v.delete_marker { Some(true) } else { None } },
|
||||
delete_marker_version_id: v.delete_marker_version_id.clone(),
|
||||
key: Some(v.object_name.clone()),
|
||||
version_id: v.version_id.clone(),
|
||||
@@ -493,7 +488,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&input.bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let output = GetBucketLocationOutput::default();
|
||||
Ok(S3Response::new(output))
|
||||
@@ -550,7 +545,7 @@ impl S3 for FS {
|
||||
|
||||
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, part_number, &req.headers)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
@@ -559,7 +554,7 @@ impl S3 for FS {
|
||||
let reader = store
|
||||
.get_object_reader(bucket.as_str(), key.as_str(), rs, h, &opts)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let info = reader.object_info;
|
||||
|
||||
@@ -606,7 +601,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&input.bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
// mc cp step 2 GetBucketInfo
|
||||
|
||||
Ok(S3Response::new(HeadBucketOutput::default()))
|
||||
@@ -651,13 +646,13 @@ impl S3 for FS {
|
||||
|
||||
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, part_number, &req.headers)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
};
|
||||
|
||||
let info = store.get_object_info(&bucket, &key, &opts).await.map_err(to_s3_error)?;
|
||||
let info = store.get_object_info(&bucket, &key, &opts).await.map_err(ApiError::from)?;
|
||||
|
||||
// warn!("head_object info {:?}", &info);
|
||||
|
||||
@@ -700,7 +695,7 @@ impl S3 for FS {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
};
|
||||
|
||||
let mut bucket_infos = store.list_bucket(&BucketOptions::default()).await.map_err(to_s3_error)?;
|
||||
let mut bucket_infos = store.list_bucket(&BucketOptions::default()).await.map_err(ApiError::from)?;
|
||||
|
||||
let mut req = req;
|
||||
|
||||
@@ -792,7 +787,7 @@ impl S3 for FS {
|
||||
start_after,
|
||||
)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// warn!("object_infos objects {:?}", object_infos.objects);
|
||||
|
||||
@@ -873,7 +868,7 @@ impl S3 for FS {
|
||||
let object_infos = store
|
||||
.list_object_versions(&bucket, &prefix, key_marker, version_id_marker, delimiter.clone(), max_keys)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let objects: Vec<ObjectVersion> = object_infos
|
||||
.objects
|
||||
@@ -960,9 +955,14 @@ impl S3 for FS {
|
||||
}
|
||||
};
|
||||
|
||||
let body = Box::new(StreamReader::new(body.map(|f| f.map_err(|e| std::io::Error::other(e.to_string())))));
|
||||
let body = StreamReader::new(body.map(|f| f.map_err(|e| std::io::Error::other(e.to_string()))));
|
||||
let body = Box::new(tokio::io::BufReader::new(body));
|
||||
let hrd = HashReader::new(body, content_length as i64, content_length as i64, None, false).map_err(ApiError::from)?;
|
||||
let mut reader = PutObjReader::new(hrd, content_length as usize);
|
||||
|
||||
let mut reader = PutObjReader::new(body, content_length as usize);
|
||||
// let body = Box::new(StreamReader::new(body.map(|f| f.map_err(|e| std::io::Error::other(e.to_string())))));
|
||||
|
||||
// let mut reader = PutObjReader::new(body, content_length as usize);
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string()));
|
||||
@@ -978,14 +978,14 @@ impl S3 for FS {
|
||||
|
||||
let opts: ObjectOptions = put_opts(&bucket, &key, version_id, &req.headers, Some(metadata))
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
debug!("put_object opts {:?}", &opts);
|
||||
|
||||
let obj_info = store
|
||||
.put_object(&bucket, &key, &mut reader, &opts)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let e_tag = obj_info.etag;
|
||||
|
||||
@@ -1027,10 +1027,12 @@ impl S3 for FS {
|
||||
|
||||
let opts: ObjectOptions = put_opts(&bucket, &key, version_id, &req.headers, Some(metadata))
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let MultipartUploadResult { upload_id, .. } =
|
||||
store.new_multipart_upload(&bucket, &key, &opts).await.map_err(to_s3_error)?;
|
||||
let MultipartUploadResult { upload_id, .. } = store
|
||||
.new_multipart_upload(&bucket, &key, &opts)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let output = CreateMultipartUploadOutput {
|
||||
bucket: Some(bucket),
|
||||
@@ -1074,10 +1076,12 @@ impl S3 for FS {
|
||||
}
|
||||
};
|
||||
|
||||
let body = Box::new(StreamReader::new(body.map(|f| f.map_err(|e| std::io::Error::other(e.to_string())))));
|
||||
let body = StreamReader::new(body.map(|f| f.map_err(|e| std::io::Error::other(e.to_string()))));
|
||||
let body = Box::new(tokio::io::BufReader::new(body));
|
||||
let hrd = HashReader::new(body, content_length as i64, content_length as i64, None, false).map_err(ApiError::from)?;
|
||||
|
||||
// mc cp step 4
|
||||
let mut data = PutObjReader::new(body, content_length as usize);
|
||||
let mut data = PutObjReader::new(hrd, content_length as usize);
|
||||
let opts = ObjectOptions::default();
|
||||
|
||||
let Some(store) = new_object_layer_fn() else {
|
||||
@@ -1089,7 +1093,7 @@ impl S3 for FS {
|
||||
let info = store
|
||||
.put_object_part(&bucket, &key, &upload_id, part_id, &mut data, &opts)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let output = UploadPartOutput {
|
||||
e_tag: info.etag,
|
||||
@@ -1155,7 +1159,7 @@ impl S3 for FS {
|
||||
let oi = store
|
||||
.complete_multipart_upload(&bucket, &key, &upload_id, uploaded_parts, opts)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let output = CompleteMultipartUploadOutput {
|
||||
bucket: Some(bucket),
|
||||
@@ -1184,7 +1188,7 @@ impl S3 for FS {
|
||||
store
|
||||
.abort_multipart_upload(bucket.as_str(), key.as_str(), upload_id.as_str(), opts)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
Ok(S3Response::new(AbortMultipartUploadOutput { ..Default::default() }))
|
||||
}
|
||||
|
||||
@@ -1222,13 +1226,13 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let data = try_!(xml::serialize(&tagging));
|
||||
|
||||
metadata_sys::update(&bucket, BUCKET_TAGGING_CONFIG, data)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(Default::default()))
|
||||
}
|
||||
@@ -1242,7 +1246,7 @@ impl S3 for FS {
|
||||
|
||||
metadata_sys::delete(&bucket, BUCKET_TAGGING_CONFIG)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(DeleteBucketTaggingOutput {}))
|
||||
}
|
||||
@@ -1268,7 +1272,7 @@ impl S3 for FS {
|
||||
store
|
||||
.put_object_tags(&bucket, &object, &tags, &ObjectOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(PutObjectTaggingOutput { version_id: None }))
|
||||
}
|
||||
@@ -1285,7 +1289,7 @@ impl S3 for FS {
|
||||
let tags = store
|
||||
.get_object_tags(&bucket, &object, &ObjectOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let tag_set = decode_tags(tags.as_str());
|
||||
|
||||
@@ -1311,7 +1315,7 @@ impl S3 for FS {
|
||||
store
|
||||
.delete_object_tags(&bucket, &object, &ObjectOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(DeleteObjectTaggingOutput { version_id: None }))
|
||||
}
|
||||
@@ -1329,9 +1333,9 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let VersioningConfiguration { status, .. } = BucketVersioningSys::get(&bucket).await.map_err(to_s3_error)?;
|
||||
let VersioningConfiguration { status, .. } = BucketVersioningSys::get(&bucket).await.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(GetBucketVersioningOutput {
|
||||
status,
|
||||
@@ -1359,7 +1363,7 @@ impl S3 for FS {
|
||||
|
||||
metadata_sys::update(&bucket, BUCKET_VERSIONING_CONFIG, data)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// TODO: globalSiteReplicationSys.BucketMetaHook
|
||||
|
||||
@@ -1379,7 +1383,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let conditions = get_condition_values(&req.headers, &auth::Credentials::default());
|
||||
|
||||
@@ -1426,15 +1430,15 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let cfg = match PolicySys::get(&bucket).await {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
if BucketMetadataError::BucketPolicyNotFound.is(&err) {
|
||||
if StorageError::BucketPolicyNotFound == err {
|
||||
return Err(s3_error!(NoSuchBucketPolicy));
|
||||
}
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, format!("{}", err)));
|
||||
return Err(S3Error::with_message(S3ErrorCode::InternalError, err.to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1453,7 +1457,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// warn!("input policy {}", &policy);
|
||||
|
||||
@@ -1469,7 +1473,7 @@ impl S3 for FS {
|
||||
|
||||
metadata_sys::update(&bucket, BUCKET_POLICY_CONFIG, data)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(PutBucketPolicyOutput {}))
|
||||
}
|
||||
@@ -1487,11 +1491,11 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
metadata_sys::delete(&bucket, BUCKET_POLICY_CONFIG)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(DeleteBucketPolicyOutput {}))
|
||||
}
|
||||
@@ -1510,7 +1514,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let rules = match metadata_sys::get_lifecycle_config(&bucket).await {
|
||||
Ok((cfg, _)) => Some(cfg.rules),
|
||||
@@ -1549,7 +1553,7 @@ impl S3 for FS {
|
||||
let data = try_!(xml::serialize(&input_cfg));
|
||||
metadata_sys::update(&bucket, BUCKET_LIFECYCLE_CONFIG, data)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(PutBucketLifecycleConfigurationOutput::default()))
|
||||
}
|
||||
@@ -1568,11 +1572,11 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
metadata_sys::delete(&bucket, BUCKET_LIFECYCLE_CONFIG)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(DeleteBucketLifecycleOutput::default()))
|
||||
}
|
||||
@@ -1590,7 +1594,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let server_side_encryption_configuration = match metadata_sys::get_sse_config(&bucket).await {
|
||||
Ok((cfg, _)) => Some(cfg),
|
||||
@@ -1627,14 +1631,14 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// TODO: check kms
|
||||
|
||||
let data = try_!(xml::serialize(&server_side_encryption_configuration));
|
||||
metadata_sys::update(&bucket, BUCKET_SSECONFIG, data)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
Ok(S3Response::new(PutBucketEncryptionOutput::default()))
|
||||
}
|
||||
|
||||
@@ -1651,8 +1655,10 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
metadata_sys::delete(&bucket, BUCKET_SSECONFIG).await.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
metadata_sys::delete(&bucket, BUCKET_SSECONFIG)
|
||||
.await
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(DeleteBucketEncryptionOutput::default()))
|
||||
}
|
||||
@@ -1699,13 +1705,13 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let data = try_!(xml::serialize(&input_cfg));
|
||||
|
||||
metadata_sys::update(&bucket, OBJECT_LOCK_CONFIG, data)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(PutObjectLockConfigurationOutput::default()))
|
||||
}
|
||||
@@ -1723,7 +1729,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let replication_configuration = match metadata_sys::get_replication_config(&bucket).await {
|
||||
Ok((cfg, _created)) => Some(cfg),
|
||||
@@ -1755,14 +1761,14 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// TODO: check enable, versioning enable
|
||||
let data = try_!(xml::serialize(&replication_configuration));
|
||||
|
||||
metadata_sys::update(&bucket, BUCKET_REPLICATION_CONFIG, data)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
Ok(S3Response::new(PutBucketReplicationOutput::default()))
|
||||
}
|
||||
@@ -1780,10 +1786,10 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
metadata_sys::delete(&bucket, BUCKET_REPLICATION_CONFIG)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// TODO: remove targets
|
||||
|
||||
@@ -1803,7 +1809,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let has_notification_config = match metadata_sys::get_notification_config(&bucket).await {
|
||||
Ok(cfg) => cfg,
|
||||
@@ -1850,13 +1856,13 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let data = try_!(xml::serialize(¬ification_configuration));
|
||||
|
||||
metadata_sys::update(&bucket, BUCKET_NOTIFICATION_CONFIG, data)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// TODO: event notice add rule
|
||||
|
||||
@@ -1873,7 +1879,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let grants = vec![Grant {
|
||||
grantee: Some(Grantee {
|
||||
@@ -1909,7 +1915,7 @@ impl S3 for FS {
|
||||
store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
if let Some(canned_acl) = acl {
|
||||
if canned_acl.as_str() != BucketCannedACL::PRIVATE {
|
||||
@@ -2085,14 +2091,14 @@ impl S3 for FS {
|
||||
let _ = store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// check object lock
|
||||
let _ = metadata_sys::get_object_lock_config(&bucket).await.map_err(to_s3_error)?;
|
||||
let _ = metadata_sys::get_object_lock_config(&bucket).await.map_err(ApiError::from)?;
|
||||
|
||||
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, None, &req.headers)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let object_info = store.get_object_info(&bucket, &key, &opts).await.map_err(|e| {
|
||||
error!("get_object_info failed, {}", e.to_string());
|
||||
@@ -2135,14 +2141,14 @@ impl S3 for FS {
|
||||
let _ = store
|
||||
.get_bucket_info(&bucket, &BucketOptions::default())
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
// check object lock
|
||||
let _ = metadata_sys::get_object_lock_config(&bucket).await.map_err(to_s3_error)?;
|
||||
let _ = metadata_sys::get_object_lock_config(&bucket).await.map_err(ApiError::from)?;
|
||||
|
||||
let opts: ObjectOptions = get_opts(&bucket, &key, version_id, None, &req.headers)
|
||||
.await
|
||||
.map_err(to_s3_error)?;
|
||||
.map_err(ApiError::from)?;
|
||||
|
||||
let mut eval_metadata = HashMap::new();
|
||||
let legal_hold = legal_hold
|
||||
@@ -2176,9 +2182,9 @@ impl S3 for FS {
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn bytes_stream<S, E>(stream: S, content_length: usize) -> impl Stream<Item = Result<Bytes, E>> + Send + 'static
|
||||
pub fn bytes_stream<S, E>(stream: S, content_length: usize) -> impl Stream<Item = std::result::Result<Bytes, E>> + Send + 'static
|
||||
where
|
||||
S: Stream<Item = Result<Bytes, E>> + Send + 'static,
|
||||
S: Stream<Item = std::result::Result<Bytes, E>> + Send + 'static,
|
||||
E: Send + 'static,
|
||||
{
|
||||
AsyncTryStream::<Bytes, E, _>::new(|mut y| async move {
|
||||
|
||||
+485
-485
@@ -1,485 +1,485 @@
|
||||
use common::error::Error;
|
||||
use ecstore::error::StorageError;
|
||||
use s3s::{S3Error, S3ErrorCode, s3_error};
|
||||
pub fn to_s3_error(err: Error) -> S3Error {
|
||||
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
return match storage_err {
|
||||
StorageError::NotImplemented => s3_error!(NotImplemented),
|
||||
StorageError::InvalidArgument(bucket, object, version_id) => {
|
||||
s3_error!(InvalidArgument, "Invalid arguments provided for {}/{}-{}", bucket, object, version_id)
|
||||
}
|
||||
StorageError::MethodNotAllowed => s3_error!(MethodNotAllowed),
|
||||
StorageError::BucketNotFound(bucket) => {
|
||||
s3_error!(NoSuchBucket, "bucket not found {}", bucket)
|
||||
}
|
||||
StorageError::BucketNotEmpty(bucket) => s3_error!(BucketNotEmpty, "bucket not empty {}", bucket),
|
||||
StorageError::BucketNameInvalid(bucket) => s3_error!(InvalidBucketName, "invalid bucket name {}", bucket),
|
||||
StorageError::ObjectNameInvalid(bucket, object) => {
|
||||
s3_error!(InvalidArgument, "invalid object name {}/{}", bucket, object)
|
||||
}
|
||||
StorageError::BucketExists(bucket) => s3_error!(BucketAlreadyExists, "{}", bucket),
|
||||
StorageError::StorageFull => s3_error!(ServiceUnavailable, "Storage reached its minimum free drive threshold."),
|
||||
StorageError::SlowDown => s3_error!(SlowDown, "Please reduce your request rate"),
|
||||
StorageError::PrefixAccessDenied(bucket, object) => {
|
||||
s3_error!(AccessDenied, "PrefixAccessDenied {}/{}", bucket, object)
|
||||
}
|
||||
StorageError::InvalidUploadIDKeyCombination(bucket, object) => {
|
||||
s3_error!(InvalidArgument, "Invalid UploadID KeyCombination: {}/{}", bucket, object)
|
||||
}
|
||||
StorageError::MalformedUploadID(bucket) => s3_error!(InvalidArgument, "Malformed UploadID: {}", bucket),
|
||||
StorageError::ObjectNameTooLong(bucket, object) => {
|
||||
s3_error!(InvalidArgument, "Object name too long: {}/{}", bucket, object)
|
||||
}
|
||||
StorageError::ObjectNamePrefixAsSlash(bucket, object) => {
|
||||
s3_error!(InvalidArgument, "Object name contains forward slash as prefix: {}/{}", bucket, object)
|
||||
}
|
||||
StorageError::ObjectNotFound(bucket, object) => s3_error!(NoSuchKey, "{}/{}", bucket, object),
|
||||
StorageError::VersionNotFound(bucket, object, version_id) => {
|
||||
s3_error!(NoSuchVersion, "{}/{}/{}", bucket, object, version_id)
|
||||
}
|
||||
StorageError::InvalidUploadID(bucket, object, version_id) => {
|
||||
s3_error!(InvalidPart, "Invalid upload id: {}/{}-{}", bucket, object, version_id)
|
||||
}
|
||||
StorageError::InvalidVersionID(bucket, object, version_id) => {
|
||||
s3_error!(InvalidArgument, "Invalid version id: {}/{}-{}", bucket, object, version_id)
|
||||
}
|
||||
// extended
|
||||
StorageError::DataMovementOverwriteErr(bucket, object, version_id) => s3_error!(
|
||||
InvalidArgument,
|
||||
"invalid data movement operation, source and destination pool are the same for : {}/{}-{}",
|
||||
bucket,
|
||||
object,
|
||||
version_id
|
||||
),
|
||||
|
||||
// extended
|
||||
StorageError::ObjectExistsAsDirectory(bucket, object) => {
|
||||
s3_error!(InvalidArgument, "Object exists on :{} as directory {}", bucket, object)
|
||||
}
|
||||
StorageError::InvalidPart(bucket, object, version_id) => {
|
||||
s3_error!(
|
||||
InvalidPart,
|
||||
"Specified part could not be found. PartNumber {}, Expected {}, got {}",
|
||||
bucket,
|
||||
object,
|
||||
version_id
|
||||
)
|
||||
}
|
||||
StorageError::DoneForNow => s3_error!(InternalError, "DoneForNow"),
|
||||
};
|
||||
}
|
||||
|
||||
if is_err_file_not_found(&err) {
|
||||
return S3Error::with_message(S3ErrorCode::NoSuchKey, format!(" ec err {}", err));
|
||||
}
|
||||
|
||||
S3Error::with_message(S3ErrorCode::InternalError, format!(" ec err {}", err))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use s3s::S3ErrorCode;
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_not_implemented() {
|
||||
let storage_err = StorageError::NotImplemented;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NotImplemented);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_argument() {
|
||||
let storage_err =
|
||||
StorageError::InvalidArgument("test-bucket".to_string(), "test-object".to_string(), "test-version".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Invalid arguments provided"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("test-version"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_method_not_allowed() {
|
||||
let storage_err = StorageError::MethodNotAllowed;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::MethodNotAllowed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_bucket_not_found() {
|
||||
let storage_err = StorageError::BucketNotFound("test-bucket".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_bucket_not_empty() {
|
||||
let storage_err = StorageError::BucketNotEmpty("test-bucket".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::BucketNotEmpty);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not empty"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_bucket_name_invalid() {
|
||||
let storage_err = StorageError::BucketNameInvalid("invalid-bucket-name".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidBucketName);
|
||||
assert!(s3_err.message().unwrap().contains("invalid bucket name"));
|
||||
assert!(s3_err.message().unwrap().contains("invalid-bucket-name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_name_invalid() {
|
||||
let storage_err = StorageError::ObjectNameInvalid("test-bucket".to_string(), "invalid-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("invalid object name"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("invalid-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_bucket_exists() {
|
||||
let storage_err = StorageError::BucketExists("existing-bucket".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::BucketAlreadyExists);
|
||||
assert!(s3_err.message().unwrap().contains("existing-bucket"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_storage_full() {
|
||||
let storage_err = StorageError::StorageFull;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::ServiceUnavailable);
|
||||
assert!(
|
||||
s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage reached its minimum free drive threshold")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_slow_down() {
|
||||
let storage_err = StorageError::SlowDown;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
assert!(s3_err.message().unwrap().contains("Please reduce your request rate"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_prefix_access_denied() {
|
||||
let storage_err = StorageError::PrefixAccessDenied("test-bucket".to_string(), "test-prefix".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::AccessDenied);
|
||||
assert!(s3_err.message().unwrap().contains("PrefixAccessDenied"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-prefix"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_upload_id_key_combination() {
|
||||
let storage_err = StorageError::InvalidUploadIDKeyCombination("test-bucket".to_string(), "test-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Invalid UploadID KeyCombination"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_malformed_upload_id() {
|
||||
let storage_err = StorageError::MalformedUploadID("malformed-id".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Malformed UploadID"));
|
||||
assert!(s3_err.message().unwrap().contains("malformed-id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_name_too_long() {
|
||||
let storage_err = StorageError::ObjectNameTooLong("test-bucket".to_string(), "very-long-object-name".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Object name too long"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("very-long-object-name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_name_prefix_as_slash() {
|
||||
let storage_err = StorageError::ObjectNamePrefixAsSlash("test-bucket".to_string(), "/invalid-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(
|
||||
s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Object name contains forward slash as prefix")
|
||||
);
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("/invalid-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_not_found() {
|
||||
let storage_err = StorageError::ObjectNotFound("test-bucket".to_string(), "missing-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchKey);
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("missing-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_version_not_found() {
|
||||
let storage_err =
|
||||
StorageError::VersionNotFound("test-bucket".to_string(), "test-object".to_string(), "missing-version".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchVersion);
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("missing-version"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_upload_id() {
|
||||
let storage_err =
|
||||
StorageError::InvalidUploadID("test-bucket".to_string(), "test-object".to_string(), "invalid-upload-id".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidPart);
|
||||
assert!(s3_err.message().unwrap().contains("Invalid upload id"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("invalid-upload-id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_version_id() {
|
||||
let storage_err = StorageError::InvalidVersionID(
|
||||
"test-bucket".to_string(),
|
||||
"test-object".to_string(),
|
||||
"invalid-version-id".to_string(),
|
||||
);
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Invalid version id"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("invalid-version-id"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_data_movement_overwrite_err() {
|
||||
let storage_err = StorageError::DataMovementOverwriteErr(
|
||||
"test-bucket".to_string(),
|
||||
"test-object".to_string(),
|
||||
"test-version".to_string(),
|
||||
);
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("invalid data movement operation"));
|
||||
assert!(s3_err.message().unwrap().contains("source and destination pool are the same"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
assert!(s3_err.message().unwrap().contains("test-version"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_object_exists_as_directory() {
|
||||
let storage_err = StorageError::ObjectExistsAsDirectory("test-bucket".to_string(), "directory-object".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Object exists on"));
|
||||
assert!(s3_err.message().unwrap().contains("as directory"));
|
||||
assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
assert!(s3_err.message().unwrap().contains("directory-object"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_insufficient_read_quorum() {
|
||||
let storage_err = StorageError::InsufficientReadQuorum;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
assert!(
|
||||
s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage resources are insufficient for the read operation")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_insufficient_write_quorum() {
|
||||
let storage_err = StorageError::InsufficientWriteQuorum;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
assert!(
|
||||
s3_err
|
||||
.message()
|
||||
.unwrap()
|
||||
.contains("Storage resources are insufficient for the write operation")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_decommission_not_started() {
|
||||
let storage_err = StorageError::DecommissionNotStarted;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("Decommission Not Started"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_decommission_already_running() {
|
||||
let storage_err = StorageError::DecommissionAlreadyRunning;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
assert!(s3_err.message().unwrap().contains("Decommission already running"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_volume_not_found() {
|
||||
let storage_err = StorageError::VolumeNotFound("test-volume".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
assert!(s3_err.message().unwrap().contains("test-volume"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_invalid_part() {
|
||||
let storage_err = StorageError::InvalidPart(1, "expected-part".to_string(), "got-part".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidPart);
|
||||
assert!(s3_err.message().unwrap().contains("Specified part could not be found"));
|
||||
assert!(s3_err.message().unwrap().contains("PartNumber"));
|
||||
assert!(s3_err.message().unwrap().contains("expected-part"));
|
||||
assert!(s3_err.message().unwrap().contains("got-part"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_done_for_now() {
|
||||
let storage_err = StorageError::DoneForNow;
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
assert!(s3_err.message().unwrap().contains("DoneForNow"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_non_storage_error() {
|
||||
// Test with a non-StorageError
|
||||
let err = Error::from_string("Generic error message".to_string());
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
assert!(s3_err.message().unwrap().contains("ec err"));
|
||||
assert!(s3_err.message().unwrap().contains("Generic error message"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_with_unicode_strings() {
|
||||
let storage_err = StorageError::BucketNotFound("测试桶".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
assert!(s3_err.message().unwrap().contains("测试桶"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_with_special_characters() {
|
||||
let storage_err = StorageError::ObjectNameInvalid("bucket-with-@#$%".to_string(), "object-with-!@#$%^&*()".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
assert!(s3_err.message().unwrap().contains("invalid object name"));
|
||||
assert!(s3_err.message().unwrap().contains("bucket-with-@#$%"));
|
||||
assert!(s3_err.message().unwrap().contains("object-with-!@#$%^&*()"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_with_empty_strings() {
|
||||
let storage_err = StorageError::BucketNotFound("".to_string());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_to_s3_error_with_very_long_strings() {
|
||||
let long_bucket_name = "a".repeat(1000);
|
||||
let storage_err = StorageError::BucketNotFound(long_bucket_name.clone());
|
||||
let err = Error::new(storage_err);
|
||||
let s3_err = to_s3_error(err);
|
||||
|
||||
assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
assert!(s3_err.message().unwrap().contains(&long_bucket_name));
|
||||
}
|
||||
}
|
||||
// use common::error::Error;
|
||||
// use ecstore::error::StorageError;
|
||||
// use s3s::{S3Error, S3ErrorCode, s3_error};
|
||||
// pub fn to_s3_error(err: Error) -> S3Error {
|
||||
// if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
// return match storage_err {
|
||||
// StorageError::NotImplemented => s3_error!(NotImplemented),
|
||||
// StorageError::InvalidArgument(bucket, object, version_id) => {
|
||||
// s3_error!(InvalidArgument, "Invalid arguments provided for {}/{}-{}", bucket, object, version_id)
|
||||
// }
|
||||
// StorageError::MethodNotAllowed => s3_error!(MethodNotAllowed),
|
||||
// StorageError::BucketNotFound(bucket) => {
|
||||
// s3_error!(NoSuchBucket, "bucket not found {}", bucket)
|
||||
// }
|
||||
// StorageError::BucketNotEmpty(bucket) => s3_error!(BucketNotEmpty, "bucket not empty {}", bucket),
|
||||
// StorageError::BucketNameInvalid(bucket) => s3_error!(InvalidBucketName, "invalid bucket name {}", bucket),
|
||||
// StorageError::ObjectNameInvalid(bucket, object) => {
|
||||
// s3_error!(InvalidArgument, "invalid object name {}/{}", bucket, object)
|
||||
// }
|
||||
// StorageError::BucketExists(bucket) => s3_error!(BucketAlreadyExists, "{}", bucket),
|
||||
// StorageError::StorageFull => s3_error!(ServiceUnavailable, "Storage reached its minimum free drive threshold."),
|
||||
// StorageError::SlowDown => s3_error!(SlowDown, "Please reduce your request rate"),
|
||||
// StorageError::PrefixAccessDenied(bucket, object) => {
|
||||
// s3_error!(AccessDenied, "PrefixAccessDenied {}/{}", bucket, object)
|
||||
// }
|
||||
// StorageError::InvalidUploadIDKeyCombination(bucket, object) => {
|
||||
// s3_error!(InvalidArgument, "Invalid UploadID KeyCombination: {}/{}", bucket, object)
|
||||
// }
|
||||
// StorageError::MalformedUploadID(bucket) => s3_error!(InvalidArgument, "Malformed UploadID: {}", bucket),
|
||||
// StorageError::ObjectNameTooLong(bucket, object) => {
|
||||
// s3_error!(InvalidArgument, "Object name too long: {}/{}", bucket, object)
|
||||
// }
|
||||
// StorageError::ObjectNamePrefixAsSlash(bucket, object) => {
|
||||
// s3_error!(InvalidArgument, "Object name contains forward slash as prefix: {}/{}", bucket, object)
|
||||
// }
|
||||
// StorageError::ObjectNotFound(bucket, object) => s3_error!(NoSuchKey, "{}/{}", bucket, object),
|
||||
// StorageError::VersionNotFound(bucket, object, version_id) => {
|
||||
// s3_error!(NoSuchVersion, "{}/{}/{}", bucket, object, version_id)
|
||||
// }
|
||||
// StorageError::InvalidUploadID(bucket, object, version_id) => {
|
||||
// s3_error!(InvalidPart, "Invalid upload id: {}/{}-{}", bucket, object, version_id)
|
||||
// }
|
||||
// StorageError::InvalidVersionID(bucket, object, version_id) => {
|
||||
// s3_error!(InvalidArgument, "Invalid version id: {}/{}-{}", bucket, object, version_id)
|
||||
// }
|
||||
// // extended
|
||||
// StorageError::DataMovementOverwriteErr(bucket, object, version_id) => s3_error!(
|
||||
// InvalidArgument,
|
||||
// "invalid data movement operation, source and destination pool are the same for : {}/{}-{}",
|
||||
// bucket,
|
||||
// object,
|
||||
// version_id
|
||||
// ),
|
||||
|
||||
// // extended
|
||||
// StorageError::ObjectExistsAsDirectory(bucket, object) => {
|
||||
// s3_error!(InvalidArgument, "Object exists on :{} as directory {}", bucket, object)
|
||||
// }
|
||||
// StorageError::InvalidPart(bucket, object, version_id) => {
|
||||
// s3_error!(
|
||||
// InvalidPart,
|
||||
// "Specified part could not be found. PartNumber {}, Expected {}, got {}",
|
||||
// bucket,
|
||||
// object,
|
||||
// version_id
|
||||
// )
|
||||
// }
|
||||
// StorageError::DoneForNow => s3_error!(InternalError, "DoneForNow"),
|
||||
// };
|
||||
// }
|
||||
|
||||
// if is_err_file_not_found(&err) {
|
||||
// return S3Error::with_message(S3ErrorCode::NoSuchKey, format!(" ec err {}", err));
|
||||
// }
|
||||
|
||||
// S3Error::with_message(S3ErrorCode::InternalError, format!(" ec err {}", err))
|
||||
// }
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use s3s::S3ErrorCode;
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_not_implemented() {
|
||||
// let storage_err = StorageError::NotImplemented;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::NotImplemented);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_invalid_argument() {
|
||||
// let storage_err =
|
||||
// StorageError::InvalidArgument("test-bucket".to_string(), "test-object".to_string(), "test-version".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("Invalid arguments provided"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-version"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_method_not_allowed() {
|
||||
// let storage_err = StorageError::MethodNotAllowed;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::MethodNotAllowed);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_bucket_not_found() {
|
||||
// let storage_err = StorageError::BucketNotFound("test-bucket".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
// assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_bucket_not_empty() {
|
||||
// let storage_err = StorageError::BucketNotEmpty("test-bucket".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::BucketNotEmpty);
|
||||
// assert!(s3_err.message().unwrap().contains("bucket not empty"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_bucket_name_invalid() {
|
||||
// let storage_err = StorageError::BucketNameInvalid("invalid-bucket-name".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidBucketName);
|
||||
// assert!(s3_err.message().unwrap().contains("invalid bucket name"));
|
||||
// assert!(s3_err.message().unwrap().contains("invalid-bucket-name"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_object_name_invalid() {
|
||||
// let storage_err = StorageError::ObjectNameInvalid("test-bucket".to_string(), "invalid-object".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("invalid object name"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("invalid-object"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_bucket_exists() {
|
||||
// let storage_err = StorageError::BucketExists("existing-bucket".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::BucketAlreadyExists);
|
||||
// assert!(s3_err.message().unwrap().contains("existing-bucket"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_storage_full() {
|
||||
// let storage_err = StorageError::StorageFull;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::ServiceUnavailable);
|
||||
// assert!(
|
||||
// s3_err
|
||||
// .message()
|
||||
// .unwrap()
|
||||
// .contains("Storage reached its minimum free drive threshold")
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_slow_down() {
|
||||
// let storage_err = StorageError::SlowDown;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
// assert!(s3_err.message().unwrap().contains("Please reduce your request rate"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_prefix_access_denied() {
|
||||
// let storage_err = StorageError::PrefixAccessDenied("test-bucket".to_string(), "test-prefix".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::AccessDenied);
|
||||
// assert!(s3_err.message().unwrap().contains("PrefixAccessDenied"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-prefix"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_invalid_upload_id_key_combination() {
|
||||
// let storage_err = StorageError::InvalidUploadIDKeyCombination("test-bucket".to_string(), "test-object".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("Invalid UploadID KeyCombination"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_malformed_upload_id() {
|
||||
// let storage_err = StorageError::MalformedUploadID("malformed-id".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("Malformed UploadID"));
|
||||
// assert!(s3_err.message().unwrap().contains("malformed-id"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_object_name_too_long() {
|
||||
// let storage_err = StorageError::ObjectNameTooLong("test-bucket".to_string(), "very-long-object-name".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("Object name too long"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("very-long-object-name"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_object_name_prefix_as_slash() {
|
||||
// let storage_err = StorageError::ObjectNamePrefixAsSlash("test-bucket".to_string(), "/invalid-object".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(
|
||||
// s3_err
|
||||
// .message()
|
||||
// .unwrap()
|
||||
// .contains("Object name contains forward slash as prefix")
|
||||
// );
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("/invalid-object"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_object_not_found() {
|
||||
// let storage_err = StorageError::ObjectNotFound("test-bucket".to_string(), "missing-object".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchKey);
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("missing-object"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_version_not_found() {
|
||||
// let storage_err =
|
||||
// StorageError::VersionNotFound("test-bucket".to_string(), "test-object".to_string(), "missing-version".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchVersion);
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
// assert!(s3_err.message().unwrap().contains("missing-version"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_invalid_upload_id() {
|
||||
// let storage_err =
|
||||
// StorageError::InvalidUploadID("test-bucket".to_string(), "test-object".to_string(), "invalid-upload-id".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidPart);
|
||||
// assert!(s3_err.message().unwrap().contains("Invalid upload id"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
// assert!(s3_err.message().unwrap().contains("invalid-upload-id"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_invalid_version_id() {
|
||||
// let storage_err = StorageError::InvalidVersionID(
|
||||
// "test-bucket".to_string(),
|
||||
// "test-object".to_string(),
|
||||
// "invalid-version-id".to_string(),
|
||||
// );
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("Invalid version id"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
// assert!(s3_err.message().unwrap().contains("invalid-version-id"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_data_movement_overwrite_err() {
|
||||
// let storage_err = StorageError::DataMovementOverwriteErr(
|
||||
// "test-bucket".to_string(),
|
||||
// "test-object".to_string(),
|
||||
// "test-version".to_string(),
|
||||
// );
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("invalid data movement operation"));
|
||||
// assert!(s3_err.message().unwrap().contains("source and destination pool are the same"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-object"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-version"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_object_exists_as_directory() {
|
||||
// let storage_err = StorageError::ObjectExistsAsDirectory("test-bucket".to_string(), "directory-object".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("Object exists on"));
|
||||
// assert!(s3_err.message().unwrap().contains("as directory"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-bucket"));
|
||||
// assert!(s3_err.message().unwrap().contains("directory-object"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_insufficient_read_quorum() {
|
||||
// let storage_err = StorageError::InsufficientReadQuorum;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
// assert!(
|
||||
// s3_err
|
||||
// .message()
|
||||
// .unwrap()
|
||||
// .contains("Storage resources are insufficient for the read operation")
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_insufficient_write_quorum() {
|
||||
// let storage_err = StorageError::InsufficientWriteQuorum;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::SlowDown);
|
||||
// assert!(
|
||||
// s3_err
|
||||
// .message()
|
||||
// .unwrap()
|
||||
// .contains("Storage resources are insufficient for the write operation")
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_decommission_not_started() {
|
||||
// let storage_err = StorageError::DecommissionNotStarted;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("Decommission Not Started"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_decommission_already_running() {
|
||||
// let storage_err = StorageError::DecommissionAlreadyRunning;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
// assert!(s3_err.message().unwrap().contains("Decommission already running"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_volume_not_found() {
|
||||
// let storage_err = StorageError::VolumeNotFound("test-volume".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
// assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
// assert!(s3_err.message().unwrap().contains("test-volume"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_invalid_part() {
|
||||
// let storage_err = StorageError::InvalidPart(1, "expected-part".to_string(), "got-part".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidPart);
|
||||
// assert!(s3_err.message().unwrap().contains("Specified part could not be found"));
|
||||
// assert!(s3_err.message().unwrap().contains("PartNumber"));
|
||||
// assert!(s3_err.message().unwrap().contains("expected-part"));
|
||||
// assert!(s3_err.message().unwrap().contains("got-part"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_done_for_now() {
|
||||
// let storage_err = StorageError::DoneForNow;
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
// assert!(s3_err.message().unwrap().contains("DoneForNow"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_non_storage_error() {
|
||||
// // Test with a non-StorageError
|
||||
// let err = Error::from_string("Generic error message".to_string());
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InternalError);
|
||||
// assert!(s3_err.message().unwrap().contains("ec err"));
|
||||
// assert!(s3_err.message().unwrap().contains("Generic error message"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_with_unicode_strings() {
|
||||
// let storage_err = StorageError::BucketNotFound("测试桶".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
// assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
// assert!(s3_err.message().unwrap().contains("测试桶"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_with_special_characters() {
|
||||
// let storage_err = StorageError::ObjectNameInvalid("bucket-with-@#$%".to_string(), "object-with-!@#$%^&*()".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::InvalidArgument);
|
||||
// assert!(s3_err.message().unwrap().contains("invalid object name"));
|
||||
// assert!(s3_err.message().unwrap().contains("bucket-with-@#$%"));
|
||||
// assert!(s3_err.message().unwrap().contains("object-with-!@#$%^&*()"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_with_empty_strings() {
|
||||
// let storage_err = StorageError::BucketNotFound("".to_string());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
// assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn test_to_s3_error_with_very_long_strings() {
|
||||
// let long_bucket_name = "a".repeat(1000);
|
||||
// let storage_err = StorageError::BucketNotFound(long_bucket_name.clone());
|
||||
// let err = Error::new(storage_err);
|
||||
// let s3_err = to_s3_error(err);
|
||||
|
||||
// assert_eq!(*s3_err.code(), S3ErrorCode::NoSuchBucket);
|
||||
// assert!(s3_err.message().unwrap().contains("bucket not found"));
|
||||
// assert!(s3_err.message().unwrap().contains(&long_bucket_name));
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod access;
|
||||
pub mod ecfs;
|
||||
pub mod error;
|
||||
// pub mod error;
|
||||
mod event_notifier;
|
||||
pub mod options;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use common::error::{Error, Result};
|
||||
use ecstore::bucket::versioning_sys::BucketVersioningSys;
|
||||
use ecstore::store_api::ObjectOptions;
|
||||
use ecstore::error::Result;
|
||||
use ecstore::error::StorageError;
|
||||
use ecstore::store_api::ObjectOptions;
|
||||
use ecstore::utils::path::is_dir_object;
|
||||
use http::{HeaderMap, HeaderValue};
|
||||
use lazy_static::lazy_static;
|
||||
@@ -25,24 +25,16 @@ pub async fn del_opts(
|
||||
|
||||
if let Some(ref id) = vid {
|
||||
if let Err(_err) = Uuid::parse_str(id.as_str()) {
|
||||
return Err(Error::new(StorageError::InvalidVersionID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidVersionID(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
|
||||
if !versioned {
|
||||
return Err(Error::new(StorageError::InvalidArgument(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut opts = put_opts_from_headers(headers, metadata)
|
||||
.map_err(|err| Error::new(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string())))?;
|
||||
.map_err(|err| StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string()))?;
|
||||
|
||||
opts.version_id = {
|
||||
if is_dir_object(object) && vid.is_none() {
|
||||
@@ -72,24 +64,16 @@ pub async fn get_opts(
|
||||
|
||||
if let Some(ref id) = vid {
|
||||
if let Err(_err) = Uuid::parse_str(id.as_str()) {
|
||||
return Err(Error::new(StorageError::InvalidVersionID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidVersionID(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
|
||||
if !versioned {
|
||||
return Err(Error::new(StorageError::InvalidArgument(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut opts = get_default_opts(headers, None, false)
|
||||
.map_err(|err| Error::new(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string())))?;
|
||||
.map_err(|err| StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string()))?;
|
||||
|
||||
opts.version_id = {
|
||||
if is_dir_object(object) && vid.is_none() {
|
||||
@@ -122,24 +106,16 @@ pub async fn put_opts(
|
||||
|
||||
if let Some(ref id) = vid {
|
||||
if let Err(_err) = Uuid::parse_str(id.as_str()) {
|
||||
return Err(Error::new(StorageError::InvalidVersionID(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidVersionID(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
|
||||
if !versioned {
|
||||
return Err(Error::new(StorageError::InvalidArgument(
|
||||
bucket.to_owned(),
|
||||
object.to_owned(),
|
||||
id.clone(),
|
||||
)));
|
||||
return Err(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let mut opts = put_opts_from_headers(headers, metadata)
|
||||
.map_err(|err| Error::new(StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string())))?;
|
||||
.map_err(|err| StorageError::InvalidArgument(bucket.to_owned(), object.to_owned(), err.to_string()))?;
|
||||
|
||||
opts.version_id = {
|
||||
if is_dir_object(object) && vid.is_none() {
|
||||
@@ -317,15 +293,13 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(err) = result {
|
||||
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
match storage_err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
match err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,15 +347,13 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(err) = result {
|
||||
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
match storage_err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
match err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -419,15 +391,13 @@ mod tests {
|
||||
|
||||
assert!(result.is_err());
|
||||
if let Err(err) = result {
|
||||
if let Some(storage_err) = err.downcast_ref::<StorageError>() {
|
||||
match storage_err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
match err {
|
||||
StorageError::InvalidVersionID(bucket, object, version) => {
|
||||
assert_eq!(bucket, "test-bucket");
|
||||
assert_eq!(object, "test-object");
|
||||
assert_eq!(version, "invalid-uuid");
|
||||
}
|
||||
_ => panic!("Expected InvalidVersionID error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user