improve webclient (#2151)

This commit is contained in:
KKRainbow
2026-04-23 13:44:18 +08:00
committed by GitHub
parent 263f4c3bc9
commit 958b246f05
19 changed files with 1585 additions and 188 deletions
+125
View File
@@ -18,6 +18,7 @@ use crate::{
instance::dns_server::DEFAULT_ET_DNS_ZONE,
proto::{
acl::Acl,
api::manage::ConfigSource as RpcConfigSource,
common::{CompressionAlgoPb, PortForwardConfigPb, SecureModeConfig, SocketType},
},
tunnel::generate_digest_from_str,
@@ -206,6 +207,11 @@ pub trait ConfigLoader: Send + Sync {
}
fn set_credential_file(&self, _path: Option<std::path::PathBuf>) {}
fn get_network_config_source(&self) -> ConfigSource {
ConfigSource::User
}
fn set_network_config_source(&self, _source: Option<ConfigSource>) {}
fn dump(&self) -> String;
}
@@ -225,6 +231,55 @@ pub struct NetworkIdentity {
pub network_secret_digest: Option<NetworkSecretDigest>,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum ConfigSource {
#[default]
User,
Webhook,
}
impl ConfigSource {
pub fn as_str(self) -> &'static str {
match self {
Self::User => "user",
Self::Webhook => "webhook",
}
}
pub fn from_rpc(source: i32) -> Option<Self> {
match RpcConfigSource::try_from(source).ok() {
Some(RpcConfigSource::Webhook) => Some(Self::Webhook),
Some(RpcConfigSource::User) => Some(Self::User),
_ => None,
}
}
pub fn to_rpc(self) -> i32 {
match self {
Self::User => RpcConfigSource::User as i32,
Self::Webhook => RpcConfigSource::Webhook as i32,
}
}
}
impl std::str::FromStr for ConfigSource {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"user" => Ok(Self::User),
"webhook" => Ok(Self::Webhook),
other => Err(format!("unknown network config source: {other}")),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
struct ConfigSourceConfig {
source: ConfigSource,
}
#[derive(Eq, PartialEq, Hash)]
struct NetworkIdentityWithOnlyDigest {
network_name: String,
@@ -466,6 +521,7 @@ struct Config {
stun_servers_v6: Option<Vec<String>>,
credential_file: Option<PathBuf>,
source: Option<ConfigSourceConfig>,
}
#[derive(Debug, Clone)]
@@ -480,10 +536,21 @@ impl Default for TomlConfigLoader {
}
impl TomlConfigLoader {
fn normalize_config_source(config: &mut Config) {
if matches!(
config.source.as_ref().map(|source| source.source),
Some(ConfigSource::User)
) {
config.source = None;
}
}
pub fn new_from_str(config_str: &str) -> Result<Self, anyhow::Error> {
let mut config = toml::de::from_str::<Config>(config_str)
.with_context(|| format!("failed to parse config file: {}", config_str))?;
Self::normalize_config_source(&mut config);
config.flags_struct = Some(Self::gen_flags(config.flags.clone().unwrap_or_default()));
let config = TomlConfigLoader {
@@ -867,6 +934,23 @@ impl ConfigLoader for TomlConfigLoader {
self.config.lock().unwrap().credential_file = path;
}
fn get_network_config_source(&self) -> ConfigSource {
self.config
.lock()
.unwrap()
.source
.as_ref()
.map(|source| source.source)
.unwrap_or(ConfigSource::User)
}
fn set_network_config_source(&self, source: Option<ConfigSource>) {
self.config.lock().unwrap().source = source.and_then(|source| match source {
ConfigSource::User => None,
other => Some(ConfigSourceConfig { source: other }),
});
}
fn dump(&self) -> String {
let default_flags_json = serde_json::to_string(&gen_default_flags()).unwrap();
let default_flags_hashmap =
@@ -888,6 +972,7 @@ impl ConfigLoader for TomlConfigLoader {
}
let mut config = self.config.lock().unwrap().clone();
Self::normalize_config_source(&mut config);
config.flags = Some(flag_map);
if config.stun_servers == Some(StunInfoCollector::get_default_servers()) {
config.stun_servers = None;
@@ -1126,6 +1211,46 @@ stun_servers = [
assert_eq!(stun_servers[2], "txt:stun.easytier.cn");
}
#[test]
fn test_network_config_source_toml_roundtrip() {
let config = TomlConfigLoader::default();
assert_eq!(config.get_network_config_source(), ConfigSource::User);
config.set_network_config_source(Some(ConfigSource::Webhook));
let dumped = config.dump();
assert!(dumped.contains("[source]"));
assert!(dumped.contains("source = \"webhook\""));
let loaded = TomlConfigLoader::new_from_str(&dumped).unwrap();
assert_eq!(loaded.get_network_config_source(), ConfigSource::Webhook);
}
#[test]
fn test_network_config_source_user_is_implicit() {
let config = TomlConfigLoader::default();
config.set_network_config_source(Some(ConfigSource::User));
let dumped = config.dump();
assert!(!dumped.contains("[source]"));
let loaded = TomlConfigLoader::new_from_str(&dumped).unwrap();
assert_eq!(loaded.get_network_config_source(), ConfigSource::User);
let explicit_user = TomlConfigLoader::new_from_str(
r#"
[source]
source = "user"
"#,
)
.unwrap();
assert_eq!(
explicit_user.get_network_config_source(),
ConfigSource::User
);
assert!(!explicit_user.dump().contains("[source]"));
}
#[tokio::test]
async fn full_example_test() {
let config_str = r#"
+6 -6
View File
@@ -813,7 +813,7 @@ impl NetworkOptions {
fn can_merge(
&self,
cfg: &TomlConfigLoader,
source: ConfigSource,
source: ConfigFileSource,
explicit_config_file_count: usize,
config_dir_file_count: usize,
) -> bool {
@@ -821,7 +821,7 @@ impl NetworkOptions {
return false;
}
if source == ConfigSource::CliConfigFile
if source == ConfigFileSource::CliConfigFile
&& explicit_config_file_count == 1
&& config_dir_file_count == 0
{
@@ -832,7 +832,7 @@ impl NetworkOptions {
return false;
};
if source == ConfigSource::ConfigDir {
if source == ConfigFileSource::ConfigDir {
return cfg.get_network_identity().network_name == *network_name;
}
@@ -1161,7 +1161,7 @@ impl NetworkOptions {
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ConfigSource {
enum ConfigFileSource {
CliConfigFile,
ConfigDir,
}
@@ -1353,7 +1353,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
let mut config_files = if let Some(v) = cli.config_file {
v.iter()
.cloned()
.map(|path| (path, ConfigSource::CliConfigFile))
.map(|path| (path, ConfigFileSource::CliConfigFile))
.collect()
} else {
vec![]
@@ -1376,7 +1376,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
continue;
}
config_dir_file_count += 1;
config_files.push((path, ConfigSource::ConfigDir));
config_files.push((path, ConfigFileSource::ConfigDir));
}
}
let config_file_count = config_files.len();
+10 -1
View File
@@ -4,7 +4,7 @@ use std::{collections::BTreeMap, path::PathBuf, sync::Arc};
use crate::{
common::{
config::{ConfigFileControl, ConfigLoader, TomlConfigLoader},
config::{ConfigFileControl, ConfigLoader, ConfigSource, TomlConfigLoader},
global_ctx::{EventBusSubscriber, GlobalCtxEvent},
log,
scoped_task::ScopedTask,
@@ -217,6 +217,15 @@ impl NetworkInstanceManager {
.map(|instance| instance.value().get_config_file_control().clone())
}
pub fn get_instance_network_config_source(
&self,
instance_id: &uuid::Uuid,
) -> Option<ConfigSource> {
self.instance_map
.get(instance_id)
.map(|instance| instance.value().get_network_config_source())
}
pub fn get_instance_service(
&self,
instance_id: &uuid::Uuid,
+7 -1
View File
@@ -1,4 +1,6 @@
use crate::common::config::{ConfigFileControl, PortForwardConfig, process_secure_mode_cfg};
use crate::common::config::{
ConfigFileControl, ConfigSource, PortForwardConfig, process_secure_mode_cfg,
};
use crate::proto::api::{self, manage};
use crate::proto::rpc_types::controller::BaseController;
use crate::rpc_service::InstanceRpcService;
@@ -434,6 +436,10 @@ impl NetworkInstance {
&self.config_file_control
}
pub fn get_network_config_source(&self) -> ConfigSource {
self.config.get_network_config_source()
}
pub fn get_latest_error_msg(&self) -> Option<String> {
if let Some(launcher) = self.launcher.as_ref() {
launcher.error_msg.read().unwrap().clone()
@@ -1575,6 +1575,41 @@ pub mod tests {
);
}
#[tokio::test]
async fn secure_center_can_serve_legacy_and_secure_foreign_networks() {
let pm_center = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
set_secure_mode_cfg(&pm_center.get_global_ctx(), true);
let legacy_a = create_mock_peer_manager_for_foreign_network("legacy-net").await;
let legacy_b = create_mock_peer_manager_for_foreign_network("legacy-net").await;
connect_peer_manager(legacy_a.clone(), pm_center.clone()).await;
connect_peer_manager(legacy_b.clone(), pm_center.clone()).await;
wait_route_appear(legacy_a.clone(), legacy_b.clone())
.await
.unwrap();
let secure_a = create_mock_peer_manager_for_secure_foreign_network("secure-net").await;
let secure_b = create_mock_peer_manager_for_secure_foreign_network("secure-net").await;
connect_peer_manager(secure_a.clone(), pm_center.clone()).await;
connect_peer_manager(secure_b.clone(), pm_center.clone()).await;
wait_route_appear(secure_a.clone(), secure_b.clone())
.await
.unwrap();
assert_eq!(2, legacy_a.list_routes().await.len());
assert_eq!(2, legacy_b.list_routes().await.len());
assert_eq!(2, secure_a.list_routes().await.len());
assert_eq!(2, secure_b.list_routes().await.len());
let rpc_resp = pm_center
.get_foreign_network_manager()
.list_foreign_networks()
.await;
assert_eq!(2, rpc_resp.foreign_networks.len());
assert_eq!(2, rpc_resp.foreign_networks["legacy-net"].peers.len());
assert_eq!(2, rpc_resp.foreign_networks["secure-net"].peers.len());
}
#[tokio::test]
async fn credential_pubkey_trust_requires_ospf_credential_source() {
let global_ctx = get_mock_global_ctx_with_network(Some(NetworkIdentity::new(
+65 -49
View File
@@ -536,6 +536,21 @@ impl PeerManager {
async fn add_new_peer_conn(&self, peer_conn: PeerConn) -> Result<(), Error> {
let my_identity = self.global_ctx.get_network_identity();
let peer_identity = peer_conn.get_network_identity();
let conn_info = peer_conn.get_conn_info();
let local_secure_mode = self
.global_ctx
.config
.get_secure_mode()
.as_ref()
.map(|cfg| cfg.enabled)
.unwrap_or(false);
let peer_secure_mode = !conn_info.noise_remote_static_pubkey.is_empty();
if local_secure_mode != peer_secure_mode {
return Err(Error::SecretKeyError(
"same-network peers must use the same secure mode".to_string(),
));
}
// For credential nodes, network_secret_digest is either None or all-zeros
// (all-zeros when received over the wire via handshake).
@@ -2717,7 +2732,7 @@ mod tests {
}
#[tokio::test]
async fn peer_manager_safe_server_accept_legacy_client() {
async fn peer_manager_same_network_secure_mode_mismatch_rejected() {
let peer_mgr_client = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
let peer_mgr_server = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
@@ -2737,64 +2752,65 @@ mod tests {
peer_mgr_client.add_client_tunnel(c_ring, false),
peer_mgr_server.add_tunnel_as_server(s_ring, true)
);
let (server_id, _) = c_ret.unwrap();
s_ret.unwrap();
let _ = c_ret;
assert!(
s_ret.is_err(),
"same-network peer with mismatched secure mode should be rejected"
);
wait_for_condition(
|| {
let peer_mgr_client = peer_mgr_client.clone();
async move {
if !peer_mgr_client
.get_peer_map()
.list_peers_with_conn()
.await
.contains(&server_id)
{
return false;
}
let Some(conns) = peer_mgr_client
.get_peer_map()
.list_peer_conns(server_id)
.await
else {
return false;
};
conns.iter().any(|c| {
c.noise_local_static_pubkey.is_empty()
&& c.noise_remote_static_pubkey.is_empty()
&& c.secure_auth_level == SecureAuthLevel::None as i32
})
}
},
Duration::from_secs(10),
)
.await;
let client_id = peer_mgr_client.my_peer_id();
wait_for_condition(
|| {
let peer_mgr_server = peer_mgr_server.clone();
async move {
if !peer_mgr_server
peer_mgr_server
.get_peer_map()
.list_peers_with_conn()
.await
.contains(&client_id)
{
return false;
}
let Some(conns) = peer_mgr_server
.is_empty()
}
},
Duration::from_secs(5),
)
.await;
}
#[tokio::test]
async fn credential_node_rejects_legacy_client() {
let peer_mgr_client = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
let peer_mgr_server = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
peer_mgr_client
.get_global_ctx()
.config
.set_network_identity(NetworkIdentity::new("net1".to_string(), "sec1".to_string()));
peer_mgr_server
.get_global_ctx()
.config
.set_network_identity(NetworkIdentity::new_credential("net1".to_string()));
set_secure_mode_cfg(&peer_mgr_server.get_global_ctx(), true);
let (c_ring, s_ring) = create_ring_tunnel_pair();
let (c_ret, s_ret) = tokio::join!(
peer_mgr_client.add_client_tunnel(c_ring, false),
peer_mgr_server.add_tunnel_as_server(s_ring, true)
);
let _ = c_ret;
assert!(
s_ret.is_err(),
"credential server should reject legacy client"
);
wait_for_condition(
|| {
let peer_mgr_server = peer_mgr_server.clone();
async move {
peer_mgr_server
.get_peer_map()
.list_peer_conns(client_id)
.list_peers_with_conn()
.await
else {
return false;
};
conns.iter().any(|c| {
c.noise_local_static_pubkey.is_empty()
&& c.noise_remote_static_pubkey.is_empty()
&& c.secure_auth_level == SecureAuthLevel::None as i32
})
.is_empty()
}
},
Duration::from_secs(5),
+12 -1
View File
@@ -13,6 +13,12 @@ enum NetworkingMethod {
Standalone = 2;
}
enum ConfigSource {
ConfigSourceUnspecified = 0;
ConfigSourceUser = 1;
ConfigSourceWebhook = 2;
}
message NetworkConfig {
optional string instance_id = 1;
@@ -132,6 +138,7 @@ message NetworkMeta {
string network_name = 2;
uint32 config_permission = 3;
string instance_name = 4;
ConfigSource source = 5;
}
message ValidateConfigRequest { NetworkConfig config = 1; }
@@ -142,6 +149,7 @@ message RunNetworkInstanceRequest {
common.UUID inst_id = 1;
NetworkConfig config = 2;
bool overwrite = 3;
ConfigSource source = 4;
}
message RunNetworkInstanceResponse { common.UUID inst_id = 1; }
@@ -168,7 +176,10 @@ message DeleteNetworkInstanceResponse {
message GetNetworkInstanceConfigRequest { common.UUID inst_id = 1; }
message GetNetworkInstanceConfigResponse { NetworkConfig config = 1; }
message GetNetworkInstanceConfigResponse {
NetworkConfig config = 1;
ConfigSource source = 2;
}
message ListNetworkInstanceMetaRequest { repeated common.UUID inst_ids = 1; }
+1
View File
@@ -21,6 +21,7 @@ message HeartbeatRequest {
repeated common.UUID running_network_instances = 7;
DeviceOsInfo device_os = 8;
bool support_config_source = 9;
}
message HeartbeatResponse {}
+76 -40
View File
@@ -1,10 +1,23 @@
use std::{collections::HashSet, sync::Arc};
use crate::{
common::config::{ConfigFileControl, ConfigFilePermission, ConfigLoader},
common::config::{ConfigFileControl, ConfigFilePermission, ConfigLoader, ConfigSource},
instance_manager::NetworkInstanceManager,
proto::{
api::{config::GetConfigRequest, manage::*},
api::{
config::GetConfigRequest,
manage::{
CollectNetworkInfoRequest, CollectNetworkInfoResponse,
DeleteNetworkInstanceRequest, DeleteNetworkInstanceResponse,
GetNetworkInstanceConfigRequest, GetNetworkInstanceConfigResponse,
ListNetworkInstanceMetaRequest, ListNetworkInstanceMetaResponse,
ListNetworkInstanceRequest, ListNetworkInstanceResponse,
NetworkInstanceRunningInfoMap, NetworkMeta, RetainNetworkInstanceRequest,
RetainNetworkInstanceResponse, RunNetworkInstanceRequest,
RunNetworkInstanceResponse, ValidateConfigRequest, ValidateConfigResponse,
WebClientService,
},
},
rpc_types::{self, controller::BaseController},
},
web_client::WebClientHooks,
@@ -44,53 +57,64 @@ impl WebClientService for InstanceManageRpcService {
return Err(anyhow::anyhow!("config is required").into());
}
let cfg = req.config.unwrap().gen_config()?;
let id = cfg.get_id();
let mut effective_id = cfg.get_id();
if let Some(inst_id) = req.inst_id {
cfg.set_id(inst_id.into());
effective_id = inst_id.into();
cfg.set_id(effective_id);
}
let requested_source = ConfigSource::from_rpc(req.source);
let resp = RunNetworkInstanceResponse {
inst_id: Some(id.into()),
inst_id: Some(effective_id.into()),
};
let mut control = if let Some(control) = self.manager.get_instance_config_control(&id) {
let error_msg = self
.manager
.get_network_info(&id)
.await
.and_then(|i| i.error_msg)
.unwrap_or_default();
let mut control =
if let Some(control) = self.manager.get_instance_config_control(&effective_id) {
let existing_source = self
.manager
.get_instance_network_config_source(&effective_id);
let error_msg = self
.manager
.get_network_info(&effective_id)
.await
.and_then(|i| i.error_msg)
.unwrap_or_default();
if !req.overwrite && error_msg.is_empty() {
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() {
if !req.overwrite && error_msg.is_empty() {
return Ok(resp);
}
if control.is_read_only() {
return Err(anyhow::anyhow!(
"config file {} is read-only, cannot be overwritten",
path.display()
"instance {} is read-only, cannot be overwritten",
effective_id
)
.into());
}
}
self.manager.delete_network_instance(vec![id])?;
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());
}
}
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())
};
self.manager.delete_network_instance(vec![effective_id])?;
cfg.set_network_config_source(requested_source.or(existing_source));
control.clone()
} else if let Some(config_dir) = self.manager.get_config_dir() {
cfg.set_network_config_source(requested_source);
ConfigFileControl::new(
Some(config_dir.join(format!("{}.toml", effective_id))),
ConfigFilePermission::default(),
)
} else {
cfg.set_network_config_source(requested_source);
ConfigFileControl::new(None, ConfigFilePermission::default())
};
if !control.is_read_only()
&& let Some(config_file) = control.path.as_ref()
@@ -109,9 +133,9 @@ impl WebClientService for InstanceManageRpcService {
}
self.manager.run_network_instance(cfg, true, control)?;
println!("instance {} started", id);
println!("instance {} started", effective_id);
if let Err(e) = self.hooks.post_run_network_instance(&id).await {
if let Err(e) = self.hooks.post_run_network_instance(&effective_id).await {
tracing::warn!("post-run hook failed: {}", e);
}
@@ -261,7 +285,14 @@ impl WebClientService for InstanceManageRpcService {
.get_config(BaseController::default(), GetConfigRequest::default())
.await?
.config;
Ok(GetNetworkInstanceConfigResponse { config })
Ok(GetNetworkInstanceConfigResponse {
config,
source: self
.manager
.get_instance_network_config_source(&inst_id)
.unwrap_or(ConfigSource::User)
.to_rpc(),
})
}
async fn list_network_instance_meta(
@@ -286,6 +317,11 @@ impl WebClientService for InstanceManageRpcService {
network_name,
config_permission: control.permission.into(),
instance_name,
source: self
.manager
.get_instance_network_config_source(&inst_id)
.unwrap_or(ConfigSource::User)
.to_rpc(),
};
metas.push(meta);
}
+64 -11
View File
@@ -1,7 +1,18 @@
use async_trait::async_trait;
use uuid::Uuid;
use crate::proto::{api::manage::*, rpc_types::controller::BaseController};
use crate::{
common::config::ConfigSource,
proto::{
api::manage::{
CollectNetworkInfoRequest, CollectNetworkInfoResponse, DeleteNetworkInstanceRequest,
GetNetworkInstanceConfigRequest, ListNetworkInstanceMetaRequest,
ListNetworkInstanceRequest, NetworkConfig, NetworkMeta, RunNetworkInstanceRequest,
ValidateConfigRequest, ValidateConfigResponse, WebClientService,
},
rpc_types::controller::BaseController,
},
};
#[async_trait]
pub trait RemoteClientManager<T, C, E>
@@ -52,6 +63,7 @@ where
inst_id: None,
config: Some(config.clone()),
overwrite: true,
source: ConfigSource::User.to_rpc(),
},
)
.await?;
@@ -62,6 +74,7 @@ where
identify,
resp.inst_id.unwrap_or_default().into(),
config,
ConfigSource::User,
)
.await
.map_err(RemoteClientError::PersistentError)?;
@@ -162,13 +175,18 @@ where
.get_rpc_client(identify.clone())
.ok_or(RemoteClientError::ClientNotFound)?;
let cfg = self
.handle_get_network_config(identify.clone(), inst_id)
let (cfg, source) = self
.handle_get_network_config_with_source(identify.clone(), inst_id)
.await?;
if disabled {
self.get_storage()
.insert_or_update_user_network_config(identify.clone(), inst_id, cfg.clone())
.insert_or_update_user_network_config(
identify.clone(),
inst_id,
cfg.clone(),
source,
)
.await
.map_err(RemoteClientError::PersistentError)?;
@@ -188,6 +206,7 @@ where
inst_id: Some(inst_id.into()),
config: Some(cfg),
overwrite: true,
source: source.to_rpc(),
},
)
.await?;
@@ -230,8 +249,8 @@ where
if metas.contains_key(&instance_id) {
continue;
}
let config = self
.handle_get_network_config(identify.clone(), instance_id)
let (config, source) = self
.handle_get_network_config_with_source(identify.clone(), instance_id)
.await?;
let network_name = config.network_name.unwrap_or_default();
metas.insert(
@@ -241,6 +260,7 @@ where
network_name: network_name.clone(),
config_permission: 0,
instance_name: network_name,
source: source.to_rpc(),
},
);
}
@@ -255,7 +275,12 @@ where
config: NetworkConfig,
) -> Result<(), RemoteClientError<E>> {
self.get_storage()
.insert_or_update_user_network_config(identify.clone(), inst_id, config)
.insert_or_update_user_network_config(
identify.clone(),
inst_id,
config,
ConfigSource::User,
)
.await
.map_err(RemoteClientError::PersistentError)?;
self.get_storage()
@@ -270,6 +295,16 @@ where
identify: T,
inst_id: uuid::Uuid,
) -> Result<NetworkConfig, RemoteClientError<E>> {
self.handle_get_network_config_with_source(identify, inst_id)
.await
.map(|(config, _)| config)
}
async fn handle_get_network_config_with_source(
&self,
identify: T,
inst_id: uuid::Uuid,
) -> Result<(NetworkConfig, ConfigSource), RemoteClientError<E>> {
if let Some(client) = self.get_rpc_client(identify.clone())
&& let Ok(resp) = client
.get_network_instance_config(
@@ -281,7 +316,17 @@ where
.await
&& let Some(config) = resp.config
{
return Ok(config);
let source = if let Some(source) = ConfigSource::from_rpc(resp.source) {
source
} else {
self.get_storage()
.get_network_config(identify.clone(), &inst_id.to_string())
.await
.map_err(RemoteClientError::PersistentError)?
.map(|cfg| cfg.get_runtime_network_config_source())
.unwrap_or(ConfigSource::User)
};
return Ok((config, source));
}
let inst_id = inst_id.to_string();
@@ -296,9 +341,12 @@ where
inst_id
)))?;
Ok(db_row
.get_network_config()
.map_err(RemoteClientError::PersistentError)?)
Ok((
db_row
.get_network_config()
.map_err(RemoteClientError::PersistentError)?,
db_row.get_runtime_network_config_source(),
))
}
}
@@ -336,6 +384,10 @@ pub struct GetNetworkMetasResponse {
pub trait PersistentConfig<E> {
fn get_network_inst_id(&self) -> &str;
fn get_network_config(&self) -> Result<NetworkConfig, E>;
fn get_network_config_source(&self) -> ConfigSource;
fn get_runtime_network_config_source(&self) -> ConfigSource {
self.get_network_config_source()
}
}
#[async_trait]
@@ -348,6 +400,7 @@ where
identify: T,
network_inst_id: Uuid,
network_config: NetworkConfig,
source: ConfigSource,
) -> Result<(), E>;
async fn delete_network_configs(&self, identify: T, network_inst_ids: &[Uuid])
+1
View File
@@ -93,6 +93,7 @@ impl Session {
hostname: hostname.clone(),
report_time: chrono::Local::now().to_rfc3339(),
device_os: Some(device_os.clone()),
support_config_source: true,
running_network_instances: controller
.list_network_instance_ids()