mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 13:16:28 +00:00
use http for remote read/write
This commit is contained in:
@@ -61,7 +61,6 @@ tracing-subscriber.workspace = true
|
||||
transform-stream.workspace = true
|
||||
uuid = "1.15.1"
|
||||
url.workspace = true
|
||||
admin = { path = "../api/admin" }
|
||||
axum.workspace = true
|
||||
matchit = "0.8.6"
|
||||
shadow-rs.workspace = true
|
||||
|
||||
+63
-2
@@ -3,9 +3,10 @@ use super::router::Operation;
|
||||
use super::router::S3Router;
|
||||
use crate::storage::ecfs::bytes_stream;
|
||||
use common::error::Result;
|
||||
use ecstore::disk::io::FileReader;
|
||||
use ecstore::disk::DiskAPI;
|
||||
use ecstore::disk::FileReader;
|
||||
use ecstore::store::find_local_disk;
|
||||
use futures::TryStreamExt;
|
||||
use http::StatusCode;
|
||||
use hyper::Method;
|
||||
use matchit::Params;
|
||||
@@ -17,6 +18,7 @@ use s3s::S3Response;
|
||||
use s3s::S3Result;
|
||||
use serde_urlencoded::from_bytes;
|
||||
use tokio_util::io::ReaderStream;
|
||||
use tokio_util::io::StreamReader;
|
||||
use tracing::warn;
|
||||
|
||||
pub const RPC_PREFIX: &str = "/rustfs/rpc";
|
||||
@@ -28,6 +30,12 @@ pub fn regist_rpc_route(r: &mut S3Router<AdminOperation>) -> Result<()> {
|
||||
AdminOperation(&ReadFile {}),
|
||||
)?;
|
||||
|
||||
r.insert(
|
||||
Method::PUT,
|
||||
format!("{}{}", RPC_PREFIX, "/put_file_stream").as_str(),
|
||||
AdminOperation(&PutFile {}),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -49,7 +57,7 @@ impl Operation for ReadFile {
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: ReadFileQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|_e| s3_error!(InvalidArgument, "get query failed1"))?;
|
||||
from_bytes(query.as_bytes()).map_err(|e| s3_error!(InvalidArgument, "get query failed1 {:?}", e))?;
|
||||
input
|
||||
} else {
|
||||
ReadFileQuery::default()
|
||||
@@ -95,3 +103,56 @@ impl Operation for ReadFile {
|
||||
// Ok(S3Response::new((StatusCode::BAD_REQUEST, Body::empty())))
|
||||
}
|
||||
}
|
||||
|
||||
// /rustfs/rpc/read_file_stream?disk={}&volume={}&path={}&offset={}&length={}"
|
||||
#[derive(Debug, Default, serde::Deserialize)]
|
||||
pub struct PutFileQuery {
|
||||
disk: String,
|
||||
volume: String,
|
||||
path: String,
|
||||
append: bool,
|
||||
size: usize,
|
||||
}
|
||||
pub struct PutFile {}
|
||||
#[async_trait::async_trait]
|
||||
impl Operation for PutFile {
|
||||
async fn call(&self, req: S3Request<Body>, _params: Params<'_, '_>) -> S3Result<S3Response<(StatusCode, Body)>> {
|
||||
warn!("handle PutFile");
|
||||
|
||||
let query = {
|
||||
if let Some(query) = req.uri.query() {
|
||||
let input: PutFileQuery =
|
||||
from_bytes(query.as_bytes()).map_err(|e| s3_error!(InvalidArgument, "get query failed1 {:?}", e))?;
|
||||
input
|
||||
} else {
|
||||
PutFileQuery::default()
|
||||
}
|
||||
};
|
||||
|
||||
let Some(disk) = find_local_disk(&query.disk).await else {
|
||||
return Err(s3_error!(InvalidArgument, "disk not found"));
|
||||
};
|
||||
|
||||
let mut file = if query.append {
|
||||
disk.append_file(&query.volume, &query.path)
|
||||
.await
|
||||
.map_err(|e| s3_error!(InternalError, "append file err {}", e))?
|
||||
} else {
|
||||
disk.create_file("", &query.volume, &query.path, query.size)
|
||||
.await
|
||||
.map_err(|e| s3_error!(InternalError, "read file err {}", e))?
|
||||
};
|
||||
|
||||
let mut body = StreamReader::new(
|
||||
req.input
|
||||
.into_stream()
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)),
|
||||
);
|
||||
|
||||
tokio::io::copy(&mut body, &mut file)
|
||||
.await
|
||||
.map_err(|e| s3_error!(InternalError, "copy err {}", e))?;
|
||||
|
||||
Ok(S3Response::new((StatusCode::OK, Body::empty())))
|
||||
}
|
||||
}
|
||||
|
||||
+143
-143
@@ -1,9 +1,4 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error::Error,
|
||||
io::{Cursor, ErrorKind},
|
||||
pin::Pin,
|
||||
};
|
||||
use std::{collections::HashMap, io::Cursor, pin::Pin};
|
||||
|
||||
use ecstore::{
|
||||
admin_server_info::get_local_server_property,
|
||||
@@ -11,7 +6,6 @@ use ecstore::{
|
||||
disk::{
|
||||
DeleteOptions, DiskAPI, DiskInfoOptions, DiskStore, FileInfoVersions, ReadMultipleReq, ReadOptions, UpdateMetadataOpts,
|
||||
},
|
||||
erasure::Writer,
|
||||
error::Error as EcsError,
|
||||
heal::{
|
||||
data_usage_cache::DataUsageCache,
|
||||
@@ -50,25 +44,25 @@ use tracing::{debug, error, info};
|
||||
|
||||
type ResponseStream<T> = Pin<Box<dyn Stream<Item = Result<T, tonic::Status>> + Send>>;
|
||||
|
||||
fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> {
|
||||
let mut err: &(dyn Error + 'static) = err_status;
|
||||
// fn match_for_io_error(err_status: &Status) -> Option<&std::io::Error> {
|
||||
// let mut err: &(dyn Error + 'static) = err_status;
|
||||
|
||||
loop {
|
||||
if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
|
||||
return Some(io_err);
|
||||
}
|
||||
// loop {
|
||||
// if let Some(io_err) = err.downcast_ref::<std::io::Error>() {
|
||||
// return Some(io_err);
|
||||
// }
|
||||
|
||||
// h2::Error do not expose std::io::Error with `source()`
|
||||
// https://github.com/hyperium/h2/pull/462
|
||||
if let Some(h2_err) = err.downcast_ref::<h2::Error>() {
|
||||
if let Some(io_err) = h2_err.get_io() {
|
||||
return Some(io_err);
|
||||
}
|
||||
}
|
||||
// // h2::Error do not expose std::io::Error with `source()`
|
||||
// // https://github.com/hyperium/h2/pull/462
|
||||
// if let Some(h2_err) = err.downcast_ref::<h2::Error>() {
|
||||
// if let Some(io_err) = h2_err.get_io() {
|
||||
// return Some(io_err);
|
||||
// }
|
||||
// }
|
||||
|
||||
err = err.source()?;
|
||||
}
|
||||
}
|
||||
// err = err.source()?;
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NodeService {
|
||||
@@ -558,138 +552,144 @@ impl Node for NodeService {
|
||||
}
|
||||
}
|
||||
|
||||
async fn write(&self, request: Request<WriteRequest>) -> Result<Response<WriteResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
if let Some(disk) = self.find_disk(&request.disk).await {
|
||||
let file_writer = if request.is_append {
|
||||
disk.append_file(&request.volume, &request.path).await
|
||||
} else {
|
||||
disk.create_file("", &request.volume, &request.path, 0).await
|
||||
};
|
||||
async fn write(&self, _request: Request<WriteRequest>) -> Result<Response<WriteResponse>, Status> {
|
||||
unimplemented!("write");
|
||||
// let request = request.into_inner();
|
||||
// if let Some(disk) = self.find_disk(&request.disk).await {
|
||||
// let file_writer = if request.is_append {
|
||||
// disk.append_file(&request.volume, &request.path).await
|
||||
// } else {
|
||||
// disk.create_file("", &request.volume, &request.path, 0).await
|
||||
// };
|
||||
|
||||
match file_writer {
|
||||
Ok(mut file_writer) => match file_writer.write(&request.data).await {
|
||||
Ok(_) => Ok(tonic::Response::new(WriteResponse {
|
||||
success: true,
|
||||
error: None,
|
||||
})),
|
||||
Err(err) => Ok(tonic::Response::new(WriteResponse {
|
||||
success: false,
|
||||
error: Some(err_to_proto_err(&err, &format!("write failed: {}", err))),
|
||||
})),
|
||||
},
|
||||
Err(err) => Ok(tonic::Response::new(WriteResponse {
|
||||
success: false,
|
||||
error: Some(err_to_proto_err(&err, &format!("get writer failed: {}", err))),
|
||||
})),
|
||||
}
|
||||
} else {
|
||||
Ok(tonic::Response::new(WriteResponse {
|
||||
success: false,
|
||||
error: Some(err_to_proto_err(
|
||||
&EcsError::new(StorageError::InvalidArgument(Default::default(), Default::default(), Default::default())),
|
||||
"can not find disk",
|
||||
)),
|
||||
}))
|
||||
}
|
||||
// match file_writer {
|
||||
// Ok(mut file_writer) => match file_writer.write(&request.data).await {
|
||||
// Ok(_) => Ok(tonic::Response::new(WriteResponse {
|
||||
// success: true,
|
||||
// error: None,
|
||||
// })),
|
||||
// Err(err) => Ok(tonic::Response::new(WriteResponse {
|
||||
// success: false,
|
||||
// error: Some(err_to_proto_err(&err, &format!("write failed: {}", err))),
|
||||
// })),
|
||||
// },
|
||||
// Err(err) => Ok(tonic::Response::new(WriteResponse {
|
||||
// success: false,
|
||||
// error: Some(err_to_proto_err(&err, &format!("get writer failed: {}", err))),
|
||||
// })),
|
||||
// }
|
||||
// } else {
|
||||
// Ok(tonic::Response::new(WriteResponse {
|
||||
// success: false,
|
||||
// error: Some(err_to_proto_err(
|
||||
// &EcsError::new(StorageError::InvalidArgument(Default::default(), Default::default(), Default::default())),
|
||||
// "can not find disk",
|
||||
// )),
|
||||
// }))
|
||||
// }
|
||||
}
|
||||
|
||||
type WriteStreamStream = ResponseStream<WriteResponse>;
|
||||
async fn write_stream(&self, request: Request<Streaming<WriteRequest>>) -> Result<Response<Self::WriteStreamStream>, Status> {
|
||||
async fn write_stream(
|
||||
&self,
|
||||
_request: Request<Streaming<WriteRequest>>,
|
||||
) -> Result<Response<Self::WriteStreamStream>, Status> {
|
||||
info!("write_stream");
|
||||
|
||||
let mut in_stream = request.into_inner();
|
||||
let (tx, rx) = mpsc::channel(128);
|
||||
unimplemented!("write_stream");
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut file_ref = None;
|
||||
while let Some(result) = in_stream.next().await {
|
||||
match result {
|
||||
// Ok(v) => tx
|
||||
// .send(Ok(EchoResponse { message: v.message }))
|
||||
// .await
|
||||
// .expect("working rx"),
|
||||
Ok(v) => {
|
||||
match file_ref.as_ref() {
|
||||
Some(_) => (),
|
||||
None => {
|
||||
if let Some(disk) = find_local_disk(&v.disk).await {
|
||||
let file_writer = if v.is_append {
|
||||
disk.append_file(&v.volume, &v.path).await
|
||||
} else {
|
||||
disk.create_file("", &v.volume, &v.path, 0).await
|
||||
};
|
||||
// let mut in_stream = request.into_inner();
|
||||
// let (tx, rx) = mpsc::channel(128);
|
||||
|
||||
match file_writer {
|
||||
Ok(file_writer) => file_ref = Some(file_writer),
|
||||
Err(err) => {
|
||||
tx.send(Ok(WriteResponse {
|
||||
success: false,
|
||||
error: Some(err_to_proto_err(
|
||||
&err,
|
||||
&format!("get get file writer failed: {}", err),
|
||||
)),
|
||||
}))
|
||||
.await
|
||||
.expect("working rx");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tx.send(Ok(WriteResponse {
|
||||
success: false,
|
||||
error: Some(err_to_proto_err(
|
||||
&EcsError::new(StorageError::InvalidArgument(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)),
|
||||
"can not find disk",
|
||||
)),
|
||||
}))
|
||||
.await
|
||||
.expect("working rx");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
// tokio::spawn(async move {
|
||||
// let mut file_ref = None;
|
||||
// while let Some(result) = in_stream.next().await {
|
||||
// match result {
|
||||
// // Ok(v) => tx
|
||||
// // .send(Ok(EchoResponse { message: v.message }))
|
||||
// // .await
|
||||
// // .expect("working rx"),
|
||||
// Ok(v) => {
|
||||
// match file_ref.as_ref() {
|
||||
// Some(_) => (),
|
||||
// None => {
|
||||
// if let Some(disk) = find_local_disk(&v.disk).await {
|
||||
// let file_writer = if v.is_append {
|
||||
// disk.append_file(&v.volume, &v.path).await
|
||||
// } else {
|
||||
// disk.create_file("", &v.volume, &v.path, 0).await
|
||||
// };
|
||||
|
||||
match file_ref.as_mut().unwrap().write(&v.data).await {
|
||||
Ok(_) => tx.send(Ok(WriteResponse {
|
||||
success: true,
|
||||
error: None,
|
||||
})),
|
||||
Err(err) => tx.send(Ok(WriteResponse {
|
||||
success: false,
|
||||
error: Some(err_to_proto_err(&err, &format!("write failed: {}", err))),
|
||||
})),
|
||||
}
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Err(err) => {
|
||||
if let Some(io_err) = match_for_io_error(&err) {
|
||||
if io_err.kind() == ErrorKind::BrokenPipe {
|
||||
// here you can handle special case when client
|
||||
// disconnected in unexpected way
|
||||
eprintln!("\tclient disconnected: broken pipe");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// match file_writer {
|
||||
// Ok(file_writer) => file_ref = Some(file_writer),
|
||||
// Err(err) => {
|
||||
// tx.send(Ok(WriteResponse {
|
||||
// success: false,
|
||||
// error: Some(err_to_proto_err(
|
||||
// &err,
|
||||
// &format!("get get file writer failed: {}", err),
|
||||
// )),
|
||||
// }))
|
||||
// .await
|
||||
// .expect("working rx");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// tx.send(Ok(WriteResponse {
|
||||
// success: false,
|
||||
// error: Some(err_to_proto_err(
|
||||
// &EcsError::new(StorageError::InvalidArgument(
|
||||
// Default::default(),
|
||||
// Default::default(),
|
||||
// Default::default(),
|
||||
// )),
|
||||
// "can not find disk",
|
||||
// )),
|
||||
// }))
|
||||
// .await
|
||||
// .expect("working rx");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
match tx.send(Err(err)).await {
|
||||
Ok(_) => (),
|
||||
Err(_err) => break, // response was dropped
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("\tstream ended");
|
||||
});
|
||||
// match file_ref.as_mut().unwrap().write(&v.data).await {
|
||||
// Ok(_) => tx.send(Ok(WriteResponse {
|
||||
// success: true,
|
||||
// error: None,
|
||||
// })),
|
||||
// Err(err) => tx.send(Ok(WriteResponse {
|
||||
// success: false,
|
||||
// error: Some(err_to_proto_err(&err, &format!("write failed: {}", err))),
|
||||
// })),
|
||||
// }
|
||||
// .await
|
||||
// .unwrap();
|
||||
// }
|
||||
// Err(err) => {
|
||||
// if let Some(io_err) = match_for_io_error(&err) {
|
||||
// if io_err.kind() == ErrorKind::BrokenPipe {
|
||||
// // here you can handle special case when client
|
||||
// // disconnected in unexpected way
|
||||
// eprintln!("\tclient disconnected: broken pipe");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
let out_stream = ReceiverStream::new(rx);
|
||||
// match tx.send(Err(err)).await {
|
||||
// Ok(_) => (),
|
||||
// Err(_err) => break, // response was dropped
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// println!("\tstream ended");
|
||||
// });
|
||||
|
||||
Ok(tonic::Response::new(Box::pin(out_stream)))
|
||||
// let out_stream = ReceiverStream::new(rx);
|
||||
|
||||
// Ok(tonic::Response::new(Box::pin(out_stream)))
|
||||
}
|
||||
|
||||
type ReadAtStream = ResponseStream<ReadAtResponse>;
|
||||
|
||||
+2
-2
@@ -152,8 +152,8 @@ async fn run(opt: config::Opt) -> Result<()> {
|
||||
|
||||
for (i, eps) in endpoint_pools.as_ref().iter().enumerate() {
|
||||
info!(
|
||||
"created endpoints {}, set_count:{}, drives_per_set: {}, cmd: {:?}",
|
||||
i, eps.set_count, eps.drives_per_set, eps.cmd_line
|
||||
"created endpoints {}, set_count:{}, drives_per_set: {}, cmd: {:?}, \n{:?}",
|
||||
i, eps.set_count, eps.drives_per_set, eps.cmd_line, eps
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user