diff --git a/ecstore/src/disk/endpoint.rs b/ecstore/src/disk/endpoint.rs new file mode 100644 index 00000000..78ae38a2 --- /dev/null +++ b/ecstore/src/disk/endpoint.rs @@ -0,0 +1,381 @@ +use crate::error::{Error, Result}; +use crate::utils::net; +use path_absolutize::Absolutize; +use path_clean::PathClean; +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)] +pub struct Endpoint { + pub url: url::Url, + pub is_local: bool, + + pub pool_idx: Option, + pub set_idx: Option, + pub disk_idx: Option, +} + +impl Display for Endpoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.url.scheme() == "file" { + write!(f, "{}", self.url.path()) + } else { + write!(f, "{}", self.url) + } + } +} + +impl TryFrom<&str> for Endpoint { + /// The type returned in the event of a conversion error. + type Error = Error; + + /// Performs the conversion. + fn try_from(value: &str) -> Result { + /// check whether given path is not empty. + fn is_empty_path(path: impl AsRef) -> bool { + ["", "/", "\\"].iter().any(|&v| Path::new(v).eq(path.as_ref())) + } + + if is_empty_path(value) { + return Err(Error::from_string("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(Error::from_string("invalid URL endpoint format")); + } + + let path = Path::new(url.path()).clean(); + if is_empty_path(&path) { + return Err(Error::from_string("empty or root path is not supported in URL endpoint")); + } + + if let Some(v) = path.to_str() { + url.set_path(v) + } + + // On windows having a preceding SlashSeparator will cause problems, if the + // command line already has C:/ { + // like d:/foo + is_local = true; + url_parse_from_file_path(value)? + } + Err(e) => match e { + ParseError::InvalidPort => { + return Err(Error::from_string("invalid URL endpoint format: port number must be between 1 to 65535")) + } + ParseError::EmptyHost => return Err(Error::from_string("invalid URL endpoint format: empty host name")), + ParseError::RelativeUrlWithoutBase => { + // like /foo + is_local = true; + url_parse_from_file_path(value)? + } + _ => return Err(Error::from_string(format!("invalid URL endpoint format: {}", e))), + }, + }; + + Ok(Endpoint { + url, + is_local, + pool_idx: None, + set_idx: None, + disk_idx: None, + }) + } +} + +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 = Some(idx) + } + + /// sets a specific set number to this node + pub fn set_set_index(&mut self, idx: usize) { + self.set_idx = Some(idx) + } + + /// sets a specific disk number to this node + pub fn set_disk_index(&mut self, idx: usize) { + self.disk_idx = Some(idx) + } + + /// resolves the host and updates if it is local or not. + pub fn update_is_local(&mut self, local_port: u16) -> Result<()> { + match (self.url.scheme(), self.url.host()) { + (v, Some(host)) if v != "file" => { + self.is_local = net::is_local_host(host, self.url.port().unwrap_or_default(), local_port)?; + } + _ => {} + } + + 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(), + } + } +} + +/// parse a file path into an URL. +fn url_parse_from_file_path(value: &str) -> Result { + // 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 net::is_socket_addr(addr[0]) { + return Err(Error::from_string("invalid URL endpoint format: missing scheme http or https")); + } + + let file_path = match Path::new(value).absolutize() { + Ok(path) => path, + Err(err) => return Err(Error::from_string(format!("absolute path failed: {}", err))), + }; + + match Url::from_file_path(file_path) { + Ok(url) => Ok(url), + Err(_) => Err(Error::from_string("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, + expected_type: Option, + expected_err: Option, + } + + 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: None, + set_idx: None, + disk_idx: None, + }), + 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: None, + set_idx: None, + disk_idx: None, + }), + 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: None, + set_idx: None, + disk_idx: None, + }), + expected_type: Some(EndpointType::Url), + expected_err: None, + }, + TestCase { + arg: "", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("empty or root endpoint is not supported")), + }, + TestCase { + arg: "/", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("empty or root endpoint is not supported")), + }, + TestCase { + arg: "\\", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("empty or root endpoint is not supported")), + }, + TestCase { + arg: "c://foo", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("invalid URL endpoint format")), + }, + TestCase { + arg: "ftp://foo", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("invalid URL endpoint format")), + }, + TestCase { + arg: "http://server/path?location", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("invalid URL endpoint format")), + }, + TestCase { + arg: "http://:/path", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("invalid URL endpoint format: empty host name")), + }, + TestCase { + arg: "http://:8080/path", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("invalid URL endpoint format: empty host name")), + }, + TestCase { + arg: "http://server:/path", + expected_endpoint: Some(Endpoint { + url: u6, + is_local: false, + pool_idx: None, + set_idx: None, + disk_idx: None, + }), + 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(Error::from_string("invalid URL endpoint format: port number must be between 1 to 65535")), + }, + TestCase { + arg: "http://server:8080//", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("empty or root path is not supported in URL endpoint")), + }, + TestCase { + arg: "http://server:8080/", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("empty or root path is not supported in URL endpoint")), + }, + TestCase { + arg: "192.168.1.210:9000", + expected_endpoint: None, + expected_type: None, + expected_err: Some(Error::from_string("invalid URL endpoint format: missing scheme http or https")), + }, + ]; + + 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 = , got = {:?}", test_case.arg, ret); + } + if test_case.expected_err.is_some() && ret.is_ok() { + panic!("{}: error: expected = {:?}, got = ", test_case.arg, test_case.expected_err); + } + match (test_case.expected_err, ret) { + (None, Err(e)) => panic!("{}: error: expected = , 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 = ", test_case.arg, e), + (Some(e), Err(e2)) => { + assert_eq!(e.to_string(), e2.to_string(), "{}: error: expected = {}, got = {}", test_case.arg, e, e2) + } + } + } + } +} diff --git a/ecstore/src/disk/local.rs b/ecstore/src/disk/local.rs index 78becf1a..269831b5 100644 --- a/ecstore/src/disk/local.rs +++ b/ecstore/src/disk/local.rs @@ -1,4 +1,4 @@ -use super::{error::DiskError, format::FormatV3}; +use super::{endpoint::Endpoint, error::DiskError, format::FormatV3}; use super::{ DeleteOptions, DiskAPI, FileReader, FileWriter, ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp, VolumeInfo, }; @@ -17,7 +17,7 @@ use tracing::{debug, warn}; use uuid::Uuid; use crate::{ - endpoint::{Endpoint, Endpoints}, + endpoints::Endpoints, error::{Error, Result}, file_meta::FileMeta, store_api::{FileInfo, RawFileInfo}, diff --git a/ecstore/src/disk/mod.rs b/ecstore/src/disk/mod.rs index 513ee824..31bf4872 100644 --- a/ecstore/src/disk/mod.rs +++ b/ecstore/src/disk/mod.rs @@ -1,3 +1,4 @@ +pub mod endpoint; pub mod error; pub mod format; mod local; diff --git a/ecstore/src/disks_layout.rs b/ecstore/src/disks_layout.rs index 359e3304..b83c41e8 100644 --- a/ecstore/src/disks_layout.rs +++ b/ecstore/src/disks_layout.rs @@ -1,4 +1,4 @@ -use super::error::{Error, Result}; +use crate::error::{Error, Result}; use crate::utils::ellipses::*; use serde::Deserialize; use std::collections::HashSet; @@ -13,51 +13,31 @@ pub struct PoolDisksLayout { layout: Vec>, } -impl AsRef>> for PoolDisksLayout { - fn as_ref(&self) -> &Vec> { - &self.layout - } -} - -impl AsMut>> for PoolDisksLayout { - fn as_mut(&mut self) -> &mut Vec> { - &mut self.layout - } -} - impl PoolDisksLayout { - pub fn new(args: impl Into, layout: Vec>) -> Self { + fn new(args: impl Into, layout: Vec>) -> Self { PoolDisksLayout { cmd_line: args.into(), layout, } } - pub fn count(&self) -> usize { + fn count(&self) -> usize { self.layout.len() } - pub fn get_cmd_line(&self) -> &str { + fn get_cmd_line(&self) -> &str { &self.cmd_line } + + pub fn iter(&self) -> impl Iterator> { + self.layout.iter() + } } #[derive(Deserialize, Debug, Default)] pub struct DisksLayout { pub legacy: bool, - pools: Vec, -} - -impl AsRef> for DisksLayout { - fn as_ref(&self) -> &Vec { - &self.pools - } -} - -impl AsMut> for DisksLayout { - fn as_mut(&mut self) -> &mut Vec { - &mut self.pools - } + pub pools: Vec, } impl> TryFrom<&[T]> for DisksLayout { @@ -119,8 +99,19 @@ impl DisksLayout { &self.pools[0].layout[0][0] } - pub fn get_layout(&self, idx: usize) -> Option<&PoolDisksLayout> { - self.pools.get(idx) + /// returns the total number of sets in the layout. + pub fn get_set_count(&self, i: usize) -> usize { + self.pools.get(i).map_or(0, |v| v.count()) + } + + /// returns the total number of drives in the layout. + pub fn get_drives_per_set(&self, i: usize) -> usize { + self.pools.get(i).map_or(0, |v| v.layout.first().map_or(0, |v| v.len())) + } + + /// returns the command line for the given index. + pub fn get_cmd_line(&self, i: usize) -> String { + self.pools.get(i).map_or(String::new(), |v| v.get_cmd_line().to_owned()) } } @@ -162,10 +153,10 @@ fn get_all_sets>(is_ellipses: bool, args: &[T]) -> Result, - pub endpoints: Vec, - pub set_indexes: Vec>, +struct EndpointSet { + _arg_patterns: Vec, + endpoints: Vec, + set_indexes: Vec>, } impl> TryFrom<&[T]> for EndpointSet { @@ -357,7 +348,6 @@ fn get_total_sizes(arg_patterns: &[ArgPattern]) -> Vec { mod test { use super::*; - use crate::utils::ellipses; impl PartialEq for EndpointSet { fn eq(&self, other: &Self) -> bool { @@ -532,7 +522,7 @@ mod test { for test_case in test_cases { let mut arg_patterns = Vec::new(); for v in test_case.args.iter() { - match ellipses::find_ellipses_patterns(v) { + match find_ellipses_patterns(v) { Ok(patterns) => { arg_patterns.push(patterns); } diff --git a/ecstore/src/endpoint.rs b/ecstore/src/endpoints.rs similarity index 75% rename from ecstore/src/endpoint.rs rename to ecstore/src/endpoints.rs index 26429060..6f781704 100644 --- a/ecstore/src/endpoint.rs +++ b/ecstore/src/endpoints.rs @@ -1,26 +1,13 @@ -use super::disks_layout::DisksLayout; -use super::error::{Error, Result}; -use super::utils::net; -use path_absolutize::Absolutize; -use path_clean::PathClean; -use std::collections::HashSet; -use std::fmt::Display; -use std::net::IpAddr; -use std::{ - collections::{hash_map::Entry, HashMap}, - path::Path, +use crate::{ + disk::endpoint::{Endpoint, EndpointType}, + disks_layout::DisksLayout, + error::{Error, Result}, + utils::net, +}; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + net::IpAddr, }; -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, -} /// enum for setup type. #[derive(PartialEq, Eq, Debug)] @@ -50,175 +37,6 @@ pub struct Node { pub grid_host: String, } -/// any type of endpoint. -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Endpoint { - pub url: url::Url, - pub is_local: bool, - - pub pool_idx: Option, - pub set_idx: Option, - pub disk_idx: Option, -} - -impl Display for Endpoint { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.url.scheme() == "file" { - write!(f, "{}", self.url.path()) - } else { - write!(f, "{}", self.url) - } - } -} - -impl TryFrom<&str> for Endpoint { - /// The type returned in the event of a conversion error. - type Error = Error; - - /// Performs the conversion. - fn try_from(value: &str) -> Result { - /// check whether given path is not empty. - fn is_empty_path(path: impl AsRef) -> bool { - ["", "/", "\\"].iter().any(|&v| Path::new(v).eq(path.as_ref())) - } - - if is_empty_path(value) { - return Err(Error::from_string("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(Error::from_string("invalid URL endpoint format")); - } - - let path = Path::new(url.path()).clean(); - if is_empty_path(&path) { - return Err(Error::from_string("empty or root path is not supported in URL endpoint")); - } - - if let Some(v) = path.to_str() { - url.set_path(v) - } - - // On windows having a preceding SlashSeparator will cause problems, if the - // command line already has C:/ { - // like d:/foo - is_local = true; - url_parse_from_file_path(value)? - } - Err(e) => match e { - ParseError::InvalidPort => { - return Err(Error::from_string("invalid URL endpoint format: port number must be between 1 to 65535")) - } - ParseError::EmptyHost => return Err(Error::from_string("invalid URL endpoint format: empty host name")), - ParseError::RelativeUrlWithoutBase => { - // like /foo - is_local = true; - url_parse_from_file_path(value)? - } - _ => return Err(Error::from_string(format!("invalid URL endpoint format: {}", e))), - }, - }; - - Ok(Endpoint { - url, - is_local, - pool_idx: None, - set_idx: None, - disk_idx: None, - }) - } -} - -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 = Some(idx) - } - - /// sets a specific set number to this node - pub fn set_set_index(&mut self, idx: usize) { - self.set_idx = Some(idx) - } - - /// sets a specific disk number to this node - pub fn set_disk_index(&mut self, idx: usize) { - self.disk_idx = Some(idx) - } - - /// resolves the host and updates if it is local or not. - fn update_is_local(&mut self, local_port: u16) -> Result<()> { - match (self.url.scheme(), self.url.host()) { - (v, Some(host)) if v != "file" => { - self.is_local = net::is_local_host(host, self.url.port().unwrap_or_default(), local_port)?; - } - _ => {} - } - - Ok(()) - } - - /// returns the host to be used for grid connections. - 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(), - } - } - - 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(), - } - } -} - /// list of same type of endpoint. #[derive(Debug, Default, Clone)] pub struct Endpoints(Vec); @@ -241,11 +59,11 @@ impl From> for Endpoints { } } -impl TryFrom<&[String]> for Endpoints { +impl> TryFrom<&[T]> for Endpoints { type Error = Error; /// returns new endpoint list based on input args. - fn try_from(args: &[String]) -> Result { + fn try_from(args: &[T]) -> Result { let mut endpoint_type = None; let mut schema = None; let mut endpoints = Vec::with_capacity(args.len()); @@ -253,9 +71,9 @@ impl TryFrom<&[String]> for Endpoints { // Loop through args and adds to endpoint list. for (i, arg) in args.iter().enumerate() { - let endpoint = match Endpoint::try_from(arg.as_str()) { + let endpoint = match Endpoint::try_from(arg.as_ref()) { Ok(ep) => ep, - Err(e) => return Err(Error::from_string(format!("'{}': {}", arg, e))), + Err(e) => return Err(Error::from_string(format!("'{}': {}", arg.as_ref(), e))), }; // All endpoints have to be same type and scheme if applicable. @@ -282,8 +100,19 @@ impl TryFrom<&[String]> for Endpoints { } } +impl Endpoints { + /// Converts `self` into its inner representation. + /// + /// This method consumes the `self` object and returns its inner `Vec`. + /// It is useful for when you need to take the endpoints out of their container + /// without needing a reference to the container itself. + pub fn into_inner(self) -> Vec { + self.0 + } +} + /// a temporary type to holds the list of endpoints -pub struct PoolEndpointList { +struct PoolEndpointList { inner: Vec, setup_type: SetupType, } @@ -303,7 +132,7 @@ impl AsMut> for PoolEndpointList { impl PoolEndpointList { /// creates a list of endpoints per pool, resolves their relevant /// hostnames and discovers those are local or remote. - pub fn create_pool_endpoints(server_addr: &str, disks_layout: &DisksLayout) -> Result { + fn create_pool_endpoints(server_addr: &str, disks_layout: &DisksLayout) -> Result { if disks_layout.is_empty_layout() { return Err(Error::from_string("invalid number of endpoints")); } @@ -331,10 +160,10 @@ impl PoolEndpointList { }); } - let mut pool_endpoints = Vec::::with_capacity(disks_layout.as_ref().len()); - for (pool_idx, pool) in disks_layout.as_ref().iter().enumerate() { + let mut pool_endpoints = Vec::::with_capacity(disks_layout.pools.len()); + for (pool_idx, pool) in disks_layout.pools.iter().enumerate() { let mut endpoints = Endpoints::default(); - for (set_idx, set_layout) in pool.as_ref().iter().enumerate() { + for (set_idx, set_layout) in pool.iter().enumerate() { // Convert args to endpoints let mut eps = Endpoints::try_from(set_layout.as_slice())?; @@ -582,7 +411,7 @@ impl EndpointServerPools { /// validates and creates new endpoints from input args, supports /// both ellipses and without ellipses transparently. pub fn create_server_endpoints(server_addr: &str, disks_layout: &DisksLayout) -> Result<(EndpointServerPools, SetupType)> { - if disks_layout.as_ref().is_empty() { + if disks_layout.pools.is_empty() { return Err(Error::from_string("Invalid arguments specified")); } @@ -590,17 +419,12 @@ impl EndpointServerPools { let mut ret: EndpointServerPools = Vec::with_capacity(pool_eps.as_ref().len()).into(); for (i, eps) in pool_eps.inner.into_iter().enumerate() { - let layout = disks_layout.get_layout(i); - let set_count = layout.map_or(0, |v| v.count()); - let drives_per_set = layout.map_or(0, |v| v.as_ref().first().map_or(0, |v| v.len())); - let cmd_line = layout.map_or(String::new(), |v| v.get_cmd_line().to_owned()); - let ep = PoolEndpoints { legacy: disks_layout.legacy, - set_count, - drives_per_set, + set_count: disks_layout.get_set_count(i), + drives_per_set: disks_layout.get_drives_per_set(i), endpoints: eps, - cmd_line, + cmd_line: disks_layout.get_cmd_line(i), platform: format!("OS: {} | Arch: {}", std::env::consts::OS, std::env::consts::ARCH), }; @@ -667,200 +491,11 @@ impl EndpointServerPools { } } -/// parse a file path into an URL. -fn url_parse_from_file_path(value: &str) -> Result { - // 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 net::is_socket_addr(addr[0]) { - return Err(Error::from_string("invalid URL endpoint format: missing scheme http or https")); - } - - let file_path = match Path::new(value).absolutize() { - Ok(path) => path, - Err(err) => return Err(Error::from_string(format!("absolute path failed: {}", err))), - }; - - match Url::from_file_path(file_path) { - Ok(url) => Ok(url), - Err(_) => Err(Error::from_string("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, - expected_type: Option, - expected_err: Option, - } - - 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: None, - set_idx: None, - disk_idx: None, - }), - 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: None, - set_idx: None, - disk_idx: None, - }), - 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: None, - set_idx: None, - disk_idx: None, - }), - expected_type: Some(EndpointType::Url), - expected_err: None, - }, - TestCase { - arg: "", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("empty or root endpoint is not supported")), - }, - TestCase { - arg: "/", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("empty or root endpoint is not supported")), - }, - TestCase { - arg: "\\", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("empty or root endpoint is not supported")), - }, - TestCase { - arg: "c://foo", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("invalid URL endpoint format")), - }, - TestCase { - arg: "ftp://foo", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("invalid URL endpoint format")), - }, - TestCase { - arg: "http://server/path?location", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("invalid URL endpoint format")), - }, - TestCase { - arg: "http://:/path", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("invalid URL endpoint format: empty host name")), - }, - TestCase { - arg: "http://:8080/path", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("invalid URL endpoint format: empty host name")), - }, - TestCase { - arg: "http://server:/path", - expected_endpoint: Some(Endpoint { - url: u6, - is_local: false, - pool_idx: None, - set_idx: None, - disk_idx: None, - }), - 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(Error::from_string("invalid URL endpoint format: port number must be between 1 to 65535")), - }, - TestCase { - arg: "http://server:8080//", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("empty or root path is not supported in URL endpoint")), - }, - TestCase { - arg: "http://server:8080/", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("empty or root path is not supported in URL endpoint")), - }, - TestCase { - arg: "192.168.1.210:9000", - expected_endpoint: None, - expected_type: None, - expected_err: Some(Error::from_string("invalid URL endpoint format: missing scheme http or https")), - }, - ]; - - 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 = , got = {:?}", test_case.arg, ret); - } - if test_case.expected_err.is_some() && ret.is_ok() { - panic!("{}: error: expected = {:?}, got = ", test_case.arg, test_case.expected_err); - } - match (test_case.expected_err, ret) { - (None, Err(e)) => panic!("{}: error: expected = , 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 = ", test_case.arg, e), - (Some(e), Err(e2)) => { - assert_eq!(e.to_string(), e2.to_string(), "{}: error: expected = {}, got = {}", test_case.arg, e, e2) - } - } - } - } + use std::path::Path; #[test] fn test_new_endpoints() { diff --git a/ecstore/src/lib.rs b/ecstore/src/lib.rs index 13a05a47..1371d1f1 100644 --- a/ecstore/src/lib.rs +++ b/ecstore/src/lib.rs @@ -2,7 +2,7 @@ mod bucket_meta; mod chunk_stream; pub mod disk; mod disks_layout; -mod endpoint; +mod endpoints; mod erasure; pub mod error; mod file_meta; diff --git a/ecstore/src/peer.rs b/ecstore/src/peer.rs index 65439c8c..4d2e582d 100644 --- a/ecstore/src/peer.rs +++ b/ecstore/src/peer.rs @@ -6,7 +6,7 @@ use tracing::warn; use crate::{ disk::VolumeInfo, disk::{error::DiskError, DiskStore}, - endpoint::{EndpointServerPools, Node}, + endpoints::{EndpointServerPools, Node}, error::{Error, Result}, store_api::{BucketInfo, BucketOptions, MakeBucketOptions}, }; diff --git a/ecstore/src/sets.rs b/ecstore/src/sets.rs index d3aa9301..bf854623 100644 --- a/ecstore/src/sets.rs +++ b/ecstore/src/sets.rs @@ -4,7 +4,7 @@ use uuid::Uuid; use crate::{ disk::format::{DistributionAlgoVersion, FormatV3}, disk::DiskStore, - endpoint::PoolEndpoints, + endpoints::PoolEndpoints, error::Result, set_disk::SetDisks, store_api::{ diff --git a/ecstore/src/store.rs b/ecstore/src/store.rs index d1e6eb6b..cb991550 100644 --- a/ecstore/src/store.rs +++ b/ecstore/src/store.rs @@ -3,7 +3,7 @@ use crate::{ disk::error::DiskError, disk::{self, DiskOption, DiskStore, RUSTFS_META_BUCKET}, disks_layout::DisksLayout, - endpoint::EndpointServerPools, + endpoints::EndpointServerPools, error::{Error, Result}, peer::{PeerS3Client, S3PeerSys}, sets::Sets,