mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 10:14:35 +00:00
feat/web (Patchset 2) (#444)
This patch implement a restful server without any auth.
usage:
```bash
# run easytier-web, which acts as an gateway and registry for all easytier-core
$> easytier-web
# run easytier-core and connect to easytier-web with a token
$> easytier-core --config-server udp://127.0.0.1:22020/fdsafdsa
# use restful api to list session
$> curl -H "Content-Type: application/json" -X GET 127.0.0.1:11211/api/v1/sessions
[{"token":"fdsafdsa","client_url":"udp://127.0.0.1:48915","machine_id":"de3f5b8f-0f2f-d9d0-fb30-a2ac8951d92f"}]%
# use restful api to run a network instance
$> curl -H "Content-Type: application/json" -X POST 127.0.0.1:11211/api/v1/network/de3f5b8f-0f2f-d9d0-fb30-a2ac8951d92f -d '{"config": "listeners = [\"udp://0.0.0.0:12344\"]"}'
# use restful api to get network instance info
$> curl -H "Content-Type: application/json" -X GET 127.0.0.1:11211/api/v1/network/de3f5b8f-0f2f-d9d0-fb30-a2ac8951d92f/65437e50-b286-4098-a624-74429f2cb839
```
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::{
|
||||
common::config::{ConfigLoader, TomlConfigLoader},
|
||||
launcher::NetworkInstance,
|
||||
proto::{
|
||||
rpc_types::{self, controller::BaseController},
|
||||
web::{
|
||||
CollectNetworkInfoRequest, CollectNetworkInfoResponse, DeleteNetworkInstanceRequest,
|
||||
DeleteNetworkInstanceResponse, ListNetworkInstanceRequest, ListNetworkInstanceResponse,
|
||||
NetworkInstanceRunningInfoMap, RetainNetworkInstanceRequest,
|
||||
RetainNetworkInstanceResponse, RunNetworkInstanceRequest, RunNetworkInstanceResponse,
|
||||
ValidateConfigRequest, ValidateConfigResponse, WebClientService,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Controller {
|
||||
token: String,
|
||||
instance_map: DashMap<uuid::Uuid, NetworkInstance>,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn new(token: String) -> Self {
|
||||
Controller {
|
||||
token,
|
||||
instance_map: DashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_network_instance(&self, cfg: TomlConfigLoader) -> Result<(), anyhow::Error> {
|
||||
let instance_id = cfg.get_id();
|
||||
if self.instance_map.contains_key(&instance_id) {
|
||||
anyhow::bail!("instance {} already exists", instance_id);
|
||||
}
|
||||
|
||||
let mut instance = NetworkInstance::new(cfg);
|
||||
instance.start()?;
|
||||
|
||||
println!("instance {} started", instance_id);
|
||||
self.instance_map.insert(instance_id, instance);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn retain_network_instance(
|
||||
&self,
|
||||
instance_ids: Vec<uuid::Uuid>,
|
||||
) -> Result<RetainNetworkInstanceResponse, anyhow::Error> {
|
||||
self.instance_map.retain(|k, _| instance_ids.contains(k));
|
||||
let remain = self
|
||||
.instance_map
|
||||
.iter()
|
||||
.map(|item| item.key().clone().into())
|
||||
.collect::<Vec<_>>();
|
||||
println!("instance {:?} retained", remain);
|
||||
Ok(RetainNetworkInstanceResponse {
|
||||
remain_inst_ids: remain,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn collect_network_infos(&self) -> Result<NetworkInstanceRunningInfoMap, anyhow::Error> {
|
||||
let mut map = BTreeMap::new();
|
||||
for instance in self.instance_map.iter() {
|
||||
if let Some(info) = instance.get_running_info() {
|
||||
map.insert(instance.key().to_string(), info);
|
||||
}
|
||||
}
|
||||
Ok(NetworkInstanceRunningInfoMap { map })
|
||||
}
|
||||
|
||||
pub fn list_network_instance_ids(&self) -> Vec<uuid::Uuid> {
|
||||
self.instance_map
|
||||
.iter()
|
||||
.map(|item| item.key().clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn token(&self) -> String {
|
||||
self.token.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl WebClientService for Controller {
|
||||
type Controller = BaseController;
|
||||
|
||||
async fn validate_config(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: ValidateConfigRequest,
|
||||
) -> Result<ValidateConfigResponse, rpc_types::error::Error> {
|
||||
let _ = TomlConfigLoader::new_from_str(&req.config)?;
|
||||
Ok(ValidateConfigResponse {})
|
||||
}
|
||||
|
||||
async fn run_network_instance(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: RunNetworkInstanceRequest,
|
||||
) -> Result<RunNetworkInstanceResponse, rpc_types::error::Error> {
|
||||
let cfg = TomlConfigLoader::new_from_str(&req.config)?;
|
||||
self.run_network_instance(cfg)?;
|
||||
Ok(RunNetworkInstanceResponse {})
|
||||
}
|
||||
|
||||
async fn retain_network_instance(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: RetainNetworkInstanceRequest,
|
||||
) -> Result<RetainNetworkInstanceResponse, rpc_types::error::Error> {
|
||||
Ok(self.retain_network_instance(req.inst_ids.into_iter().map(Into::into).collect())?)
|
||||
}
|
||||
|
||||
async fn collect_network_info(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: CollectNetworkInfoRequest,
|
||||
) -> Result<CollectNetworkInfoResponse, rpc_types::error::Error> {
|
||||
let mut ret = self.collect_network_infos()?;
|
||||
let include_inst_ids = req
|
||||
.inst_ids
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|id| id.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
if !include_inst_ids.is_empty() {
|
||||
let mut to_remove = Vec::new();
|
||||
for (k, _) in ret.map.iter() {
|
||||
if !include_inst_ids.contains(&k) {
|
||||
to_remove.push(k.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for k in to_remove {
|
||||
ret.map.remove(&k);
|
||||
}
|
||||
}
|
||||
Ok(CollectNetworkInfoResponse { info: Some(ret) })
|
||||
}
|
||||
|
||||
// rpc ListNetworkInstance(ListNetworkInstanceRequest) returns (ListNetworkInstanceResponse) {}
|
||||
async fn list_network_instance(
|
||||
&self,
|
||||
_: BaseController,
|
||||
_: ListNetworkInstanceRequest,
|
||||
) -> Result<ListNetworkInstanceResponse, rpc_types::error::Error> {
|
||||
Ok(ListNetworkInstanceResponse {
|
||||
inst_ids: self
|
||||
.list_network_instance_ids()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
// rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest) returns (DeleteNetworkInstanceResponse) {}
|
||||
async fn delete_network_instance(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: DeleteNetworkInstanceRequest,
|
||||
) -> Result<DeleteNetworkInstanceResponse, rpc_types::error::Error> {
|
||||
let mut inst_ids = self.list_network_instance_ids();
|
||||
inst_ids.retain(|id| !req.inst_ids.contains(&(id.clone().into())));
|
||||
self.retain_network_instance(inst_ids.clone())?;
|
||||
Ok(DeleteNetworkInstanceResponse {
|
||||
remain_inst_ids: inst_ids.into_iter().map(Into::into).collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{common::scoped_task::ScopedTask, tunnel::TunnelConnector};
|
||||
|
||||
pub mod controller;
|
||||
pub mod session;
|
||||
|
||||
pub struct WebClient {
|
||||
controller: Arc<controller::Controller>,
|
||||
tasks: ScopedTask<()>,
|
||||
}
|
||||
|
||||
impl WebClient {
|
||||
pub fn new<T: TunnelConnector + 'static, S: ToString>(connector: T, token: S) -> Self {
|
||||
let controller = Arc::new(controller::Controller::new(token.to_string()));
|
||||
|
||||
let controller_clone = controller.clone();
|
||||
let tasks = ScopedTask::from(tokio::spawn(async move {
|
||||
Self::routine(controller_clone, Box::new(connector)).await;
|
||||
}));
|
||||
|
||||
WebClient { controller, tasks }
|
||||
}
|
||||
|
||||
async fn routine(
|
||||
controller: Arc<controller::Controller>,
|
||||
mut connector: Box<dyn TunnelConnector>,
|
||||
) {
|
||||
loop {
|
||||
let conn = match connector.connect().await {
|
||||
Ok(conn) => conn,
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Failed to connect to the server ({}), retrying in 5 seconds...",
|
||||
e
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
println!("Successfully connected to {:?}", conn.info());
|
||||
|
||||
let mut session = session::Session::new(conn, controller.clone());
|
||||
session.wait().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::{
|
||||
sync::{broadcast, Mutex},
|
||||
task::JoinSet,
|
||||
time::interval,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
common::get_machine_id,
|
||||
proto::{
|
||||
rpc_impl::bidirect::BidirectRpcManager,
|
||||
rpc_types::controller::BaseController,
|
||||
web::{
|
||||
HeartbeatRequest, HeartbeatResponse, WebClientServiceServer,
|
||||
WebServerServiceClientFactory,
|
||||
},
|
||||
},
|
||||
tunnel::Tunnel,
|
||||
};
|
||||
|
||||
use super::controller::Controller;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct HeartbeatCtx {
|
||||
notifier: Arc<broadcast::Sender<HeartbeatResponse>>,
|
||||
resp: Arc<Mutex<Option<HeartbeatResponse>>>,
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
rpc_mgr: BidirectRpcManager,
|
||||
controller: Arc<Controller>,
|
||||
|
||||
heartbeat_ctx: HeartbeatCtx,
|
||||
|
||||
tasks: Mutex<JoinSet<()>>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn new(tunnel: Box<dyn Tunnel>, controller: Arc<Controller>) -> Self {
|
||||
let rpc_mgr = BidirectRpcManager::new();
|
||||
rpc_mgr.run_with_tunnel(tunnel);
|
||||
|
||||
rpc_mgr
|
||||
.rpc_server()
|
||||
.registry()
|
||||
.register(WebClientServiceServer::new(controller.clone()), "");
|
||||
|
||||
let mut tasks: JoinSet<()> = JoinSet::new();
|
||||
let heartbeat_ctx = Self::heartbeat_routine(&rpc_mgr, controller.token(), &mut tasks);
|
||||
|
||||
Session {
|
||||
rpc_mgr,
|
||||
controller,
|
||||
heartbeat_ctx,
|
||||
tasks: Mutex::new(tasks),
|
||||
}
|
||||
}
|
||||
|
||||
fn heartbeat_routine(
|
||||
rpc_mgr: &BidirectRpcManager,
|
||||
token: String,
|
||||
tasks: &mut JoinSet<()>,
|
||||
) -> HeartbeatCtx {
|
||||
let (tx, _rx1) = broadcast::channel(2);
|
||||
|
||||
let ctx = HeartbeatCtx {
|
||||
notifier: Arc::new(tx),
|
||||
resp: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
|
||||
let mid = get_machine_id();
|
||||
let inst_id = uuid::Uuid::new_v4();
|
||||
let token = token;
|
||||
|
||||
let ctx_clone = ctx.clone();
|
||||
let mut tick = interval(std::time::Duration::from_secs(1));
|
||||
let client = rpc_mgr
|
||||
.rpc_client()
|
||||
.scoped_client::<WebServerServiceClientFactory<BaseController>>(1, 1, "".to_string());
|
||||
tasks.spawn(async move {
|
||||
let req = HeartbeatRequest {
|
||||
machine_id: Some(mid.into()),
|
||||
inst_id: Some(inst_id.into()),
|
||||
user_token: token.to_string(),
|
||||
};
|
||||
loop {
|
||||
tick.tick().await;
|
||||
match client
|
||||
.heartbeat(BaseController::default(), req.clone())
|
||||
.await
|
||||
{
|
||||
Err(e) => {
|
||||
tracing::error!("heartbeat failed: {:?}", e);
|
||||
break;
|
||||
}
|
||||
Ok(resp) => {
|
||||
tracing::debug!("heartbeat response: {:?}", resp);
|
||||
let _ = ctx_clone.notifier.send(resp.clone());
|
||||
ctx_clone.resp.lock().await.replace(resp);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ctx
|
||||
}
|
||||
|
||||
async fn wait_routines(&self) {
|
||||
self.tasks.lock().await.join_next().await;
|
||||
// if any task failed, we should abort all tasks
|
||||
self.tasks.lock().await.abort_all();
|
||||
}
|
||||
|
||||
pub async fn wait(&mut self) {
|
||||
tokio::select! {
|
||||
_ = self.rpc_mgr.wait() => {}
|
||||
_ = self.wait_routines() => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait_next_heartbeat(&self) -> Option<HeartbeatResponse> {
|
||||
let mut rx = self.heartbeat_ctx.notifier.subscribe();
|
||||
rx.recv().await.ok()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user