mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 02:09:06 +00:00
feat: Enable core to use local config files while being managed via the web (#1540)
This commit is contained in:
@@ -7,6 +7,7 @@ use std::{
|
||||
|
||||
use anyhow::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::AsyncReadExt as _;
|
||||
|
||||
use crate::{
|
||||
common::stun::StunInfoCollector,
|
||||
@@ -829,6 +830,157 @@ impl ConfigLoader for TomlConfigLoader {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct ConfigFilePermission(u8);
|
||||
impl ConfigFilePermission {
|
||||
pub const READ_ONLY: u8 = 1 << 0;
|
||||
pub const NO_DELETE: u8 = 1 << 1;
|
||||
|
||||
pub fn with_flag(self, flag: u8) -> Self {
|
||||
Self(self.0 | flag)
|
||||
}
|
||||
pub fn remove_flag(self, flag: u8) -> Self {
|
||||
Self(self.0 & !flag)
|
||||
}
|
||||
pub fn has_flag(&self, flag: u8) -> bool {
|
||||
(self.0 & flag) != 0
|
||||
}
|
||||
}
|
||||
impl From<u8> for ConfigFilePermission {
|
||||
fn from(value: u8) -> Self {
|
||||
ConfigFilePermission(value)
|
||||
}
|
||||
}
|
||||
impl From<u32> for ConfigFilePermission {
|
||||
fn from(value: u32) -> Self {
|
||||
ConfigFilePermission(value as u8)
|
||||
}
|
||||
}
|
||||
impl From<ConfigFilePermission> for u8 {
|
||||
fn from(value: ConfigFilePermission) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
impl From<ConfigFilePermission> for u32 {
|
||||
fn from(value: ConfigFilePermission) -> Self {
|
||||
value.0 as u32
|
||||
}
|
||||
}
|
||||
impl std::fmt::Debug for ConfigFilePermission {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut flags = vec![];
|
||||
if self.has_flag(ConfigFilePermission::READ_ONLY) {
|
||||
flags.push("READ_ONLY");
|
||||
} else {
|
||||
flags.push("EDITABLE");
|
||||
}
|
||||
if self.has_flag(ConfigFilePermission::NO_DELETE) {
|
||||
flags.push("NO_DELETE");
|
||||
} else {
|
||||
flags.push("DELETABLE");
|
||||
}
|
||||
write!(f, "{}", flags.join("|"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConfigFileControl {
|
||||
pub path: Option<PathBuf>,
|
||||
pub permission: ConfigFilePermission,
|
||||
}
|
||||
|
||||
impl ConfigFileControl {
|
||||
pub const STATIC_CONFIG: ConfigFileControl = Self {
|
||||
path: None,
|
||||
permission: ConfigFilePermission(
|
||||
ConfigFilePermission::READ_ONLY | ConfigFilePermission::NO_DELETE,
|
||||
),
|
||||
};
|
||||
|
||||
pub fn new(path: Option<PathBuf>, permission: ConfigFilePermission) -> Self {
|
||||
ConfigFileControl { path, permission }
|
||||
}
|
||||
|
||||
pub async fn from_path(path: PathBuf) -> Self {
|
||||
let read_only = if let Ok(metadata) = tokio::fs::metadata(&path).await {
|
||||
metadata.permissions().readonly()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
Self::new(
|
||||
Some(path),
|
||||
if read_only {
|
||||
ConfigFilePermission(ConfigFilePermission::READ_ONLY)
|
||||
} else {
|
||||
ConfigFilePermission(0)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_read_only(&self) -> bool {
|
||||
self.permission.has_flag(ConfigFilePermission::READ_ONLY)
|
||||
}
|
||||
pub fn set_read_only(&mut self, read_only: bool) {
|
||||
if read_only {
|
||||
self.permission = self.permission.with_flag(ConfigFilePermission::READ_ONLY);
|
||||
} else {
|
||||
self.permission = self.permission.remove_flag(ConfigFilePermission::READ_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_no_delete(&self) -> bool {
|
||||
self.permission.has_flag(ConfigFilePermission::NO_DELETE)
|
||||
}
|
||||
pub fn set_no_delete(&mut self, no_delete: bool) {
|
||||
if no_delete {
|
||||
self.permission = self.permission.with_flag(ConfigFilePermission::NO_DELETE);
|
||||
} else {
|
||||
self.permission = self.permission.remove_flag(ConfigFilePermission::NO_DELETE);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_deletable(&self) -> bool {
|
||||
!self.is_no_delete()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_config_from_file(
|
||||
config_file: &PathBuf,
|
||||
config_dir: Option<&PathBuf>,
|
||||
) -> Result<(TomlConfigLoader, ConfigFileControl), anyhow::Error> {
|
||||
if config_file.as_os_str() == "-" {
|
||||
let mut stdin = String::new();
|
||||
_ = tokio::io::stdin()
|
||||
.read_to_string(&mut stdin)
|
||||
.await
|
||||
.context("failed to read config from stdin")?;
|
||||
let config = TomlConfigLoader::new_from_str(&stdin)?;
|
||||
return Ok((config, ConfigFileControl::STATIC_CONFIG));
|
||||
}
|
||||
let config = TomlConfigLoader::new(config_file)
|
||||
.with_context(|| format!("failed to load config file: {:?}", config_file))?;
|
||||
let mut control = ConfigFileControl::from_path(config_file.clone()).await;
|
||||
if control.is_read_only() {
|
||||
control.set_no_delete(true);
|
||||
} else if let Some(config_dir) = config_dir {
|
||||
if let Some(config_file_dir) = config_file.parent() {
|
||||
// if the config file is in the config dir and named as the instance id, it can be saved remotely
|
||||
if config_file_dir == config_dir
|
||||
&& config_file.file_stem() == Some(config.get_id().to_string().as_ref())
|
||||
&& config_file.extension() == Some(std::ffi::OsStr::new("toml"))
|
||||
{
|
||||
control.set_no_delete(false);
|
||||
} else {
|
||||
control.set_no_delete(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
control.set_no_delete(true);
|
||||
}
|
||||
|
||||
Ok((config, control))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -17,22 +17,18 @@ use clap_complete::Shell;
|
||||
use easytier::{
|
||||
common::{
|
||||
config::{
|
||||
get_avaliable_encrypt_methods, ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig,
|
||||
LoggingConfigLoader, NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader,
|
||||
VpnPortalConfig,
|
||||
get_avaliable_encrypt_methods, load_config_from_file, ConfigFileControl, ConfigLoader,
|
||||
ConsoleLoggerConfig, FileLoggerConfig, LoggingConfigLoader, NetworkIdentity,
|
||||
PeerConfig, PortForwardConfig, TomlConfigLoader, VpnPortalConfig,
|
||||
},
|
||||
constants::EASYTIER_VERSION,
|
||||
global_ctx::GlobalCtx,
|
||||
set_default_machine_id,
|
||||
stun::MockStunInfoCollector,
|
||||
},
|
||||
connector::create_connector_by_url,
|
||||
defer,
|
||||
instance_manager::NetworkInstanceManager,
|
||||
launcher::add_proxy_network_to_config,
|
||||
proto::common::{CompressionAlgoPb, NatType},
|
||||
proto::common::CompressionAlgoPb,
|
||||
rpc_service::ApiRpcServer,
|
||||
tunnel::{IpVersion, PROTO_PORT_OFFSET},
|
||||
tunnel::PROTO_PORT_OFFSET,
|
||||
utils::{init_logger, setup_panic_handler},
|
||||
web_client,
|
||||
};
|
||||
@@ -136,6 +132,13 @@ struct Cli {
|
||||
)]
|
||||
config_file: Option<Vec<PathBuf>>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "ET_CONFIG_DIR",
|
||||
help = t!("core_clap.config_dir").to_string()
|
||||
)]
|
||||
config_dir: Option<PathBuf>,
|
||||
|
||||
#[command(flatten)]
|
||||
network_options: NetworkOptions,
|
||||
|
||||
@@ -152,7 +155,7 @@ struct Cli {
|
||||
check_config: bool,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[derive(Parser, Debug, Default, PartialEq, Eq)]
|
||||
struct NetworkOptions {
|
||||
#[arg(
|
||||
long,
|
||||
@@ -707,6 +710,9 @@ impl Cli {
|
||||
|
||||
impl NetworkOptions {
|
||||
fn can_merge(&self, cfg: &TomlConfigLoader, config_file_count: usize) -> bool {
|
||||
if (*self) == NetworkOptions::default() {
|
||||
return false;
|
||||
}
|
||||
if config_file_count == 1 {
|
||||
return true;
|
||||
}
|
||||
@@ -1141,7 +1147,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
defer!(dump_profile(0););
|
||||
init_logger(&cli.logging_options, true)?;
|
||||
|
||||
let manager = Arc::new(NetworkInstanceManager::new());
|
||||
let manager = Arc::new(NetworkInstanceManager::new().with_config_path(cli.config_dir.clone()));
|
||||
|
||||
let _rpc_server = ApiRpcServer::new(
|
||||
cli.rpc_portal_options.rpc_portal,
|
||||
@@ -1151,93 +1157,77 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
.serve()
|
||||
.await?;
|
||||
|
||||
if cli.config_server.is_some() {
|
||||
set_default_machine_id(cli.machine_id);
|
||||
let config_server_url_s = cli.config_server.clone().unwrap();
|
||||
let config_server_url = match url::Url::parse(&config_server_url_s) {
|
||||
Ok(u) => u,
|
||||
Err(_) => format!(
|
||||
"udp://config-server.easytier.cn:22020/{}",
|
||||
config_server_url_s
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
};
|
||||
let _web_client = if let Some(config_server_url_s) = cli.config_server.as_ref() {
|
||||
let wc = web_client::run_web_client(
|
||||
config_server_url_s,
|
||||
cli.machine_id.clone(),
|
||||
cli.network_options.hostname.clone(),
|
||||
manager.clone(),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| {
|
||||
println!(
|
||||
"Web client started successfully...\nserver: {}",
|
||||
config_server_url_s,
|
||||
);
|
||||
|
||||
let mut c_url = config_server_url.clone();
|
||||
c_url.set_path("");
|
||||
let token = config_server_url
|
||||
.path_segments()
|
||||
.and_then(|mut x| x.next())
|
||||
.map(|x| percent_encoding::percent_decode_str(x).decode_utf8())
|
||||
.transpose()
|
||||
.with_context(|| "failed to decode config server token")?
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_default();
|
||||
println!("Official config website: https://easytier.cn/web");
|
||||
})?;
|
||||
|
||||
Some(wc)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut config_files = if let Some(v) = cli.config_file {
|
||||
v.clone()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
if let Some(config_dir) = cli.config_dir.as_ref() {
|
||||
if !config_dir.is_dir() {
|
||||
anyhow::bail!("config_dir {} is not a directory", config_dir.display());
|
||||
}
|
||||
|
||||
for entry in std::fs::read_dir(config_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
let Some(ext) = path.extension() else {
|
||||
continue;
|
||||
};
|
||||
if ext != "toml" {
|
||||
continue;
|
||||
}
|
||||
config_files.push(path);
|
||||
}
|
||||
}
|
||||
let config_file_count = config_files.len();
|
||||
let mut crate_cli_network = (config_file_count == 0 && cli.config_server.is_none())
|
||||
|| cli.network_options.network_name.is_some();
|
||||
for config_file in config_files {
|
||||
let (mut cfg, mut control) =
|
||||
load_config_from_file(&config_file, cli.config_dir.as_ref()).await?;
|
||||
|
||||
if cli.network_options.can_merge(&cfg, config_file_count) {
|
||||
cli.network_options
|
||||
.merge_into(&mut cfg)
|
||||
.with_context(|| format!("failed to merge config from cli: {:?}", config_file))?;
|
||||
crate_cli_network = false;
|
||||
control.set_read_only(true);
|
||||
control.set_no_delete(true);
|
||||
}
|
||||
|
||||
println!(
|
||||
"Entering config client mode...\n server: {}\n token: {}",
|
||||
c_url, token,
|
||||
"Starting easytier from config file {:?}({:?}) with config:",
|
||||
config_file, control.permission
|
||||
);
|
||||
|
||||
println!("Official config website: https://easytier.cn/web");
|
||||
|
||||
if token.is_empty() {
|
||||
panic!("empty token");
|
||||
}
|
||||
|
||||
let config = TomlConfigLoader::default();
|
||||
let global_ctx = Arc::new(GlobalCtx::new(config));
|
||||
global_ctx.replace_stun_info_collector(Box::new(MockStunInfoCollector {
|
||||
udp_nat_type: NatType::Unknown,
|
||||
}));
|
||||
let mut flags = global_ctx.get_flags();
|
||||
flags.bind_device = false;
|
||||
global_ctx.set_flags(flags);
|
||||
let hostname = match cli.network_options.hostname {
|
||||
None => gethostname::gethostname().to_string_lossy().to_string(),
|
||||
Some(hostname) => hostname.to_string(),
|
||||
};
|
||||
let _wc = web_client::WebClient::new(
|
||||
create_connector_by_url(c_url.as_str(), &global_ctx, IpVersion::Both).await?,
|
||||
token.to_string(),
|
||||
hostname,
|
||||
manager,
|
||||
);
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
return Ok(());
|
||||
}
|
||||
let mut crate_cli_network =
|
||||
cli.config_file.is_none() || cli.network_options.network_name.is_some();
|
||||
if let Some(config_files) = cli.config_file {
|
||||
let config_file_count = config_files.len();
|
||||
for config_file in config_files {
|
||||
let mut cfg = if config_file.as_os_str() == "-" {
|
||||
let mut stdin = String::new();
|
||||
_ = tokio::io::stdin().read_to_string(&mut stdin).await?;
|
||||
TomlConfigLoader::new_from_str(stdin.as_str())
|
||||
.with_context(|| "failed to load config from stdin")?
|
||||
} else {
|
||||
TomlConfigLoader::new(&config_file)
|
||||
.with_context(|| format!("failed to load config file: {:?}", config_file))?
|
||||
};
|
||||
|
||||
if cli.network_options.can_merge(&cfg, config_file_count) {
|
||||
cli.network_options.merge_into(&mut cfg).with_context(|| {
|
||||
format!("failed to merge config from cli: {:?}", config_file)
|
||||
})?;
|
||||
crate_cli_network = false;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Starting easytier from config file {:?} with config:",
|
||||
config_file
|
||||
);
|
||||
println!("############### TOML ###############\n");
|
||||
println!("{}", cfg.dump());
|
||||
println!("-----------------------------------");
|
||||
manager.run_network_instance(cfg, true)?;
|
||||
}
|
||||
println!("############### TOML ###############\n");
|
||||
println!("{}", cfg.dump());
|
||||
println!("-----------------------------------");
|
||||
manager.run_network_instance(cfg, true, control)?;
|
||||
}
|
||||
|
||||
if crate_cli_network {
|
||||
@@ -1249,7 +1239,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
println!("############### TOML ###############\n");
|
||||
println!("{}", cfg.dump());
|
||||
println!("-----------------------------------");
|
||||
manager.run_network_instance(cfg, true)?;
|
||||
manager.run_network_instance(cfg, true, ConfigFileControl::STATIC_CONFIG)?;
|
||||
}
|
||||
|
||||
tokio::select! {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
|
||||
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
config::{ConfigLoader, TomlConfigLoader},
|
||||
config::{ConfigFileControl, ConfigLoader, TomlConfigLoader},
|
||||
global_ctx::{EventBusSubscriber, GlobalCtxEvent},
|
||||
scoped_task::ScopedTask,
|
||||
},
|
||||
@@ -13,11 +13,24 @@ use crate::{
|
||||
rpc_service::InstanceRpcService,
|
||||
};
|
||||
|
||||
pub(crate) struct WebClientGuard {
|
||||
guard: Option<Arc<()>>,
|
||||
stop_check_notifier: Arc<tokio::sync::Notify>,
|
||||
}
|
||||
impl Drop for WebClientGuard {
|
||||
fn drop(&mut self) {
|
||||
drop(self.guard.take());
|
||||
self.stop_check_notifier.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NetworkInstanceManager {
|
||||
instance_map: Arc<DashMap<uuid::Uuid, NetworkInstance>>,
|
||||
instance_stop_tasks: Arc<DashMap<uuid::Uuid, ScopedTask<()>>>,
|
||||
stop_check_notifier: Arc<tokio::sync::Notify>,
|
||||
instance_error_messages: Arc<DashMap<uuid::Uuid, String>>,
|
||||
config_dir: Option<PathBuf>,
|
||||
web_client_counter: Arc<()>,
|
||||
}
|
||||
|
||||
impl Default for NetworkInstanceManager {
|
||||
@@ -33,9 +46,16 @@ impl NetworkInstanceManager {
|
||||
instance_stop_tasks: Arc::new(DashMap::new()),
|
||||
stop_check_notifier: Arc::new(tokio::sync::Notify::new()),
|
||||
instance_error_messages: Arc::new(DashMap::new()),
|
||||
config_dir: None,
|
||||
web_client_counter: Arc::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_config_path(mut self, config_dir: Option<PathBuf>) -> Self {
|
||||
self.config_dir = config_dir;
|
||||
self
|
||||
}
|
||||
|
||||
fn start_instance_task(&self, instance_id: uuid::Uuid) -> Result<(), anyhow::Error> {
|
||||
if tokio::runtime::Handle::try_current().is_err() {
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -83,13 +103,14 @@ impl NetworkInstanceManager {
|
||||
&self,
|
||||
cfg: TomlConfigLoader,
|
||||
watch_event: bool,
|
||||
config_file_control: ConfigFileControl,
|
||||
) -> Result<uuid::Uuid, 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);
|
||||
let mut instance = NetworkInstance::new(cfg, config_file_control);
|
||||
instance.start()?;
|
||||
|
||||
self.instance_map.insert(instance_id, instance);
|
||||
@@ -174,18 +195,20 @@ impl NetworkInstanceManager {
|
||||
pub fn get_network_instance_name(&self, instance_id: &uuid::Uuid) -> Option<String> {
|
||||
self.instance_map
|
||||
.get(instance_id)
|
||||
.map(|instance| instance.value().get_inst_name())
|
||||
.map(|instance| instance.value().get_network_name())
|
||||
}
|
||||
|
||||
pub fn filter_network_instance(
|
||||
pub fn iter(&self) -> dashmap::iter::Iter<'_, uuid::Uuid, NetworkInstance> {
|
||||
self.instance_map.iter()
|
||||
}
|
||||
|
||||
pub fn get_instance_config_control(
|
||||
&self,
|
||||
filter: impl Fn(&uuid::Uuid, &NetworkInstance) -> bool,
|
||||
) -> Vec<uuid::Uuid> {
|
||||
instance_id: &uuid::Uuid,
|
||||
) -> Option<ConfigFileControl> {
|
||||
self.instance_map
|
||||
.iter()
|
||||
.filter(|item| filter(item.key(), item.value()))
|
||||
.map(|item| *item.key())
|
||||
.collect()
|
||||
.get(instance_id)
|
||||
.map(|instance| instance.value().get_config_file_control().clone())
|
||||
}
|
||||
|
||||
pub fn get_instance_service(
|
||||
@@ -206,12 +229,33 @@ impl NetworkInstanceManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_config_dir(&self) -> Option<&PathBuf> {
|
||||
self.config_dir.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn register_web_client(&self) -> WebClientGuard {
|
||||
WebClientGuard {
|
||||
guard: Some(self.web_client_counter.clone()),
|
||||
stop_check_notifier: self.stop_check_notifier.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn notify_stop_check(&self) {
|
||||
self.stop_check_notifier.notify_one();
|
||||
}
|
||||
|
||||
pub async fn wait(&self) {
|
||||
while self
|
||||
.instance_map
|
||||
.iter()
|
||||
.any(|item| item.value().is_easytier_running())
|
||||
{
|
||||
loop {
|
||||
let local_instance_running = self
|
||||
.instance_map
|
||||
.iter()
|
||||
.any(|item| item.value().is_easytier_running());
|
||||
let web_client_running = Arc::strong_count(&self.web_client_counter) > 1;
|
||||
|
||||
if !local_instance_running && !web_client_running {
|
||||
break;
|
||||
}
|
||||
|
||||
self.stop_check_notifier.notified().await;
|
||||
}
|
||||
}
|
||||
@@ -417,19 +461,36 @@ mod tests {
|
||||
})
|
||||
.unwrap(),
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_id2 = manager
|
||||
.run_network_instance(TomlConfigLoader::new_from_str(cfg_str).unwrap(), true)
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_id3 = manager
|
||||
.run_network_instance(TomlConfigLoader::new_from_str(cfg_str).unwrap(), false)
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
false,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_id4 = manager
|
||||
.run_network_instance(TomlConfigLoader::new_from_str(cfg_str).unwrap(), true)
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
let instance_id5 = manager
|
||||
.run_network_instance(TomlConfigLoader::new_from_str(cfg_str).unwrap(), false)
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
false,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await; // to make instance actually started
|
||||
@@ -464,10 +525,18 @@ mod tests {
|
||||
let port = crate::utils::find_free_tcp_port(10012..65534).expect("no free tcp port found");
|
||||
|
||||
assert!(manager
|
||||
.run_network_instance(TomlConfigLoader::new_from_str(cfg_str).unwrap(), true,)
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_err());
|
||||
assert!(manager
|
||||
.run_network_instance(TomlConfigLoader::new_from_str(cfg_str).unwrap(), true,)
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_err());
|
||||
assert!(manager
|
||||
.run_network_instance(
|
||||
@@ -477,13 +546,22 @@ mod tests {
|
||||
})
|
||||
.unwrap(),
|
||||
false,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_ok());
|
||||
assert!(manager
|
||||
.run_network_instance(TomlConfigLoader::new_from_str(cfg_str).unwrap(), true,)
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_err());
|
||||
assert!(manager
|
||||
.run_network_instance(TomlConfigLoader::new_from_str(cfg_str).unwrap(), false,)
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str).unwrap(),
|
||||
false,
|
||||
ConfigFileControl::STATIC_CONFIG
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(1)); // wait instance actually started
|
||||
@@ -526,6 +604,7 @@ mod tests {
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str.as_str()).unwrap(),
|
||||
watch_event,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -557,6 +636,7 @@ mod tests {
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str.as_str()).unwrap(),
|
||||
watch_event,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -584,6 +664,7 @@ mod tests {
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str.as_str()).unwrap(),
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -593,6 +674,7 @@ mod tests {
|
||||
.run_network_instance(
|
||||
TomlConfigLoader::new_from_str(cfg_str.as_str()).unwrap(),
|
||||
true,
|
||||
ConfigFileControl::STATIC_CONFIG,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::common::config::PortForwardConfig;
|
||||
use crate::common::config::{ConfigFileControl, PortForwardConfig};
|
||||
use crate::proto::api::{self, manage};
|
||||
use crate::proto::rpc_types::controller::BaseController;
|
||||
use crate::rpc_service::InstanceRpcService;
|
||||
@@ -284,13 +284,15 @@ pub type NetworkInstanceRunningInfo = crate::proto::api::manage::NetworkInstance
|
||||
pub struct NetworkInstance {
|
||||
config: TomlConfigLoader,
|
||||
launcher: Option<EasyTierLauncher>,
|
||||
config_file_control: ConfigFileControl,
|
||||
}
|
||||
|
||||
impl NetworkInstance {
|
||||
pub fn new(config: TomlConfigLoader) -> Self {
|
||||
pub fn new(config: TomlConfigLoader, config_file_control: ConfigFileControl) -> Self {
|
||||
Self {
|
||||
config,
|
||||
launcher: None,
|
||||
config_file_control,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,6 +389,10 @@ impl NetworkInstance {
|
||||
self.config.get_inst_name()
|
||||
}
|
||||
|
||||
pub fn get_network_name(&self) -> String {
|
||||
self.config.get_network_identity().network_name
|
||||
}
|
||||
|
||||
pub fn set_tun_fd(&mut self, tun_fd: i32) {
|
||||
if let Some(launcher) = self.launcher.as_ref() {
|
||||
launcher.data.tun_fd.write().unwrap().replace(tun_fd);
|
||||
@@ -422,6 +428,10 @@ impl NetworkInstance {
|
||||
.map(|launcher| launcher.data.instance_stop_notifier.clone())
|
||||
}
|
||||
|
||||
pub fn get_config_file_control(&self) -> &ConfigFileControl {
|
||||
&self.config_file_control
|
||||
}
|
||||
|
||||
pub fn get_latest_error_msg(&self) -> Option<String> {
|
||||
if let Some(launcher) = self.launcher.as_ref() {
|
||||
launcher.error_msg.read().unwrap().clone()
|
||||
|
||||
@@ -112,6 +112,12 @@ message NetworkInstanceRunningInfoMap {
|
||||
map<string, NetworkInstanceRunningInfo> map = 1;
|
||||
}
|
||||
|
||||
message NetworkMeta {
|
||||
common.UUID inst_id = 1;
|
||||
string network_name = 2;
|
||||
uint32 config_permission = 3;
|
||||
}
|
||||
|
||||
message ValidateConfigRequest { NetworkConfig config = 1; }
|
||||
|
||||
message ValidateConfigResponse { string toml_config = 1; }
|
||||
@@ -119,6 +125,7 @@ message ValidateConfigResponse { string toml_config = 1; }
|
||||
message RunNetworkInstanceRequest {
|
||||
common.UUID inst_id = 1;
|
||||
NetworkConfig config = 2;
|
||||
bool overwrite = 3;
|
||||
}
|
||||
|
||||
message RunNetworkInstanceResponse { common.UUID inst_id = 1; }
|
||||
@@ -143,6 +150,14 @@ message DeleteNetworkInstanceResponse {
|
||||
repeated common.UUID remain_inst_ids = 1;
|
||||
}
|
||||
|
||||
message GetNetworkInstanceConfigRequest { common.UUID inst_id = 1; }
|
||||
|
||||
message GetNetworkInstanceConfigResponse { NetworkConfig config = 1; }
|
||||
|
||||
message ListNetworkInstanceMetaRequest { repeated common.UUID inst_ids = 1; }
|
||||
|
||||
message ListNetworkInstanceMetaResponse { repeated NetworkMeta metas = 1; }
|
||||
|
||||
service WebClientService {
|
||||
rpc ValidateConfig(ValidateConfigRequest) returns (ValidateConfigResponse) {}
|
||||
rpc RunNetworkInstance(RunNetworkInstanceRequest)
|
||||
@@ -155,4 +170,8 @@ service WebClientService {
|
||||
returns (ListNetworkInstanceResponse) {}
|
||||
rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest)
|
||||
returns (DeleteNetworkInstanceResponse) {}
|
||||
rpc GetNetworkInstanceConfig(GetNetworkInstanceConfigRequest)
|
||||
returns (GetNetworkInstanceConfigResponse) {}
|
||||
rpc ListNetworkInstanceMeta(ListNetworkInstanceMetaRequest)
|
||||
returns (ListNetworkInstanceMetaResponse) {}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
common::config::ConfigLoader,
|
||||
common::config::{ConfigFileControl, ConfigFilePermission, ConfigLoader},
|
||||
instance_manager::NetworkInstanceManager,
|
||||
proto::{
|
||||
api::manage::*,
|
||||
api::{config::GetConfigRequest, manage::*},
|
||||
rpc_types::{self, controller::BaseController},
|
||||
},
|
||||
};
|
||||
@@ -46,11 +46,59 @@ impl WebClientService for InstanceManageRpcService {
|
||||
if let Some(inst_id) = req.inst_id {
|
||||
cfg.set_id(inst_id.into());
|
||||
}
|
||||
self.manager.run_network_instance(cfg, true)?;
|
||||
println!("instance {} started", id);
|
||||
Ok(RunNetworkInstanceResponse {
|
||||
let resp = RunNetworkInstanceResponse {
|
||||
inst_id: Some(id.into()),
|
||||
})
|
||||
};
|
||||
|
||||
let mut control = if let Some(control) = self.manager.get_instance_config_control(&id) {
|
||||
if !req.overwrite {
|
||||
return Ok(resp);
|
||||
}
|
||||
if control.is_read_only() {
|
||||
return Err(
|
||||
anyhow::anyhow!("instance {} is read-only, cannot be overwritten", id).into(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = control.path.as_ref() {
|
||||
let real_control = ConfigFileControl::from_path(path.clone()).await;
|
||||
if real_control.is_read_only() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"config file {} is read-only, cannot be overwritten",
|
||||
path.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
self.manager.delete_network_instance(vec![id])?;
|
||||
|
||||
control.clone()
|
||||
} else if let Some(config_dir) = self.manager.get_config_dir() {
|
||||
ConfigFileControl::new(
|
||||
Some(config_dir.join(format!("{}.toml", id))),
|
||||
ConfigFilePermission::default(),
|
||||
)
|
||||
} else {
|
||||
ConfigFileControl::new(None, ConfigFilePermission::default())
|
||||
};
|
||||
|
||||
if !control.is_read_only() {
|
||||
if let Some(config_file) = control.path.as_ref() {
|
||||
if let Err(e) = std::fs::write(config_file, cfg.dump()) {
|
||||
tracing::warn!(
|
||||
"failed to write config file {}: {}",
|
||||
config_file.display(),
|
||||
e
|
||||
);
|
||||
control.set_read_only(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.manager.run_network_instance(cfg, true, control)?;
|
||||
println!("instance {} started", id);
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
async fn retain_network_instance(
|
||||
@@ -124,12 +172,79 @@ impl WebClientService for InstanceManageRpcService {
|
||||
_: BaseController,
|
||||
req: DeleteNetworkInstanceRequest,
|
||||
) -> Result<DeleteNetworkInstanceResponse, rpc_types::error::Error> {
|
||||
let remain_inst_ids = self
|
||||
let inst_ids: HashSet<uuid::Uuid> = req.inst_ids.into_iter().map(Into::into).collect();
|
||||
let inst_ids = self
|
||||
.manager
|
||||
.delete_network_instance(req.inst_ids.into_iter().map(Into::into).collect())?;
|
||||
.iter()
|
||||
.filter(|v| inst_ids.contains(v.key()))
|
||||
.filter(|v| v.get_config_file_control().is_deletable())
|
||||
.map(|v| *v.key())
|
||||
.collect::<Vec<_>>();
|
||||
let config_files = inst_ids
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
self.manager
|
||||
.get_instance_config_control(id)
|
||||
.and_then(|control| control.path.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let remain_inst_ids = self.manager.delete_network_instance(inst_ids)?;
|
||||
println!("instance {:?} retained", remain_inst_ids);
|
||||
for config_file in config_files {
|
||||
if let Err(e) = std::fs::remove_file(&config_file) {
|
||||
tracing::warn!(
|
||||
"failed to remove config file {}: {}",
|
||||
config_file.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(DeleteNetworkInstanceResponse {
|
||||
remain_inst_ids: remain_inst_ids.into_iter().map(Into::into).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_network_instance_config(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: GetNetworkInstanceConfigRequest,
|
||||
) -> Result<GetNetworkInstanceConfigResponse, rpc_types::error::Error> {
|
||||
let inst_id: uuid::Uuid = req
|
||||
.inst_id
|
||||
.ok_or_else(|| anyhow::anyhow!("instance id is required"))?
|
||||
.into();
|
||||
let config = self
|
||||
.manager
|
||||
.get_instance_service(&inst_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("instance service not found"))?
|
||||
.get_config_service()
|
||||
.get_config(BaseController::default(), GetConfigRequest::default())
|
||||
.await?
|
||||
.config;
|
||||
Ok(GetNetworkInstanceConfigResponse { config })
|
||||
}
|
||||
|
||||
async fn list_network_instance_meta(
|
||||
&self,
|
||||
_: BaseController,
|
||||
req: ListNetworkInstanceMetaRequest,
|
||||
) -> Result<ListNetworkInstanceMetaResponse, rpc_types::error::Error> {
|
||||
let mut metas = Vec::with_capacity(req.inst_ids.len());
|
||||
for inst_id in req.inst_ids {
|
||||
let inst_id: uuid::Uuid = (inst_id).into();
|
||||
let Some(control) = self.manager.get_instance_config_control(&inst_id) else {
|
||||
continue;
|
||||
};
|
||||
let Some(name) = self.manager.get_network_instance_name(&inst_id) else {
|
||||
continue;
|
||||
};
|
||||
let meta = NetworkMeta {
|
||||
inst_id: Some(inst_id.into()),
|
||||
network_name: name,
|
||||
config_permission: control.permission.into(),
|
||||
};
|
||||
metas.push(meta);
|
||||
}
|
||||
Ok(ListNetworkInstanceMetaResponse { metas })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,18 +79,23 @@ fn get_instance_service(
|
||||
let id = if let Some(api::instance::instance_identifier::Selector::Id(id)) = selector {
|
||||
(*id).into()
|
||||
} else {
|
||||
let ids = instance_manager.filter_network_instance(|_, i| {
|
||||
if let Some(api::instance::instance_identifier::Selector::InstanceSelector(selector)) =
|
||||
selector
|
||||
{
|
||||
if let Some(name) = selector.name.as_ref() {
|
||||
if i.get_inst_name() != *name {
|
||||
return false;
|
||||
let ids = instance_manager
|
||||
.iter()
|
||||
.filter(|v| {
|
||||
if let Some(api::instance::instance_identifier::Selector::InstanceSelector(
|
||||
selector,
|
||||
)) = selector
|
||||
{
|
||||
if let Some(name) = selector.name.as_ref() {
|
||||
if v.get_inst_name() != *name {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
true
|
||||
})
|
||||
.map(|v| *v.key())
|
||||
.collect::<Vec<_>>();
|
||||
match ids.len() {
|
||||
0 => return Err(anyhow::anyhow!("No instance matches the selector")),
|
||||
1 => ids[0],
|
||||
|
||||
@@ -40,6 +40,7 @@ where
|
||||
&self,
|
||||
identify: T,
|
||||
config: NetworkConfig,
|
||||
save: bool,
|
||||
) -> Result<(), RemoteClientError<E>> {
|
||||
let client = self
|
||||
.get_rpc_client(identify.clone())
|
||||
@@ -50,18 +51,21 @@ where
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: None,
|
||||
config: Some(config.clone()),
|
||||
overwrite: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.get_storage()
|
||||
.insert_or_update_user_network_config(
|
||||
identify,
|
||||
resp.inst_id.unwrap_or_default().into(),
|
||||
config,
|
||||
)
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
if save {
|
||||
self.get_storage()
|
||||
.insert_or_update_user_network_config(
|
||||
identify,
|
||||
resp.inst_id.unwrap_or_default().into(),
|
||||
config,
|
||||
)
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -156,13 +160,17 @@ where
|
||||
let client = self
|
||||
.get_rpc_client(identify.clone())
|
||||
.ok_or(RemoteClientError::ClientNotFound)?;
|
||||
|
||||
let cfg = self
|
||||
.get_storage()
|
||||
.update_network_config_state(identify, inst_id, disabled)
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
.handle_get_network_config(identify.clone(), inst_id)
|
||||
.await?;
|
||||
|
||||
if disabled {
|
||||
self.get_storage()
|
||||
.insert_or_update_user_network_config(identify.clone(), inst_id, cfg.clone())
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
|
||||
client
|
||||
.delete_network_instance(
|
||||
BaseController::default(),
|
||||
@@ -177,15 +185,18 @@ where
|
||||
BaseController::default(),
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: Some(inst_id.into()),
|
||||
config: Some(
|
||||
cfg.get_network_config()
|
||||
.map_err(RemoteClientError::PersistentError)?,
|
||||
),
|
||||
config: Some(cfg),
|
||||
overwrite: true,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
self.get_storage()
|
||||
.update_network_config_state(identify, inst_id, disabled)
|
||||
.await
|
||||
.map_err(RemoteClientError::PersistentError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -196,14 +207,38 @@ where
|
||||
) -> Result<GetNetworkMetasResponse, RemoteClientError<E>> {
|
||||
let mut metas = std::collections::HashMap::new();
|
||||
|
||||
if let Some(client) = self.get_rpc_client(identify.clone()) {
|
||||
if let Ok(resp) = client
|
||||
.list_network_instance_meta(
|
||||
BaseController::default(),
|
||||
ListNetworkInstanceMetaRequest {
|
||||
inst_ids: inst_ids.iter().cloned().map(|id| id.into()).collect(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
for meta in resp.metas {
|
||||
if let Some(inst_id) = meta.inst_id.as_ref() {
|
||||
let inst_id: uuid::Uuid = (*inst_id).into();
|
||||
metas.insert(inst_id, meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for instance_id in inst_ids {
|
||||
if metas.contains_key(&instance_id) {
|
||||
continue;
|
||||
}
|
||||
let config = self
|
||||
.handle_get_network_config(identify.clone(), instance_id)
|
||||
.await?;
|
||||
metas.insert(
|
||||
instance_id,
|
||||
NetworkMeta {
|
||||
instance_name: config.network_name.unwrap_or_default(),
|
||||
inst_id: Some(instance_id.into()),
|
||||
network_name: config.network_name.unwrap_or_default(),
|
||||
config_permission: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -233,6 +268,22 @@ where
|
||||
identify: T,
|
||||
inst_id: uuid::Uuid,
|
||||
) -> Result<NetworkConfig, RemoteClientError<E>> {
|
||||
if let Some(client) = self.get_rpc_client(identify.clone()) {
|
||||
if let Ok(resp) = client
|
||||
.get_network_instance_config(
|
||||
BaseController::default(),
|
||||
GetNetworkInstanceConfigRequest {
|
||||
inst_id: Some(inst_id.into()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
if let Some(config) = resp.config {
|
||||
return Ok(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let inst_id = inst_id.to_string();
|
||||
|
||||
let db_row = self
|
||||
@@ -277,11 +328,6 @@ pub struct ListNetworkInstanceIdsJsonResp {
|
||||
disabled_inst_ids: Vec<crate::proto::common::Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct NetworkMeta {
|
||||
instance_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct GetNetworkMetasResponse {
|
||||
metas: std::collections::HashMap<uuid::Uuid, NetworkMeta>,
|
||||
@@ -312,7 +358,7 @@ where
|
||||
identify: T,
|
||||
network_inst_id: Uuid,
|
||||
disabled: bool,
|
||||
) -> Result<C, E>;
|
||||
) -> Result<(), E>;
|
||||
|
||||
async fn list_network_configs(&self, identify: T, props: ListNetworkProps)
|
||||
-> Result<Vec<C>, E>;
|
||||
|
||||
@@ -35,4 +35,8 @@ impl Controller {
|
||||
pub fn get_rpc_service(&self) -> InstanceManageRpcService {
|
||||
InstanceManageRpcService::new(self.manager.clone())
|
||||
}
|
||||
|
||||
pub(super) fn notify_manager_stopping(&self) {
|
||||
self.manager.notify_stop_check();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
common::scoped_task::ScopedTask, instance_manager::NetworkInstanceManager,
|
||||
tunnel::TunnelConnector,
|
||||
common::{
|
||||
config::TomlConfigLoader, global_ctx::GlobalCtx, scoped_task::ScopedTask,
|
||||
set_default_machine_id, stun::MockStunInfoCollector,
|
||||
},
|
||||
connector::create_connector_by_url,
|
||||
instance_manager::{NetworkInstanceManager, WebClientGuard},
|
||||
proto::common::NatType,
|
||||
tunnel::{IpVersion, TunnelConnector},
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use url::Url;
|
||||
|
||||
pub mod controller;
|
||||
pub mod session;
|
||||
@@ -11,6 +19,7 @@ pub mod session;
|
||||
pub struct WebClient {
|
||||
controller: Arc<controller::Controller>,
|
||||
tasks: ScopedTask<()>,
|
||||
manager_guard: WebClientGuard,
|
||||
}
|
||||
|
||||
impl WebClient {
|
||||
@@ -20,6 +29,7 @@ impl WebClient {
|
||||
hostname: H,
|
||||
manager: Arc<NetworkInstanceManager>,
|
||||
) -> Self {
|
||||
let manager_guard = manager.register_web_client();
|
||||
let controller = Arc::new(controller::Controller::new(
|
||||
token.to_string(),
|
||||
hostname.to_string(),
|
||||
@@ -31,7 +41,11 @@ impl WebClient {
|
||||
Self::routine(controller_clone, Box::new(connector)).await;
|
||||
}));
|
||||
|
||||
WebClient { controller, tasks }
|
||||
WebClient {
|
||||
controller,
|
||||
tasks,
|
||||
manager_guard,
|
||||
}
|
||||
}
|
||||
|
||||
async fn routine(
|
||||
@@ -58,3 +72,90 @@ impl WebClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_web_client(
|
||||
config_server_url_s: &str,
|
||||
machine_id: Option<String>,
|
||||
hostname: Option<String>,
|
||||
manager: Arc<NetworkInstanceManager>,
|
||||
) -> Result<WebClient> {
|
||||
set_default_machine_id(machine_id);
|
||||
let config_server_url = match Url::parse(config_server_url_s) {
|
||||
Ok(u) => u,
|
||||
Err(_) => format!(
|
||||
"udp://config-server.easytier.cn:22020/{}",
|
||||
config_server_url_s
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
};
|
||||
|
||||
let mut c_url = config_server_url.clone();
|
||||
c_url.set_path("");
|
||||
let token = config_server_url
|
||||
.path_segments()
|
||||
.and_then(|mut x| x.next())
|
||||
.map(|x| percent_encoding::percent_decode_str(x).decode_utf8())
|
||||
.transpose()
|
||||
.with_context(|| "failed to decode config server token")?
|
||||
.map(|x| x.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
if token.is_empty() {
|
||||
return Err(anyhow::anyhow!("empty token"));
|
||||
}
|
||||
|
||||
let config = TomlConfigLoader::default();
|
||||
let global_ctx = Arc::new(GlobalCtx::new(config));
|
||||
global_ctx.replace_stun_info_collector(Box::new(MockStunInfoCollector {
|
||||
udp_nat_type: NatType::Unknown,
|
||||
}));
|
||||
let mut flags = global_ctx.get_flags();
|
||||
flags.bind_device = false;
|
||||
global_ctx.set_flags(flags);
|
||||
let hostname = match hostname {
|
||||
None => gethostname::gethostname().to_string_lossy().to_string(),
|
||||
Some(hostname) => hostname,
|
||||
};
|
||||
Ok(WebClient::new(
|
||||
create_connector_by_url(c_url.as_str(), &global_ctx, IpVersion::Both).await?,
|
||||
token.to_string(),
|
||||
hostname,
|
||||
manager.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
use crate::instance_manager::NetworkInstanceManager;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_manager_wait() {
|
||||
let manager = Arc::new(NetworkInstanceManager::new());
|
||||
let client = super::run_web_client(
|
||||
format!("ring://{}/test", uuid::Uuid::new_v4()).as_str(),
|
||||
None,
|
||||
None,
|
||||
manager.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let sleep_finish = Arc::new(AtomicBool::new(false));
|
||||
let sleep_finish_clone = sleep_finish.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
println!("Dropping client...");
|
||||
sleep_finish_clone.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
drop(client);
|
||||
println!("Client dropped.");
|
||||
});
|
||||
|
||||
println!("Waiting for manager...");
|
||||
manager.wait().await;
|
||||
assert!(sleep_finish.load(std::sync::atomic::Ordering::Relaxed));
|
||||
println!("Manager stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user