diff --git a/Cargo.lock b/Cargo.lock index ac19a510d..5b1c17ecc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -276,6 +276,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -327,6 +336,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -473,16 +488,21 @@ dependencies = [ "async-trait", "backon", "base64-simd", + "blake2", "byteorder", "bytes", "common", "crc32fast", "futures", + "glob", "hex-simd", + "highway", "http", "lazy_static", "lock", "netif", + "nix", + "num", "num_cpus", "openssl", "path-absolutize", @@ -498,6 +518,7 @@ dependencies = [ "serde_json", "sha2", "siphasher", + "tempfile", "thiserror", "time", "tokio", @@ -510,6 +531,7 @@ dependencies = [ "transform-stream", "url", "uuid", + "winapi", "xxhash-rust", ] @@ -713,6 +735,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "gloo-timers" version = "0.3.0" @@ -781,6 +809,12 @@ dependencies = [ "vsimd", ] +[[package]] +name = "highway" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c706f1711006204c2ba8fb1a7bd55f689bbf7feca9ff40325206b5e140cff6df" + [[package]] name = "hmac" version = "0.12.1" @@ -1098,6 +1132,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -1127,12 +1173,76 @@ dependencies = [ "simdutf8", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" diff --git a/Cargo.toml b/Cargo.toml index 2a50af2f9..c81b87305 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ s3s = { git = "https://github.com/Nugine/s3s.git", rev = "0caf79822ae1f2e7a7fad3 s3s-policy = { git = "https://github.com/Nugine/s3s.git", rev = "0caf79822ae1f2e7a7fad3c4a093c94dd2e2c33f" } serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" +tempfile = "3.13.0" thiserror = "1.0.64" time = { version = "0.3.36", features = [ "std", diff --git a/common/protos/src/generated/flatbuffers_generated/models.rs b/common/protos/src/generated/flatbuffers_generated/models.rs index e4949fdcf..aa1f6ae2f 100644 --- a/common/protos/src/generated/flatbuffers_generated/models.rs +++ b/common/protos/src/generated/flatbuffers_generated/models.rs @@ -1,9 +1,10 @@ // automatically generated by the FlatBuffers compiler, do not modify + // @generated -use core::cmp::Ordering; use core::mem; +use core::cmp::Ordering; extern crate flatbuffers; use self::flatbuffers::{EndianScalar, Follow}; @@ -11,114 +12,112 @@ use self::flatbuffers::{EndianScalar, Follow}; #[allow(unused_imports, dead_code)] pub mod models { - use core::cmp::Ordering; - use core::mem; + use core::mem; + use core::cmp::Ordering; - extern crate flatbuffers; - use self::flatbuffers::{EndianScalar, Follow}; + extern crate flatbuffers; + use self::flatbuffers::{EndianScalar, Follow}; - pub enum PingBodyOffset {} - #[derive(Copy, Clone, PartialEq)] +pub enum PingBodyOffset {} +#[derive(Copy, Clone, PartialEq)] - pub struct PingBody<'a> { - pub _tab: flatbuffers::Table<'a>, +pub struct PingBody<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for PingBody<'a> { + type Inner = PingBody<'a>; + #[inline] + unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table::new(buf, loc) } + } +} + +impl<'a> PingBody<'a> { + pub const VT_PAYLOAD: flatbuffers::VOffsetT = 4; + + pub const fn get_fully_qualified_name() -> &'static str { + "models.PingBody" + } + + #[inline] + pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + PingBody { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, + args: &'args PingBodyArgs<'args> + ) -> flatbuffers::WIPOffset> { + let mut builder = PingBodyBuilder::new(_fbb); + if let Some(x) = args.payload { builder.add_payload(x); } + builder.finish() + } + + + #[inline] + pub fn payload(&self) -> Option> { + // Safety: + // Created from valid Table for this object + // which contains a valid value in this slot + unsafe { self._tab.get::>>(PingBody::VT_PAYLOAD, None)} + } +} + +impl flatbuffers::Verifiable for PingBody<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::>>("payload", Self::VT_PAYLOAD, false)? + .finish(); + Ok(()) + } +} +pub struct PingBodyArgs<'a> { + pub payload: Option>>, +} +impl<'a> Default for PingBodyArgs<'a> { + #[inline] + fn default() -> Self { + PingBodyArgs { + payload: None, } + } +} - impl<'a> flatbuffers::Follow<'a> for PingBody<'a> { - type Inner = PingBody<'a>; - #[inline] - unsafe fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { - Self { - _tab: flatbuffers::Table::new(buf, loc), - } - } +pub struct PingBodyBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> PingBodyBuilder<'a, 'b, A> { + #[inline] + pub fn add_payload(&mut self, payload: flatbuffers::WIPOffset>) { + self.fbb_.push_slot_always::>(PingBody::VT_PAYLOAD, payload); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> PingBodyBuilder<'a, 'b, A> { + let start = _fbb.start_table(); + PingBodyBuilder { + fbb_: _fbb, + start_: start, } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} - impl<'a> PingBody<'a> { - pub const VT_PAYLOAD: flatbuffers::VOffsetT = 4; +impl core::fmt::Debug for PingBody<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("PingBody"); + ds.field("payload", &self.payload()); + ds.finish() + } +} +} // pub mod models - pub const fn get_fully_qualified_name() -> &'static str { - "models.PingBody" - } - - #[inline] - pub unsafe fn init_from_table(table: flatbuffers::Table<'a>) -> Self { - PingBody { _tab: table } - } - #[allow(unused_mut)] - pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr, A: flatbuffers::Allocator + 'bldr>( - _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr, A>, - args: &'args PingBodyArgs<'args>, - ) -> flatbuffers::WIPOffset> { - let mut builder = PingBodyBuilder::new(_fbb); - if let Some(x) = args.payload { - builder.add_payload(x); - } - builder.finish() - } - - #[inline] - pub fn payload(&self) -> Option> { - // Safety: - // Created from valid Table for this object - // which contains a valid value in this slot - unsafe { - self._tab - .get::>>(PingBody::VT_PAYLOAD, None) - } - } - } - - impl flatbuffers::Verifiable for PingBody<'_> { - #[inline] - fn run_verifier(v: &mut flatbuffers::Verifier, pos: usize) -> Result<(), flatbuffers::InvalidFlatbuffer> { - use self::flatbuffers::Verifiable; - v.visit_table(pos)? - .visit_field::>>("payload", Self::VT_PAYLOAD, false)? - .finish(); - Ok(()) - } - } - pub struct PingBodyArgs<'a> { - pub payload: Option>>, - } - impl<'a> Default for PingBodyArgs<'a> { - #[inline] - fn default() -> Self { - PingBodyArgs { payload: None } - } - } - - pub struct PingBodyBuilder<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> { - fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a, A>, - start_: flatbuffers::WIPOffset, - } - impl<'a: 'b, 'b, A: flatbuffers::Allocator + 'a> PingBodyBuilder<'a, 'b, A> { - #[inline] - pub fn add_payload(&mut self, payload: flatbuffers::WIPOffset>) { - self.fbb_ - .push_slot_always::>(PingBody::VT_PAYLOAD, payload); - } - #[inline] - pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a, A>) -> PingBodyBuilder<'a, 'b, A> { - let start = _fbb.start_table(); - PingBodyBuilder { - fbb_: _fbb, - start_: start, - } - } - #[inline] - pub fn finish(self) -> flatbuffers::WIPOffset> { - let o = self.fbb_.end_table(self.start_); - flatbuffers::WIPOffset::new(o.value()) - } - } - - impl core::fmt::Debug for PingBody<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - let mut ds = f.debug_struct("PingBody"); - ds.field("payload", &self.payload()); - ds.finish() - } - } -} // pub mod models diff --git a/common/protos/src/generated/proto_gen/node_service.rs b/common/protos/src/generated/proto_gen/node_service.rs index c31199d6f..9d13aa58b 100644 --- a/common/protos/src/generated/proto_gen/node_service.rs +++ b/common/protos/src/generated/proto_gen/node_service.rs @@ -128,6 +128,48 @@ pub struct DeleteResponse { pub error_info: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifyFileRequest { + /// indicate which one in the disks + #[prost(string, tag = "1")] + pub disk: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub volume: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub file_info: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct VerifyFileResponse { + #[prost(bool, tag = "1")] + pub success: bool, + #[prost(string, tag = "2")] + pub check_parts_resp: ::prost::alloc::string::String, + #[prost(string, optional, tag = "3")] + pub error_info: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CheckPartsRequest { + /// indicate which one in the disks + #[prost(string, tag = "1")] + pub disk: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub volume: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub file_info: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CheckPartsResponse { + #[prost(bool, tag = "1")] + pub success: bool, + #[prost(string, tag = "2")] + pub check_parts_resp: ::prost::alloc::string::String, + #[prost(string, optional, tag = "3")] + pub error_info: ::core::option::Option<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct RenamePartRequst { #[prost(string, tag = "1")] pub disk: ::prost::alloc::string::String, @@ -507,6 +549,22 @@ pub struct DeleteVolumeResponse { #[prost(string, optional, tag = "2")] pub error_info: ::core::option::Option<::prost::alloc::string::String>, } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DiskInfoRequest { + #[prost(string, tag = "1")] + pub disk: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub opts: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DiskInfoResponse { + #[prost(bool, tag = "1")] + pub success: bool, + #[prost(string, tag = "2")] + pub disk_info: ::prost::alloc::string::String, + #[prost(string, optional, tag = "3")] + pub error_info: ::core::option::Option<::prost::alloc::string::String>, +} /// lock api have same argument type #[derive(Clone, PartialEq, ::prost::Message)] pub struct GenerallyLockRequest { @@ -522,9 +580,15 @@ pub struct GenerallyLockResponse { } /// Generated client implementations. pub mod node_service_client { - #![allow(unused_variables, dead_code, missing_docs, clippy::wildcard_imports, clippy::let_unit_value)] - use tonic::codegen::http::Uri; + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] use tonic::codegen::*; + use tonic::codegen::http::Uri; #[derive(Debug, Clone)] pub struct NodeServiceClient { inner: tonic::client::Grpc, @@ -555,16 +619,22 @@ pub mod node_service_client { let inner = tonic::client::Grpc::with_origin(inner, origin); Self { inner } } - pub fn with_interceptor(inner: T, interceptor: F) -> NodeServiceClient> + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> NodeServiceClient> where F: tonic::service::Interceptor, T::ResponseBody: Default, T: tonic::codegen::Service< http::Request, - Response = http::Response<>::ResponseBody>, + Response = http::Response< + >::ResponseBody, + >, >, - >>::Error: - Into + std::marker::Send + std::marker::Sync, + , + >>::Error: Into + std::marker::Send + std::marker::Sync, { NodeServiceClient::new(InterceptedService::new(inner, interceptor)) } @@ -607,9 +677,15 @@ pub mod node_service_client { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/Ping"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/Ping", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "Ping")); @@ -618,13 +694,22 @@ pub mod node_service_client { pub async fn list_bucket( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ListBucket"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ListBucket", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ListBucket")); @@ -633,13 +718,22 @@ pub mod node_service_client { pub async fn make_bucket( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/MakeBucket"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/MakeBucket", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "MakeBucket")); @@ -648,13 +742,22 @@ pub mod node_service_client { pub async fn get_bucket_info( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/GetBucketInfo"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/GetBucketInfo", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "GetBucketInfo")); @@ -663,13 +766,22 @@ pub mod node_service_client { pub async fn delete_bucket( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/DeleteBucket"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/DeleteBucket", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "DeleteBucket")); @@ -678,13 +790,22 @@ pub mod node_service_client { pub async fn read_all( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ReadAll"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ReadAll", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ReadAll")); @@ -693,13 +814,22 @@ pub mod node_service_client { pub async fn write_all( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/WriteAll"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/WriteAll", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "WriteAll")); @@ -712,24 +842,87 @@ pub mod node_service_client { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/Delete"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/Delete", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "Delete")); self.inner.unary(req, path, codec).await } - pub async fn rename_part( + pub async fn verify_file( &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/RenamePart"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/VerifyFile", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("node_service.NodeService", "VerifyFile")); + self.inner.unary(req, path, codec).await + } + pub async fn check_parts( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/CheckParts", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("node_service.NodeService", "CheckParts")); + self.inner.unary(req, path, codec).await + } + pub async fn rename_part( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/RenamePart", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "RenamePart")); @@ -738,13 +931,22 @@ pub mod node_service_client { pub async fn rename_file( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/RenameFile"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/RenameFile", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "RenameFile")); @@ -757,9 +959,15 @@ pub mod node_service_client { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/Write"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/Write", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "Write")); @@ -768,13 +976,22 @@ pub mod node_service_client { pub async fn write_stream( &mut self, request: impl tonic::IntoStreamingRequest, - ) -> std::result::Result>, tonic::Status> { + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/WriteStream"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/WriteStream", + ); let mut req = request.into_streaming_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "WriteStream")); @@ -784,13 +1001,22 @@ pub mod node_service_client { pub async fn read_at( &mut self, request: impl tonic::IntoStreamingRequest, - ) -> std::result::Result>, tonic::Status> { + ) -> std::result::Result< + tonic::Response>, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ReadAt"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ReadAt", + ); let mut req = request.into_streaming_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ReadAt")); @@ -799,13 +1025,22 @@ pub mod node_service_client { pub async fn list_dir( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ListDir"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ListDir", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ListDir")); @@ -814,13 +1049,22 @@ pub mod node_service_client { pub async fn walk_dir( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/WalkDir"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/WalkDir", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "WalkDir")); @@ -829,13 +1073,22 @@ pub mod node_service_client { pub async fn rename_data( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/RenameData"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/RenameData", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "RenameData")); @@ -844,13 +1097,22 @@ pub mod node_service_client { pub async fn make_volumes( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/MakeVolumes"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/MakeVolumes", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "MakeVolumes")); @@ -859,13 +1121,22 @@ pub mod node_service_client { pub async fn make_volume( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/MakeVolume"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/MakeVolume", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "MakeVolume")); @@ -874,13 +1145,22 @@ pub mod node_service_client { pub async fn list_volumes( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ListVolumes"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ListVolumes", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ListVolumes")); @@ -889,13 +1169,22 @@ pub mod node_service_client { pub async fn stat_volume( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/StatVolume"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/StatVolume", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "StatVolume")); @@ -904,13 +1193,22 @@ pub mod node_service_client { pub async fn delete_paths( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/DeletePaths"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/DeletePaths", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "DeletePaths")); @@ -919,13 +1217,22 @@ pub mod node_service_client { pub async fn update_metadata( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/UpdateMetadata"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/UpdateMetadata", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "UpdateMetadata")); @@ -934,13 +1241,22 @@ pub mod node_service_client { pub async fn write_metadata( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/WriteMetadata"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/WriteMetadata", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "WriteMetadata")); @@ -949,13 +1265,22 @@ pub mod node_service_client { pub async fn read_version( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ReadVersion"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ReadVersion", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ReadVersion")); @@ -968,9 +1293,15 @@ pub mod node_service_client { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ReadXL"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ReadXL", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ReadXL")); @@ -979,13 +1310,22 @@ pub mod node_service_client { pub async fn delete_version( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/DeleteVersion"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/DeleteVersion", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "DeleteVersion")); @@ -994,13 +1334,22 @@ pub mod node_service_client { pub async fn delete_versions( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/DeleteVersions"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/DeleteVersions", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "DeleteVersions")); @@ -1009,13 +1358,22 @@ pub mod node_service_client { pub async fn read_multiple( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ReadMultiple"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ReadMultiple", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ReadMultiple")); @@ -1024,28 +1382,70 @@ pub mod node_service_client { pub async fn delete_volume( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/DeleteVolume"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/DeleteVolume", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "DeleteVolume")); self.inner.unary(req, path, codec).await } - pub async fn lock( + pub async fn disk_info( &mut self, - request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/Lock"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/DiskInfo", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert(GrpcMethod::new("node_service.NodeService", "DiskInfo")); + self.inner.unary(req, path, codec).await + } + pub async fn lock( + &mut self, + request: impl tonic::IntoRequest, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { + self.inner + .ready() + .await + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; + let codec = tonic::codec::ProstCodec::default(); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/Lock", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "Lock")); @@ -1054,13 +1454,22 @@ pub mod node_service_client { pub async fn un_lock( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/UnLock"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/UnLock", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "UnLock")); @@ -1069,13 +1478,22 @@ pub mod node_service_client { pub async fn r_lock( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/RLock"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/RLock", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "RLock")); @@ -1084,13 +1502,22 @@ pub mod node_service_client { pub async fn r_un_lock( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/RUnLock"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/RUnLock", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "RUnLock")); @@ -1099,13 +1526,22 @@ pub mod node_service_client { pub async fn force_un_lock( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/ForceUnLock"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/ForceUnLock", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "ForceUnLock")); @@ -1114,13 +1550,22 @@ pub mod node_service_client { pub async fn refresh( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result, tonic::Status> { + ) -> std::result::Result< + tonic::Response, + tonic::Status, + > { self.inner .ready() .await - .map_err(|e| tonic::Status::unknown(format!("Service was not ready: {}", e.into())))?; + .map_err(|e| { + tonic::Status::unknown( + format!("Service was not ready: {}", e.into()), + ) + })?; let codec = tonic::codec::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static("/node_service.NodeService/Refresh"); + let path = http::uri::PathAndQuery::from_static( + "/node_service.NodeService/Refresh", + ); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("node_service.NodeService", "Refresh")); @@ -1130,7 +1575,13 @@ pub mod node_service_client { } /// Generated server implementations. pub mod node_service_server { - #![allow(unused_variables, dead_code, missing_docs, clippy::wildcard_imports, clippy::let_unit_value)] + #![allow( + unused_variables, + dead_code, + missing_docs, + clippy::wildcard_imports, + clippy::let_unit_value, + )] use tonic::codegen::*; /// Generated trait containing gRPC methods that should be implemented for use with NodeServiceServer. #[async_trait] @@ -1143,19 +1594,31 @@ pub mod node_service_server { async fn list_bucket( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn make_bucket( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn get_bucket_info( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn delete_bucket( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn read_all( &self, request: tonic::Request, @@ -1163,33 +1626,63 @@ pub mod node_service_server { async fn write_all( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn delete( &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; + async fn verify_file( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn check_parts( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn rename_part( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn rename_file( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn write( &self, request: tonic::Request, ) -> std::result::Result, tonic::Status>; /// Server streaming response type for the WriteStream method. - type WriteStreamStream: tonic::codegen::tokio_stream::Stream> + type WriteStreamStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + std::marker::Send + 'static; async fn write_stream( &self, request: tonic::Request>, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; /// Server streaming response type for the ReadAt method. - type ReadAtStream: tonic::codegen::tokio_stream::Stream> + type ReadAtStream: tonic::codegen::tokio_stream::Stream< + Item = std::result::Result, + > + std::marker::Send + 'static; /// rpc Append(AppendRequest) returns (AppendResponse) {}; @@ -1208,39 +1701,66 @@ pub mod node_service_server { async fn rename_data( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn make_volumes( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn make_volume( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn list_volumes( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn stat_volume( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn delete_paths( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn update_metadata( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn write_metadata( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn read_version( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn read_xl( &self, request: tonic::Request, @@ -1248,43 +1768,80 @@ pub mod node_service_server { async fn delete_version( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn delete_versions( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn read_multiple( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn delete_volume( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; + async fn disk_info( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn lock( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn un_lock( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn r_lock( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn r_un_lock( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn force_un_lock( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; async fn refresh( &self, request: tonic::Request, - ) -> std::result::Result, tonic::Status>; + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } #[derive(Debug)] pub struct NodeServiceServer { @@ -1307,7 +1864,10 @@ pub mod node_service_server { max_encoding_message_size: None, } } - pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService + pub fn with_interceptor( + inner: T, + interceptor: F, + ) -> InterceptedService where F: tonic::service::Interceptor, { @@ -1351,7 +1911,10 @@ pub mod node_service_server { type Response = http::Response; type Error = std::convert::Infallible; type Future = BoxFuture; - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + fn poll_ready( + &mut self, + _cx: &mut Context<'_>, + ) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: http::Request) -> Self::Future { @@ -1359,12 +1922,21 @@ pub mod node_service_server { "/node_service.NodeService/Ping" => { #[allow(non_camel_case_types)] struct PingSvc(pub Arc); - impl tonic::server::UnaryService for PingSvc { + impl tonic::server::UnaryService + for PingSvc { type Response = super::PingResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::ping(&inner, request).await }; + let fut = async move { + ::ping(&inner, request).await + }; Box::pin(fut) } } @@ -1377,8 +1949,14 @@ pub mod node_service_server { let method = PingSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1387,12 +1965,23 @@ pub mod node_service_server { "/node_service.NodeService/ListBucket" => { #[allow(non_camel_case_types)] struct ListBucketSvc(pub Arc); - impl tonic::server::UnaryService for ListBucketSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for ListBucketSvc { type Response = super::ListBucketResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::list_bucket(&inner, request).await }; + let fut = async move { + ::list_bucket(&inner, request).await + }; Box::pin(fut) } } @@ -1405,8 +1994,14 @@ pub mod node_service_server { let method = ListBucketSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1415,12 +2010,23 @@ pub mod node_service_server { "/node_service.NodeService/MakeBucket" => { #[allow(non_camel_case_types)] struct MakeBucketSvc(pub Arc); - impl tonic::server::UnaryService for MakeBucketSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for MakeBucketSvc { type Response = super::MakeBucketResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::make_bucket(&inner, request).await }; + let fut = async move { + ::make_bucket(&inner, request).await + }; Box::pin(fut) } } @@ -1433,8 +2039,14 @@ pub mod node_service_server { let method = MakeBucketSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1443,12 +2055,23 @@ pub mod node_service_server { "/node_service.NodeService/GetBucketInfo" => { #[allow(non_camel_case_types)] struct GetBucketInfoSvc(pub Arc); - impl tonic::server::UnaryService for GetBucketInfoSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for GetBucketInfoSvc { type Response = super::GetBucketInfoResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::get_bucket_info(&inner, request).await }; + let fut = async move { + ::get_bucket_info(&inner, request).await + }; Box::pin(fut) } } @@ -1461,8 +2084,14 @@ pub mod node_service_server { let method = GetBucketInfoSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1471,12 +2100,23 @@ pub mod node_service_server { "/node_service.NodeService/DeleteBucket" => { #[allow(non_camel_case_types)] struct DeleteBucketSvc(pub Arc); - impl tonic::server::UnaryService for DeleteBucketSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for DeleteBucketSvc { type Response = super::DeleteBucketResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::delete_bucket(&inner, request).await }; + let fut = async move { + ::delete_bucket(&inner, request).await + }; Box::pin(fut) } } @@ -1489,8 +2129,14 @@ pub mod node_service_server { let method = DeleteBucketSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1499,12 +2145,23 @@ pub mod node_service_server { "/node_service.NodeService/ReadAll" => { #[allow(non_camel_case_types)] struct ReadAllSvc(pub Arc); - impl tonic::server::UnaryService for ReadAllSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for ReadAllSvc { type Response = super::ReadAllResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::read_all(&inner, request).await }; + let fut = async move { + ::read_all(&inner, request).await + }; Box::pin(fut) } } @@ -1517,8 +2174,14 @@ pub mod node_service_server { let method = ReadAllSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1527,12 +2190,23 @@ pub mod node_service_server { "/node_service.NodeService/WriteAll" => { #[allow(non_camel_case_types)] struct WriteAllSvc(pub Arc); - impl tonic::server::UnaryService for WriteAllSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for WriteAllSvc { type Response = super::WriteAllResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::write_all(&inner, request).await }; + let fut = async move { + ::write_all(&inner, request).await + }; Box::pin(fut) } } @@ -1545,8 +2219,14 @@ pub mod node_service_server { let method = WriteAllSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1555,12 +2235,23 @@ pub mod node_service_server { "/node_service.NodeService/Delete" => { #[allow(non_camel_case_types)] struct DeleteSvc(pub Arc); - impl tonic::server::UnaryService for DeleteSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for DeleteSvc { type Response = super::DeleteResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::delete(&inner, request).await }; + let fut = async move { + ::delete(&inner, request).await + }; Box::pin(fut) } } @@ -1573,8 +2264,104 @@ pub mod node_service_server { let method = DeleteSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/node_service.NodeService/VerifyFile" => { + #[allow(non_camel_case_types)] + struct VerifyFileSvc(pub Arc); + impl< + T: NodeService, + > tonic::server::UnaryService + for VerifyFileSvc { + type Response = super::VerifyFileResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::verify_file(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = VerifyFileSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/node_service.NodeService/CheckParts" => { + #[allow(non_camel_case_types)] + struct CheckPartsSvc(pub Arc); + impl< + T: NodeService, + > tonic::server::UnaryService + for CheckPartsSvc { + type Response = super::CheckPartsResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::check_parts(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = CheckPartsSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1583,12 +2370,23 @@ pub mod node_service_server { "/node_service.NodeService/RenamePart" => { #[allow(non_camel_case_types)] struct RenamePartSvc(pub Arc); - impl tonic::server::UnaryService for RenamePartSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for RenamePartSvc { type Response = super::RenamePartResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::rename_part(&inner, request).await }; + let fut = async move { + ::rename_part(&inner, request).await + }; Box::pin(fut) } } @@ -1601,8 +2399,14 @@ pub mod node_service_server { let method = RenamePartSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1611,12 +2415,23 @@ pub mod node_service_server { "/node_service.NodeService/RenameFile" => { #[allow(non_camel_case_types)] struct RenameFileSvc(pub Arc); - impl tonic::server::UnaryService for RenameFileSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for RenameFileSvc { type Response = super::RenameFileResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::rename_file(&inner, request).await }; + let fut = async move { + ::rename_file(&inner, request).await + }; Box::pin(fut) } } @@ -1629,8 +2444,14 @@ pub mod node_service_server { let method = RenameFileSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1639,12 +2460,21 @@ pub mod node_service_server { "/node_service.NodeService/Write" => { #[allow(non_camel_case_types)] struct WriteSvc(pub Arc); - impl tonic::server::UnaryService for WriteSvc { + impl tonic::server::UnaryService + for WriteSvc { type Response = super::WriteResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::write(&inner, request).await }; + let fut = async move { + ::write(&inner, request).await + }; Box::pin(fut) } } @@ -1657,8 +2487,14 @@ pub mod node_service_server { let method = WriteSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1667,13 +2503,26 @@ pub mod node_service_server { "/node_service.NodeService/WriteStream" => { #[allow(non_camel_case_types)] struct WriteStreamSvc(pub Arc); - impl tonic::server::StreamingService for WriteStreamSvc { + impl< + T: NodeService, + > tonic::server::StreamingService + for WriteStreamSvc { type Response = super::WriteResponse; type ResponseStream = T::WriteStreamStream; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request>) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + tonic::Streaming, + >, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::write_stream(&inner, request).await }; + let fut = async move { + ::write_stream(&inner, request).await + }; Box::pin(fut) } } @@ -1686,8 +2535,14 @@ pub mod node_service_server { let method = WriteStreamSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.streaming(method, req).await; Ok(res) }; @@ -1696,13 +2551,26 @@ pub mod node_service_server { "/node_service.NodeService/ReadAt" => { #[allow(non_camel_case_types)] struct ReadAtSvc(pub Arc); - impl tonic::server::StreamingService for ReadAtSvc { + impl< + T: NodeService, + > tonic::server::StreamingService + for ReadAtSvc { type Response = super::ReadAtResponse; type ResponseStream = T::ReadAtStream; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request>) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request< + tonic::Streaming, + >, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::read_at(&inner, request).await }; + let fut = async move { + ::read_at(&inner, request).await + }; Box::pin(fut) } } @@ -1715,8 +2583,14 @@ pub mod node_service_server { let method = ReadAtSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.streaming(method, req).await; Ok(res) }; @@ -1725,12 +2599,23 @@ pub mod node_service_server { "/node_service.NodeService/ListDir" => { #[allow(non_camel_case_types)] struct ListDirSvc(pub Arc); - impl tonic::server::UnaryService for ListDirSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for ListDirSvc { type Response = super::ListDirResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::list_dir(&inner, request).await }; + let fut = async move { + ::list_dir(&inner, request).await + }; Box::pin(fut) } } @@ -1743,8 +2628,14 @@ pub mod node_service_server { let method = ListDirSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1753,12 +2644,23 @@ pub mod node_service_server { "/node_service.NodeService/WalkDir" => { #[allow(non_camel_case_types)] struct WalkDirSvc(pub Arc); - impl tonic::server::UnaryService for WalkDirSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for WalkDirSvc { type Response = super::WalkDirResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::walk_dir(&inner, request).await }; + let fut = async move { + ::walk_dir(&inner, request).await + }; Box::pin(fut) } } @@ -1771,8 +2673,14 @@ pub mod node_service_server { let method = WalkDirSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1781,12 +2689,23 @@ pub mod node_service_server { "/node_service.NodeService/RenameData" => { #[allow(non_camel_case_types)] struct RenameDataSvc(pub Arc); - impl tonic::server::UnaryService for RenameDataSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for RenameDataSvc { type Response = super::RenameDataResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::rename_data(&inner, request).await }; + let fut = async move { + ::rename_data(&inner, request).await + }; Box::pin(fut) } } @@ -1799,8 +2718,14 @@ pub mod node_service_server { let method = RenameDataSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1809,12 +2734,23 @@ pub mod node_service_server { "/node_service.NodeService/MakeVolumes" => { #[allow(non_camel_case_types)] struct MakeVolumesSvc(pub Arc); - impl tonic::server::UnaryService for MakeVolumesSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for MakeVolumesSvc { type Response = super::MakeVolumesResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::make_volumes(&inner, request).await }; + let fut = async move { + ::make_volumes(&inner, request).await + }; Box::pin(fut) } } @@ -1827,8 +2763,14 @@ pub mod node_service_server { let method = MakeVolumesSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1837,12 +2779,23 @@ pub mod node_service_server { "/node_service.NodeService/MakeVolume" => { #[allow(non_camel_case_types)] struct MakeVolumeSvc(pub Arc); - impl tonic::server::UnaryService for MakeVolumeSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for MakeVolumeSvc { type Response = super::MakeVolumeResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::make_volume(&inner, request).await }; + let fut = async move { + ::make_volume(&inner, request).await + }; Box::pin(fut) } } @@ -1855,8 +2808,14 @@ pub mod node_service_server { let method = MakeVolumeSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1865,12 +2824,23 @@ pub mod node_service_server { "/node_service.NodeService/ListVolumes" => { #[allow(non_camel_case_types)] struct ListVolumesSvc(pub Arc); - impl tonic::server::UnaryService for ListVolumesSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for ListVolumesSvc { type Response = super::ListVolumesResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::list_volumes(&inner, request).await }; + let fut = async move { + ::list_volumes(&inner, request).await + }; Box::pin(fut) } } @@ -1883,8 +2853,14 @@ pub mod node_service_server { let method = ListVolumesSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1893,12 +2869,23 @@ pub mod node_service_server { "/node_service.NodeService/StatVolume" => { #[allow(non_camel_case_types)] struct StatVolumeSvc(pub Arc); - impl tonic::server::UnaryService for StatVolumeSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for StatVolumeSvc { type Response = super::StatVolumeResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::stat_volume(&inner, request).await }; + let fut = async move { + ::stat_volume(&inner, request).await + }; Box::pin(fut) } } @@ -1911,8 +2898,14 @@ pub mod node_service_server { let method = StatVolumeSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1921,12 +2914,23 @@ pub mod node_service_server { "/node_service.NodeService/DeletePaths" => { #[allow(non_camel_case_types)] struct DeletePathsSvc(pub Arc); - impl tonic::server::UnaryService for DeletePathsSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for DeletePathsSvc { type Response = super::DeletePathsResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::delete_paths(&inner, request).await }; + let fut = async move { + ::delete_paths(&inner, request).await + }; Box::pin(fut) } } @@ -1939,8 +2943,14 @@ pub mod node_service_server { let method = DeletePathsSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1949,12 +2959,23 @@ pub mod node_service_server { "/node_service.NodeService/UpdateMetadata" => { #[allow(non_camel_case_types)] struct UpdateMetadataSvc(pub Arc); - impl tonic::server::UnaryService for UpdateMetadataSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for UpdateMetadataSvc { type Response = super::UpdateMetadataResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::update_metadata(&inner, request).await }; + let fut = async move { + ::update_metadata(&inner, request).await + }; Box::pin(fut) } } @@ -1967,8 +2988,14 @@ pub mod node_service_server { let method = UpdateMetadataSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -1977,12 +3004,23 @@ pub mod node_service_server { "/node_service.NodeService/WriteMetadata" => { #[allow(non_camel_case_types)] struct WriteMetadataSvc(pub Arc); - impl tonic::server::UnaryService for WriteMetadataSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for WriteMetadataSvc { type Response = super::WriteMetadataResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::write_metadata(&inner, request).await }; + let fut = async move { + ::write_metadata(&inner, request).await + }; Box::pin(fut) } } @@ -1995,8 +3033,14 @@ pub mod node_service_server { let method = WriteMetadataSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2005,12 +3049,23 @@ pub mod node_service_server { "/node_service.NodeService/ReadVersion" => { #[allow(non_camel_case_types)] struct ReadVersionSvc(pub Arc); - impl tonic::server::UnaryService for ReadVersionSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for ReadVersionSvc { type Response = super::ReadVersionResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::read_version(&inner, request).await }; + let fut = async move { + ::read_version(&inner, request).await + }; Box::pin(fut) } } @@ -2023,8 +3078,14 @@ pub mod node_service_server { let method = ReadVersionSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2033,12 +3094,23 @@ pub mod node_service_server { "/node_service.NodeService/ReadXL" => { #[allow(non_camel_case_types)] struct ReadXLSvc(pub Arc); - impl tonic::server::UnaryService for ReadXLSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for ReadXLSvc { type Response = super::ReadXlResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::read_xl(&inner, request).await }; + let fut = async move { + ::read_xl(&inner, request).await + }; Box::pin(fut) } } @@ -2051,8 +3123,14 @@ pub mod node_service_server { let method = ReadXLSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2061,12 +3139,23 @@ pub mod node_service_server { "/node_service.NodeService/DeleteVersion" => { #[allow(non_camel_case_types)] struct DeleteVersionSvc(pub Arc); - impl tonic::server::UnaryService for DeleteVersionSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for DeleteVersionSvc { type Response = super::DeleteVersionResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::delete_version(&inner, request).await }; + let fut = async move { + ::delete_version(&inner, request).await + }; Box::pin(fut) } } @@ -2079,8 +3168,14 @@ pub mod node_service_server { let method = DeleteVersionSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2089,12 +3184,23 @@ pub mod node_service_server { "/node_service.NodeService/DeleteVersions" => { #[allow(non_camel_case_types)] struct DeleteVersionsSvc(pub Arc); - impl tonic::server::UnaryService for DeleteVersionsSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for DeleteVersionsSvc { type Response = super::DeleteVersionsResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::delete_versions(&inner, request).await }; + let fut = async move { + ::delete_versions(&inner, request).await + }; Box::pin(fut) } } @@ -2107,8 +3213,14 @@ pub mod node_service_server { let method = DeleteVersionsSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2117,12 +3229,23 @@ pub mod node_service_server { "/node_service.NodeService/ReadMultiple" => { #[allow(non_camel_case_types)] struct ReadMultipleSvc(pub Arc); - impl tonic::server::UnaryService for ReadMultipleSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for ReadMultipleSvc { type Response = super::ReadMultipleResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::read_multiple(&inner, request).await }; + let fut = async move { + ::read_multiple(&inner, request).await + }; Box::pin(fut) } } @@ -2135,8 +3258,14 @@ pub mod node_service_server { let method = ReadMultipleSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2145,12 +3274,23 @@ pub mod node_service_server { "/node_service.NodeService/DeleteVolume" => { #[allow(non_camel_case_types)] struct DeleteVolumeSvc(pub Arc); - impl tonic::server::UnaryService for DeleteVolumeSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for DeleteVolumeSvc { type Response = super::DeleteVolumeResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::delete_volume(&inner, request).await }; + let fut = async move { + ::delete_volume(&inner, request).await + }; Box::pin(fut) } } @@ -2163,8 +3303,59 @@ pub mod node_service_server { let method = DeleteVolumeSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); + let res = grpc.unary(method, req).await; + Ok(res) + }; + Box::pin(fut) + } + "/node_service.NodeService/DiskInfo" => { + #[allow(non_camel_case_types)] + struct DiskInfoSvc(pub Arc); + impl< + T: NodeService, + > tonic::server::UnaryService + for DiskInfoSvc { + type Response = super::DiskInfoResponse; + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { + let inner = Arc::clone(&self.0); + let fut = async move { + ::disk_info(&inner, request).await + }; + Box::pin(fut) + } + } + let accept_compression_encodings = self.accept_compression_encodings; + let send_compression_encodings = self.send_compression_encodings; + let max_decoding_message_size = self.max_decoding_message_size; + let max_encoding_message_size = self.max_encoding_message_size; + let inner = self.inner.clone(); + let fut = async move { + let method = DiskInfoSvc(inner); + let codec = tonic::codec::ProstCodec::default(); + let mut grpc = tonic::server::Grpc::new(codec) + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2173,12 +3364,23 @@ pub mod node_service_server { "/node_service.NodeService/Lock" => { #[allow(non_camel_case_types)] struct LockSvc(pub Arc); - impl tonic::server::UnaryService for LockSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for LockSvc { type Response = super::GenerallyLockResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::lock(&inner, request).await }; + let fut = async move { + ::lock(&inner, request).await + }; Box::pin(fut) } } @@ -2191,8 +3393,14 @@ pub mod node_service_server { let method = LockSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2201,12 +3409,23 @@ pub mod node_service_server { "/node_service.NodeService/UnLock" => { #[allow(non_camel_case_types)] struct UnLockSvc(pub Arc); - impl tonic::server::UnaryService for UnLockSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for UnLockSvc { type Response = super::GenerallyLockResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::un_lock(&inner, request).await }; + let fut = async move { + ::un_lock(&inner, request).await + }; Box::pin(fut) } } @@ -2219,8 +3438,14 @@ pub mod node_service_server { let method = UnLockSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2229,12 +3454,23 @@ pub mod node_service_server { "/node_service.NodeService/RLock" => { #[allow(non_camel_case_types)] struct RLockSvc(pub Arc); - impl tonic::server::UnaryService for RLockSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for RLockSvc { type Response = super::GenerallyLockResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::r_lock(&inner, request).await }; + let fut = async move { + ::r_lock(&inner, request).await + }; Box::pin(fut) } } @@ -2247,8 +3483,14 @@ pub mod node_service_server { let method = RLockSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2257,12 +3499,23 @@ pub mod node_service_server { "/node_service.NodeService/RUnLock" => { #[allow(non_camel_case_types)] struct RUnLockSvc(pub Arc); - impl tonic::server::UnaryService for RUnLockSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for RUnLockSvc { type Response = super::GenerallyLockResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::r_un_lock(&inner, request).await }; + let fut = async move { + ::r_un_lock(&inner, request).await + }; Box::pin(fut) } } @@ -2275,8 +3528,14 @@ pub mod node_service_server { let method = RUnLockSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2285,12 +3544,23 @@ pub mod node_service_server { "/node_service.NodeService/ForceUnLock" => { #[allow(non_camel_case_types)] struct ForceUnLockSvc(pub Arc); - impl tonic::server::UnaryService for ForceUnLockSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for ForceUnLockSvc { type Response = super::GenerallyLockResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::force_un_lock(&inner, request).await }; + let fut = async move { + ::force_un_lock(&inner, request).await + }; Box::pin(fut) } } @@ -2303,8 +3573,14 @@ pub mod node_service_server { let method = ForceUnLockSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; @@ -2313,12 +3589,23 @@ pub mod node_service_server { "/node_service.NodeService/Refresh" => { #[allow(non_camel_case_types)] struct RefreshSvc(pub Arc); - impl tonic::server::UnaryService for RefreshSvc { + impl< + T: NodeService, + > tonic::server::UnaryService + for RefreshSvc { type Response = super::GenerallyLockResponse; - type Future = BoxFuture, tonic::Status>; - fn call(&mut self, request: tonic::Request) -> Self::Future { + type Future = BoxFuture< + tonic::Response, + tonic::Status, + >; + fn call( + &mut self, + request: tonic::Request, + ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { ::refresh(&inner, request).await }; + let fut = async move { + ::refresh(&inner, request).await + }; Box::pin(fut) } } @@ -2331,20 +3618,36 @@ pub mod node_service_server { let method = RefreshSvc(inner); let codec = tonic::codec::ProstCodec::default(); let mut grpc = tonic::server::Grpc::new(codec) - .apply_compression_config(accept_compression_encodings, send_compression_encodings) - .apply_max_message_size_config(max_decoding_message_size, max_encoding_message_size); + .apply_compression_config( + accept_compression_encodings, + send_compression_encodings, + ) + .apply_max_message_size_config( + max_decoding_message_size, + max_encoding_message_size, + ); let res = grpc.unary(method, req).await; Ok(res) }; Box::pin(fut) } - _ => Box::pin(async move { - let mut response = http::Response::new(empty_body()); - let headers = response.headers_mut(); - headers.insert(tonic::Status::GRPC_STATUS, (tonic::Code::Unimplemented as i32).into()); - headers.insert(http::header::CONTENT_TYPE, tonic::metadata::GRPC_CONTENT_TYPE); - Ok(response) - }), + _ => { + Box::pin(async move { + let mut response = http::Response::new(empty_body()); + let headers = response.headers_mut(); + headers + .insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers + .insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }) + } } } } diff --git a/common/protos/src/node.proto b/common/protos/src/node.proto index 4e84eedba..f789a302f 100644 --- a/common/protos/src/node.proto +++ b/common/protos/src/node.proto @@ -88,6 +88,32 @@ message DeleteResponse { optional string error_info = 2; } +message VerifyFileRequest { + string disk = 1; // indicate which one in the disks + string volume = 2; + string path = 3; + string file_info = 4; +} + +message VerifyFileResponse { + bool success = 1; + string check_parts_resp = 2; + optional string error_info = 3; +} + +message CheckPartsRequest { + string disk = 1; // indicate which one in the disks + string volume = 2; + string path = 3; + string file_info = 4; +} + +message CheckPartsResponse { + bool success = 1; + string check_parts_resp = 2; + optional string error_info = 3; +} + message RenamePartRequst { string disk = 1; string src_volume = 2; @@ -345,6 +371,17 @@ message DeleteVolumeResponse { optional string error_info = 2; } +message DiskInfoRequest { + string disk = 1; + string opts = 2; +} + +message DiskInfoResponse { + bool success = 1; + string disk_info = 2; + optional string error_info = 3; +} + // lock api have same argument type message GenerallyLockRequest { string args = 1; @@ -370,6 +407,8 @@ service NodeService { rpc ReadAll(ReadAllRequest) returns (ReadAllResponse) {}; rpc WriteAll(WriteAllRequest) returns (WriteAllResponse) {}; rpc Delete(DeleteRequest) returns (DeleteResponse) {}; + rpc VerifyFile(VerifyFileRequest) returns (VerifyFileResponse) {}; + rpc CheckParts(CheckPartsRequest) returns (CheckPartsResponse) {}; rpc RenamePart(RenamePartRequst) returns (RenamePartResponse) {}; rpc RenameFile(RenameFileRequst) returns (RenameFileResponse) {}; rpc Write(WriteRequest) returns (WriteResponse) {}; @@ -392,6 +431,7 @@ service NodeService { rpc DeleteVersions(DeleteVersionsRequest) returns (DeleteVersionsResponse) {}; rpc ReadMultiple(ReadMultipleRequest) returns (ReadMultipleResponse) {}; rpc DeleteVolume(DeleteVolumeRequest) returns (DeleteVolumeResponse) {}; + rpc DiskInfo(DiskInfoRequest) returns (DiskInfoResponse) {}; /* -------------------------------lock service-------------------------- */ diff --git a/ecstore/Cargo.toml b/ecstore/Cargo.toml index af02e32eb..7dbd0afff 100644 --- a/ecstore/Cargo.toml +++ b/ecstore/Cargo.toml @@ -11,10 +11,12 @@ rust-version.workspace = true [dependencies] async-trait.workspace = true backon.workspace = true +blake2 = "0.10.6" bytes.workspace = true +common.workspace = true +glob = "0.3.1" thiserror.workspace = true futures.workspace = true -common.workspace = true tracing.workspace = true serde.workspace = true time.workspace = true @@ -22,6 +24,7 @@ serde_json.workspace = true tracing-error.workspace = true s3s.workspace = true http.workspace = true +highway = "1.2.0" url.workspace = true uuid = { version = "1.8.0", features = ["v4", "fast-rng", "serde"] } reed-solomon-erasure = { version = "6.0.0", features = ["simd-accel"] } @@ -40,6 +43,7 @@ base64-simd = "0.8.0" sha2 = { version = "0.10.8", features = ["asm"] } hex-simd = "0.8.0" path-clean = "1.0.1" +tempfile.workspace = true tokio = { workspace = true, features = ["io-util", "sync"] } tokio-stream = "0.1.15" tonic.workspace = true @@ -47,11 +51,16 @@ tower.workspace = true rmp = "0.8.14" byteorder = "1.5.0" xxhash-rust = { version = "0.8.12", features = ["xxh64"] } +num = "0.4.3" num_cpus = "1.16" s3s-policy.workspace = true [target.'cfg(not(windows))'.dependencies] +nix = { version = "0.29.0", features = ["fs"] } openssl = "0.10.66" +[target.'cfg(windows)'.dependencies] +winapi = "0.3.9" + [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/ecstore/src/bitrot.rs b/ecstore/src/bitrot.rs new file mode 100644 index 000000000..7d24aefa3 --- /dev/null +++ b/ecstore/src/bitrot.rs @@ -0,0 +1,584 @@ +use std::{ + any::Any, + collections::HashMap, + io::{Cursor, Read}, +}; + +use blake2::Blake2b512; +use highway::{HighwayHash, HighwayHasher, Key}; +use lazy_static::lazy_static; +use sha2::{digest::core_api::BlockSizeUser, Digest, Sha256}; +use tokio::{ + spawn, + sync::mpsc::{self, Sender}, + task::JoinHandle, +}; + +use crate::{ + disk::{error::DiskError, DiskStore}, + erasure::{ReadAt, Write}, + error::{Error, Result}, + store_api::BitrotAlgorithm, +}; + +lazy_static! { + static ref BITROT_ALGORITHMS: HashMap = { + let mut m = HashMap::new(); + m.insert(BitrotAlgorithm::SHA256, "sha256"); + m.insert(BitrotAlgorithm::BLAKE2b512, "blake2b"); + m.insert(BitrotAlgorithm::HighwayHash256, "highwayhash256"); + m.insert(BitrotAlgorithm::HighwayHash256S, "highwayhash256S"); + m + }; +} + +// const MAGIC_HIGHWAY_HASH256_KEY: &[u8] = &[ +// 0x4b, 0xe7, 0x34, 0xfa, 0x8e, 0x23, 0x8a, 0xcd, 0x26, 0x3e, 0x83, 0xe6, 0xbb, 0x96, 0x85, 0x52, 0x04, 0x0f, 0x93, 0x5d, 0xa3, +// 0x9f, 0x44, 0x14, 0x97, 0xe0, 0x9d, 0x13, 0x22, 0xde, 0x36, 0xa0, +// ]; +const MAGIC_HIGHWAY_HASH256_KEY: &[u64; 4] = &[3, 4, 2, 1]; + +#[derive(Clone, Debug)] +pub enum Hasher { + SHA256(Sha256), + HighwayHash256(HighwayHasher), + BLAKE2b512(Blake2b512), +} + +impl Hasher { + pub fn update(&mut self, data: impl AsRef<[u8]>) { + match self { + Hasher::SHA256(core_wrapper) => { + core_wrapper.update(data); + } + Hasher::HighwayHash256(highway_hasher) => { + highway_hasher.append(data.as_ref()); + } + Hasher::BLAKE2b512(core_wrapper) => { + core_wrapper.update(data); + } + } + } + + pub fn finalize(self) -> Vec { + match self { + Hasher::SHA256(core_wrapper) => core_wrapper.finalize().to_vec(), + Hasher::HighwayHash256(highway_hasher) => highway_hasher + .finalize256() + .iter() + .flat_map(|&n| n.to_le_bytes()) // 使用小端字节序转换 + .collect(), + Hasher::BLAKE2b512(core_wrapper) => core_wrapper.finalize().to_vec(), + } + } + + pub fn size(&self) -> usize { + match self { + Hasher::SHA256(_) => Sha256::output_size(), + Hasher::HighwayHash256(_) => 32, + Hasher::BLAKE2b512(_) => Blake2b512::output_size(), + } + } + + pub fn block_size(&self) -> usize { + match self { + Hasher::SHA256(_) => Sha256::block_size(), + Hasher::HighwayHash256(_) => 64, + Hasher::BLAKE2b512(_) => Blake2b512::block_size(), + } + } + + pub fn reset(&mut self) { + match self { + Hasher::SHA256(core_wrapper) => core_wrapper.reset(), + Hasher::HighwayHash256(highway_hasher) => { + let key = Key(*MAGIC_HIGHWAY_HASH256_KEY); + *highway_hasher = HighwayHasher::new(key); + } + Hasher::BLAKE2b512(core_wrapper) => core_wrapper.reset(), + } + } +} + +impl BitrotAlgorithm { + pub fn new(&self) -> Hasher { + match self { + BitrotAlgorithm::SHA256 => Hasher::SHA256(Sha256::new()), + BitrotAlgorithm::HighwayHash256 | BitrotAlgorithm::HighwayHash256S => { + let key = Key(*MAGIC_HIGHWAY_HASH256_KEY); + Hasher::HighwayHash256(HighwayHasher::new(key)) + } + BitrotAlgorithm::BLAKE2b512 => Hasher::BLAKE2b512(Blake2b512::new()), + } + } + + pub fn available(&self) -> bool { + BITROT_ALGORITHMS.get(self).is_some() + } + + pub fn string(&self) -> String { + BITROT_ALGORITHMS.get(self).map_or("".to_string(), |s| s.to_string()) + } +} + +#[derive(Debug)] +pub struct BitrotVerifier { + _algorithm: BitrotAlgorithm, + _sum: Vec, +} + +impl BitrotVerifier { + pub fn new(algorithm: BitrotAlgorithm, checksum: &[u8]) -> BitrotVerifier { + BitrotVerifier { + _algorithm: algorithm, + _sum: checksum.to_vec(), + } + } +} + +pub fn bitrot_algorithm_from_string(s: &str) -> BitrotAlgorithm { + for (k, v) in BITROT_ALGORITHMS.iter() { + if *v == s { + return k.clone(); + } + } + + BitrotAlgorithm::HighwayHash256S +} + +pub type BitrotWriter = Box; + +pub async fn new_bitrot_writer( + disk: DiskStore, + orig_volume: &str, + volume: &str, + file_path: &str, + length: usize, + algo: BitrotAlgorithm, + shard_size: usize, +) -> Result { + if algo == BitrotAlgorithm::HighwayHash256S { + return Ok(Box::new( + StreamingBitrotWriter::new(disk, orig_volume, volume, file_path, length, algo, shard_size).await?, + )); + } + Ok(Box::new(WholeBitrotWriter::new(disk, volume, file_path, algo, shard_size))) +} + +pub type BitrotReader = Box; + +pub fn new_bitrot_reader( + disk: DiskStore, + data: &[u8], + bucket: &str, + file_path: &str, + till_offset: usize, + algo: BitrotAlgorithm, + sum: &[u8], + shard_size: usize, +) -> BitrotReader { + if algo == BitrotAlgorithm::HighwayHash256S { + return Box::new(StreamingBitrotReader::new(disk, data, bucket, file_path, algo, till_offset, shard_size)); + } + Box::new(WholeBitrotReader::new(disk, bucket, file_path, algo, till_offset, sum)) +} + +pub async fn close_bitrot_writers(writers: &mut [Option]) -> Result<()> { + for w in writers.into_iter() { + if let Some(w) = w { + let _ = w.close().await?; + } + } + + Ok(()) +} + +pub fn bitrot_writer_sum(w: &BitrotWriter) -> Vec { + if let Some(w) = w.as_any().downcast_ref::() { + return w.hash.clone().finalize(); + } + + Vec::new() +} + +pub fn bitrot_shard_file_size(size: usize, shard_size: usize, algo: BitrotAlgorithm) -> usize { + if algo != BitrotAlgorithm::HighwayHash256S { + return size; + } + size.div_ceil(shard_size) * algo.new().size() + size +} + +pub fn bitrot_verify( + r: &mut Cursor>, + want_size: usize, + part_size: usize, + algo: BitrotAlgorithm, + want: Vec, + mut shard_size: usize, +) -> Result<()> { + if algo != BitrotAlgorithm::HighwayHash256S { + let mut h = algo.new(); + h.update(r.get_ref()); + if h.finalize() != want { + return Err(Error::new(DiskError::FileCorrupt)); + } + + return Ok(()); + } + let mut h = algo.new(); + let mut hash_buf = vec![0; h.size()]; + let mut left = want_size; + + if left != bitrot_shard_file_size(part_size, shard_size, algo) { + return Err(Error::new(DiskError::FileCorrupt)); + } + + while left > 0 { + h.reset(); + let n = r.read(&mut hash_buf)?; + left -= n; + + if left < shard_size { + shard_size = left; + } + + let mut buf = vec![0; shard_size]; + let read = r.read(&mut buf)?; + h.update(buf); + left -= read; + if h.clone().finalize() != hash_buf[0..n] { + return Err(Error::new(DiskError::FileCorrupt)); + } + } + + Ok(()) +} + +pub struct WholeBitrotWriter { + disk: DiskStore, + volume: String, + file_path: String, + _shard_size: usize, + pub hash: Hasher, +} + +impl WholeBitrotWriter { + pub fn new(disk: DiskStore, volume: &str, file_path: &str, algo: BitrotAlgorithm, shard_size: usize) -> Self { + WholeBitrotWriter { + disk, + volume: volume.to_string(), + file_path: file_path.to_string(), + _shard_size: shard_size, + hash: algo.new(), + } + } +} + +#[async_trait::async_trait] +impl Write for WholeBitrotWriter { + fn as_any(&self) -> &dyn Any { + self + } + + async fn write(&mut self, buf: &[u8]) -> Result<()> { + let mut file = self.disk.append_file(&self.volume, &self.file_path).await?; + let _ = file.write(buf).await?; + self.hash.update(buf); + + Ok(()) + } +} + +#[derive(Debug)] +pub struct WholeBitrotReader { + disk: DiskStore, + volume: String, + file_path: String, + _verifier: BitrotVerifier, + till_offset: usize, + buf: Option>, +} + +impl WholeBitrotReader { + pub fn new(disk: DiskStore, volume: &str, file_path: &str, algo: BitrotAlgorithm, till_offset: usize, sum: &[u8]) -> Self { + Self { + disk, + volume: volume.to_string(), + file_path: file_path.to_string(), + _verifier: BitrotVerifier::new(algo, sum), + till_offset, + buf: None, + } + } +} + +#[async_trait::async_trait] +impl ReadAt for WholeBitrotReader { + async fn read_at(&mut self, offset: usize, length: usize) -> Result<(Vec, usize)> { + if self.buf.is_none() { + let buf_len = self.till_offset - offset; + let mut file = self.disk.read_file(&self.volume, &self.file_path).await?; + let (buf, _) = file.read_at(offset, buf_len).await?; + self.buf = Some(buf); + } + + if let Some(buf) = &mut self.buf { + if buf.len() < length { + return Err(Error::new(DiskError::LessData)); + } + + return Ok((buf.drain(0..length).collect::>(), length)); + } + + Err(Error::new(DiskError::LessData)) + } +} + +struct StreamingBitrotWriter { + hasher: Hasher, + tx: Sender>>, + task: Option>, +} + +impl StreamingBitrotWriter { + pub async fn new( + disk: DiskStore, + orig_volume: &str, + volume: &str, + file_path: &str, + length: usize, + algo: BitrotAlgorithm, + shard_size: usize, + ) -> Result { + let hasher = algo.new(); + let (tx, mut rx) = mpsc::channel::>>(10); + + let total_file_size = length.div_ceil(shard_size) * hasher.size() + length; + let mut writer = disk.create_file(orig_volume, volume, file_path, total_file_size).await?; + + let task = spawn(async move { + loop { + if let Some(Some(buf)) = rx.recv().await { + let _ = writer.write(&buf).await.unwrap(); + continue; + } + + break; + } + }); + + Ok(StreamingBitrotWriter { + hasher, + tx, + task: Some(task), + }) + } +} + +#[async_trait::async_trait] +impl Write for StreamingBitrotWriter { + fn as_any(&self) -> &dyn Any { + self + } + + async fn write(&mut self, buf: &[u8]) -> Result<()> { + if buf.is_empty() { + return Ok(()); + } + self.hasher.reset(); + self.hasher.update(&buf); + let hash_bytes = self.hasher.clone().finalize(); + let _ = self.tx.send(Some(hash_bytes)).await?; + let _ = self.tx.send(Some(buf.to_vec())).await?; + + Ok(()) + } + + async fn close(&mut self) -> Result<()> { + let _ = self.tx.send(None).await?; + if let Some(task) = self.task.take() { + let _ = task.await; // 等待任务完成 + } + Ok(()) + } +} + +#[derive(Debug)] +struct StreamingBitrotReader { + disk: DiskStore, + _data: Vec, + volume: String, + file_path: String, + till_offset: usize, + curr_offset: usize, + hasher: Hasher, + shard_size: usize, + buf: Vec, + hash_bytes: Vec, +} + +impl StreamingBitrotReader { + pub fn new( + disk: DiskStore, + data: &[u8], + volume: &str, + file_path: &str, + algo: BitrotAlgorithm, + till_offset: usize, + shard_size: usize, + ) -> Self { + let hasher = algo.new(); + Self { + disk, + _data: data.to_vec(), + volume: volume.to_string(), + file_path: file_path.to_string(), + till_offset: till_offset.div_ceil(shard_size) * hasher.size() + till_offset, + curr_offset: 0, + hash_bytes: Vec::with_capacity(hasher.size()), + hasher, + shard_size, + buf: Vec::new(), + } + } +} + +#[async_trait::async_trait] +impl ReadAt for StreamingBitrotReader { + async fn read_at(&mut self, offset: usize, length: usize) -> Result<(Vec, usize)> { + if offset % self.shard_size != 0 { + return Err(Error::new(DiskError::Unexpected)); + } + if self.buf.is_empty() { + self.curr_offset = offset; + let stream_offset = (offset / self.shard_size) * self.hasher.size() + offset; + let buf_len = self.till_offset - stream_offset; + let mut file = self.disk.read_file(&self.volume, &self.file_path).await?; + let (buf, _) = file.read_at(stream_offset, buf_len).await?; + self.buf = buf; + } + if offset != self.curr_offset { + return Err(Error::new(DiskError::Unexpected)); + } + + self.hash_bytes = self.buf.drain(0..self.hash_bytes.capacity()).collect(); + let buf = self.buf.drain(0..length).collect::>(); + self.hasher.reset(); + self.hasher.update(&buf); + let actual = self.hasher.clone().finalize(); + if actual != self.hash_bytes { + return Err(Error::new(DiskError::FileCorrupt)); + } + + let readed_len = buf.len(); + self.curr_offset += readed_len; + + Ok((buf, readed_len)) + } +} + +#[cfg(test)] +mod test { + use std::{collections::HashMap, fs}; + + use hex_simd::decode_to_vec; + use tempfile::TempDir; + + use crate::{ + bitrot::{new_bitrot_writer, BITROT_ALGORITHMS}, disk::{endpoint::Endpoint, error::DiskError, new_disk, DiskOption}, error::{Error, Result}, store_api::BitrotAlgorithm + }; + + use super::{bitrot_writer_sum, new_bitrot_reader}; + + #[test] + fn bitrot_self_test() -> Result<()> { + let mut checksums = HashMap::new(); + checksums.insert( + BitrotAlgorithm::SHA256, + "a7677ff19e0182e4d52e3a3db727804abc82a5818749336369552e54b838b004", + ); + checksums.insert(BitrotAlgorithm::BLAKE2b512, "e519b7d84b1c3c917985f544773a35cf265dcab10948be3550320d156bab612124a5ae2ae5a8c73c0eea360f68b0e28136f26e858756dbfe7375a7389f26c669"); + checksums.insert( + BitrotAlgorithm::HighwayHash256, + "c81c2386a1f565e805513d630d4e50ff26d11269b21c221cf50fc6c29d6ff75b", + ); + checksums.insert( + BitrotAlgorithm::HighwayHash256S, + "c81c2386a1f565e805513d630d4e50ff26d11269b21c221cf50fc6c29d6ff75b", + ); + + let iter = [ + BitrotAlgorithm::SHA256, + BitrotAlgorithm::BLAKE2b512, + BitrotAlgorithm::HighwayHash256, + ]; + + for algo in iter.iter() { + if !algo.available() || *algo != BitrotAlgorithm::HighwayHash256 { + continue; + } + let checksum = decode_to_vec(checksums.get(algo).unwrap()).unwrap(); + + let mut h = algo.new(); + let mut msg = Vec::with_capacity(h.size() * h.block_size()); + let mut sum = Vec::with_capacity(h.size()); + + for _ in (0..h.size() * h.block_size()).step_by(h.size()) { + h.update(&msg); + sum = h.finalize(); + msg.extend(sum.clone()); + h = algo.new(); + } + + if checksum != sum { + return Err(Error::new(DiskError::FileCorrupt)); + } + } + + Ok(()) + } + + #[tokio::test] + async fn test_all_bitrot_algorithms() -> Result<()> { + for algo in BITROT_ALGORITHMS.keys() { + test_bitrot_reader_writer_algo(algo.clone()).await?; + } + + Ok(()) + } + + async fn test_bitrot_reader_writer_algo(algo: BitrotAlgorithm) -> Result<()> { + let temp_dir = TempDir::new().unwrap().path().to_string_lossy().to_string(); + fs::create_dir_all(&temp_dir)?; + let volume = "testvol"; + let file_path = "testfile"; + + let ep = Endpoint::try_from(temp_dir.as_str())?; + let opt = DiskOption::default(); + let disk = new_disk(&ep, &opt).await?; + let _ = disk.make_volume(volume).await?; + let mut writer = new_bitrot_writer(disk.clone(), "", volume, file_path, 35, algo.clone(), 10).await?; + + let _ = writer.write(b"aaaaaaaaaa").await?; + let _ = writer.write(b"aaaaaaaaaa").await?; + let _ = writer.write(b"aaaaaaaaaa").await?; + let _ = writer.write(b"aaaaa").await?; + + let sum = bitrot_writer_sum(&writer); + let _ = writer.close().await?; + + let mut reader = new_bitrot_reader(disk, b"", volume, file_path, 35, algo, &sum, 10); + let read_len = 10; + let mut result: Vec; + (result, _) = reader.read_at(0, read_len).await?; + assert_eq!(result, b"aaaaaaaaaa"); + (result, _) = reader.read_at(10, read_len).await?; + assert_eq!(result, b"aaaaaaaaaa"); + (result, _) = reader.read_at(20, read_len).await?; + assert_eq!(result, b"aaaaaaaaaa"); + (result, _) = reader.read_at(30, read_len / 2).await?; + assert_eq!(result, b"aaaaa"); + + Ok(()) + } +} diff --git a/ecstore/src/cache_value/cache.rs b/ecstore/src/cache_value/cache.rs new file mode 100644 index 000000000..fa4367197 --- /dev/null +++ b/ecstore/src/cache_value/cache.rs @@ -0,0 +1,117 @@ +use std::{ + fmt::Debug, + ptr, + sync::{ + atomic::{AtomicPtr, AtomicU64, Ordering}, + Arc, + }, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +use tokio::{spawn, sync::Mutex}; + +use crate::error::Result; + +type UpdateFn = Box Result + Send + Sync>; + +#[derive(Clone, Debug, Default)] +pub struct Opts { + return_last_good: bool, + no_wait: bool, +} + +pub struct Cache { + update_fn: UpdateFn, + ttl: Duration, + opts: Opts, + val: AtomicPtr, + last_update_ms: AtomicU64, + updating: Arc>, +} + +impl Cache { + pub fn new(update_fn: UpdateFn, ttl: Duration, opts: Opts) -> Self { + let val = AtomicPtr::new(ptr::null_mut()); + Self { + update_fn, + ttl, + opts, + val, + last_update_ms: AtomicU64::new(0), + updating: Arc::new(Mutex::new(false)), + } + } + + pub async fn get(self: Arc) -> Result { + let v_ptr = self.val.load(Ordering::SeqCst); + let v = if v_ptr.is_null() { + None + } else { + Some(unsafe { (*v_ptr).clone() }) + }; + + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + if v.is_some() && now - self.last_update_ms.load(Ordering::SeqCst) < self.ttl.as_secs() { + return Ok(v.unwrap()); + } + + if self.opts.no_wait && v.is_some() && now - self.last_update_ms.load(Ordering::SeqCst) < self.ttl.as_secs() * 2 { + if self.updating.try_lock().is_ok() { + let this = Arc::clone(&self); + spawn(async move { + let _ = this.update().await; + }); + } + + return Ok(v.unwrap()); + } + + let _ = self.updating.lock().await; + + if let Ok(duration) = + SystemTime::now().duration_since(UNIX_EPOCH + Duration::from_secs(self.last_update_ms.load(Ordering::SeqCst))) + { + if duration < self.ttl { + return Ok(v.unwrap()); + } + } + + match self.update().await { + Ok(_) => { + let v_ptr = self.val.load(Ordering::SeqCst); + let v = if v_ptr.is_null() { + None + } else { + Some(unsafe { (*v_ptr).clone() }) + }; + Ok(v.unwrap()) + } + Err(err) => Err(err), + } + } + + async fn update(&self) -> Result<()> { + match (self.update_fn)() { + Ok(val) => { + self.val.store(Box::into_raw(Box::new(val)), Ordering::SeqCst); + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + self.last_update_ms.store(now, Ordering::SeqCst); + Ok(()) + } + Err(err) => { + let v_ptr = self.val.load(Ordering::SeqCst); + if self.opts.return_last_good && !v_ptr.is_null() { + return Ok(()); + } + + return Err(err); + } + } + } +} diff --git a/ecstore/src/cache_value/metacache_set.rs b/ecstore/src/cache_value/metacache_set.rs new file mode 100644 index 000000000..845f5630a --- /dev/null +++ b/ecstore/src/cache_value/metacache_set.rs @@ -0,0 +1,230 @@ +use std::sync::Arc; + +use tokio::{ + spawn, + sync::{ + broadcast::Receiver as B_Receiver, + mpsc::{self}, + RwLock, + }, +}; + +use crate::{ + disk::{DiskStore, MetaCacheEntries, MetaCacheEntry, WalkDirOptions}, + error::{Error, Result}, +}; + +#[derive(Default)] +pub struct ListPathRawOptions { + pub disks: Vec>, + pub fallback_disks: Vec>, + pub bucket: String, + pub path: String, + pub recursice: bool, + pub filter_prefix: String, + pub forward_to: String, + pub min_disks: usize, + pub report_not_found: bool, + pub per_disk_limit: i32, + pub agreed: Option>, + pub partial: Option]) + Send + Sync>>, + pub finished: Option]) + Send + Sync>>, +} + +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(), + recursice: self.recursice.clone(), + filter_prefix: self.filter_prefix.clone(), + forward_to: self.forward_to.clone(), + min_disks: self.min_disks.clone(), + report_not_found: self.report_not_found.clone(), + per_disk_limit: self.per_disk_limit.clone(), + ..Default::default() + } + } +} + +pub async fn list_path_raw(mut rx: B_Receiver, opts: ListPathRawOptions) -> Result<()> { + if opts.disks.is_empty() { + return Err(Error::from_string("list_path_raw: 0 drives provided")); + } + + let mut readers = Vec::with_capacity(opts.disks.len()); + let fds = Arc::new(RwLock::new(opts.fallback_disks.clone())); + for disk in opts.disks.iter() { + let disk = disk.clone(); + let opts_clone = opts.clone(); + let fds_clone = fds.clone(); + let (m_tx, m_rx) = mpsc::channel::(100); + readers.push(m_rx); + spawn(async move { + let mut need_fallback = false; + if disk.is_none() { + need_fallback = true; + } else { + match disk + .as_ref() + .unwrap() + .walk_dir(WalkDirOptions { + bucket: opts_clone.bucket.clone(), + base_dir: opts_clone.path.clone(), + recursive: opts_clone.recursice.clone(), + 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() + }) + .await + { + Ok(r) => { + for v in r.iter() { + let _ = m_tx.send(v.to_owned()).await; + } + } + Err(_) => need_fallback = true, + } + } + + while need_fallback { + let f_disk = loop { + let mut fds_w = fds_clone.write().await; + if fds_w.is_empty() { + break None; + } + let fd = fds_w.remove(0); + if fd.is_some() && fd.as_ref().unwrap().is_online().await { + break fd; + } + }; + if f_disk.is_none() { + break; + } + match disk + .as_ref() + .unwrap() + .walk_dir(WalkDirOptions { + bucket: opts_clone.bucket.clone(), + base_dir: opts_clone.path.clone(), + recursive: opts_clone.recursice, + 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() + }) + .await + { + Ok(r) => { + for v in r.iter() { + let _ = m_tx.send(v.to_owned()).await; + } + need_fallback = false; + } + Err(_) => break, + } + } + }); + } + + let errs: Vec> = Vec::with_capacity(readers.len()); + loop { + let mut current = MetaCacheEntry::default(); + let (mut at_eof, mut has_err, mut agree) = (0, 0, 0); + if rx.try_recv().is_ok() { + return Err(Error::from_string("canceled")); + } + let mut top_entries: Vec = Vec::with_capacity(readers.len()); + // top_entries.clear(); + + for (i, r) in readers.iter_mut().enumerate() { + if errs[i].is_none() { + has_err += 1; + continue; + } + let entry = match r.recv().await { + Some(entry) => entry, + None => { + at_eof += 1; + continue; + }, + }; + // If no current, add it. + if current.name.is_empty() { + top_entries.insert(i, entry.clone()); + current = entry; + agree += 1; + continue; + } + // If exact match, we agree. + if let Ok((_, true)) = current.matches(&entry, true) { + top_entries.insert(i, 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.insert(i, entry); + continue; + } + // We got different entries + if entry.name > current.name { + continue; + } + // We got a new, better current. + // Clear existing entries. + top_entries.clear(); + agree += 1; + top_entries.insert(i, entry.clone()); + current = entry; + } + + if has_err > 0 && has_err > opts.disks.len() - opts.min_disks { + if let Some(finished_fn) = opts.finished.clone() { + finished_fn(&errs); + } + 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::from_string(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.clone() { + finished_fn(&errs); + } + break; + } + } + + if agree == readers.len() { + if let Some(agreed_fn) = opts.agreed.clone() { + agreed_fn(current); + } + continue; + } + + if let Some(partial_fn) = opts.partial.clone() { + partial_fn(MetaCacheEntries(top_entries), &errs); + } + } + + Ok(()) +} diff --git a/ecstore/src/cache_value/mod.rs b/ecstore/src/cache_value/mod.rs new file mode 100644 index 000000000..0a4c430d1 --- /dev/null +++ b/ecstore/src/cache_value/mod.rs @@ -0,0 +1,2 @@ +pub mod cache; +pub mod metacache_set; diff --git a/ecstore/src/disk/endpoint.rs b/ecstore/src/disk/endpoint.rs index 78ae38a26..6a6ba743f 100644 --- a/ecstore/src/disk/endpoint.rs +++ b/ecstore/src/disk/endpoint.rs @@ -16,7 +16,7 @@ pub enum EndpointType { } /// any type of endpoint. -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct Endpoint { pub url: url::Url, pub is_local: bool, diff --git a/ecstore/src/disk/error.rs b/ecstore/src/disk/error.rs index 2a5414302..8fbfbf474 100644 --- a/ecstore/src/disk/error.rs +++ b/ecstore/src/disk/error.rs @@ -100,6 +100,12 @@ pub enum DiskError { #[error("more data was sent than what was advertised")] MoreData, + + #[error("outdated XL meta")] + OutdatedXLMeta, + + #[error("part missing or corrupt")] + PartMissingOrCorrupt, } impl DiskError { @@ -202,6 +208,8 @@ pub fn clone_disk_err(e: &DiskError) -> Error { 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), } } @@ -401,3 +409,20 @@ pub fn convert_access_error(e: io::Error, per_err: DiskError) -> Error { Error::new(e) } + +pub fn is_all_not_found(errs: &[Option]) -> bool { + for err in errs.iter() { + if let Some(err) = err { + if let Some(err) = err.downcast_ref::() { + match err { + DiskError::FileNotFound | DiskError::VolumeNotFound | &DiskError::FileVersionNotFound =>{ + continue; + }, + _ => return false, + } + } + } + } + + !errs.is_empty() +} diff --git a/ecstore/src/disk/local.rs b/ecstore/src/disk/local.rs index 6a28a8951..1450d0b46 100644 --- a/ecstore/src/disk/local.rs +++ b/ecstore/src/disk/local.rs @@ -1,9 +1,13 @@ use super::error::{is_sys_err_io, is_sys_err_not_empty, is_sys_err_too_many_files, os_is_not_exist, os_is_permission}; +use super::os::is_root_disk; use super::{endpoint::Endpoint, error::DiskError, format::FormatV3}; use super::{ - os, DeleteOptions, DiskAPI, DiskLocation, FileInfoVersions, FileReader, FileWriter, MetaCacheEntry, ReadMultipleReq, - ReadMultipleResp, ReadOptions, RenameDataResp, UpdateMetadataOpts, VolumeInfo, WalkDirOptions, + os, CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo, DiskInfoOptions, DiskLocation, DiskMetrics, FileInfoVersions, + FileReader, FileWriter, Info, MetaCacheEntry, ReadMultipleReq, ReadMultipleResp, ReadOptions, RenameDataResp, + UpdateMetadataOpts, VolumeInfo, WalkDirOptions, }; +use crate::bitrot::bitrot_verify; +use crate::cache_value::cache::{Cache, Opts}; use crate::disk::error::{ convert_access_error, is_sys_err_handle_invalid, is_sys_err_invalid_arg, is_sys_err_is_dir, is_sys_err_not_dir, map_err_not_exists, os_err_to_file_err, @@ -11,7 +15,11 @@ use crate::disk::error::{ use crate::disk::os::check_path_length; use crate::disk::{LocalFileReader, LocalFileWriter, STORAGE_FORMAT_FILE}; use crate::error::{Error, Result}; -use crate::utils::fs::{lstat, O_APPEND, O_CREATE, O_RDONLY, O_WRONLY}; +use crate::global::{GLOBAL_IsErasureSD, GLOBAL_RootDiskThreshold}; +use crate::set_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}; +use crate::store_api::BitrotAlgorithm; +use crate::utils::fs::{access, lstat, O_APPEND, O_CREATE, O_RDONLY, O_WRONLY}; +use crate::utils::os::get_info; use crate::utils::path::{clean, has_suffix, SLASH_SEPARATOR}; use crate::{ file_meta::FileMeta, @@ -19,6 +27,12 @@ use crate::{ utils, }; use path_absolutize::Absolutize; +use std::fmt::Debug; +use std::io::Cursor; +use std::os::unix::fs::MetadataExt; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Arc; +use std::time::Duration; use std::{ fs::Metadata, path::{Path, PathBuf}, @@ -26,8 +40,9 @@ use std::{ use time::OffsetDateTime; use tokio::fs::{self, File}; use tokio::io::{AsyncReadExt, AsyncWriteExt, ErrorKind}; +use tokio::runtime::Runtime; use tokio::sync::RwLock; -use tracing::{error, warn}; +use tracing::{error, info, warn}; use uuid::Uuid; #[derive(Debug)] @@ -48,18 +63,35 @@ impl FormatInfo { } } -#[derive(Debug)] pub struct LocalDisk { pub root: PathBuf, pub format_path: PathBuf, pub format_info: RwLock, pub endpoint: Endpoint, + pub disk_info_cache: Arc>, + pub scanning: AtomicU32, + pub rotational: bool, + pub fstype: String, + pub major: u64, + pub minor: u64, + pub nrrequests: u64, // pub id: Mutex>, // pub format_data: Mutex>, // pub format_file_info: Mutex>, // pub format_last_check: Mutex>, } +impl Debug for LocalDisk { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("LocalDisk") + .field("root", &self.root) + .field("format_path", &self.format_path) + .field("format_info", &self.format_info) + .field("endpoint", &self.endpoint) + .finish() + } +} + impl LocalDisk { pub async fn new(ep: &Endpoint, cleanup: bool) -> Result { let root = fs::canonicalize(ep.url.path()).await?; @@ -99,19 +131,75 @@ impl LocalDisk { file_info: format_meta, last_check: format_last_check, }; + let root_clone = root.clone(); + let disk_id = Arc::new(id.map_or("".to_string(), |id| id.to_string())); + let update_fn = move || { + let rt = Runtime::new().unwrap(); + rt.block_on(async { + match get_disk_info(root.clone()).await { + Ok((info, root)) => { + let disk_info = DiskInfo { + total: info.total, + free: info.free, + used: info.used, + used_inodes: info.files - info.ffree, + free_inodes: info.ffree, + major: info.major, + minor: info.minor, + fs_type: info.fstype, + root_disk: root, + id: disk_id.to_string(), + ..Default::default() + }; + // if root { + // return Err(Error::new(DiskError::DriveIsRoot)); + // } + + // disk_info.healing = + Ok(disk_info) + } + Err(err) => Err(err), + } + }) + }; + + let cache = Cache::new(Box::new(update_fn), Duration::from_secs(1), Opts::default()); // TODO: DIRECT suport // TODD: DiskInfo - let disk = Self { - root, + let mut disk = Self { + root: root_clone.clone(), endpoint: ep.clone(), format_path, format_info: RwLock::new(format_info), + disk_info_cache: Arc::new(cache), + scanning: AtomicU32::new(0), + rotational: Default::default(), + fstype: Default::default(), + minor: Default::default(), + major: Default::default(), + nrrequests: Default::default(), // // format_legacy, // format_file_info: Mutex::new(format_meta), // format_data: Mutex::new(format_data), // format_last_check: Mutex::new(format_last_check), }; + let (info, _root) = get_disk_info(root_clone).await?; + disk.major = info.major; + disk.minor = info.minor; + disk.fstype = info.fstype; + + // if root { + // return Err(Error::new(DiskError::DriveIsRoot)); + // } + + if info.nrrequests > 0 { + disk.nrrequests = info.nrrequests; + } + + if info.rotational { + disk.rotational = true; + } disk.make_meta_volumes().await?; @@ -560,6 +648,28 @@ impl LocalDisk { Ok(f) } + + #[allow(dead_code)] + fn get_metrics(&self) -> DiskMetrics { + DiskMetrics::default() + } + + async fn bitrot_verify( + &self, + part_path: &PathBuf, + part_size: usize, + algo: BitrotAlgorithm, + sum: &[u8], + shard_size: usize, + ) -> Result<()> { + let mut file = utils::fs::open_file(part_path, O_CREATE | O_WRONLY) + .await + .map_err(os_err_to_file_err)?; + + let mut data = Vec::new(); + let n = file.read_to_end(&mut data).await?; + bitrot_verify(&mut Cursor::new(data), n, part_size, algo, sum.to_vec(), shard_size) + } } fn is_root_path(path: impl AsRef) -> bool { @@ -756,6 +866,106 @@ impl DiskAPI for LocalDisk { Ok(()) } + async fn verify_file(&self, volume: &str, path: &str, fi: &FileInfo) -> Result { + let volume_dir = self.get_bucket_path(volume)?; + if !skip_access_checks(volume) { + if let Err(e) = utils::fs::access(&volume_dir).await { + return Err(convert_access_error(e, DiskError::VolumeAccessDenied)); + } + } + + let mut resp = CheckPartsResp { + results: Vec::with_capacity(fi.parts.len()), + }; + + let erasure = &fi.erasure; + for (i, part) in fi.parts.iter().enumerate() { + let checksum_info = erasure.get_checksum_info(part.number); + let part_path = Path::new(&volume_dir) + .join(path) + .join(fi.data_dir.map_or("".to_string(), |dir| dir.to_string())) + .join(format!("part.{}", part.number)); + let err = match self + .bitrot_verify( + &part_path, + erasure.shard_file_size(part.size), + checksum_info.algorithm, + &checksum_info.hash, + erasure.shard_size(erasure.block_size), + ) + .await + { + Ok(_) => None, + Err(err) => Some(err), + }; + resp.results[i] = conv_part_err_to_int(&err); + if resp.results[i] == CHECK_PART_UNKNOWN { + if let Some(err) = err { + match err.downcast_ref::() { + Some(DiskError::FileAccessDenied) => {} + _ => { + info!("part unknown, disk: {}, path: {:?}", self.to_string(), part_path); + } + } + } + } + } + + Ok(resp) + } + + async fn check_parts(&self, volume: &str, path: &str, fi: &FileInfo) -> Result { + let volume_dir = self.get_bucket_path(&volume)?; + check_path_length(volume_dir.join(path).to_string_lossy().as_ref())?; + let mut resp = CheckPartsResp { + results: vec![0; fi.parts.len()], + }; + + for (i, part) in fi.parts.iter().enumerate() { + let file_path = Path::new(&volume_dir) + .join(path) + .join(fi.data_dir.map_or("".to_string(), |dir| dir.to_string())) + .join(format!("part.{}", part.number)); + + match lstat(file_path).await { + Ok(st) => { + if st.is_dir() { + resp.results[i] = CHECK_PART_FILE_NOT_FOUND; + continue; + } + if (st.size() as usize) < fi.erasure.shard_file_size(part.size) { + resp.results[i] = CHECK_PART_FILE_CORRUPT; + continue; + } + + resp.results[i] = CHECK_PART_SUCCESS; + }, + Err(err) => { + match os_err_to_file_err(err).downcast_ref() { + Some(DiskError::FileNotFound) => { + if !skip_access_checks(volume) { + if let Err(err) = access(&volume_dir).await { + match err.kind() { + ErrorKind::NotFound => { + resp.results[i] = CHECK_PART_VOLUME_NOT_FOUND; + continue; + }, + _ => {}, + } + } + } + resp.results[i] = CHECK_PART_FILE_NOT_FOUND; + }, + _ => {}, + } + continue; + } + } + } + + Ok(resp) + } + async fn rename_part(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str, meta: Vec) -> Result<()> { let src_volume_dir = self.get_bucket_path(src_volume)?; let dst_volume_dir = self.get_bucket_path(dst_volume)?; @@ -1599,6 +1809,40 @@ impl DiskAPI for LocalDisk { Ok(()) } + + async fn disk_info(&self, _: &DiskInfoOptions) -> Result { + let mut info = Cache::get(self.disk_info_cache.clone()).await?; + // TODO: nr_requests, rotational + info.nr_requests = self.nrrequests; + info.rotational = self.rotational; + info.mount_path = self.path().to_str().unwrap().to_string(); + info.endpoint = self.endpoint.to_string(); + info.scanning = self.scanning.load(Ordering::SeqCst) == 1; + + Ok(info) + } +} + +async fn get_disk_info(drive_path: PathBuf) -> Result<(Info, bool)> { + let drive_path = drive_path.to_string_lossy().to_string(); + check_path_length(&drive_path)?; + + let disk_info = get_info(&drive_path, false)?; + let root_drive = if !*GLOBAL_IsErasureSD.read().await { + let root_disk_threshold = *GLOBAL_RootDiskThreshold.read().await; + if root_disk_threshold > 0 { + disk_info.total <= root_disk_threshold + } else { + match is_root_disk(&drive_path, SLASH_SEPARATOR) { + Ok(result) => result, + Err(_) => false, + } + } + } else { + false + }; + + Ok((disk_info, root_drive)) } #[cfg(test)] diff --git a/ecstore/src/disk/mod.rs b/ecstore/src/disk/mod.rs index f420168b9..7ce541ee6 100644 --- a/ecstore/src/disk/mod.rs +++ b/ecstore/src/disk/mod.rs @@ -16,7 +16,7 @@ const STORAGE_FORMAT_FILE: &str = "xl.meta"; use crate::{ erasure::{ReadAt, Write}, error::{Error, Result}, - file_meta::FileMeta, + file_meta::{merge_file_meta_versions, FileMeta, FileMetaShallowVersion}, store_api::{FileInfo, RawFileInfo}, }; @@ -26,7 +26,7 @@ use protos::proto_gen::node_service::{ node_service_client::NodeServiceClient, ReadAtRequest, ReadAtResponse, WriteRequest, WriteResponse, }; use serde::{Deserialize, Serialize}; -use std::{fmt::Debug, io::SeekFrom, path::PathBuf, sync::Arc}; +use std::{any::Any, cmp::Ordering, collections::HashMap, fmt::Debug, io::SeekFrom, path::PathBuf, sync::Arc, usize}; use time::OffsetDateTime; use tokio::{ fs::File, @@ -124,15 +124,23 @@ pub trait DiskAPI: Debug + Send + Sync + 'static { // 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) -> Result<()>; - // CheckParts async fn delete(&self, volume: &str, path: &str, opt: DeleteOptions) -> Result<()>; // VerifyFile + async fn verify_file(&self, volume: &str, path: &str, fi: &FileInfo) -> Result; + // CheckParts + async fn check_parts(&self, volume: &str, path: &str, fi: &FileInfo) -> Result; // StatInfoFile // ReadParts async fn read_multiple(&self, req: ReadMultipleReq) -> Result>; // CleanAbandonedData async fn write_all(&self, volume: &str, path: &str, data: Vec) -> Result<()>; async fn read_all(&self, volume: &str, path: &str) -> Result>; + async fn disk_info(&self, opts: &DiskInfoOptions) -> Result; +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct CheckPartsResp { + pub results: Vec, } #[derive(Debug, Serialize, Deserialize)] @@ -152,6 +160,60 @@ impl DiskLocation { } } +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct DiskInfoOptions { + pub disk_id: String, + pub metrics: bool, + pub noop: bool, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +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, Serialize, Deserialize)] +pub struct DiskMetrics { + api_calls: HashMap, + total_waiting: u32, + total_errors_availability: u64, + total_errors_timeout: u64, + total_writes: u64, + total_deletes: u64, +} + +#[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. @@ -195,7 +257,17 @@ pub struct WalkDirOptions { pub disk_id: String, } -#[derive(Debug, Default, Serialize, Deserialize)] +#[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>, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct MetaCacheEntry { // name is the full name of the object including prefixes pub name: String, @@ -263,6 +335,204 @@ impl MetaCacheEntry { return Ok(Some(fi)); } + + pub fn file_info_versions(&self, bucket: &str) -> Result { + 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)?; + + Ok(fm.into_file_info_versions(bucket, self.name.as_str(), false)?) + } + + pub fn matches(&self, other: &MetaCacheEntry, strict: bool) -> Result<(Option, bool)> { + let mut prefer = None; + if self.name != other.name { + if self.name < other.name { + return Ok((Some(self.clone()), false)); + } + return Ok((Some(other.clone()), false)); + } + + if other.is_dir() || self.is_dir() { + if self.is_dir() { + return Ok((Some(self.clone()), other.is_dir())); + } + + return Ok((Some(other.clone()), other.is_dir() == self.is_dir())); + } + let self_vers = match &self.cached { + Some(file_meta) => file_meta.clone(), + None => FileMeta::load(&self.metadata)?, + }; + let other_vers = match &other.cached { + Some(file_meta) => file_meta.clone(), + None => FileMeta::load(&other.metadata)?, + }; + + if self_vers.versions.len() != other_vers.versions.len() { + match self_vers.lastest_mod_time().cmp(&other_vers.lastest_mod_time()) { + Ordering::Greater => { + return Ok((Some(self.clone()), false)); + } + Ordering::Less => { + return Ok((Some(self.clone()), false)); + } + _ => {} + } + + if self_vers.versions.len() > other_vers.versions.len() { + return Ok((Some(self.clone()), false)); + } + return Ok((Some(self.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 Ok((prefer, false)); + } + + if s_version.header.sorts_before(&o_version.header) { + return Ok((Some(self.clone()), false)); + } + + return Ok((Some(other.clone()), false)); + } + } + + if prefer.is_none() { + prefer = Some(self.clone()); + } + + Ok((prefer, true)) + } +} + +pub struct MetaCacheEntries(pub Vec); + +impl MetaCacheEntries { + pub fn resolve(&self, mut params: MetadataResolutionParams) -> Result> { + if self.0.is_empty() { + return Ok(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() { + if entry.name.is_empty() { + continue; + } + if entry.is_dir() { + dir_exists += 1; + selected = Some(entry.clone()); + continue; + } + + objs_valid += 1; + + match &entry.cached { + Some(file_meta) => { + params.candidates.push(file_meta.versions.clone()); + } + None => { + params.candidates.push(FileMeta::load(&entry.metadata)?.versions); + } + } + + if selected.is_none() { + selected = Some(entry.clone()); + objs_agree = 1; + continue; + } + + if let (Some(prefer), true) = entry.matches(selected.as_ref().unwrap(), params.strict)? { + selected = Some(prefer); + objs_agree += 1; + continue; + } + } + + // Return dir entries, if enough... + if selected.is_some() && selected.as_ref().unwrap().is_dir() && dir_exists >= params.dir_quorum { + return Ok(selected); + } + // If we would never be able to reach read quorum. + if objs_valid < params.obj_quorum { + return Ok(None); + } + // If all objects agree. + if selected.is_some() && objs_agree == objs_valid { + return Ok(selected); + } + // If cached is nil we shall skip the entry. + if selected.is_none() || (selected.is_some() && selected.as_ref().unwrap().cached.is_none()) { + return Ok(None); + } + // Merge if we have disagreement. + // Create a new merged result. + selected = Some(MetaCacheEntry { + name: selected.as_ref().unwrap().name.clone(), + cached: Some(FileMeta { + meta_ver: selected.as_ref().unwrap().cached.as_ref().unwrap().meta_ver.clone(), + ..Default::default() + }), + _reusable: true, + ..Default::default() + }); + + selected.as_mut().unwrap().cached.as_mut().unwrap().versions = + merge_file_meta_versions(params.obj_quorum, params.strict, params.requested_versions, ¶ms.candidates); + if selected.as_ref().unwrap().cached.as_ref().unwrap().versions.is_empty() { + return Ok(None); + } + + selected.as_mut().unwrap().metadata = selected.as_ref().unwrap().cached.as_ref().unwrap().marshal_msg()?; + + Ok(selected) + } + + pub fn first_found(&self) -> (Option, usize) { + (self.0.iter().find(|x| !x.name.is_empty()).cloned(), self.0.len()) + } } #[derive(Debug, Default)] @@ -378,6 +648,10 @@ pub enum FileWriter { #[async_trait::async_trait] impl Write for FileWriter { + fn as_any(&self) -> &dyn Any { + self + } + async fn write(&mut self, buf: &[u8]) -> Result<()> { match self { Self::Local(local_file_writer) => local_file_writer.write(buf).await, @@ -398,6 +672,10 @@ impl LocalFileWriter { #[async_trait::async_trait] impl Write for LocalFileWriter { + fn as_any(&self) -> &dyn Any { + self + } + async fn write(&mut self, buf: &[u8]) -> Result<()> { let _ = self.inner.write(buf).await?; self.inner.flush().await?; @@ -446,6 +724,10 @@ impl RemoteFileWriter { #[async_trait::async_trait] impl Write for RemoteFileWriter { + fn as_any(&self) -> &dyn Any { + self + } + async fn write(&mut self, buf: &[u8]) -> Result<()> { let request = WriteRequest { disk: self.root.to_string_lossy().to_string(), diff --git a/ecstore/src/disk/os.rs b/ecstore/src/disk/os.rs index 367a36ccd..033cbb9e9 100644 --- a/ecstore/src/disk/os.rs +++ b/ecstore/src/disk/os.rs @@ -8,7 +8,7 @@ use tokio::fs; use crate::{ disk::error::{is_sys_err_not_dir, is_sys_err_path_not_found, os_is_not_exist}, error::{Error, Result}, - utils, + utils::{self, os::same_disk}, }; use super::error::{os_err_to_file_err, os_is_exist, DiskError}; @@ -51,6 +51,14 @@ pub fn check_path_length(path_name: &str) -> Result<()> { Ok(()) } +pub fn is_root_disk(disk_path: &str, root_disk: &str) -> Result { + if cfg!(target_os = "windows") { + return Ok(false); + } + + same_disk(disk_path, root_disk) +} + pub async fn make_dir_all(path: impl AsRef, base_dir: impl AsRef) -> Result<()> { check_path_length(path.as_ref().to_string_lossy().to_string().as_str())?; diff --git a/ecstore/src/disk/remote.rs b/ecstore/src/disk/remote.rs index 43fbd4086..496aa575e 100644 --- a/ecstore/src/disk/remote.rs +++ b/ecstore/src/disk/remote.rs @@ -4,10 +4,7 @@ use futures::lock::Mutex; use protos::{ node_service_time_out_client, proto_gen::node_service::{ - DeletePathsRequest, DeleteRequest, DeleteVersionRequest, DeleteVersionsRequest, DeleteVolumeRequest, ListDirRequest, - ListVolumesRequest, MakeVolumeRequest, MakeVolumesRequest, ReadAllRequest, ReadMultipleRequest, ReadVersionRequest, - ReadXlRequest, RenameDataRequest, RenameFileRequst, StatVolumeRequest, UpdateMetadataRequest, WalkDirRequest, - WriteAllRequest, WriteMetadataRequest, + CheckPartsRequest, DeletePathsRequest, DeleteRequest, DeleteVersionRequest, DeleteVersionsRequest, DeleteVolumeRequest, DiskInfoRequest, ListDirRequest, ListVolumesRequest, MakeVolumeRequest, MakeVolumesRequest, ReadAllRequest, ReadMultipleRequest, ReadVersionRequest, ReadXlRequest, RenameDataRequest, RenameFileRequst, StatVolumeRequest, UpdateMetadataRequest, VerifyFileRequest, WalkDirRequest, WriteAllRequest, WriteMetadataRequest }, }; use tonic::Request; @@ -15,9 +12,7 @@ use tracing::info; use uuid::Uuid; use super::{ - endpoint::Endpoint, DeleteOptions, DiskAPI, DiskLocation, DiskOption, FileInfoVersions, FileReader, FileWriter, - MetaCacheEntry, ReadMultipleReq, ReadMultipleResp, ReadOptions, RemoteFileReader, RemoteFileWriter, RenameDataResp, - UpdateMetadataOpts, VolumeInfo, WalkDirOptions, + endpoint::Endpoint, CheckPartsResp, DeleteOptions, DiskAPI, DiskInfo, DiskInfoOptions, DiskLocation, DiskOption, FileInfoVersions, FileReader, FileWriter, MetaCacheEntry, ReadMultipleReq, ReadMultipleResp, ReadOptions, RemoteFileReader, RemoteFileWriter, RenameDataResp, UpdateMetadataOpts, VolumeInfo, WalkDirOptions }; use crate::{ disk::error::DiskError, @@ -163,6 +158,55 @@ impl DiskAPI for RemoteDisk { Ok(()) } + + async fn verify_file(&self, volume: &str, path: &str, fi: &FileInfo) -> Result { + 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::from_string(format!("can not get client, err: {}", err)))?; + let request = Request::new(VerifyFileRequest { + disk: self.root.to_string_lossy().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(Error::from_string(response.error_info.unwrap_or("".to_string()))); + } + + let check_parts_resp = serde_json::from_str::(&response.check_parts_resp)?; + + Ok(check_parts_resp) + } + + async fn check_parts(&self, volume: &str, path: &str, fi: &FileInfo) -> Result { + 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::from_string(format!("can not get client, err: {}", err)))?; + let request = Request::new(CheckPartsRequest { + disk: self.root.to_string_lossy().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(Error::from_string(response.error_info.unwrap_or("".to_string()))); + } + + let check_parts_resp = serde_json::from_str::(&response.check_parts_resp)?; + + Ok(check_parts_resp) + } + async fn rename_part(&self, src_volume: &str, src_path: &str, dst_volume: &str, dst_path: &str, meta: Vec) -> Result<()> { info!("rename_part"); let mut client = node_service_time_out_client(&self.addr) @@ -658,4 +702,26 @@ impl DiskAPI for RemoteDisk { Ok(()) } + + async fn disk_info(&self, opts: &DiskInfoOptions) -> Result { + info!("delete_volume"); + let opts = serde_json::to_string(&opts)?; + let mut client = node_service_time_out_client(&self.addr) + .await + .map_err(|err| Error::from_string(format!("can not get client, err: {}", err)))?; + let request = Request::new(DiskInfoRequest { + disk: self.root.to_string_lossy().to_string(), + opts, + }); + + let response = client.disk_info(request).await?.into_inner(); + + if !response.success { + return Err(Error::from_string(response.error_info.unwrap_or("".to_string()))); + } + + let disk_info = serde_json::from_str::(&response.disk_info)?; + + Ok(disk_info) + } } diff --git a/ecstore/src/erasure.rs b/ecstore/src/erasure.rs index aae5ea128..9ef335a1a 100644 --- a/ecstore/src/erasure.rs +++ b/ecstore/src/erasure.rs @@ -1,9 +1,11 @@ +use crate::bitrot::{close_bitrot_writers, BitrotReader, BitrotWriter}; use crate::error::{Error, Result, StdError}; use crate::quorum::{object_op_ignored_errs, reduce_write_quorum_errs}; use bytes::Bytes; use futures::future::join_all; use futures::{Stream, StreamExt}; use reed_solomon_erasure::galois_8::ReedSolomon; +use std::any::Any; use std::fmt::Debug; use tokio::io::AsyncWriteExt; use tokio::io::DuplexStream; @@ -14,13 +16,12 @@ use uuid::Uuid; use crate::chunk_stream::ChunkedStream; use crate::disk::error::DiskError; -use crate::disk::{FileReader, FileWriter}; pub struct Erasure { data_shards: usize, parity_shards: usize, encoder: Option, - block_size: usize, + pub block_size: usize, _id: Uuid, } @@ -47,7 +48,7 @@ impl Erasure { pub async fn encode( &self, body: S, - writers: &mut [Option], + writers: &mut [Option], // block_size: usize, total_size: usize, write_quorum: usize, @@ -110,6 +111,8 @@ impl Erasure { // debug!(" encode_data done shard block num {}", idx); + let _ = close_bitrot_writers(writers).await?; + Ok(total) // loop { @@ -123,7 +126,7 @@ impl Erasure { pub async fn decode( &self, writer: &mut DuplexStream, - readers: Vec>, + readers: Vec>, offset: usize, length: usize, total_length: usize, @@ -301,7 +304,7 @@ impl Erasure { } // 算出每个分片大小 - fn shard_size(&self, data_size: usize) -> usize { + pub fn shard_size(&self, data_size: usize) -> usize { (data_size + self.data_shards - 1) / self.data_shards } // returns final erasure size from original size. @@ -321,21 +324,37 @@ impl Erasure { // } // num_shards * self.shard_size(self.block_size) } + + pub fn shard_file_offset(&self, start_offset: usize, length: usize, total_length: usize) -> usize { + let shard_size = self.shard_size(self.block_size); + let shard_file_size = self.shard_file_size(total_length); + let end_shard = (start_offset + length) / self.block_size; + let mut till_offset = end_shard * shard_size + shard_size; + if till_offset > shard_file_size { + till_offset = shard_file_size; + } + + till_offset + } } #[async_trait::async_trait] pub trait Write { + fn as_any(&self) -> &dyn Any; async fn write(&mut self, buf: &[u8]) -> Result<()>; + async fn close(&mut self) -> Result<()> { + Ok(()) + } } #[async_trait::async_trait] -pub trait ReadAt { +pub trait ReadAt: Debug { async fn read_at(&mut self, offset: usize, length: usize) -> Result<(Vec, usize)>; } #[derive(Debug)] pub struct ShardReader { - readers: Vec>, // 磁盘 + readers: Vec>, // 磁盘 data_block_count: usize, // 总的分片数量 parity_block_count: usize, shard_size: usize, // 每个分片的块大小 一次读取一块 @@ -344,7 +363,7 @@ pub struct ShardReader { } impl ShardReader { - pub fn new(readers: Vec>, ec: &Erasure, offset: usize, total_length: usize) -> Self { + pub fn new(readers: Vec>, ec: &Erasure, offset: usize, total_length: usize) -> Self { Self { readers, data_block_count: ec.data_shards, @@ -381,7 +400,7 @@ impl ShardReader { continue; } - let disk: &mut FileReader = disk.as_mut().unwrap(); + let disk: &mut BitrotReader = disk.as_mut().unwrap(); futures.push(disk.read_at(self.offset, read_length)); } diff --git a/ecstore/src/global.rs b/ecstore/src/global.rs index a5735544e..c263e0f55 100644 --- a/ecstore/src/global.rs +++ b/ecstore/src/global.rs @@ -11,12 +11,13 @@ use crate::{ lazy_static! { pub static ref GLOBAL_OBJECT_API: Arc>> = Arc::new(RwLock::new(None)); pub static ref GLOBAL_LOCAL_DISK: Arc>>> = Arc::new(RwLock::new(Vec::new())); - static ref GLOBAL_IsErasure: RwLock = RwLock::new(false); - static ref GLOBAL_IsDistErasure: RwLock = RwLock::new(false); - static ref GLOBAL_IsErasureSD: RwLock = RwLock::new(false); + pub static ref GLOBAL_IsErasure: RwLock = RwLock::new(false); + pub static ref GLOBAL_IsDistErasure: RwLock = RwLock::new(false); + pub static ref GLOBAL_IsErasureSD: RwLock = RwLock::new(false); pub static ref GLOBAL_LOCAL_DISK_MAP: Arc>>> = Arc::new(RwLock::new(HashMap::new())); pub static ref GLOBAL_LOCAL_DISK_SET_DRIVES: Arc> = Arc::new(RwLock::new(Vec::new())); pub static ref GLOBAL_Endpoints: RwLock = RwLock::new(EndpointServerPools(Vec::new())); + pub static ref GLOBAL_RootDiskThreshold: RwLock = RwLock::new(0); } pub async fn set_global_endpoints(eps: Vec) { diff --git a/ecstore/src/heal/background_heal_ops.rs b/ecstore/src/heal/background_heal_ops.rs new file mode 100644 index 000000000..136d56836 --- /dev/null +++ b/ecstore/src/heal/background_heal_ops.rs @@ -0,0 +1,89 @@ +use std::sync::Arc; + +use tokio::{ + select, + sync::{ + broadcast::Receiver as B_Receiver, + mpsc::{self, Receiver, Sender}, + }, +}; + +use crate::{ + error::Error, + heal::heal_ops::NOP_HEAL, + utils::path::SLASH_SEPARATOR, +}; + +use super::{ + heal_commands::{HealOpts, HealResultItem}, + heal_ops::HealSequence, +}; + +#[derive(Clone, Debug)] +pub struct HealTask { + pub bucket: String, + pub object: String, + pub version_id: String, + pub opts: HealOpts, + pub resp_tx: Arc>, + pub resp_rx: Arc>, +} + +impl HealTask { + pub fn new(bucket: &str, object: &str, version_id: &str, opts: &HealOpts) -> Self { + let (tx, rx) = mpsc::channel(10); + Self { + bucket: bucket.to_string(), + object: object.to_string(), + version_id: version_id.to_string(), + opts: opts.clone(), + resp_tx: tx.into(), + resp_rx: rx.into(), + } + } +} + +pub struct HealResult { + pub result: HealResultItem, + err: Error, +} + +pub struct HealRoutine { + tasks_tx: Sender, + tasks_rx: Receiver, + workers: usize, +} + +impl HealRoutine { + pub async fn add_worker(&mut self, mut ctx: B_Receiver, bgseq: &HealSequence) { + loop { + select! { + task = self.tasks_rx.recv() => { + let mut res = HealResultItem::default(); + let mut err: Error; + match task { + Some(task) => { + if task.bucket == NOP_HEAL { + err = Error::from_string("skip file"); + } else if task.bucket == SLASH_SEPARATOR { + (res, err) = heal_disk_format(task.opts).await; + } + }, + None => return, + } + } + _ = ctx.recv() => { + return; + } + } + } + } +} + +// pub fn active_listeners() -> Result { + +// } + +async fn heal_disk_format(opts: HealOpts) -> (HealResultItem, Error) { + todo!() +} diff --git a/ecstore/src/heal/heal_commands.rs b/ecstore/src/heal/heal_commands.rs new file mode 100644 index 000000000..29d9bd550 --- /dev/null +++ b/ecstore/src/heal/heal_commands.rs @@ -0,0 +1,466 @@ +use std::{ + path::Path, + time::{SystemTime, UNIX_EPOCH}, +}; + +use serde::{Deserialize, Serialize}; +use tokio::sync::RwLock; + +use crate::{ + disk::{DeleteOptions, DiskStore, BUCKET_META_PREFIX, RUSTFS_META_BUCKET}, + error::{Error, Result}, + heal::heal_ops::HEALING_TRACKER_FILENAME, + new_object_layer_fn, + store_api::{BucketInfo, StorageAPI}, + utils::fs::read_file, +}; + +pub type HealScanMode = usize; +pub type HealItemType = String; + +pub const HEAL_UNKNOWN_SCAN: HealScanMode = 0; +pub const HEAL_NORMAL_SCAN: HealScanMode = 1; +pub const HEAL_DEEP_SCAN: HealScanMode = 2; + +pub const HEAL_ITEM_METADATA: &str = "metadata"; +pub const HEAL_ITEM_BUCKET: &str = "bucket"; +pub const HEAL_ITEM_BUCKET_METADATA: &str = "bucket-metadata"; +pub const HEAL_ITEM_OBJECT: &str = "object"; + +pub const DRIVE_STATE_OK: &str = "ok"; +pub const DRIVE_STATE_OFFLINE: &str = "offline"; +pub const DRIVE_STATE_CORRUPT: &str = "corrupt"; +pub const DRIVE_STATE_MISSING: &str = "missing"; +pub const DRIVE_STATE_PERMISSION: &str = "permission-denied"; +pub const DRIVE_STATE_FAULTY: &str = "faulty"; +pub const DRIVE_STATE_ROOT_MOUNT: &str = "root-mount"; +pub const DRIVE_STATE_UNKNOWN: &str = "unknown"; +pub const DRIVE_STATE_UNFORMATTED: &str = "unformatted"; // only returned by disk + +#[derive(Clone, Copy, Debug, Default)] +pub struct HealOpts { + pub recursive: bool, + pub dry_run: bool, + pub remove: bool, + pub recreate: bool, + pub scan_mode: HealScanMode, + pub update_parity: bool, + pub no_lock: bool, + pub pool: Option, + pub set: Option, +} + +#[derive(Clone, Debug)] +pub struct HealDriveInfo { + pub uuid: String, + pub endpoint: String, + pub state: String, +} + +#[derive(Clone, Debug, Default)] +pub struct HealResultItem { + pub result_index: usize, + pub heal_item_type: HealItemType, + pub bucket: String, + pub object: String, + pub version_id: String, + pub detail: String, + pub parity_blocks: usize, + pub data_blocks: usize, + pub disk_count: usize, + pub set_count: usize, + pub before: Vec, + pub after: Vec, + pub object_size: usize, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct HealStartSuccess { + pub client_token: String, + pub client_address: String, + pub start_time: u64, +} + +pub type HealStopSuccess = HealStartSuccess; + +pub struct HealingDisk { + pub id: String, + pub heal_id: String, + pub pool_index: Option, + pub set_index: Option, + pub disk_index: Option, + pub endpoint: String, + pub path: String, + pub started: u64, + pub last_update: u64, + pub retry_attempts: u64, + pub objects_total_count: u64, + pub objects_total_size: u64, + pub items_healed: u64, + pub items_failed: u64, + pub item_skipped: u64, + pub bytes_done: u64, + pub bytes_failed: u64, + pub bytes_skipped: u64, + pub objects_healed: u64, + pub objects_failed: u64, + pub bucket: String, + pub object: String, + pub queue_buckets: Vec, + pub healed_buckets: Vec, + pub finished: bool, +} + +#[derive(Debug, Default, Deserialize, Serialize)] +pub struct HealingTracker { + #[serde(skip_serializing, skip_deserializing)] + pub disk: Option, + pub id: String, + pub pool_index: Option, + pub set_index: Option, + pub disk_index: Option, + pub path: String, + pub endpoint: String, + pub started: u64, + pub last_update: u64, + pub objects_total_count: u64, + pub objects_total_size: u64, + pub items_healed: u64, + pub items_failed: u64, + pub item_skipped: u64, + pub bytes_done: u64, + pub bytes_failed: u64, + pub bytes_skipped: u64, + pub bucket: String, + pub object: String, + pub resume_items_healed: u64, + pub resume_items_failed: u64, + pub resume_items_skipped: u64, + pub resume_bytes_done: u64, + pub resume_bytes_failed: u64, + pub resume_bytes_skipped: u64, + pub queue_buckets: Vec, + pub healed_buckets: Vec, + pub heal_id: String, + pub retry_attempts: u64, + pub finished: bool, + #[serde(skip_serializing, skip_deserializing)] + pub mu: RwLock, +} + +impl HealingTracker { + pub fn marshal_msg(&self) -> Result> { + serde_json::to_string(self) + .map(|s| s.as_bytes().to_vec()) + .map_err(|err| Error::from_string(err.to_string())) + } + + pub fn unmarshal_msg(data: &[u8]) -> Result { + serde_json::from_slice::(data).map_err(|err| Error::from_string(err.to_string())) + } + + pub async fn reset_healing(&mut self) { + let _ = self.mu.write().await; + self.items_healed = 0; + self.items_failed = 0; + self.bytes_done = 0; + self.bytes_failed = 0; + self.resume_items_healed = 0; + self.resume_items_failed = 0; + self.resume_bytes_done = 0; + self.resume_bytes_failed = 0; + self.item_skipped = 0; + self.bytes_skipped = 0; + + self.healed_buckets = Vec::new(); + self.bucket = String::new(); + self.object = String::new(); + } + + pub async fn get_last_update(&self) -> u64 { + let _ = self.mu.read().await; + + self.last_update + } + + pub async fn get_bucket(&self) -> String { + let _ = self.mu.read().await; + + self.bucket.clone() + } + + pub async fn set_bucket(&mut self, bucket: &str) { + let _ = self.mu.write().await; + + self.bucket = bucket.to_string(); + } + + pub async fn get_object(&self) -> String { + let _ = self.mu.read().await; + + self.object.clone() + } + + pub async fn set_object(&mut self, object: &str) { + let _ = self.mu.write().await; + + self.object = object.to_string(); + } + + pub async fn update_progress(&mut self, success: bool, skipped: bool, by: u64) { + let _ = self.mu.write().await; + + if success { + self.items_healed += 1; + self.bytes_done += by; + } else if skipped { + self.item_skipped += 1; + self.bytes_skipped += by; + } else { + self.items_failed += 1; + self.bytes_failed += by; + } + } + + pub async fn update(&mut self) -> Result<()> { + if let Some(disk) = &self.disk { + if healing(&disk.path().to_string_lossy().to_string()).await?.is_none() { + return Err(Error::from_string(format!("healingTracker: drive {} is not marked as healing", self.id))); + } + let _ = self.mu.write().await; + if self.id.is_empty() || self.pool_index.is_none() || self.set_index.is_none() || self.disk_index.is_none() { + self.id = disk.get_disk_id().await?.map_or("".to_string(), |id| id.to_string()); + let disk_location = disk.get_disk_location(); + self.pool_index = disk_location.pool_idx; + self.set_index = disk_location.set_idx; + self.disk_index = disk_location.disk_idx; + } + } + + self.save().await + } + + pub async fn save(&mut self) -> Result<()> { + let _ = self.mu.write().await; + if self.pool_index.is_none() || self.set_index.is_none() || self.disk_index.is_none() { + let layer = new_object_layer_fn(); + let lock = layer.read().await; + let store = match lock.as_ref() { + Some(s) => s, + None => return Err(Error::from_string("Not init".to_string())), + }; + (self.pool_index, self.set_index, self.disk_index) = store.get_pool_and_set(&self.id).await?; + } + + self.last_update = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + + let htracker_bytes = self.marshal_msg()?; + + // TODO: globalBackgroundHealState + + if let Some(disk) = &self.disk { + let file_path = Path::new(BUCKET_META_PREFIX).join(HEALING_TRACKER_FILENAME); + return disk + .write_all(RUSTFS_META_BUCKET, file_path.to_str().unwrap(), htracker_bytes) + .await; + } + Ok(()) + } + + async fn delete(&self) -> Result<()> { + if let Some(disk) = &self.disk { + let file_path = Path::new(BUCKET_META_PREFIX).join(HEALING_TRACKER_FILENAME); + return disk + .delete( + RUSTFS_META_BUCKET, + file_path.to_str().unwrap(), + DeleteOptions { + recursive: false, + immediate: false, + ..Default::default() + }, + ) + .await; + } + + Ok(()) + } + + async fn is_healed(&self, bucket: &str) -> bool { + let _ = self.mu.read().await; + for v in self.healed_buckets.iter() { + if v == bucket { + return true; + } + } + + false + } + + async fn resume(&mut self) { + let _ = self.mu.write().await; + + self.items_healed = self.resume_items_healed; + self.items_failed = self.resume_items_failed; + self.item_skipped = self.resume_items_skipped; + self.bytes_done = self.resume_bytes_done; + self.bytes_failed = self.resume_bytes_failed; + self.bytes_skipped = self.resume_bytes_skipped; + } + + async fn bucket_done(&mut self, bucket: &str) { + let _ = self.mu.write().await; + + self.resume_items_healed = self.items_healed; + self.resume_items_failed = self.items_failed; + self.resume_items_skipped = self.item_skipped; + self.resume_bytes_done = self.bytes_done; + self.resume_bytes_failed = self.bytes_failed; + self.resume_bytes_skipped = self.bytes_skipped; + self.healed_buckets.push(bucket.to_string()); + + self.queue_buckets.retain(|x| x != bucket); + } + + async fn set_queue_buckets(&mut self, buckets: &[BucketInfo]) { + let _ = self.mu.write().await; + + buckets.iter().for_each(|bucket| { + if !self.healed_buckets.contains(&bucket.name) { + self.queue_buckets.push(bucket.name.clone()); + } + }); + } + + pub async fn to_healing_disk(&self) -> HealingDisk { + let _ = self.mu.read().await; + + HealingDisk { + id: self.id.clone(), + heal_id: self.heal_id.clone(), + pool_index: self.pool_index, + set_index: self.set_index, + disk_index: self.disk_index, + endpoint: self.endpoint.clone(), + path: self.path.clone(), + started: self.started, + last_update: self.last_update, + retry_attempts: self.retry_attempts, + objects_total_count: self.objects_total_count, + objects_total_size: self.objects_total_size, + items_healed: self.items_healed, + items_failed: self.items_failed, + item_skipped: self.item_skipped, + bytes_done: self.bytes_done, + bytes_failed: self.bytes_failed, + bytes_skipped: self.bytes_skipped, + objects_healed: self.items_healed, + objects_failed: self.items_failed, + bucket: self.bucket.clone(), + object: self.object.clone(), + queue_buckets: self.queue_buckets.clone(), + healed_buckets: self.healed_buckets.clone(), + finished: self.finished, + } + } +} + +impl Clone for HealingTracker { + fn clone(&self) -> Self { + Self { + disk: self.disk.clone(), + id: self.id.clone(), + pool_index: self.pool_index.clone(), + set_index: self.set_index.clone(), + disk_index: self.disk_index.clone(), + path: self.path.clone(), + endpoint: self.endpoint.clone(), + started: self.started.clone(), + last_update: self.last_update.clone(), + objects_total_count: self.objects_total_count.clone(), + objects_total_size: self.objects_total_size.clone(), + items_healed: self.items_healed.clone(), + items_failed: self.items_failed.clone(), + item_skipped: self.item_skipped.clone(), + bytes_done: self.bytes_done.clone(), + bytes_failed: self.bytes_failed.clone(), + bytes_skipped: self.bytes_skipped.clone(), + bucket: self.bucket.clone(), + object: self.object.clone(), + resume_items_healed: self.resume_items_healed.clone(), + resume_items_failed: self.resume_items_failed.clone(), + resume_items_skipped: self.resume_items_skipped.clone(), + resume_bytes_done: self.resume_bytes_done.clone(), + resume_bytes_failed: self.resume_bytes_failed.clone(), + resume_bytes_skipped: self.resume_bytes_skipped.clone(), + queue_buckets: self.queue_buckets.clone(), + healed_buckets: self.healed_buckets.clone(), + heal_id: self.heal_id.clone(), + retry_attempts: self.retry_attempts.clone(), + finished: self.finished.clone(), + mu: RwLock::new(false), + } + } +} + +async fn load_healing_tracker(disk: &Option) -> Result { + if let Some(disk) = disk { + let disk_id = disk.get_disk_id().await?; + if let Some(disk_id) = disk_id { + let disk_id = disk_id.to_string(); + let file_path = Path::new(BUCKET_META_PREFIX).join(HEALING_TRACKER_FILENAME); + let data = disk.read_all(RUSTFS_META_BUCKET, file_path.to_str().unwrap()).await?; + let mut healing_tracker = HealingTracker::unmarshal_msg(&data)?; + if healing_tracker.id != disk_id && !healing_tracker.id.is_empty() { + return Err(Error::from_string(format!( + "loadHealingTracker: drive id mismatch expected {}, got {}", + healing_tracker.id, disk_id + ))); + } + healing_tracker.id = disk_id; + return Ok(healing_tracker); + } else { + return Err(Error::from_string("loadHealingTracker: disk not have id")); + } + } else { + return Err(Error::from_string("loadHealingTracker: nil drive given")); + } +} + +async fn init_healing_tracker(disk: DiskStore, heal_id: String) -> Result { + let mut healing_tracker = HealingTracker::default(); + healing_tracker.id = disk.get_disk_id().await?.map_or("".to_string(), |id| id.to_string()); + healing_tracker.heal_id = heal_id; + healing_tracker.path = disk.to_string(); + healing_tracker.endpoint = disk.endpoint().to_string(); + healing_tracker.started = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + let disk_location = disk.get_disk_location(); + healing_tracker.pool_index = disk_location.pool_idx; + healing_tracker.set_index = disk_location.set_idx; + healing_tracker.disk_index = disk_location.disk_idx; + healing_tracker.disk = Some(disk); + + Ok(healing_tracker) +} + +pub async fn healing(derive_path: &str) -> Result> { + let healing_file = Path::new(derive_path) + .join(RUSTFS_META_BUCKET) + .join(BUCKET_META_PREFIX) + .join(HEALING_TRACKER_FILENAME); + + let b = read_file(healing_file).await?; + if b.is_empty() { + return Ok(None); + } + + let healing_tracker = HealingTracker::unmarshal_msg(&b)?; + + Ok(Some(healing_tracker)) +} diff --git a/ecstore/src/heal/heal_ops.rs b/ecstore/src/heal/heal_ops.rs new file mode 100644 index 000000000..c75a15e4b --- /dev/null +++ b/ecstore/src/heal/heal_ops.rs @@ -0,0 +1,553 @@ +use crate::{ + disk::{endpoint::Endpoint, MetaCacheEntry}, + endpoints::Endpoints, + error::{Error, Result}, + global::GLOBAL_IsDistErasure, + heal::heal_commands::HEAL_UNKNOWN_SCAN, + utils::path::has_profix, +}; +use lazy_static::lazy_static; +use std::{ + collections::HashMap, + future::Future, + path::Path, + pin::Pin, + sync::Arc, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; +use tokio::{ + select, spawn, + sync::{ + broadcast::{self, Receiver, Sender}, + mpsc::{self, Receiver as M_Receiver, Sender as M_Sender}, + RwLock, + }, + time::{interval, sleep}, +}; +use uuid::Uuid; + +use super::{ + background_heal_ops::HealTask, + heal_commands::{HealItemType, HealOpts, HealResultItem, HealScanMode, HealStopSuccess, HealingDisk, HealingTracker}, +}; + +type HealStatusSummary = String; +type ItemsMap = HashMap; +pub type HealObjectFn = Arc Result<()> + Send + Sync>; +pub type HealEntryFn = + Box Pin> + Send>> + Send>; + +pub const BG_HEALING_UUID: &str = "0000-0000-0000-0000"; +pub const HEALING_TRACKER_FILENAME: &str = ".healing.bin"; +const KEEP_HEAL_SEQ_STATE_DURATION: std::time::Duration = Duration::from_secs(10 * 60); +const HEAL_NOT_STARTED_STATUS: &str = "not started"; +const HEAL_RUNNING_STATUS: &str = "running"; +const HEAL_STOPPED_STATUS: &str = "stopped"; +const HEAL_FINISHED_STATUS: &str = "finished"; + +const MAX_UNCONSUMED_HEAL_RESULT_ITEMS: usize = 1000; +const HEAL_UNCONSUMED_TIMEOUT: std::time::Duration = Duration::from_secs(24 * 60 * 60); +pub const NOP_HEAL: &str = ""; + +lazy_static! {} + +#[derive(Clone, Debug, Default)] +pub struct HealSequenceStatus { + pub summary: HealStatusSummary, + pub failure_detail: String, + pub start_time: u64, + pub heal_setting: HealOpts, + pub items: Vec, +} + +pub struct HealSource { + pub bucket: String, + pub object: String, + pub version_id: String, + pub no_wait: bool, + opts: Option, +} + +#[derive(Clone, Debug)] +pub struct HealSequence { + pub bucket: String, + pub object: String, + pub report_progress: bool, + pub start_time: u64, + pub end_time: Arc>, + pub client_token: String, + pub client_address: String, + pub force_started: bool, + pub setting: HealOpts, + pub current_status: Arc>, + pub last_sent_result_index: usize, + pub scanned_items_map: ItemsMap, + pub healed_items_map: ItemsMap, + pub heal_failed_items_map: ItemsMap, + pub last_heal_activity: u64, + + traverse_and_heal_done_tx: Arc>>>, + traverse_and_heal_done_rx: Arc>>>, + + tx: Arc>>, + rx: Arc>>, +} + +impl Default for HealSequence { + fn default() -> Self { + let (h_tx, h_rx) = mpsc::channel(1); + let (tx, rx) = broadcast::channel(1); + Self { + bucket: Default::default(), + object: Default::default(), + report_progress: Default::default(), + start_time: Default::default(), + end_time: Default::default(), + client_token: Default::default(), + client_address: Default::default(), + force_started: Default::default(), + setting: Default::default(), + current_status: Default::default(), + last_sent_result_index: Default::default(), + scanned_items_map: Default::default(), + healed_items_map: Default::default(), + heal_failed_items_map: Default::default(), + last_heal_activity: Default::default(), + traverse_and_heal_done_tx: Arc::new(RwLock::new(h_tx)), + traverse_and_heal_done_rx: Arc::new(RwLock::new(h_rx)), + tx: Arc::new(RwLock::new(tx)), + rx: Arc::new(RwLock::new(rx)), + } + } +} + +impl HealSequence { + pub fn new(bucket: &str, obj_profix: &str, client_addr: &str, hs: HealOpts, force_start: bool) -> Self { + let client_token = Uuid::new_v4().to_string(); + + Self { + bucket: bucket.to_string(), + object: obj_profix.to_string(), + report_progress: true, + client_token, + client_address: client_addr.to_string(), + force_started: force_start, + setting: hs, + current_status: Arc::new(RwLock::new(HealSequenceStatus { + summary: HEAL_NOT_STARTED_STATUS.to_string(), + heal_setting: hs, + ..Default::default() + })), + ..Default::default() + } + } +} + +impl HealSequence { + fn get_scanned_items_count(&self) -> usize { + self.scanned_items_map.values().sum() + } + + fn get_scanned_items_map(&self) -> ItemsMap { + self.scanned_items_map.clone() + } + + fn get_healed_items_map(&self) -> ItemsMap { + self.healed_items_map.clone() + } + + fn get_heal_failed_items_map(&self) -> ItemsMap { + self.heal_failed_items_map.clone() + } + + fn count_failed(&mut self, heal_type: HealItemType) { + *self.heal_failed_items_map.entry(heal_type).or_insert(0) += 1; + self.last_heal_activity = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + } + + fn count_scanned(&mut self, heal_type: HealItemType) { + *self.scanned_items_map.entry(heal_type).or_insert(0) += 1; + self.last_heal_activity = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + } + + fn count_healed(&mut self, heal_type: HealItemType) { + *self.healed_items_map.entry(heal_type).or_insert(0) += 1; + self.last_heal_activity = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + } + + async fn is_quitting(&self) -> bool { + let mut w = self.rx.write().await; + if w.try_recv().is_ok() { + return true; + } + false + } + + async fn has_ended(&self) -> bool { + if self.client_token == BG_HEALING_UUID.to_string() { + return false; + } + + !(*(self.end_time.read().await) == self.start_time) + } + + async fn stop(&self) { + let w = self.tx.write().await; + let _ = w.send(true); + } + + async fn push_heal_result_item(&self, r: &HealResultItem) -> Result<()> { + let mut r = r.clone(); + let mut interval_timer = interval(HEAL_UNCONSUMED_TIMEOUT); + let mut items_len = 0; + loop { + { + let current_status_r = self.current_status.read().await; + items_len = current_status_r.items.len(); + } + + if items_len == MAX_UNCONSUMED_HEAL_RESULT_ITEMS { + select! { + _ = sleep(Duration::from_secs(1)) => { + + } + _ = self.is_done() => { + return Err(Error::from_string("stopped")); + } + _ = interval_timer.tick() => { + return Err(Error::from_string("timeout")); + } + } + } else { + break; + } + } + + let mut current_status_w = self.current_status.write().await; + if items_len > 0 { + r.result_index = 1 + current_status_w.items[items_len - 1].result_index; + } else { + r.result_index = 1 + self.last_sent_result_index; + } + + current_status_w.items.push(r); + + Ok(()) + } + + async fn queue_heal_task(&mut self, source: HealSource, heal_type: HealItemType) -> Result<()> { + let mut task = HealTask::new(&source.bucket, &source.object, &source.version_id, &self.setting); + if let Some(opts) = source.opts { + task.opts = opts; + } else { + task.opts.scan_mode = HEAL_UNKNOWN_SCAN; + } + + self.count_scanned(heal_type); + + if source.no_wait {} + + todo!() + } + + fn heal_disk_meta() -> Result<()> { + todo!() + } + + fn heal_items(&self, buckets_only: bool) -> Result<()> { + if self.client_token == BG_HEALING_UUID.to_string() { + return Ok(()); + } + + todo!() + } + + async fn traverse_and_heal(&self) { + let buckets_only = false; + } + + fn heal_rustfs_sys_meta(&self, meta_prefix: String) -> Result<()> { + todo!() + } + + async fn is_done(&self) -> bool { + let mut rx_w = self.rx.write().await; + if let Ok(true) = rx_w.recv().await { + return true; + } + false + } +} + +pub async fn heal_sequence_start(h: Arc) { + { + let mut current_status_w = h.current_status.write().await; + (*current_status_w).summary = HEAL_RUNNING_STATUS.to_string(); + (*current_status_w).start_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + } + + let h_clone = h.clone(); + spawn(async move { + h_clone.traverse_and_heal().await; + }); + + let h_clone_1 = h.clone(); + let mut x = h.traverse_and_heal_done_rx.write().await; + select! { + _ = h.is_done() => { + *(h.end_time.write().await) = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs(); + let mut current_status_w = h.current_status.write().await; + (*current_status_w).summary = HEAL_FINISHED_STATUS.to_string(); + + spawn(async move { + let mut rx_w = h_clone_1.traverse_and_heal_done_rx.write().await; + rx_w.recv().await; + }); + } + result = x.recv() => { + match result { + Some(err) => { + match err { + Some(err) => { + let mut current_status_w = h.current_status.write().await; + (*current_status_w).summary = HEAL_STOPPED_STATUS.to_string(); + (*current_status_w).failure_detail = err.to_string(); + }, + None => { + let mut current_status_w = h.current_status.write().await; + (*current_status_w).summary = HEAL_FINISHED_STATUS.to_string(); + } + } + }, + None => { + return; + } + } + + } + } +} + +#[derive(Debug, Default)] +pub struct AllHealState { + mu: RwLock, + + heal_seq_map: HashMap, + heal_local_disks: HashMap, + heal_status: HashMap, +} + +impl AllHealState { + pub fn new(cleanup: bool) -> Self { + let hstate = AllHealState::default(); + if cleanup { + // spawn(f); + } + + hstate + } + + async fn pop_heal_local_disks(&mut self, heal_local_disks: &[Endpoint]) { + let _ = self.mu.write().await; + + self.heal_local_disks.retain(|k, _| { + if heal_local_disks.contains(k) { + return false; + } + true + }); + + let heal_local_disks = heal_local_disks.iter().map(|s| s.to_string()).collect::>(); + self.heal_status.retain(|_, v| { + if heal_local_disks.contains(&v.endpoint) { + return false; + } + + true + }); + } + + async fn update_heal_status(&mut self, tracker: &HealingTracker) { + let _ = self.mu.write().await; + let _ = tracker.mu.read().await; + + self.heal_status.insert(tracker.id.clone(), tracker.clone()); + } + + async fn get_local_healing_disks(&self) -> HashMap { + let _ = self.mu.read().await; + + let mut dst = HashMap::new(); + for v in self.heal_status.values() { + dst.insert(v.endpoint.clone(), v.to_healing_disk().await); + } + + dst + } + + async fn get_heal_local_disk_endpoints(&self) -> Endpoints { + let _ = self.mu.read().await; + + let mut endpoints = Vec::new(); + self.heal_local_disks.iter().for_each(|(k, v)| { + if !v { + endpoints.push(k.clone()); + } + }); + + Endpoints::from(endpoints) + } + + async fn set_disk_healing_status(&mut self, ep: Endpoint, healing: bool) { + let _ = self.mu.write().await; + + self.heal_local_disks.insert(ep, healing); + } + + async fn push_heal_local_disks(&mut self, heal_local_disks: &[Endpoint]) { + let _ = self.mu.write().await; + + heal_local_disks.iter().for_each(|heal_local_disk| { + self.heal_local_disks.insert(heal_local_disk.clone(), false); + }); + } + + async fn periodic_heal_seqs_clean(&mut self, mut rx: Receiver) { + loop { + select! { + result = rx.recv() =>{ + if let Ok(true) = result { + return; + } + } + _ = sleep(Duration::from_secs(5 * 60)) => { + let _ = self.mu.write().await; + let now = SystemTime::now(); + + let mut keys_to_reomve = Vec::new(); + for (k, v) in self.heal_seq_map.iter() { + if v.has_ended().await && (UNIX_EPOCH + Duration::from_secs(*(v.end_time.read().await)) + KEEP_HEAL_SEQ_STATE_DURATION) < now { + keys_to_reomve.push(k.clone()) + } + } + for key in keys_to_reomve.iter() { + self.heal_seq_map.remove(key); + } + } + } + } + } + + async fn get_heal_sequence_by_token(&self, token: &str) -> (Option, bool) { + let _ = self.mu.read().await; + + for v in self.heal_seq_map.values() { + if v.client_token == token { + return (Some(v.clone()), true); + } + } + + return (None, false); + } + + async fn get_heal_sequence(&self, path: &str) -> Option { + let _ = self.mu.read().await; + + self.heal_seq_map.get(path).cloned() + } + + async fn stop_heal_sequence(&mut self, path: &str) -> Result> { + let mut hsp = HealStopSuccess::default(); + if let Some(he) = self.get_heal_sequence(path).await { + let client_token = he.client_token.clone(); + if *GLOBAL_IsDistErasure.read().await { + // TODO: proxy + } + + hsp.client_token = client_token; + hsp.client_address = he.client_address.clone(); + hsp.start_time = he.start_time; + + he.stop().await; + + loop { + if he.has_ended().await { + break; + } + + sleep(Duration::from_secs(1)).await; + } + + let _ = self.mu.write().await; + self.heal_seq_map.remove(path); + } else { + hsp.client_token = "unknown".to_string(); + } + + let b = serde_json::to_string(&hsp)?; + Ok(b.as_bytes().to_vec()) + } + + // LaunchNewHealSequence - launches a background routine that performs + // healing according to the healSequence argument. For each heal + // sequence, state is stored in the `globalAllHealState`, which is a + // map of the heal path to `healSequence` which holds state about the + // heal sequence. + // + // Heal results are persisted in server memory for + // `keepHealSeqStateDuration`. This function also launches a + // background routine to clean up heal results after the + // aforementioned duration. + pub async fn launch_new_heal_sequence(&mut self, heal_sequence: &HealSequence) -> Result> { + let path = Path::new(&heal_sequence.bucket).join(heal_sequence.object.clone()); + let path_s = path.to_str().unwrap(); + if heal_sequence.force_started { + self.stop_heal_sequence(path_s).await?; + } else { + if let Some(hs) = self.get_heal_sequence(path_s).await { + if !hs.has_ended().await { + return Err(Error::from_string(format!("Heal is already running on the given path (use force-start option to stop and start afresh). The heal was started by IP {} at {}, token is {}", heal_sequence.client_address, heal_sequence.start_time, heal_sequence.client_token))); + } + } + } + + let _ = self.mu.write().await; + + for (k, v) in self.heal_seq_map.iter() { + if !v.has_ended().await && (has_profix(&k, path_s) || has_profix(path_s, &k)) { + return Err(Error::from_string(format!( + "The provided heal sequence path overlaps with an existing heal path: {}", + k + ))); + } + } + + self.heal_seq_map.insert(path_s.to_string(), heal_sequence.clone()); + + let client_token = heal_sequence.client_token.clone(); + if *GLOBAL_IsDistErasure.read().await { + // TODO: proxy + } + + if heal_sequence.client_token == BG_HEALING_UUID { + // For background heal do nothing, do not spawn an unnecessary goroutine. + } else { + } + todo!() + } +} diff --git a/ecstore/src/heal/mod.rs b/ecstore/src/heal/mod.rs new file mode 100644 index 000000000..38db00f2c --- /dev/null +++ b/ecstore/src/heal/mod.rs @@ -0,0 +1,3 @@ +pub mod background_heal_ops; +pub mod heal_commands; +pub mod heal_ops; diff --git a/ecstore/src/lib.rs b/ecstore/src/lib.rs index 6d9e13a2a..4533f1266 100644 --- a/ecstore/src/lib.rs +++ b/ecstore/src/lib.rs @@ -1,3 +1,5 @@ +pub mod bitrot; +pub mod cache_value; mod chunk_stream; mod config; pub mod disk; @@ -7,6 +9,7 @@ pub mod erasure; pub mod error; mod file_meta; mod global; +mod heal; pub mod peer; mod quorum; pub mod set_disk; diff --git a/ecstore/src/sets.rs b/ecstore/src/sets.rs index 9a5444aa7..fdaf7c724 100644 --- a/ecstore/src/sets.rs +++ b/ecstore/src/sets.rs @@ -12,17 +12,11 @@ use crate::{ disk::{ format::{DistributionAlgoVersion, FormatV3}, DiskStore, - }, - endpoints::PoolEndpoints, - error::{Error, Result}, - global::{is_dist_erasure, GLOBAL_LOCAL_DISK_SET_DRIVES}, - set_disk::SetDisks, - store_api::{ + }, endpoints::PoolEndpoints, error::{Error, Result}, global::{is_dist_erasure, GLOBAL_LOCAL_DISK_SET_DRIVES}, heal::{heal_commands::{HealOpts, HealResultItem}, heal_ops::HealObjectFn}, set_disk::SetDisks, store_api::{ BucketInfo, BucketOptions, CompletePart, DeleteBucketOptions, DeletedObject, GetObjectReader, HTTPRangeSpec, ListObjectsV2Info, MakeBucketOptions, MultipartUploadResult, ObjectIO, ObjectInfo, ObjectOptions, ObjectToDelete, PartInfo, PutObjReader, StorageAPI, - }, - utils::hash, + }, utils::hash }; use tokio::time::Duration; @@ -440,4 +434,22 @@ impl StorageAPI for Sets { async fn delete_bucket(&self, _bucket: &str, _opts: &DeleteBucketOptions) -> Result<()> { unimplemented!() } + async fn heal_format(&self, dry_run: bool) -> Result { + unimplemented!() + } + async fn heal_bucket(&self, bucket: &str, opts: &HealOpts) -> Result { + unimplemented!() + } + async fn heal_object(&self, bucket: &str, object: &str, version_id: &str, opts: &HealOpts) -> Result { + self.get_disks_by_key(object).heal_object(bucket, object, version_id, opts).await + } + async fn heal_objects(&self, bucket: &str, prefix: &str, opts: &HealOpts, func: HealObjectFn) -> Result<()> { + unimplemented!() + } + async fn get_pool_and_set(&self, id: &str) -> Result<(Option, Option, Option)> { + unimplemented!() + } + async fn check_abandoned_parts(&self, bucket: &str, object: &str, opts: &HealOpts) -> Result<()> { + unimplemented!() + } } diff --git a/ecstore/src/store.rs b/ecstore/src/store.rs index 1f12dd94e..d47a65a19 100644 --- a/ecstore/src/store.rs +++ b/ecstore/src/store.rs @@ -3,7 +3,11 @@ use crate::bucket::metadata; use crate::bucket::metadata_sys::set_bucket_metadata; use crate::disk::endpoint::EndpointType; +use crate::disk::MetaCacheEntry; use crate::global::{is_dist_erasure, set_object_layer, GLOBAL_LOCAL_DISK_MAP, GLOBAL_LOCAL_DISK_SET_DRIVES}; +use crate::heal::heal_commands::{HealOpts, HealResultItem, HealScanMode}; +use crate::heal::heal_ops::HealObjectFn; +use crate::new_object_layer_fn; use crate::store_api::ObjectIO; use crate::{ bucket::metadata::BucketMetadata, @@ -23,6 +27,7 @@ use crate::{ use backon::{ExponentialBuilder, Retryable}; use common::globals::{GLOBAL_Local_Node_Name, GLOBAL_Rustfs_Host, GLOBAL_Rustfs_Port}; use futures::future::join_all; +use glob::Pattern; use http::HeaderMap; use s3s::dto::{ObjectLockConfiguration, ObjectLockEnabled}; use std::{ @@ -776,6 +781,114 @@ impl StorageAPI for ECStore { } unimplemented!() } + + async fn heal_format(&self, dry_run: bool) -> Result { + unimplemented!() + } + async fn heal_bucket(&self, bucket: &str, opts: &HealOpts) -> Result { + unimplemented!() + } + async fn heal_object(&self, bucket: &str, object: &str, version_id: &str, opts: &HealOpts) -> Result { + let object = utils::path::encode_dir_object(object); + let mut errs = HashMap::new(); + let mut results = HashMap::new(); + for (idx, pool) in self.pools.iter().enumerate() { + //TODO: IsSuspended + match pool.heal_object(bucket, &object, version_id, opts).await { + Ok(mut result) => { + result.object = utils::path::decode_dir_object(&result.object); + results.insert(idx, result); + } + Err(err) => { + errs.insert(idx, err); + } + } + } + + // Return the first nil error + for i in 0..self.pools.len() { + if errs.get(&i).is_none() { + return Ok(results.remove(&i).unwrap()); + } + } + + // No pool returned a nil error, return the first non 'not found' error + for (k, err) in errs.iter() { + match err.downcast_ref::() { + Some(DiskError::FileNotFound) | Some(DiskError::FileVersionNotFound) => {} + _ => return Ok(results.remove(&k).unwrap()), + } + } + + // At this stage, all errors are 'not found' + if !version_id.is_empty() { + return Err(Error::new(DiskError::FileVersionNotFound)); + } + + Err(Error::new(DiskError::FileNotFound)) + } + async fn heal_objects(&self, bucket: &str, prefix: &str, opts: &HealOpts, func: HealObjectFn) -> Result<()> { + let mut first_err = None; + for (idx, pool) in self.pools.iter().enumerate() { + if opts.pool.is_some() && opts.pool.unwrap() != idx { + continue; + } + //TODO: IsSuspended + + for (idx, set) in pool.disk_set.iter().enumerate() { + if opts.set.is_some() && opts.set.unwrap() != idx { + continue; + } + + if let Err(err) = set.list_and_heal(bucket, prefix, opts, func.clone()).await { + if first_err.is_none() { + first_err = Some(err) + } + } + } + } + + if first_err.is_some() { + return Err(first_err.unwrap()); + } + + Ok(()) + } + + async fn get_pool_and_set(&self, id: &str) -> Result<(Option, Option, Option)> { + for (pool_idx, pool) in self.pools.iter().enumerate() { + for (set_idx, set) in pool.format.erasure.sets.iter().enumerate() { + for (disk_idx, disk_id) in set.iter().enumerate() { + if disk_id.to_string() == id { + return Ok((Some(pool_idx), Some(set_idx), Some(disk_idx))); + } + } + } + } + + Err(Error::new(DiskError::DiskNotFound)) + } + + async fn check_abandoned_parts(&self, bucket: &str, object: &str, opts: &HealOpts) -> Result<()> { + let object = utils::path::encode_dir_object(object); + if self.single_pool() { + return self.pools[0].check_abandoned_parts(bucket, &object, opts).await; + } + + let mut errs = Vec::new(); + for pool in self.pools.iter() { + //TODO: IsSuspended + if let Err(err) = pool.check_abandoned_parts(bucket, &object, opts).await { + errs.push(err); + } + } + + if !errs.is_empty() { + return Err(errs[0].clone()); + } + + Ok(()) + } } async fn init_local_peer(endpoint_pools: &EndpointServerPools, host: &String, port: &String) { @@ -800,3 +913,64 @@ async fn init_local_peer(endpoint_pools: &EndpointServerPools, host: &String, po *GLOBAL_Local_Node_Name.write().await = peer_set[0].clone(); } + +pub async fn heal_entry( + bucket: String, + entry: MetaCacheEntry, + scan_mode: HealScanMode, + opts: HealOpts, + func: HealObjectFn, +) -> Result<()> { + if entry.is_dir() { + return Ok(()); + } + + // We might land at .metacache, .trash, .multipart + // no need to heal them skip, only when bucket + // is '.rustfs.sys' + if bucket == RUSTFS_META_BUCKET { + if Pattern::new("buckets/*/.metacache/*") + .map(|p| p.matches(&entry.name)) + .unwrap_or(false) + || Pattern::new("tmp/*").map(|p| p.matches(&entry.name)).unwrap_or(false) + || Pattern::new("multipart/*").map(|p| p.matches(&entry.name)).unwrap_or(false) + || Pattern::new("tmp-old/*").map(|p| p.matches(&entry.name)).unwrap_or(false) + { + return Ok(()); + } + } + + let layer = new_object_layer_fn(); + let lock = layer.read().await; + let store = match lock.as_ref() { + Some(s) => s, + None => return Err(Error::msg("errServerNotInitialized")), + }; + + match entry.file_info_versions(&bucket) { + Ok(fivs) => { + if opts.remove && !opts.dry_run { + if let Err(err) = store.check_abandoned_parts(&bucket, &entry.name, &opts).await { + return Err(Error::from_string(format!( + "unable to check object {}/{} for abandoned data: {}", + bucket, entry.name, err + ))); + } + } + + for version in fivs.versions.iter() { + let version_id = version.version_id.map_or("".to_string(), |version_id| version_id.to_string()); + if let Err(err) = func(&bucket, &entry.name, &version_id, scan_mode) { + match err.downcast_ref::() { + Some(DiskError::FileNotFound) | Some(DiskError::FileVersionNotFound) => {} + _ => return Err(err), + } + } + } + } + Err(_) => { + return func(&bucket, &entry.name, "", scan_mode); + } + } + Ok(()) +} diff --git a/ecstore/src/store_api.rs b/ecstore/src/store_api.rs index 948d31e49..ab53e802e 100644 --- a/ecstore/src/store_api.rs +++ b/ecstore/src/store_api.rs @@ -1,6 +1,11 @@ use std::collections::HashMap; -use crate::error::{Error, Result}; +use crate::{ + disk::error::DiskError, error::{Error, Result}, heal::{ + heal_commands::{HealOpts, HealResultItem}, + heal_ops::HealObjectFn, + } +}; use futures::StreamExt; use http::HeaderMap; use rmp_serde::Serializer; @@ -11,6 +16,8 @@ use uuid::Uuid; pub const ERASURE_ALGORITHM: &str = "rs-vandermonde"; pub const BLOCK_SIZE_V2: usize = 1048576; // 1M +pub const RESERVED_METADATA_PREFIX: &str = "X-Rustfs-Internal-"; +pub const RESERVED_METADATA_PREFIX_LOWER: &str = "X-Rustfs-Internal-"; // #[derive(Debug, Clone)] #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] @@ -166,6 +173,7 @@ impl FileInfo { parts: self.parts.clone(), is_latest: self.is_latest, tags: self.tags.clone(), + ..Default::default() } } // to_part_offset 取offset 所在的part index, 返回part index, offset @@ -234,6 +242,41 @@ pub struct ErasureInfo { pub checksums: Vec, } +impl ErasureInfo { + pub fn get_checksum_info(&self, part_number: usize) -> ChecksumInfo { + for sum in &self.checksums { + if sum.part_number == part_number { + return sum.clone(); + } + } + + ChecksumInfo {algorithm: DEFAULT_BITROT_ALGO, ..Default::default()} + } + + // 算出每个分片大小 + pub fn shard_size(&self, data_size: usize) -> usize { + (data_size + self.data_blocks - 1) / self.data_blocks + } + + // returns final erasure size from original size. + pub fn shard_file_size(&self, total_size: usize) -> usize { + if total_size == 0 { + return 0; + } + + let num_shards = total_size / self.block_size; + let last_block_size = total_size % self.block_size; + let last_shard_size = (last_block_size + self.data_blocks - 1) / self.data_blocks; + num_shards * self.shard_size(self.block_size) + last_shard_size + + // // 因为写入的时候ec需要补全,所以最后一个长度应该也是一样的 + // if last_block_size != 0 { + // num_shards += 1 + // } + // num_shards * self.shard_size(self.block_size) + } +} + #[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)] // ChecksumInfo - carries checksums of individual scattered parts per disk. pub struct ChecksumInfo { @@ -242,7 +285,9 @@ pub struct ChecksumInfo { pub hash: Vec, } -#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone)] +pub const DEFAULT_BITROT_ALGO: BitrotAlgorithm = BitrotAlgorithm::HighwayHash256S; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone, Eq, Hash)] // BitrotAlgorithm specifies a algorithm used for bitrot protection. pub enum BitrotAlgorithm { // SHA256 represents the SHA-256 hash function @@ -461,7 +506,10 @@ pub struct ObjectInfo { pub name: String, pub mod_time: Option, pub size: usize, + // Actual size is the real size of the object uploaded by client. + pub actual_size: Option, pub is_dir: bool, + pub user_defined: HashMap, pub parity_blocks: usize, pub data_blocks: usize, pub version_id: Option, @@ -471,6 +519,40 @@ pub struct ObjectInfo { pub tags: Option>, } +impl ObjectInfo { + pub fn is_compressed(&self) -> bool { + self.user_defined.contains_key(&format!("{}compression", RESERVED_METADATA_PREFIX)) + } + + pub fn get_actual_size(&self) -> Result { + if let Some(actual_size) = self.actual_size { + return Ok(actual_size); + } + + if self.is_compressed() { + if let Some(size_str) = self.user_defined.get(&format!("{}actual-size", RESERVED_METADATA_PREFIX)) { + if !size_str.is_empty() { + // Todo: deal with error + let size = size_str.parse::()?; + return Ok(size); + } + } + let mut actual_size = 0; + self.parts.iter().for_each(|part| { + actual_size += part.actual_size; + }); + if actual_size == 0 && actual_size != self.size { + return Err(Error::from_string("invalid decompressed size")); + } + return Ok(actual_size); + } + + // TODO: IsEncrypted + + Ok(self.size) + } +} + #[derive(Debug, Default)] pub struct ListObjectsInfo { // Indicates whether the returned list objects response is truncated. A @@ -601,4 +683,10 @@ pub trait StorageAPI: ObjectIO { uploaded_parts: Vec, opts: &ObjectOptions, ) -> Result; + async fn heal_format(&self, dry_run: bool) -> Result; + async fn heal_bucket(&self, bucket: &str, opts: &HealOpts) -> Result; + async fn heal_object(&self, bucket: &str, object: &str, version_id: &str, opts: &HealOpts) -> Result; + async fn heal_objects(&self, bucket: &str, prefix: &str, opts: &HealOpts, func: HealObjectFn) -> Result<()>; + async fn get_pool_and_set(&self, id: &str) -> Result<(Option, Option, Option)>; + async fn check_abandoned_parts(&self, bucket: &str, object: &str, opts: &HealOpts) -> Result<()>; } diff --git a/ecstore/src/utils/mod.rs b/ecstore/src/utils/mod.rs index b5ca93bd3..b12bf8048 100644 --- a/ecstore/src/utils/mod.rs +++ b/ecstore/src/utils/mod.rs @@ -4,4 +4,5 @@ pub mod fs; pub mod hash; pub mod net; pub mod path; +pub mod os; pub mod wildcard; diff --git a/ecstore/src/utils/os/linux.rs b/ecstore/src/utils/os/linux.rs new file mode 100644 index 000000000..59b3951ca --- /dev/null +++ b/ecstore/src/utils/os/linux.rs @@ -0,0 +1,112 @@ +use nix::sys::stat::{self, stat}; +use nix::sys::statfs::{self, statfs, FsType}; +use std::io::{Error, ErrorKind}; +use std::path::Path; + +use crate::{disk::Info, error::Result}; + +/// returns total and free bytes available in a directory, e.g. `/`. +pub fn get_info(p: impl AsRef, _first_time: bool) -> std::io::Result { + let stat_fs = statfs(p.as_ref())?; + + let bsize = stat_fs.block_size() as u64; + let bfree = stat_fs.blocks_free() as u64; + let bavail = stat_fs.blocks_available() as u64; + let blocks = stat_fs.blocks() as u64; + + let reserved = match bfree.checked_sub(bavail) { + Some(reserved) => reserved, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected f_bavail space ({}) > f_bfree space ({}), fs corruption at ({}). please run 'fsck'", + bavail, + bfree, + p.as_ref().display() + ), + )) + } + }; + + let total = match blocks.checked_sub(reserved) { + Some(total) => total * bsize, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected reserved space ({}) > blocks space ({}), fs corruption at ({}). please run 'fsck'", + reserved, + blocks, + p.as_ref().display() + ), + )) + } + }; + + let free = bavail * bsize; + let used = match total.checked_sub(free) { + Some(used) => used, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected free space ({}) > total drive space ({}), fs corruption at ({}). please run 'fsck'", + free, + total, + p.as_ref().display() + ), + )) + } + }; + + let st = stat(p.as_ref())?; + + Ok(Info { + total, + free, + used, + files: stat_fs.files(), + ffree: stat_fs.files_free(), + fstype: get_fs_type(stat_fs.filesystem_type()).to_string(), + + major: stat::major(st.st_dev), + minor: stat::minor(st.st_dev), + + ..Default::default() + }) +} + +/// returns the filesystem type of the underlying mounted filesystem +/// +/// TODO The following mapping could not find the corresponding constant in `nix`: +/// +/// "137d" => "EXT", +/// "4244" => "HFS", +/// "5346544e" => "NTFS", +/// "61756673" => "AUFS", +/// "ef51" => "EXT2OLD", +/// "2fc12fc1" => "zfs", +/// "ff534d42" => "cifs", +/// "53464846" => "wslfs", +fn get_fs_type(fs_type: FsType) -> &'static str { + match fs_type { + statfs::TMPFS_MAGIC => "TMPFS", + statfs::MSDOS_SUPER_MAGIC => "MSDOS", + statfs::XFS_SUPER_MAGIC => "XFS", + statfs::NFS_SUPER_MAGIC => "NFS", + statfs::EXT4_SUPER_MAGIC => "EXT4", + statfs::ECRYPTFS_SUPER_MAGIC => "ecryptfs", + statfs::OVERLAYFS_SUPER_MAGIC => "overlayfs", + statfs::REISERFS_SUPER_MAGIC => "REISERFS", + + _ => "UNKNOWN", + } +} + +pub fn same_disk(disk1: &str, disk2: &str) -> Result { + let stat1 = stat(disk1)?; + let stat2 = stat(disk2)?; + + Ok(stat1.st_dev == stat2.st_dev) +} \ No newline at end of file diff --git a/ecstore/src/utils/os/mod.rs b/ecstore/src/utils/os/mod.rs new file mode 100644 index 000000000..7524d2ecb --- /dev/null +++ b/ecstore/src/utils/os/mod.rs @@ -0,0 +1,14 @@ +#[cfg(target_os = "linux")] +mod linux; +#[cfg(all(unix, not(target_os = "linux")))] +mod unix; +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "linux")] +pub use linux::get_info; +pub use linux::same_disk; +#[cfg(all(unix, not(target_os = "linux")))] +pub use unix::get_info; +#[cfg(target_os = "windows")] +pub use windows::get_info; \ No newline at end of file diff --git a/ecstore/src/utils/os/unix.rs b/ecstore/src/utils/os/unix.rs new file mode 100644 index 000000000..b60d8290f --- /dev/null +++ b/ecstore/src/utils/os/unix.rs @@ -0,0 +1,77 @@ +use crate::disk::Info; +use nix::sys::{statfs::statfs, stat::stat}; +use std::io::{Error, ErrorKind}; +use std::path::Path; + +/// returns total and free bytes available in a directory, e.g. `/`. +pub fn get_info(p: impl AsRef) -> std::io::Result { + let stat = statfs(p.as_ref())?; + + let bsize = stat.block_size() as u64; + let bfree = stat.blocks_free() as u64; + let bavail = stat.blocks_available() as u64; + let blocks = stat.blocks() as u64; + + let reserved = match bfree.checked_sub(bavail) { + Some(reserved) => reserved, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected f_bavail space ({}) > f_bfree space ({}), fs corruption at ({}). please run 'fsck'", + bavail, + bfree, + p.as_ref().display() + ), + )) + } + }; + + let total = match blocks.checked_sub(reserved) { + Some(total) => total * bsize, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected reserved space ({}) > blocks space ({}), fs corruption at ({}). please run 'fsck'", + reserved, + blocks, + p.as_ref().display() + ), + )) + } + }; + + let free = bavail * bsize; + let used = match total.checked_sub(free) { + Some(used) => used, + None => { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected free space ({}) > total drive space ({}), fs corruption at ({}). please run 'fsck'", + free, + total, + p.as_ref().display() + ), + )) + } + }; + + Ok(Info { + total, + free, + used, + files: stat.files(), + ffree: stat.files_free(), + fstype: stat.filesystem_type_name().to_string(), + ..Default::default() + }) +} + +pub fn same_disk(disk1: &str, disk2: &str) -> Result { + let stat1 = stat(disk1)?; + let stat2 = stat(disk2)?; + + Ok(stat1.st_dev == stat2.st_dev) +} \ No newline at end of file diff --git a/ecstore/src/utils/os/windows.rs b/ecstore/src/utils/os/windows.rs new file mode 100644 index 000000000..65a9d0022 --- /dev/null +++ b/ecstore/src/utils/os/windows.rs @@ -0,0 +1,136 @@ +use crate::disk::Info; +use std::io::{Error, ErrorKind, Result}; +use std::mem; +use std::os::windows::ffi::OsStrExt; +use std::path::Path; +use winapi::shared::minwindef::{DWORD, MAX_PATH}; +use winapi::shared::ntdef::ULARGE_INTEGER; +use winapi::um::fileapi::{GetDiskFreeSpaceExW, GetDiskFreeSpaceW, GetVolumeInformationW, GetVolumePathNameW}; +use winapi::um::winnt::{LPCWSTR, WCHAR}; + +/// returns total and free bytes available in a directory, e.g. `C:\`. +pub fn get_info(p: impl AsRef) -> Result { + let path_wide: Vec = p + .as_ref() + .canonicalize()? + .into_os_string() + .encode_wide() + .chain(std::iter::once(0)) // Null-terminate the string + .collect(); + + let mut lp_free_bytes_available: ULARGE_INTEGER = unsafe { mem::zeroed() }; + let mut lp_total_number_of_bytes: ULARGE_INTEGER = unsafe { mem::zeroed() }; + let mut lp_total_number_of_free_bytes: ULARGE_INTEGER = unsafe { mem::zeroed() }; + + let success = unsafe { + GetDiskFreeSpaceExW( + path_wide.as_ptr(), + &mut lp_free_bytes_available, + &mut lp_total_number_of_bytes, + &mut lp_total_number_of_free_bytes, + ) + }; + if success == 0 { + return Err(Error::last_os_error()); + } + + let total = unsafe { *lp_total_number_of_bytes.QuadPart() }; + let free = unsafe { *lp_total_number_of_free_bytes.QuadPart() }; + + if free > total { + return Err(Error::new( + ErrorKind::Other, + format!( + "detected free space ({}) > total drive space ({}), fs corruption at ({}). please run 'fsck'", + free, + total, + p.as_ref().display() + ), + )); + } + + let mut lp_sectors_per_cluster: DWORD = 0; + let mut lp_bytes_per_sector: DWORD = 0; + let mut lp_number_of_free_clusters: DWORD = 0; + let mut lp_total_number_of_clusters: DWORD = 0; + + let success = unsafe { + GetDiskFreeSpaceW( + path_wide.as_ptr(), + &mut lp_sectors_per_cluster, + &mut lp_bytes_per_sector, + &mut lp_number_of_free_clusters, + &mut lp_total_number_of_clusters, + ) + }; + if success == 0 { + return Err(Error::last_os_error()); + } + + Ok(Info { + total, + free, + used: total - free, + files: lp_total_number_of_clusters as u64, + ffree: lp_number_of_free_clusters as u64, + fstype: get_fs_type(&path_wide)?, + ..Default::default() + }) +} + +/// returns leading volume name. +fn get_volume_name(v: &[WCHAR]) -> Result { + let volume_name_size: DWORD = MAX_PATH as _; + let mut lp_volume_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH]; + + let success = unsafe { GetVolumePathNameW(v.as_ptr(), lp_volume_name_buffer.as_mut_ptr(), volume_name_size) }; + + if success == 0 { + return Err(Error::last_os_error()); + } + + Ok(lp_volume_name_buffer.as_ptr()) +} + +fn utf16_to_string(v: &[WCHAR]) -> String { + let len = v.iter().position(|&x| x == 0).unwrap_or(v.len()); + String::from_utf16_lossy(&v[..len]) +} + +/// returns the filesystem type of the underlying mounted filesystem +fn get_fs_type(p: &[WCHAR]) -> Result { + let path = get_volume_name(p)?; + + let volume_name_size: DWORD = MAX_PATH as _; + let n_file_system_name_size: DWORD = MAX_PATH as _; + + let mut lp_volume_serial_number: DWORD = 0; + let mut lp_maximum_component_length: DWORD = 0; + let mut lp_file_system_flags: DWORD = 0; + + let mut lp_volume_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH]; + let mut lp_file_system_name_buffer: [WCHAR; MAX_PATH] = [0; MAX_PATH]; + + let success = unsafe { + GetVolumeInformationW( + path, + lp_volume_name_buffer.as_mut_ptr(), + volume_name_size, + &mut lp_volume_serial_number, + &mut lp_maximum_component_length, + &mut lp_file_system_flags, + lp_file_system_name_buffer.as_mut_ptr(), + n_file_system_name_size, + ) + }; + + if success == 0 { + return Err(Error::last_os_error()); + } + + Ok(utf16_to_string(&lp_file_system_name_buffer)) +} + +pub fn same_disk(disk1: &str, disk2: &str) -> Result { + Ok(false) +} \ No newline at end of file diff --git a/ecstore/src/utils/path.rs b/ecstore/src/utils/path.rs index 9b714d629..fc35031fc 100644 --- a/ecstore/src/utils/path.rs +++ b/ecstore/src/utils/path.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + const GLOBAL_DIR_SUFFIX: &str = "__XLDIR__"; pub const SLASH_SEPARATOR: &str = "/"; @@ -38,6 +40,28 @@ pub fn retain_slash(s: &str) -> String { } } +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_profix(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 struct LazyBuf { s: String, buf: Option>, diff --git a/rustfs/src/grpc.rs b/rustfs/src/grpc.rs index 0780a96ed..e5caf61c4 100644 --- a/rustfs/src/grpc.rs +++ b/rustfs/src/grpc.rs @@ -1,7 +1,10 @@ use std::{error::Error, io::ErrorKind, pin::Pin}; use ecstore::{ - disk::{DeleteOptions, DiskStore, FileInfoVersions, ReadMultipleReq, ReadOptions, UpdateMetadataOpts, WalkDirOptions}, + disk::{ + DeleteOptions, DiskInfoOptions, DiskStore, FileInfoVersions, ReadMultipleReq, ReadOptions, UpdateMetadataOpts, + WalkDirOptions, + }, erasure::{ReadAt, Write}, peer::{LocalPeerS3Client, PeerS3Client}, store::{all_local_disk_path, find_local_disk}, @@ -13,17 +16,7 @@ use lock::{lock_args::LockArgs, Locker, GLOBAL_LOCAL_SERVER}; use protos::{ models::{PingBody, PingBodyBuilder}, proto_gen::node_service::{ - node_service_server::NodeService as Node, DeleteBucketRequest, DeleteBucketResponse, DeletePathsRequest, - DeletePathsResponse, DeleteRequest, DeleteResponse, DeleteVersionRequest, DeleteVersionResponse, DeleteVersionsRequest, - DeleteVersionsResponse, DeleteVolumeRequest, DeleteVolumeResponse, GenerallyLockRequest, GenerallyLockResponse, - GetBucketInfoRequest, GetBucketInfoResponse, ListBucketRequest, ListBucketResponse, ListDirRequest, ListDirResponse, - ListVolumesRequest, ListVolumesResponse, MakeBucketRequest, MakeBucketResponse, MakeVolumeRequest, MakeVolumeResponse, - MakeVolumesRequest, MakeVolumesResponse, PingRequest, PingResponse, ReadAllRequest, ReadAllResponse, ReadAtRequest, - ReadAtResponse, ReadMultipleRequest, ReadMultipleResponse, ReadVersionRequest, ReadVersionResponse, ReadXlRequest, - ReadXlResponse, RenameDataRequest, RenameDataResponse, RenameFileRequst, RenameFileResponse, RenamePartRequst, - RenamePartResponse, StatVolumeRequest, StatVolumeResponse, UpdateMetadataRequest, UpdateMetadataResponse, WalkDirRequest, - WalkDirResponse, WriteAllRequest, WriteAllResponse, WriteMetadataRequest, WriteMetadataResponse, WriteRequest, - WriteResponse, + node_service_server::NodeService as Node, CheckPartsRequest, CheckPartsResponse, DeleteBucketRequest, DeleteBucketResponse, DeletePathsRequest, DeletePathsResponse, DeleteRequest, DeleteResponse, DeleteVersionRequest, DeleteVersionResponse, DeleteVersionsRequest, DeleteVersionsResponse, DeleteVolumeRequest, DeleteVolumeResponse, DiskInfoRequest, DiskInfoResponse, GenerallyLockRequest, GenerallyLockResponse, GetBucketInfoRequest, GetBucketInfoResponse, ListBucketRequest, ListBucketResponse, ListDirRequest, ListDirResponse, ListVolumesRequest, ListVolumesResponse, MakeBucketRequest, MakeBucketResponse, MakeVolumeRequest, MakeVolumeResponse, MakeVolumesRequest, MakeVolumesResponse, PingRequest, PingResponse, ReadAllRequest, ReadAllResponse, ReadAtRequest, ReadAtResponse, ReadMultipleRequest, ReadMultipleResponse, ReadVersionRequest, ReadVersionResponse, ReadXlRequest, ReadXlResponse, RenameDataRequest, RenameDataResponse, RenameFileRequst, RenameFileResponse, RenamePartRequst, RenamePartResponse, StatVolumeRequest, StatVolumeResponse, UpdateMetadataRequest, UpdateMetadataResponse, VerifyFileRequest, VerifyFileResponse, WalkDirRequest, WalkDirResponse, WriteAllRequest, WriteAllResponse, WriteMetadataRequest, WriteMetadataResponse, WriteRequest, WriteResponse }, }; use tokio::sync::mpsc; @@ -303,6 +296,98 @@ impl Node for NodeService { } } + async fn verify_file(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + if let Some(disk) = self.find_disk(&request.disk).await { + let file_info = match serde_json::from_str::(&request.file_info) { + Ok(file_info) => file_info, + Err(_) => { + return Ok(tonic::Response::new(VerifyFileResponse { + success: false, + check_parts_resp: "".to_string(), + error_info: Some("can not decode FileInfo".to_string()), + })); + } + }; + match disk.verify_file(&request.volume, &request.path, &file_info).await { + Ok(check_parts_resp) => { + let check_parts_resp = match serde_json::to_string(&check_parts_resp) { + Ok(check_parts_resp) => check_parts_resp, + Err(_) => { + return Ok(tonic::Response::new(VerifyFileResponse { + success: false, + check_parts_resp: String::new(), + error_info: Some("can not encode RenameDataResp".to_string()), + })); + } + }; + Ok(tonic::Response::new(VerifyFileResponse { + success: true, + check_parts_resp, + error_info: None, + })) + }, + Err(err) => Ok(tonic::Response::new(VerifyFileResponse { + success: false, + check_parts_resp: "".to_string(), + error_info: Some(err.to_string()), + })), + } + } else { + Ok(tonic::Response::new(VerifyFileResponse { + success: false, + check_parts_resp: "".to_string(), + error_info: Some("can not find disk".to_string()), + })) + } + } + + async fn check_parts(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + if let Some(disk) = self.find_disk(&request.disk).await { + let file_info = match serde_json::from_str::(&request.file_info) { + Ok(file_info) => file_info, + Err(_) => { + return Ok(tonic::Response::new(CheckPartsResponse { + success: false, + check_parts_resp: "".to_string(), + error_info: Some("can not decode FileInfo".to_string()), + })); + } + }; + match disk.verify_file(&request.volume, &request.path, &file_info).await { + Ok(check_parts_resp) => { + let check_parts_resp = match serde_json::to_string(&check_parts_resp) { + Ok(check_parts_resp) => check_parts_resp, + Err(_) => { + return Ok(tonic::Response::new(CheckPartsResponse { + success: false, + check_parts_resp: String::new(), + error_info: Some("can not encode RenameDataResp".to_string()), + })); + } + }; + Ok(tonic::Response::new(CheckPartsResponse { + success: true, + check_parts_resp, + error_info: None, + })) + }, + Err(err) => Ok(tonic::Response::new(CheckPartsResponse { + success: false, + check_parts_resp: "".to_string(), + error_info: Some(err.to_string()), + })), + } + } else { + Ok(tonic::Response::new(CheckPartsResponse { + success: false, + check_parts_resp: "".to_string(), + error_info: Some("can not find disk".to_string()), + })) + } + } + async fn rename_part(&self, request: Request) -> Result, Status> { let request = request.into_inner(); if let Some(disk) = self.find_disk(&request.disk).await { @@ -1125,6 +1210,47 @@ impl Node for NodeService { } } + async fn disk_info(&self, request: Request) -> Result, Status> { + let request = request.into_inner(); + if let Some(disk) = self.find_disk(&request.disk).await { + let opts = match serde_json::from_str::(&request.opts) { + Ok(opts) => opts, + Err(_) => { + return Ok(tonic::Response::new(DiskInfoResponse { + success: false, + disk_info: "".to_string(), + error_info: Some("can not decode DiskInfoOptions".to_string()), + })); + } + }; + match disk.disk_info(&opts).await { + Ok(disk_info) => match serde_json::to_string(&disk_info) { + Ok(disk_info) => Ok(tonic::Response::new(DiskInfoResponse { + success: true, + disk_info, + error_info: None, + })), + Err(err) => Ok(tonic::Response::new(DiskInfoResponse { + success: false, + disk_info: "".to_string(), + error_info: Some(err.to_string()), + })), + }, + Err(err) => Ok(tonic::Response::new(DiskInfoResponse { + success: false, + disk_info: "".to_string(), + error_info: Some(err.to_string()), + })), + } + } else { + Ok(tonic::Response::new(DiskInfoResponse { + success: false, + disk_info: "".to_string(), + error_info: Some("can not find disk".to_string()), + })) + } + } + async fn lock(&self, request: Request) -> Result, Status> { let request = request.into_inner(); match &serde_json::from_str::(&request.args) {