refactor(web): Refactor web logic to extract reusable remote client management module (#1465)

This commit is contained in:
Mg Pig
2025-10-13 23:59:46 +08:00
committed by GitHub
parent 999a486928
commit 87b7b7ed7c
24 changed files with 1382 additions and 995 deletions
+35 -3
View File
@@ -7,13 +7,19 @@ use std::sync::{
};
use dashmap::DashMap;
use easytier::{proto::web::HeartbeatRequest, tunnel::TunnelListener};
use easytier::{
proto::{
api::manage::WebClientService, rpc_types::controller::BaseController, web::HeartbeatRequest,
},
rpc_service::remote_client::{self, RemoteClientManager},
tunnel::TunnelListener,
};
use maxminddb::geoip2;
use session::{Location, Session};
use storage::{Storage, StorageToken};
use tokio::task::JoinSet;
use crate::db::{Db, UserIdInDb};
use crate::db::{entity::user_running_network_configs, Db, UserIdInDb};
#[derive(rust_embed::Embed)]
#[folder = "resources/"]
@@ -152,7 +158,7 @@ impl ClientManager {
s.data().read().await.location().cloned()
}
pub fn db(&self) -> &Db {
fn db(&self) -> &Db {
self.storage.db()
}
@@ -245,6 +251,32 @@ impl ClientManager {
}
}
impl
RemoteClientManager<
(UserIdInDb, uuid::Uuid),
user_running_network_configs::Model,
sea_orm::DbErr,
> for ClientManager
{
fn get_rpc_client(
&self,
(user_id, machine_id): (UserIdInDb, uuid::Uuid),
) -> Option<Box<dyn WebClientService<Controller = BaseController> + Send>> {
let s = self.get_session_by_machine_id(user_id, &machine_id)?;
Some(s.scoped_rpc_client())
}
fn get_storage(
&self,
) -> &impl remote_client::Storage<
(UserIdInDb, uuid::Uuid),
user_running_network_configs::Model,
sea_orm::DbErr,
> {
self.storage.db()
}
}
#[cfg(test)]
mod tests {
use std::{sync::Arc, time::Duration};
+4 -9
View File
@@ -12,12 +12,11 @@ use easytier::{
rpc_types::{self, controller::BaseController},
web::{HeartbeatRequest, HeartbeatResponse, WebServerService, WebServerServiceServer},
},
rpc_service::remote_client::{ListNetworkProps, Storage as _},
tunnel::Tunnel,
};
use tokio::sync::{broadcast, RwLock};
use crate::db::ListNetworkProps;
use super::storage::{Storage, StorageToken, WeakRefStorage};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -224,10 +223,10 @@ impl Session {
}
let req = req.unwrap();
if req.machine_id.is_none() {
let Some(machine_id) = req.machine_id else {
tracing::warn!(?req, "Machine id is not set, ignore");
continue;
}
};
let running_inst_ids = req
.running_network_instances
@@ -257,11 +256,7 @@ impl Session {
let local_configs = match storage
.db
.list_network_configs(
user_id,
Some(req.machine_id.unwrap().into()),
ListNetworkProps::EnabledOnly,
)
.list_network_configs((user_id, machine_id.into()), ListNetworkProps::EnabledOnly)
.await
{
Ok(configs) => configs,
@@ -1,5 +1,6 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
use easytier::rpc_service::remote_client::PersistentConfig;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
@@ -39,3 +40,12 @@ impl Related<super::users::Entity> for Entity {
}
impl ActiveModelBehavior for ActiveModel {}
impl PersistentConfig for Model {
fn get_network_inst_id(&self) -> &str {
&self.network_instance_id
}
fn get_network_config(&self) -> &str {
&self.network_config
}
}
+52 -52
View File
@@ -2,6 +2,7 @@
#[allow(unused_imports)]
pub mod entity;
use easytier::rpc_service::remote_client::{ListNetworkProps, Storage};
use entity::user_running_network_configs;
use sea_orm::{
prelude::Expr, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
@@ -9,17 +10,13 @@ use sea_orm::{
};
use sea_orm_migration::MigratorTrait as _;
use sqlx::{migrate::MigrateDatabase as _, types::chrono, Sqlite, SqlitePool};
use uuid::Uuid;
use crate::migrator;
use async_trait::async_trait;
pub type UserIdInDb = i32;
pub enum ListNetworkProps {
All,
EnabledOnly,
DisabledOnly,
}
#[derive(Debug, Clone)]
pub struct Db {
db_path: String,
@@ -68,12 +65,36 @@ impl Db {
&self.orm_db
}
pub async fn insert_or_update_user_network_config<T: ToString>(
pub async fn get_user_id<T: ToString>(
&self,
user_id: UserIdInDb,
device_id: uuid::Uuid,
network_inst_id: uuid::Uuid,
network_config: T,
user_name: T,
) -> Result<Option<UserIdInDb>, DbErr> {
use entity::users as u;
let user = u::Entity::find()
.filter(u::Column::Username.eq(user_name.to_string()))
.one(self.orm_db())
.await?;
Ok(user.map(|u| u.id))
}
// TODO: currently we don't have a token system, so we just use the user name as token
pub async fn get_user_id_by_token<T: ToString>(
&self,
token: T,
) -> Result<Option<UserIdInDb>, DbErr> {
self.get_user_id(token).await
}
}
#[async_trait]
impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for Db {
async fn insert_or_update_user_network_config(
&self,
(user_id, device_id): (UserIdInDb, Uuid),
network_inst_id: Uuid,
network_config: impl ToString + Send,
) -> Result<(), DbErr> {
let txn = self.orm_db().begin().await?;
@@ -105,10 +126,10 @@ impl Db {
txn.commit().await
}
pub async fn delete_network_config(
async fn delete_network_config(
&self,
user_id: UserIdInDb,
network_inst_id: uuid::Uuid,
(user_id, _): (UserIdInDb, Uuid),
network_inst_id: Uuid,
) -> Result<(), DbErr> {
use entity::user_running_network_configs as urnc;
@@ -121,12 +142,12 @@ impl Db {
Ok(())
}
pub async fn update_network_config_state(
async fn update_network_config_state(
&self,
user_id: UserIdInDb,
network_inst_id: uuid::Uuid,
(user_id, _): (UserIdInDb, Uuid),
network_inst_id: Uuid,
disabled: bool,
) -> Result<entity::user_running_network_configs::Model, DbErr> {
) -> Result<user_running_network_configs::Model, DbErr> {
use entity::user_running_network_configs as urnc;
urnc::Entity::update_many()
@@ -151,10 +172,9 @@ impl Db {
)))
}
pub async fn list_network_configs(
async fn list_network_configs(
&self,
user_id: UserIdInDb,
device_id: Option<uuid::Uuid>,
(user_id, device_id): (UserIdInDb, Uuid),
props: ListNetworkProps,
) -> Result<Vec<user_running_network_configs::Model>, DbErr> {
use entity::user_running_network_configs as urnc;
@@ -169,7 +189,7 @@ impl Db {
} else {
configs
};
let configs = if let Some(device_id) = device_id {
let configs = if !device_id.is_nil() {
configs.filter(urnc::Column::DeviceId.eq(device_id.to_string()))
} else {
configs
@@ -180,11 +200,10 @@ impl Db {
Ok(configs)
}
pub async fn get_network_config(
async fn get_network_config(
&self,
user_id: UserIdInDb,
device_id: &uuid::Uuid,
network_inst_id: &String,
(user_id, device_id): (UserIdInDb, Uuid),
network_inst_id: &str,
) -> Result<Option<user_running_network_configs::Model>, DbErr> {
use entity::user_running_network_configs as urnc;
@@ -197,32 +216,11 @@ impl Db {
Ok(config)
}
pub async fn get_user_id<T: ToString>(
&self,
user_name: T,
) -> Result<Option<UserIdInDb>, DbErr> {
use entity::users as u;
let user = u::Entity::find()
.filter(u::Column::Username.eq(user_name.to_string()))
.one(self.orm_db())
.await?;
Ok(user.map(|u| u.id))
}
// TODO: currently we don't have a token system, so we just use the user name as token
pub async fn get_user_id_by_token<T: ToString>(
&self,
token: T,
) -> Result<Option<UserIdInDb>, DbErr> {
self.get_user_id(token).await
}
}
#[cfg(test)]
mod tests {
use easytier::rpc_service::remote_client::Storage;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
use crate::db::{entity::user_running_network_configs, Db, ListNetworkProps};
@@ -235,7 +233,7 @@ mod tests {
let inst_id = uuid::Uuid::new_v4();
let device_id = uuid::Uuid::new_v4();
db.insert_or_update_user_network_config(user_id, device_id, inst_id, network_config)
db.insert_or_update_user_network_config((user_id, device_id), inst_id, network_config)
.await
.unwrap();
@@ -250,7 +248,7 @@ mod tests {
// overwrite the config
let network_config = "test_config2";
db.insert_or_update_user_network_config(user_id, device_id, inst_id, network_config)
db.insert_or_update_user_network_config((user_id, device_id), inst_id, network_config)
.await
.unwrap();
@@ -267,14 +265,16 @@ mod tests {
assert_ne!(result.update_time, result2.update_time);
assert_eq!(
db.list_network_configs(user_id, Some(device_id), ListNetworkProps::All)
db.list_network_configs((user_id, device_id), ListNetworkProps::All)
.await
.unwrap()
.len(),
1
);
db.delete_network_config(user_id, inst_id).await.unwrap();
db.delete_network_config((user_id, device_id), inst_id)
.await
.unwrap();
let result3 = user_running_network_configs::Entity::find()
.filter(user_running_network_configs::Column::UserId.eq(user_id))
.one(db.orm_db())
+5 -5
View File
@@ -41,8 +41,7 @@ pub struct RestfulServer {
// serve_task: Option<ScopedTask<()>>,
// delete_task: Option<ScopedTask<tower_sessions::session_store::Result<()>>>,
network_api: NetworkApi,
// network_api: NetworkApi<WebClientManager>,
web_router: Option<Router>,
}
@@ -108,7 +107,7 @@ impl RestfulServer {
) -> anyhow::Result<Self> {
assert!(client_mgr.is_running());
let network_api = NetworkApi::new();
// let network_api = NetworkApi::new();
Ok(RestfulServer {
bind_addr,
@@ -116,7 +115,7 @@ impl RestfulServer {
db,
// serve_task: None,
// delete_task: None,
network_api,
// network_api,
web_router,
})
}
@@ -188,6 +187,7 @@ impl RestfulServer {
}
}
#[allow(unused_mut)]
pub async fn start(
mut self,
) -> Result<
@@ -238,7 +238,7 @@ impl RestfulServer {
let app = Router::new()
.route("/api/v1/summary", get(Self::handle_get_summary))
.route("/api/v1/sessions", get(Self::handle_list_all_sessions))
.merge(self.network_api.build_route())
.merge(NetworkApi::build_route())
.route_layer(login_required!(Backend))
.merge(auth::router())
.with_state(self.client_mgr.clone())
+67 -219
View File
@@ -1,5 +1,3 @@
use std::sync::Arc;
use axum::extract::Path;
use axum::http::StatusCode;
use axum::routing::{delete, post};
@@ -7,12 +5,14 @@ use axum::{extract::State, routing::get, Json, Router};
use axum_login::AuthUser;
use easytier::launcher::NetworkConfig;
use easytier::proto::common::Void;
use easytier::proto::rpc_types::controller::BaseController;
use easytier::proto::{self, api::manage::*, web::*};
use easytier::proto::{api::manage::*, web::*};
use easytier::rpc_service::remote_client::{
ListNetworkInstanceIdsJsonResp, RemoteClientError, RemoteClientManager,
};
use sea_orm::DbErr;
use crate::client_manager::session::{Location, Session};
use crate::client_manager::ClientManager;
use crate::db::{ListNetworkProps, UserIdInDb};
use crate::client_manager::session::Location;
use crate::db::UserIdInDb;
use super::users::AuthSession;
use super::{
@@ -31,6 +31,21 @@ fn convert_rpc_error(e: RpcError) -> (StatusCode, Json<Error>) {
(status_code, Json(error))
}
fn convert_error(e: RemoteClientError<DbErr>) -> (StatusCode, Json<Error>) {
match e {
RemoteClientError::PersistentError(e) => convert_db_error(e),
RemoteClientError::RpcError(e) => convert_rpc_error(e),
RemoteClientError::ClientNotFound => (
StatusCode::NOT_FOUND,
other_error("Client not found").into(),
),
RemoteClientError::NotFound(msg) => (StatusCode::NOT_FOUND, other_error(msg).into()),
RemoteClientError::Other(msg) => {
(StatusCode::INTERNAL_SERVER_ERROR, other_error(msg).into())
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct ValidateConfigJsonReq {
config: NetworkConfig,
@@ -42,7 +57,7 @@ struct RunNetworkJsonReq {
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct ColletNetworkInfoJsonReq {
struct CollectNetworkInfoJsonReq {
inst_ids: Option<Vec<uuid::Uuid>>,
}
@@ -56,12 +71,6 @@ struct RemoveNetworkJsonReq {
inst_ids: Vec<uuid::Uuid>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct ListNetworkInstanceIdsJsonResp {
running_inst_ids: Vec<easytier::proto::common::Uuid>,
disabled_inst_ids: Vec<easytier::proto::common::Uuid>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct ListMachineItem {
client_url: Option<url::Url>,
@@ -74,13 +83,9 @@ struct ListMachineJsonResp {
machines: Vec<ListMachineItem>,
}
pub struct NetworkApi {}
pub struct NetworkApi;
impl NetworkApi {
pub fn new() -> Self {
Self {}
}
fn get_user_id(auth_session: &AuthSession) -> Result<UserIdInDb, (StatusCode, Json<Error>)> {
let Some(user_id) = auth_session.user.as_ref().map(|x| x.id()) else {
return Err((
@@ -91,63 +96,20 @@ impl NetworkApi {
Ok(user_id)
}
async fn get_session_by_machine_id(
auth_session: &AuthSession,
client_mgr: &ClientManager,
machine_id: &uuid::Uuid,
) -> Result<Arc<Session>, HttpHandleError> {
let user_id = Self::get_user_id(auth_session)?;
let Some(result) = client_mgr.get_session_by_machine_id(user_id, machine_id) else {
return Err((
StatusCode::NOT_FOUND,
other_error(format!("No such session: {}", machine_id)).into(),
));
};
let Some(token) = result.get_token().await else {
return Err((
StatusCode::UNAUTHORIZED,
other_error("No token reported".to_string()).into(),
));
};
if !auth_session
.user
.as_ref()
.map(|x| x.tokens.contains(&token.token))
.unwrap_or(false)
{
return Err((
StatusCode::FORBIDDEN,
other_error("Token mismatch".to_string()).into(),
));
}
Ok(result)
}
async fn handle_validate_config(
auth_session: AuthSession,
State(client_mgr): AppState,
Path(machine_id): Path<uuid::Uuid>,
Json(payload): Json<ValidateConfigJsonReq>,
) -> Result<Json<ValidateConfigResponse>, HttpHandleError> {
let config = payload.config;
let result =
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
let c = result.scoped_rpc_client();
let ret = c
.validate_config(
BaseController::default(),
ValidateConfigRequest {
config: Some(config),
},
Ok(client_mgr
.handle_validate_config(
(Self::get_user_id(&auth_session)?, machine_id),
payload.config,
)
.await
.map_err(convert_rpc_error)?;
Ok(ret.into())
.map_err(convert_error)?
.into())
}
async fn handle_run_network_instance(
@@ -156,33 +118,13 @@ impl NetworkApi {
Path(machine_id): Path<uuid::Uuid>,
Json(payload): Json<RunNetworkJsonReq>,
) -> Result<Json<Void>, HttpHandleError> {
let config = payload.config;
let result =
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
let c = result.scoped_rpc_client();
let resp = c
.run_network_instance(
BaseController::default(),
RunNetworkInstanceRequest {
inst_id: None,
config: Some(config.clone()),
},
)
.await
.map_err(convert_rpc_error)?;
client_mgr
.db()
.insert_or_update_user_network_config(
auth_session.user.as_ref().unwrap().id(),
machine_id,
resp.inst_id.unwrap_or_default().into(),
serde_json::to_string(&config).unwrap(),
.handle_run_network_instance(
(Self::get_user_id(&auth_session)?, machine_id),
payload.config,
)
.await
.map_err(convert_db_error)?;
.map_err(convert_error)?;
Ok(Void::default().into())
}
@@ -191,47 +133,30 @@ impl NetworkApi {
State(client_mgr): AppState,
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
) -> Result<Json<CollectNetworkInfoResponse>, HttpHandleError> {
let result =
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
let c = result.scoped_rpc_client();
let ret = c
.collect_network_info(
BaseController::default(),
CollectNetworkInfoRequest {
inst_ids: vec![inst_id.into()],
},
Ok(client_mgr
.handle_collect_network_info(
(Self::get_user_id(&auth_session)?, machine_id),
Some(vec![inst_id]),
)
.await
.map_err(convert_rpc_error)?;
Ok(ret.into())
.map_err(convert_error)?
.into())
}
async fn handle_collect_network_info(
auth_session: AuthSession,
State(client_mgr): AppState,
Path(machine_id): Path<uuid::Uuid>,
Json(payload): Json<ColletNetworkInfoJsonReq>,
Json(payload): Json<CollectNetworkInfoJsonReq>,
) -> Result<Json<CollectNetworkInfoResponse>, HttpHandleError> {
let result =
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
let c = result.scoped_rpc_client();
let ret = c
.collect_network_info(
BaseController::default(),
CollectNetworkInfoRequest {
inst_ids: payload
.inst_ids
.unwrap_or_default()
.into_iter()
.map(Into::into)
.collect(),
},
Ok(client_mgr
.handle_collect_network_info(
(Self::get_user_id(&auth_session)?, machine_id),
payload.inst_ids,
)
.await
.map_err(convert_rpc_error)?;
Ok(ret.into())
.map_err(convert_error)?
.into())
}
async fn handle_list_network_instance_ids(
@@ -239,36 +164,11 @@ impl NetworkApi {
State(client_mgr): AppState,
Path(machine_id): Path<uuid::Uuid>,
) -> Result<Json<ListNetworkInstanceIdsJsonResp>, HttpHandleError> {
let result =
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
let c = result.scoped_rpc_client();
let ret = c
.list_network_instance(BaseController::default(), ListNetworkInstanceRequest {})
Ok(client_mgr
.handle_list_network_instance_ids((Self::get_user_id(&auth_session)?, machine_id))
.await
.map_err(convert_rpc_error)?;
let running_inst_ids = ret.inst_ids.clone().into_iter().collect();
// collect networks that are disabled
let disabled_inst_ids = client_mgr
.db()
.list_network_configs(
auth_session.user.unwrap().id(),
Some(machine_id),
ListNetworkProps::DisabledOnly,
)
.await
.map_err(convert_db_error)?
.iter()
.map(|x| Into::<proto::common::Uuid>::into(x.network_instance_id.clone()))
.collect::<Vec<_>>();
Ok(ListNetworkInstanceIdsJsonResp {
running_inst_ids,
disabled_inst_ids,
}
.into())
.map_err(convert_error)?
.into())
}
async fn handle_remove_network_instance(
@@ -276,25 +176,13 @@ impl NetworkApi {
State(client_mgr): AppState,
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
) -> Result<(), HttpHandleError> {
let result =
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
client_mgr
.db()
.delete_network_config(auth_session.user.as_ref().unwrap().id(), inst_id)
.handle_remove_network_instance(
(Self::get_user_id(&auth_session)?, machine_id),
inst_id,
)
.await
.map_err(convert_db_error)?;
let c = result.scoped_rpc_client();
c.delete_network_instance(
BaseController::default(),
DeleteNetworkInstanceRequest {
inst_ids: vec![inst_id.into()],
},
)
.await
.map_err(convert_rpc_error)?;
Ok(())
.map_err(convert_error)
}
async fn handle_list_machines(
@@ -334,37 +222,14 @@ impl NetworkApi {
));
};
let sess = Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
let cfg = client_mgr
.db()
.update_network_config_state(auth_session.user.unwrap().id(), inst_id, payload.disabled)
.await
.map_err(convert_db_error)?;
let c = sess.scoped_rpc_client();
if payload.disabled {
c.delete_network_instance(
BaseController::default(),
DeleteNetworkInstanceRequest {
inst_ids: vec![inst_id.into()],
},
client_mgr
.handle_update_network_state(
(auth_session.user.unwrap().id(), machine_id),
inst_id,
payload.disabled,
)
.await
.map_err(convert_rpc_error)?;
} else {
c.run_network_instance(
BaseController::default(),
RunNetworkInstanceRequest {
inst_id: Some(inst_id.into()),
config: Some(serde_json::from_str(&cfg.network_config).unwrap()),
},
)
.await
.map_err(convert_rpc_error)?;
}
Ok(())
.map_err(convert_error)
}
async fn handle_get_network_config(
@@ -372,31 +237,14 @@ impl NetworkApi {
State(client_mgr): AppState,
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
) -> Result<Json<NetworkConfig>, HttpHandleError> {
let inst_id = inst_id.to_string();
let db_row = client_mgr
.db()
.get_network_config(auth_session.user.unwrap().id(), &machine_id, &inst_id)
Ok(client_mgr
.handle_get_network_config((auth_session.user.unwrap().id(), machine_id), inst_id)
.await
.map_err(convert_db_error)?
.ok_or((
StatusCode::NOT_FOUND,
other_error(format!("No such network instance: {}", inst_id)).into(),
))?;
Ok(
serde_json::from_str::<NetworkConfig>(&db_row.network_config)
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
other_error(format!("Failed to parse network config: {:?}", e)).into(),
)
})?
.into(),
)
.map_err(convert_error)?
.into())
}
pub fn build_route(&mut self) -> Router<AppStateInner> {
pub fn build_route() -> Router<AppStateInner> {
Router::new()
.route("/api/v1/machines", get(Self::handle_list_machines))
.route(