From a96ac8c0249294abdddaff9624e63bd4b262af78 Mon Sep 17 00:00:00 2001 From: bestgopher <84328409@qq.com> Date: Tue, 15 Oct 2024 11:09:40 +0800 Subject: [PATCH 1/2] feat: introduce admin-api Signed-off-by: bestgopher <84328409@qq.com> --- Cargo.lock | 37 ++++++++++++ Cargo.toml | 2 + admin/Cargo.toml | 18 ++++++ admin/src/error.rs | 98 ++++++++++++++++++++++++++++++++ admin/src/handlers.rs | 1 + admin/src/handlers/list_pools.rs | 79 +++++++++++++++++++++++++ admin/src/lib.rs | 21 +++++++ admin/src/middlewares.rs | 0 admin/src/object_api.rs | 20 +++++++ ecstore/src/global.rs | 6 ++ ecstore/src/lib.rs | 1 + rustfs/Cargo.toml | 3 +- rustfs/src/main.rs | 90 +++++++++++++++-------------- rustfs/src/service.rs | 72 +++++++++++++++++++---- 14 files changed, 392 insertions(+), 56 deletions(-) create mode 100644 admin/Cargo.toml create mode 100644 admin/src/error.rs create mode 100644 admin/src/handlers.rs create mode 100644 admin/src/handlers/list_pools.rs create mode 100644 admin/src/lib.rs create mode 100644 admin/src/middlewares.rs create mode 100644 admin/src/object_api.rs diff --git a/Cargo.lock b/Cargo.lock index 62d20bfc..ac19a510 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,21 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "admin" +version = "0.0.1" +dependencies = [ + "axum", + "ecstore", + "futures-util", + "hyper", + "mime", + "serde", + "serde_json", + "time", + "tower 0.4.13", +] + [[package]] name = "ahash" version = "0.7.8" @@ -165,6 +180,8 @@ dependencies = [ "http", "http-body", "http-body-util", + "hyper", + "hyper-util", "itoa", "matchit", "memchr", @@ -173,10 +190,15 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", "sync_wrapper 1.0.1", + "tokio", "tower 0.5.1", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -197,6 +219,7 @@ dependencies = [ "sync_wrapper 1.0.1", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -1604,7 +1627,9 @@ dependencies = [ name = "rustfs" version = "0.1.0" dependencies = [ + "admin", "async-trait", + "axum", "bytes", "clap", "common", @@ -1810,6 +1835,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2198,8 +2233,10 @@ dependencies = [ "futures-util", "pin-project-lite", "sync_wrapper 0.1.2", + "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index e45a7fba..f8592455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "common/common", "common/lock", "common/protos", + "admin", ] [workspace.package] @@ -77,3 +78,4 @@ uuid = { version = "1.10.0", features = [ "macro-diagnostics", ] } log = "0.4.22" +axum = "0.7.7" diff --git a/admin/Cargo.toml b/admin/Cargo.toml new file mode 100644 index 00000000..05583907 --- /dev/null +++ b/admin/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "admin" +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[dependencies] +axum.workspace = true +mime.workspace = true +serde.workspace = true +serde_json.workspace = true +ecstore = { path = "../ecstore" } +time = { workspace = true, features = ["serde"] } +tower.workspace = true +futures-util = "0.3.31" +hyper.workspace = true diff --git a/admin/src/error.rs b/admin/src/error.rs new file mode 100644 index 00000000..bbe24460 --- /dev/null +++ b/admin/src/error.rs @@ -0,0 +1,98 @@ +use axum::{ + body::Body, + http::{header::CONTENT_TYPE, HeaderValue, StatusCode}, + response::{IntoResponse, Response}, +}; +use mime::APPLICATION_JSON; +use serde::Serialize; + +#[derive(Serialize, Default)] +#[serde(rename_all = "PascalCase")] +pub struct ErrorResponse { + pub code: String, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub key: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub bucket_name: Option, + pub resource: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub region: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub request_id: Option, + pub host_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub actual_object_size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub range_requested: Option, +} + +impl IntoResponse for APIError { + fn into_response(self) -> Response { + let code = self.http_status_code; + let err_response = ErrorResponse::from(self); + let json_res = match serde_json::to_vec(&err_response) { + Ok(r) => r, + Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")).into_response(), + }; + + Response::builder() + .status(code) + .header(CONTENT_TYPE, HeaderValue::from_static(APPLICATION_JSON.as_ref())) + .body(Body::from(json_res)) + .unwrap_or_else(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")).into_response()) + } +} + +#[derive(Default)] +pub struct APIError { + code: String, + description: String, + http_status_code: StatusCode, + object_size: Option, + range_requested: Option, +} + +pub enum ErrorCode { + ErrNotImplemented, + ErrServerNotInitialized, +} + +impl IntoResponse for ErrorCode { + fn into_response(self) -> Response { + APIError::from(self).into_response() + } +} + +impl From for APIError { + fn from(value: ErrorCode) -> Self { + use ErrorCode::*; + + match value { + ErrNotImplemented => APIError { + code: "NotImplemented".into(), + description: "A header you provided implies functionality that is not implemented.".into(), + http_status_code: StatusCode::NOT_IMPLEMENTED, + ..Default::default() + }, + ErrServerNotInitialized => APIError { + code: "ServerNotInitialized".into(), + description: "Server not initialized yet, please try again.".into(), + http_status_code: StatusCode::SERVICE_UNAVAILABLE, + ..Default::default() + }, + } + } +} + +impl From for ErrorResponse { + fn from(value: APIError) -> Self { + Self { + code: value.code, + message: value.description, + actual_object_size: value.object_size, + range_requested: value.range_requested, + ..Default::default() + } + } +} diff --git a/admin/src/handlers.rs b/admin/src/handlers.rs new file mode 100644 index 00000000..fa5c33dd --- /dev/null +++ b/admin/src/handlers.rs @@ -0,0 +1 @@ +pub mod list_pools; diff --git a/admin/src/handlers/list_pools.rs b/admin/src/handlers/list_pools.rs new file mode 100644 index 00000000..b74e2d25 --- /dev/null +++ b/admin/src/handlers/list_pools.rs @@ -0,0 +1,79 @@ +use crate::Result as LocalResult; +use crate::{error::ErrorCode, object_api::ObjectApi}; + +use axum::{extract::State, Json}; +use serde::Serialize; +use time::OffsetDateTime; + +#[derive(Serialize)] +pub struct PoolStatus { + id: i64, + cmdline: String, + #[serde(rename = "lastUpdate")] + #[serde(serialize_with = "time::serde::rfc3339::serialize")] + last_updat: OffsetDateTime, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "decommissionInfo")] + decommission_info: Option, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct PoolDecommissionInfo { + #[serde(serialize_with = "time::serde::rfc3339::serialize")] + start_time: OffsetDateTime, + start_size: i64, + total_size: i64, + current_size: i64, + complete: bool, + failed: bool, + canceled: bool, + + #[serde(rename = "objectsDecommissioned")] + items_decommissioned: i64, + #[serde(rename = "objectsDecommissionedFailed")] + items_decommission_failed: i64, + #[serde(rename = "bytesDecommissioned")] + bytes_done: i64, + #[serde(rename = "bytesDecommissionedFailed")] + bytes_failed: i64, +} + +pub async fn handler(State(ec_store): State) -> LocalResult>> { + // if ecstore::is_legacy().await { + // return Err(ErrorCode::ErrNotImplemented); + // } + + let pools = (*ec_store).as_ref().ok_or(ErrorCode::ErrNotImplemented)?; + + // todo, 调用pool.status()接口获取每个池的数据 + // + let mut result = Vec::new(); + for (idx, _pool) in pools.pools.iter().enumerate() { + // 这里mock一下数据 + result.push(PoolStatus { + id: idx as _, + cmdline: "cmdline".into(), + last_updat: OffsetDateTime::now_utc(), + decommission_info: if idx % 2 == 0 { + Some(PoolDecommissionInfo { + start_time: OffsetDateTime::now_utc(), + start_size: 1, + total_size: 2, + current_size: 2, + complete: true, + failed: true, + canceled: true, + items_decommissioned: 1, + items_decommission_failed: 1, + bytes_done: 1, + bytes_failed: 1, + }) + } else { + None + }, + }) + } + + Ok(Json(result)) +} diff --git a/admin/src/lib.rs b/admin/src/lib.rs new file mode 100644 index 00000000..58b7163d --- /dev/null +++ b/admin/src/lib.rs @@ -0,0 +1,21 @@ +pub mod error; +pub mod handlers; +pub mod object_api; + +use axum::{extract::Request, response::Response, routing::get, BoxError, Router}; +use ecstore::store::ECStore; +use error::ErrorCode; +use handlers::list_pools; +use object_api::ObjectApi; +use tower::Service; + +pub type Result = std::result::Result; + +pub fn register_admin_router( + ec_store: Option, +) -> impl Service, Future: Send> + Clone { + Router::new() + .nest("/admin/v3", Router::new().route("/pools/list", get(list_pools::handler))) + .with_state::<()>(ObjectApi::new(ec_store)) + .into_service() +} diff --git a/admin/src/middlewares.rs b/admin/src/middlewares.rs new file mode 100644 index 00000000..e69de29b diff --git a/admin/src/object_api.rs b/admin/src/object_api.rs new file mode 100644 index 00000000..2395df40 --- /dev/null +++ b/admin/src/object_api.rs @@ -0,0 +1,20 @@ +use std::ops::Deref; + +use ecstore::store::ECStore; + +#[derive(Clone)] +pub struct ObjectApi(Option); + +impl Deref for ObjectApi { + type Target = Option; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ObjectApi { + pub fn new(t: Option) -> Self { + Self(t) + } +} diff --git a/ecstore/src/global.rs b/ecstore/src/global.rs index 878c6194..a5735544 100644 --- a/ecstore/src/global.rs +++ b/ecstore/src/global.rs @@ -58,4 +58,10 @@ pub async fn update_erasure_type(setup_type: SetupType) { *is_erasure_sd = setup_type == SetupType::ErasureSD; } +pub async fn is_legacy() -> bool { + let lock = GLOBAL_Endpoints.read().await; + let endpoints = lock.as_ref(); + endpoints.len() == 1 && endpoints[0].legacy +} + type TypeLocalDiskSetDrives = Vec>>>; diff --git a/ecstore/src/lib.rs b/ecstore/src/lib.rs index 542213cb..6d9e13a2 100644 --- a/ecstore/src/lib.rs +++ b/ecstore/src/lib.rs @@ -19,6 +19,7 @@ mod utils; pub mod bucket; +pub use global::is_legacy; pub use global::new_object_layer_fn; pub use global::set_global_endpoints; pub use global::update_erasure_type; diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index 5c3c7913..d2343793 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -49,6 +49,8 @@ tracing-error.workspace = true tracing-subscriber.workspace = true transform-stream.workspace = true uuid = "1.10.0" +admin = { path = "../admin" } +axum.workspace = true [build-dependencies] prost-build.workspace = true @@ -67,7 +69,6 @@ hyper-util = { version = "0.1.9", features = [ "server-auto", "server-graceful", ] } -mime = "0.3.17" transform-stream = "0.3.0" netif = "0.1.6" # pin-utils = "0.1.0" diff --git a/rustfs/src/main.rs b/rustfs/src/main.rs index 20568883..feadc029 100644 --- a/rustfs/src/main.rs +++ b/rustfs/src/main.rs @@ -147,55 +147,59 @@ async fn run(opt: config::Opt) -> Result<()> { let rpc_service = NodeServiceServer::with_interceptor(make_server(), check_auth); - tokio::spawn(async move { - let hyper_service = service.into_shared(); - - let hybrid_service = TowerToHyperService::new(hybrid(hyper_service, rpc_service)); - - let http_server = ConnBuilder::new(TokioExecutor::new()); - let mut ctrl_c = std::pin::pin!(tokio::signal::ctrl_c()); - let graceful = hyper_util::server::graceful::GracefulShutdown::new(); - info!("server is running at http://{local_addr}"); - - loop { - let (socket, _) = tokio::select! { - res = listener.accept() => { - match res { - Ok(conn) => conn, - Err(err) => { - tracing::error!("error accepting connection: {err}"); - continue; - } - } - } - _ = ctrl_c.as_mut() => { - break; - } - }; - - let conn = http_server.serve_connection(TokioIo::new(socket), hybrid_service.clone()); - let conn = graceful.watch(conn.into_owned()); - tokio::spawn(async move { - let _ = conn.await; - }); - } - - tokio::select! { - () = graceful.shutdown() => { - tracing::debug!("Gracefully shutdown!"); - }, - () = tokio::time::sleep(std::time::Duration::from_secs(10)) => { - tracing::debug!("Waited 10 seconds for graceful shutdown, aborting..."); - } - } - }); - // init store let store = ECStore::new(opt.address.clone(), endpoint_pools.clone()) .await .map_err(|err| Error::from_string(err.to_string()))?; info!(" init store success!"); + tokio::spawn({ + let store = store.clone(); + async move { + let hyper_service = service.into_shared(); + let adm_service = admin::register_admin_router(Some(store)); + + let hybrid_service = TowerToHyperService::new(hybrid(hyper_service, rpc_service, adm_service)); + + let http_server = ConnBuilder::new(TokioExecutor::new()); + let mut ctrl_c = std::pin::pin!(tokio::signal::ctrl_c()); + let graceful = hyper_util::server::graceful::GracefulShutdown::new(); + info!("server is running at http://{local_addr}"); + + loop { + let (socket, _) = tokio::select! { + res = listener.accept() => { + match res { + Ok(conn) => conn, + Err(err) => { + tracing::error!("error accepting connection: {err}"); + continue; + } + } + } + _ = ctrl_c.as_mut() => { + break; + } + }; + + let conn = http_server.serve_connection(TokioIo::new(socket), hybrid_service.clone()); + let conn = graceful.watch(conn.into_owned()); + tokio::spawn(async move { + let _ = conn.await; + }); + } + + tokio::select! { + () = graceful.shutdown() => { + tracing::debug!("Gracefully shutdown!"); + }, + () = tokio::time::sleep(std::time::Duration::from_secs(10)) => { + tracing::debug!("Waited 10 seconds for graceful shutdown, aborting..."); + } + } + } + }); + let buckets_list = store .list_bucket(&BucketOptions { no_metadata: true, diff --git a/rustfs/src/service.rs b/rustfs/src/service.rs index ea1d4272..049ec94e 100644 --- a/rustfs/src/service.rs +++ b/rustfs/src/service.rs @@ -1,6 +1,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; +use axum::body::Body; use futures::Future; use http_body::Frame; use hyper::body::Incoming; @@ -11,35 +12,52 @@ use tower::Service; type BoxError = Box; /// Generate a [`HybridService`] -pub(crate) fn hybrid(make_rest: MakeRest, grpc: Grpc) -> HybridService { - HybridService { rest: make_rest, grpc } +pub(crate) fn hybrid( + make_rest: MakeRest, + grpc: Grpc, + admin: Admin, +) -> HybridService { + HybridService { + rest: make_rest, + grpc, + admin, + } } /// The service that can serve both gRPC and REST HTTP Requests #[derive(Clone)] -pub struct HybridService { +pub struct HybridService { rest: Rest, grpc: Grpc, + admin: Admin, } -impl Service> for HybridService +impl Service> for HybridService where Rest: Service, Response = Response>, Grpc: Service, Response = Response>, + Admin: Service, Response = Response>, Rest::Error: Into, Grpc::Error: Into, + Admin::Error: Into, { - type Response = Response>; + type Response = Response>; type Error = BoxError; - type Future = HybridFuture; + type Future = HybridFuture; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self.rest.poll_ready(cx) { Poll::Ready(Ok(())) => match self.grpc.poll_ready(cx) { - Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Ok(())) => match self.admin.poll_ready(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), + Poll::Pending => Poll::Pending, + }, + Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), Poll::Pending => Poll::Pending, }, + Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), Poll::Pending => Poll::Pending, } @@ -53,6 +71,14 @@ where (hyper::Version::HTTP_2, Some(hv)) if hv.as_bytes().starts_with(b"application/grpc") => HybridFuture::Grpc { grpc_future: self.grpc.call(req), }, + + _ if req.uri().path().starts_with("/admin/v3") => HybridFuture::Admin { + admin_future: self.admin.call({ + let (parts, body) = req.into_parts(); + Request::from_parts(parts, Body::new(body).into()) + }), + }, + _ => HybridFuture::Rest { rest_future: self.rest.call(req), }, @@ -64,7 +90,7 @@ pin_project! { /// A hybrid HTTP body that will be used in the response type for the /// [`HybridFuture`], i.e., the output of the [`HybridService`] #[project = HybridBodyProj] - pub enum HybridBody { + pub enum HybridBody { Rest { #[pin] rest_body: RestBody @@ -73,15 +99,21 @@ pin_project! { #[pin] grpc_body: GrpcBody }, + Admin { + #[pin] + admin_body: AdminBody + }, } } -impl http_body::Body for HybridBody +impl http_body::Body for HybridBody where RestBody: http_body::Body + Send + Unpin, GrpcBody: http_body::Body + Send + Unpin, + AdminBody: http_body::Body + Send + Unpin, RestBody::Error: Into, GrpcBody::Error: Into, + AdminBody::Error: Into, { type Data = RestBody::Data; type Error = BoxError; @@ -90,6 +122,7 @@ where match self { Self::Rest { rest_body } => rest_body.is_end_stream(), Self::Grpc { grpc_body } => grpc_body.is_end_stream(), + Self::Admin { admin_body } => admin_body.is_end_stream(), } } @@ -97,6 +130,7 @@ where match self.project() { HybridBodyProj::Rest { rest_body } => rest_body.poll_frame(cx).map_err(Into::into), HybridBodyProj::Grpc { grpc_body } => grpc_body.poll_frame(cx).map_err(Into::into), + HybridBodyProj::Admin { admin_body } => admin_body.poll_frame(cx).map_err(Into::into), } } @@ -104,6 +138,7 @@ where match self { Self::Rest { rest_body } => rest_body.size_hint(), Self::Grpc { grpc_body } => grpc_body.size_hint(), + Self::Admin { admin_body } => admin_body.size_hint(), } } } @@ -112,7 +147,7 @@ pin_project! { /// A future that accepts an HTTP request as input and returns an HTTP /// response as output for the [`HybridService`] #[project = HybridFutureProj] - pub enum HybridFuture { + pub enum HybridFuture { Rest { #[pin] rest_future: RestFuture, @@ -120,18 +155,26 @@ pin_project! { Grpc { #[pin] grpc_future: GrpcFuture, + }, + + Admin { + #[pin] + admin_future: AdminFuture, } } } -impl Future for HybridFuture +impl Future + for HybridFuture where RestFuture: Future, RestError>>, GrpcFuture: Future, GrpcError>>, + AdminFuture: Future, AdminError>>, RestError: Into, GrpcError: Into, + AdminError: Into, { - type Output = Result>, BoxError>; + type Output = Result>, BoxError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { @@ -145,6 +188,11 @@ where Poll::Ready(Err(err)) => Poll::Ready(Err(err.into())), Poll::Pending => Poll::Pending, }, + HybridFutureProj::Admin { admin_future } => match admin_future.poll(cx) { + Poll::Ready(Ok(res)) => Poll::Ready(Ok(res.map(|admin_body| HybridBody::Admin { admin_body }))), + Poll::Ready(Err(err)) => Poll::Ready(Err(err.into())), + Poll::Pending => Poll::Pending, + }, } } } From 8de9b72c49d8ba3cc2866b80212474bea44f97c5 Mon Sep 17 00:00:00 2001 From: bestgopher <84328409@qq.com> Date: Wed, 16 Oct 2024 10:31:02 +0800 Subject: [PATCH 2/2] refactor: move api crate to api dir Signed-off-by: bestgopher <84328409@qq.com> --- Cargo.toml | 2 +- {admin => api/admin}/Cargo.toml | 2 +- {admin => api/admin}/src/error.rs | 0 {admin => api/admin}/src/handlers.rs | 0 {admin => api/admin}/src/handlers/list_pools.rs | 0 {admin => api/admin}/src/lib.rs | 0 {admin => api/admin}/src/middlewares.rs | 0 {admin => api/admin}/src/object_api.rs | 0 rustfs/Cargo.toml | 2 +- 9 files changed, 3 insertions(+), 3 deletions(-) rename {admin => api/admin}/Cargo.toml (91%) rename {admin => api/admin}/src/error.rs (100%) rename {admin => api/admin}/src/handlers.rs (100%) rename {admin => api/admin}/src/handlers/list_pools.rs (100%) rename {admin => api/admin}/src/lib.rs (100%) rename {admin => api/admin}/src/middlewares.rs (100%) rename {admin => api/admin}/src/object_api.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index f8592455..2a50af2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ members = [ "common/common", "common/lock", "common/protos", - "admin", + "api/admin", ] [workspace.package] diff --git a/admin/Cargo.toml b/api/admin/Cargo.toml similarity index 91% rename from admin/Cargo.toml rename to api/admin/Cargo.toml index 05583907..40a762cd 100644 --- a/admin/Cargo.toml +++ b/api/admin/Cargo.toml @@ -11,7 +11,7 @@ axum.workspace = true mime.workspace = true serde.workspace = true serde_json.workspace = true -ecstore = { path = "../ecstore" } +ecstore = { path = "../../ecstore" } time = { workspace = true, features = ["serde"] } tower.workspace = true futures-util = "0.3.31" diff --git a/admin/src/error.rs b/api/admin/src/error.rs similarity index 100% rename from admin/src/error.rs rename to api/admin/src/error.rs diff --git a/admin/src/handlers.rs b/api/admin/src/handlers.rs similarity index 100% rename from admin/src/handlers.rs rename to api/admin/src/handlers.rs diff --git a/admin/src/handlers/list_pools.rs b/api/admin/src/handlers/list_pools.rs similarity index 100% rename from admin/src/handlers/list_pools.rs rename to api/admin/src/handlers/list_pools.rs diff --git a/admin/src/lib.rs b/api/admin/src/lib.rs similarity index 100% rename from admin/src/lib.rs rename to api/admin/src/lib.rs diff --git a/admin/src/middlewares.rs b/api/admin/src/middlewares.rs similarity index 100% rename from admin/src/middlewares.rs rename to api/admin/src/middlewares.rs diff --git a/admin/src/object_api.rs b/api/admin/src/object_api.rs similarity index 100% rename from admin/src/object_api.rs rename to api/admin/src/object_api.rs diff --git a/rustfs/Cargo.toml b/rustfs/Cargo.toml index d2343793..bb10c8ee 100644 --- a/rustfs/Cargo.toml +++ b/rustfs/Cargo.toml @@ -49,7 +49,7 @@ tracing-error.workspace = true tracing-subscriber.workspace = true transform-stream.workspace = true uuid = "1.10.0" -admin = { path = "../admin" } +admin = { path = "../api/admin" } axum.workspace = true [build-dependencies]