mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-06 17:59:11 +00:00
improve webclient (#2151)
This commit is contained in:
@@ -7,17 +7,17 @@ use std::{
|
||||
|
||||
use anyhow::Context;
|
||||
use easytier::{
|
||||
common::scoped_task::ScopedTask,
|
||||
common::{config::ConfigSource, scoped_task::ScopedTask},
|
||||
proto::{
|
||||
api::manage::{
|
||||
NetworkConfig, RunNetworkInstanceRequest, WebClientService,
|
||||
WebClientServiceClientFactory,
|
||||
ConfigSource as RpcConfigSource, NetworkConfig, NetworkMeta, RunNetworkInstanceRequest,
|
||||
WebClientService, WebClientServiceClientFactory,
|
||||
},
|
||||
rpc_impl::bidirect::BidirectRpcManager,
|
||||
rpc_types::{self, controller::BaseController},
|
||||
web::{HeartbeatRequest, HeartbeatResponse, WebServerService, WebServerServiceServer},
|
||||
},
|
||||
rpc_service::remote_client::{ListNetworkProps, Storage as _},
|
||||
rpc_service::remote_client::{ListNetworkProps, PersistentConfig as _, Storage as _},
|
||||
tunnel::Tunnel,
|
||||
};
|
||||
use tokio::sync::{RwLock, broadcast};
|
||||
@@ -26,6 +26,50 @@ use super::storage::{Storage, StorageToken, WeakRefStorage};
|
||||
use crate::FeatureFlags;
|
||||
use crate::webhook::SharedWebhookConfig;
|
||||
|
||||
const LEGACY_NETWORK_CONFIG_SOURCE: &str = "legacy";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum PersistedConfigSource {
|
||||
User,
|
||||
Webhook,
|
||||
Legacy,
|
||||
}
|
||||
|
||||
impl PersistedConfigSource {
|
||||
fn from_db(source: &str) -> Self {
|
||||
match source {
|
||||
"webhook" => Self::Webhook,
|
||||
"user" => Self::User,
|
||||
LEGACY_NETWORK_CONFIG_SOURCE => Self::Legacy,
|
||||
_ => Self::User,
|
||||
}
|
||||
}
|
||||
|
||||
fn should_update_from_runtime(self, runtime_source: ConfigSource) -> bool {
|
||||
match (self, runtime_source) {
|
||||
// Older clients report missing source as `user`, which is not authoritative enough
|
||||
// to downgrade an existing webhook-owned or legacy row.
|
||||
(Self::Webhook | Self::Legacy, ConfigSource::User) => false,
|
||||
_ => self.as_runtime_source() != runtime_source,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_runtime_source(self) -> ConfigSource {
|
||||
match self {
|
||||
Self::User | Self::Legacy => ConfigSource::User,
|
||||
Self::Webhook => ConfigSource::Webhook,
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_run_rpc_source(self) -> Option<RpcConfigSource> {
|
||||
match self {
|
||||
Self::User => Some(RpcConfigSource::User),
|
||||
Self::Webhook => Some(RpcConfigSource::Webhook),
|
||||
Self::Legacy => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Location {
|
||||
pub country: String,
|
||||
@@ -148,7 +192,7 @@ impl SessionRpcService {
|
||||
Ok(serde_json::from_value::<NetworkConfig>(network_config)?)
|
||||
}
|
||||
|
||||
async fn reconcile_managed_network_configs(
|
||||
async fn reconcile_webhook_source_configs(
|
||||
storage: &Storage,
|
||||
user_id: i32,
|
||||
machine_id: uuid::Uuid,
|
||||
@@ -159,9 +203,19 @@ impl SessionRpcService {
|
||||
.list_network_configs((user_id, machine_id), ListNetworkProps::All)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!("failed to list existing network configs: {:?}", e))?;
|
||||
let existing_ids = existing_configs
|
||||
let existing_sources = existing_configs
|
||||
.iter()
|
||||
.filter_map(|cfg| uuid::Uuid::parse_str(&cfg.network_instance_id).ok())
|
||||
.filter_map(|cfg| {
|
||||
uuid::Uuid::parse_str(&cfg.network_instance_id)
|
||||
.ok()
|
||||
.map(|inst_id| (inst_id, PersistedConfigSource::from_db(&cfg.source)))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let existing_webhook_ids = existing_sources
|
||||
.iter()
|
||||
.filter_map(|(inst_id, source)| {
|
||||
(*source == PersistedConfigSource::Webhook).then_some(*inst_id)
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut desired_ids = HashSet::with_capacity(desired_configs.len());
|
||||
@@ -169,10 +223,30 @@ impl SessionRpcService {
|
||||
for desired in desired_configs {
|
||||
let inst_id = uuid::Uuid::parse_str(&desired.instance_id).with_context(|| {
|
||||
format!(
|
||||
"invalid desired managed instance id: {}",
|
||||
"invalid desired webhook config instance id: {}",
|
||||
desired.instance_id
|
||||
)
|
||||
})?;
|
||||
match existing_sources.get(&inst_id) {
|
||||
Some(PersistedConfigSource::User) => {
|
||||
tracing::warn!(
|
||||
?user_id,
|
||||
?machine_id,
|
||||
instance_id = %inst_id,
|
||||
"skip webhook config because a user-owned config already exists"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
Some(PersistedConfigSource::Legacy) => {
|
||||
tracing::info!(
|
||||
?user_id,
|
||||
?machine_id,
|
||||
instance_id = %inst_id,
|
||||
"adopt legacy config as webhook-owned during reconciliation"
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let config = Self::normalize_network_config(desired.network_config, inst_id)?;
|
||||
desired_ids.insert(inst_id);
|
||||
normalized.insert(inst_id, config);
|
||||
@@ -181,18 +255,23 @@ impl SessionRpcService {
|
||||
for (inst_id, config) in normalized {
|
||||
storage
|
||||
.db()
|
||||
.insert_or_update_user_network_config((user_id, machine_id), inst_id, config)
|
||||
.insert_or_update_user_network_config(
|
||||
(user_id, machine_id),
|
||||
inst_id,
|
||||
config,
|
||||
ConfigSource::Webhook,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to persist managed network config {}: {:?}",
|
||||
"failed to persist webhook network config {}: {:?}",
|
||||
inst_id,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
let stale_ids = existing_ids
|
||||
let stale_ids = existing_webhook_ids
|
||||
.difference(&desired_ids)
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
@@ -225,7 +304,7 @@ impl SessionRpcService {
|
||||
|
||||
let (
|
||||
user_id,
|
||||
webhook_managed_network_configs,
|
||||
webhook_source_configs,
|
||||
webhook_config_revision,
|
||||
webhook_validated,
|
||||
binding_version,
|
||||
@@ -306,11 +385,11 @@ impl SessionRpcService {
|
||||
if webhook_validated
|
||||
&& data.applied_config_revision.as_deref() != Some(webhook_config_revision.as_str())
|
||||
{
|
||||
Self::reconcile_managed_network_configs(
|
||||
Self::reconcile_webhook_source_configs(
|
||||
&storage,
|
||||
user_id,
|
||||
machine_id,
|
||||
webhook_managed_network_configs,
|
||||
webhook_source_configs,
|
||||
)
|
||||
.await
|
||||
.map_err(rpc_types::error::Error::from)?;
|
||||
@@ -448,13 +527,133 @@ impl Session {
|
||||
);
|
||||
}
|
||||
|
||||
fn collect_webhook_source_instance_ids(
|
||||
metas: Vec<easytier::proto::api::manage::NetworkMeta>,
|
||||
) -> HashSet<String> {
|
||||
metas
|
||||
.into_iter()
|
||||
.filter_map(|meta| {
|
||||
(RpcConfigSource::try_from(meta.source).ok() == Some(RpcConfigSource::Webhook))
|
||||
.then(|| {
|
||||
meta.inst_id
|
||||
.map(|inst_id| Into::<uuid::Uuid>::into(inst_id).to_string())
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn sync_running_config_sources(
|
||||
db: &crate::db::Db,
|
||||
user_id: i32,
|
||||
machine_id: uuid::Uuid,
|
||||
local_configs: &[crate::db::entity::user_running_network_configs::Model],
|
||||
metas: &[NetworkMeta],
|
||||
) -> anyhow::Result<()> {
|
||||
let local_configs_by_id = local_configs
|
||||
.iter()
|
||||
.map(|cfg| (cfg.network_instance_id.clone(), cfg))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
for meta in metas {
|
||||
let Some(inst_id) = meta.inst_id.as_ref().map(|inst_id| {
|
||||
let inst_id: uuid::Uuid = (*inst_id).into();
|
||||
inst_id
|
||||
}) else {
|
||||
continue;
|
||||
};
|
||||
let inst_id_str = inst_id.to_string();
|
||||
let Some(local_cfg) = local_configs_by_id.get(&inst_id_str) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(running_source) = ConfigSource::from_rpc(meta.source) else {
|
||||
continue;
|
||||
};
|
||||
let local_source = PersistedConfigSource::from_db(&local_cfg.source);
|
||||
if !local_source.should_update_from_runtime(running_source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
db.insert_or_update_user_network_config(
|
||||
(user_id, machine_id),
|
||||
inst_id,
|
||||
local_cfg.get_network_config().map_err(|e| {
|
||||
anyhow::anyhow!("failed to decode local network config {}: {:?}", inst_id, e)
|
||||
})?,
|
||||
running_source,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to sync running network config source {}: {:?}",
|
||||
inst_id,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn repair_legacy_running_config_sources(
|
||||
db: &crate::db::Db,
|
||||
user_id: i32,
|
||||
machine_id: uuid::Uuid,
|
||||
local_configs: &[crate::db::entity::user_running_network_configs::Model],
|
||||
) -> anyhow::Result<bool> {
|
||||
let legacy_configs = local_configs
|
||||
.iter()
|
||||
.filter(|cfg| {
|
||||
PersistedConfigSource::from_db(&cfg.source) == PersistedConfigSource::Legacy
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if legacy_configs.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
for local_cfg in legacy_configs {
|
||||
let inst_id =
|
||||
uuid::Uuid::parse_str(&local_cfg.network_instance_id).with_context(|| {
|
||||
format!(
|
||||
"failed to parse legacy network config instance id {}",
|
||||
local_cfg.network_instance_id
|
||||
)
|
||||
})?;
|
||||
|
||||
db.insert_or_update_user_network_config(
|
||||
(user_id, machine_id),
|
||||
inst_id,
|
||||
local_cfg.get_network_config().map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to decode legacy network config {}: {:?}",
|
||||
inst_id,
|
||||
e
|
||||
)
|
||||
})?,
|
||||
ConfigSource::User,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to repair legacy network config source {}: {:?}",
|
||||
inst_id,
|
||||
e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn run_network_on_start(
|
||||
mut heartbeat_waiter: broadcast::Receiver<HeartbeatRequest>,
|
||||
storage: WeakRefStorage,
|
||||
rpc_client: SessionRpcClient,
|
||||
) {
|
||||
let mut cleaned_web_managed_instances = false;
|
||||
let mut last_desired_inst_ids: Option<HashSet<String>> = None;
|
||||
let mut cleaned_webhook_source_instances = false;
|
||||
let mut last_desired_webhook_inst_ids: Option<HashSet<String>> = None;
|
||||
loop {
|
||||
heartbeat_waiter = heartbeat_waiter.resubscribe();
|
||||
let req = heartbeat_waiter.recv().await;
|
||||
@@ -510,37 +709,160 @@ impl Session {
|
||||
}
|
||||
};
|
||||
|
||||
let mut local_configs = local_configs;
|
||||
let running_metas = if req.support_config_source {
|
||||
let ret = if running_inst_ids.is_empty() {
|
||||
Ok(Vec::new())
|
||||
} else {
|
||||
rpc_client
|
||||
.list_network_instance_meta(
|
||||
BaseController::default(),
|
||||
easytier::proto::api::manage::ListNetworkInstanceMetaRequest {
|
||||
inst_ids: running_inst_ids
|
||||
.iter()
|
||||
.filter_map(|inst_id| uuid::Uuid::parse_str(inst_id).ok())
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map(|resp| resp.metas)
|
||||
};
|
||||
|
||||
match ret {
|
||||
Ok(metas) => {
|
||||
if let Err(e) = Self::sync_running_config_sources(
|
||||
&storage.db,
|
||||
user_id,
|
||||
machine_id.into(),
|
||||
&local_configs,
|
||||
&metas,
|
||||
)
|
||||
.await
|
||||
{
|
||||
tracing::warn!(
|
||||
?user_id,
|
||||
?machine_id,
|
||||
%e,
|
||||
"Failed to sync running network config sources"
|
||||
);
|
||||
} else if !metas.is_empty() {
|
||||
local_configs = match storage
|
||||
.db
|
||||
.list_network_configs(
|
||||
(user_id, machine_id.into()),
|
||||
ListNetworkProps::EnabledOnly,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(configs) => configs,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to reload network configs after source sync, error: {:?}",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(metas)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
?user_id,
|
||||
%e,
|
||||
"Failed to list running network instance metadata"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match Self::repair_legacy_running_config_sources(
|
||||
&storage.db,
|
||||
user_id,
|
||||
machine_id.into(),
|
||||
&local_configs,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(true) => {
|
||||
local_configs = match storage
|
||||
.db
|
||||
.list_network_configs(
|
||||
(user_id, machine_id.into()),
|
||||
ListNetworkProps::EnabledOnly,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(configs) => configs,
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"Failed to reload network configs after legacy source repair, error: {:?}",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(false) => {}
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
?user_id,
|
||||
?machine_id,
|
||||
%e,
|
||||
"Failed to repair legacy running network config sources"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut has_failed = false;
|
||||
let should_be_alive_inst_ids = local_configs
|
||||
let should_be_alive_webhook_inst_ids = local_configs
|
||||
.iter()
|
||||
.filter(|cfg| cfg.get_runtime_network_config_source() == ConfigSource::Webhook)
|
||||
.map(|cfg| cfg.network_instance_id.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
let desired_changed = last_desired_inst_ids
|
||||
let desired_changed = last_desired_webhook_inst_ids
|
||||
.as_ref()
|
||||
.is_none_or(|last| last != &should_be_alive_inst_ids);
|
||||
.is_none_or(|last| last != &should_be_alive_webhook_inst_ids);
|
||||
|
||||
if !cleaned_web_managed_instances || desired_changed {
|
||||
let all_local_configs = match storage
|
||||
if !cleaned_webhook_source_instances || desired_changed {
|
||||
let db_webhook_inst_ids = match storage
|
||||
.db
|
||||
.list_network_configs((user_id, machine_id.into()), ListNetworkProps::All)
|
||||
.await
|
||||
{
|
||||
Ok(configs) => configs,
|
||||
Ok(configs) => configs
|
||||
.iter()
|
||||
.filter(|cfg| {
|
||||
cfg.get_runtime_network_config_source() == ConfigSource::Webhook
|
||||
})
|
||||
.map(|cfg| cfg.network_instance_id.clone())
|
||||
.collect::<HashSet<_>>(),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to list all network configs, error: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let all_inst_ids = all_local_configs
|
||||
.iter()
|
||||
.map(|cfg| cfg.network_instance_id.clone())
|
||||
let running_webhook_inst_ids = if let Some(metas) = running_metas.as_ref() {
|
||||
Self::collect_webhook_source_instance_ids(metas.clone())
|
||||
} else {
|
||||
running_inst_ids
|
||||
.intersection(&db_webhook_inst_ids)
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
|
||||
let should_delete_inst_ids = running_webhook_inst_ids
|
||||
.difference(&should_be_alive_webhook_inst_ids)
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let should_delete_ids = running_inst_ids
|
||||
let should_delete_ids = should_delete_inst_ids
|
||||
.iter()
|
||||
.chain(all_inst_ids.iter())
|
||||
.filter(|inst_id| !should_be_alive_inst_ids.contains(*inst_id))
|
||||
.filter_map(|inst_id| uuid::Uuid::parse_str(inst_id).ok())
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
@@ -556,7 +878,7 @@ impl Session {
|
||||
.await;
|
||||
tracing::info!(
|
||||
?user_id,
|
||||
"Clean non-web-managed network instances on start: {:?}, user_token: {:?}",
|
||||
"Clean stale webhook-source network instances on start: {:?}, user_token: {:?}",
|
||||
ret,
|
||||
req.user_token
|
||||
);
|
||||
@@ -564,8 +886,8 @@ impl Session {
|
||||
}
|
||||
|
||||
if !has_failed {
|
||||
cleaned_web_managed_instances = true;
|
||||
last_desired_inst_ids = Some(should_be_alive_inst_ids.clone());
|
||||
cleaned_webhook_source_instances = true;
|
||||
last_desired_webhook_inst_ids = Some(should_be_alive_webhook_inst_ids.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -573,6 +895,16 @@ impl Session {
|
||||
if running_inst_ids.contains(&c.network_instance_id) {
|
||||
continue;
|
||||
}
|
||||
let Some(source) = PersistedConfigSource::from_db(&c.source).auto_run_rpc_source()
|
||||
else {
|
||||
tracing::warn!(
|
||||
?user_id,
|
||||
?machine_id,
|
||||
instance_id = %c.network_instance_id,
|
||||
"skip auto-run for legacy config until source ownership is repaired"
|
||||
);
|
||||
continue;
|
||||
};
|
||||
let ret = rpc_client
|
||||
.run_network_instance(
|
||||
BaseController::default(),
|
||||
@@ -582,6 +914,7 @@ impl Session {
|
||||
serde_json::from_str::<NetworkConfig>(&c.network_config).unwrap(),
|
||||
),
|
||||
overwrite: false,
|
||||
source: source as i32,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -596,7 +929,7 @@ impl Session {
|
||||
}
|
||||
|
||||
if !has_failed {
|
||||
last_desired_inst_ids = Some(should_be_alive_inst_ids);
|
||||
last_desired_webhook_inst_ids = Some(should_be_alive_webhook_inst_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -634,13 +967,17 @@ impl Session {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use easytier::rpc_service::remote_client::{ListNetworkProps, Storage as _};
|
||||
use easytier::{
|
||||
common::config::ConfigSource,
|
||||
rpc_service::remote_client::{ListNetworkProps, PersistentConfig as _, Storage as _},
|
||||
};
|
||||
use sea_orm::{ActiveModelTrait, Set};
|
||||
use serde_json::json;
|
||||
|
||||
use super::{super::storage::Storage, *};
|
||||
|
||||
#[tokio::test]
|
||||
async fn reconcile_managed_network_configs_upserts_and_deletes_exact_set() {
|
||||
async fn reconcile_webhook_source_configs_upserts_and_deletes_exact_set() {
|
||||
let storage = Storage::new(crate::db::Db::memory_db().await);
|
||||
let user_id = storage
|
||||
.db()
|
||||
@@ -662,6 +999,7 @@ mod tests {
|
||||
network_name: Some("old-name".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigSource::Webhook,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -674,11 +1012,12 @@ mod tests {
|
||||
network_name: Some("stale".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigSource::Webhook,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
SessionRpcService::reconcile_managed_network_configs(
|
||||
SessionRpcService::reconcile_webhook_source_configs(
|
||||
&storage,
|
||||
user_id,
|
||||
machine_id,
|
||||
@@ -729,5 +1068,353 @@ mod tests {
|
||||
updated_keep_config.network_name.as_deref(),
|
||||
Some("updated-name")
|
||||
);
|
||||
assert_eq!(
|
||||
updated_keep.get_network_config_source(),
|
||||
ConfigSource::Webhook
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reconcile_webhook_source_configs_keep_user_owned_configs() {
|
||||
let storage = Storage::new(crate::db::Db::memory_db().await);
|
||||
let user_id = storage
|
||||
.db()
|
||||
.auto_create_user("webhook-user-keep-user")
|
||||
.await
|
||||
.unwrap()
|
||||
.id;
|
||||
let machine_id = uuid::Uuid::new_v4();
|
||||
let user_owned_id = uuid::Uuid::new_v4();
|
||||
let webhook_owned_id = uuid::Uuid::new_v4();
|
||||
|
||||
storage
|
||||
.db()
|
||||
.insert_or_update_user_network_config(
|
||||
(user_id, machine_id),
|
||||
user_owned_id,
|
||||
NetworkConfig {
|
||||
network_name: Some("user-owned".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigSource::User,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
storage
|
||||
.db()
|
||||
.insert_or_update_user_network_config(
|
||||
(user_id, machine_id),
|
||||
webhook_owned_id,
|
||||
NetworkConfig {
|
||||
network_name: Some("webhook-owned".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigSource::Webhook,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
SessionRpcService::reconcile_webhook_source_configs(
|
||||
&storage,
|
||||
user_id,
|
||||
machine_id,
|
||||
vec![crate::webhook::ManagedNetworkConfig {
|
||||
instance_id: user_owned_id.to_string(),
|
||||
network_config: json!({
|
||||
"instance_id": user_owned_id.to_string(),
|
||||
"network_name": "webhook-tries-to-take-over"
|
||||
}),
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user_owned = storage
|
||||
.db()
|
||||
.get_network_config((user_id, machine_id), &user_owned_id.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(user_owned.get_network_config_source(), ConfigSource::User);
|
||||
let user_owned_cfg: NetworkConfig =
|
||||
serde_json::from_str(&user_owned.network_config).unwrap();
|
||||
assert_eq!(user_owned_cfg.network_name.as_deref(), Some("user-owned"));
|
||||
|
||||
let webhook_owned = storage
|
||||
.db()
|
||||
.get_network_config((user_id, machine_id), &webhook_owned_id.to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(webhook_owned.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn reconcile_webhook_source_configs_adopts_legacy_rows_for_webhook() {
|
||||
let storage = Storage::new(crate::db::Db::memory_db().await);
|
||||
let user_id = storage
|
||||
.db()
|
||||
.auto_create_user("webhook-user-legacy")
|
||||
.await
|
||||
.unwrap()
|
||||
.id;
|
||||
let machine_id = uuid::Uuid::new_v4();
|
||||
let legacy_match_id = uuid::Uuid::new_v4();
|
||||
let legacy_user_id = uuid::Uuid::new_v4();
|
||||
|
||||
crate::db::entity::user_running_network_configs::ActiveModel {
|
||||
user_id: Set(user_id),
|
||||
device_id: Set(machine_id.to_string()),
|
||||
network_instance_id: Set(legacy_match_id.to_string()),
|
||||
network_config: Set(serde_json::to_string(&NetworkConfig {
|
||||
network_name: Some("legacy-webhook".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap()),
|
||||
source: Set(LEGACY_NETWORK_CONFIG_SOURCE.to_string()),
|
||||
disabled: Set(false),
|
||||
create_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
update_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(storage.db().orm_db())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
crate::db::entity::user_running_network_configs::ActiveModel {
|
||||
user_id: Set(user_id),
|
||||
device_id: Set(machine_id.to_string()),
|
||||
network_instance_id: Set(legacy_user_id.to_string()),
|
||||
network_config: Set(serde_json::to_string(&NetworkConfig {
|
||||
network_name: Some("legacy-user".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap()),
|
||||
source: Set(LEGACY_NETWORK_CONFIG_SOURCE.to_string()),
|
||||
disabled: Set(false),
|
||||
create_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
update_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(storage.db().orm_db())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
SessionRpcService::reconcile_webhook_source_configs(
|
||||
&storage,
|
||||
user_id,
|
||||
machine_id,
|
||||
vec![crate::webhook::ManagedNetworkConfig {
|
||||
instance_id: legacy_match_id.to_string(),
|
||||
network_config: json!({
|
||||
"instance_id": legacy_match_id.to_string(),
|
||||
"network_name": "managed-by-webhook"
|
||||
}),
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let adopted = storage
|
||||
.db()
|
||||
.get_network_config((user_id, machine_id), &legacy_match_id.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(adopted.source, ConfigSource::Webhook.as_str());
|
||||
let adopted_cfg: NetworkConfig = serde_json::from_str(&adopted.network_config).unwrap();
|
||||
assert_eq!(
|
||||
adopted_cfg.network_name.as_deref(),
|
||||
Some("managed-by-webhook")
|
||||
);
|
||||
|
||||
let untouched_legacy = storage
|
||||
.db()
|
||||
.get_network_config((user_id, machine_id), &legacy_user_id.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(untouched_legacy.source, LEGACY_NETWORK_CONFIG_SOURCE);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_running_config_sources_updates_enabled_config_source_from_runtime() {
|
||||
let storage = Storage::new(crate::db::Db::memory_db().await);
|
||||
let user_id = storage
|
||||
.db()
|
||||
.auto_create_user("webhook-user-sync-source")
|
||||
.await
|
||||
.unwrap()
|
||||
.id;
|
||||
let machine_id = uuid::Uuid::new_v4();
|
||||
let inst_id = uuid::Uuid::new_v4();
|
||||
|
||||
storage
|
||||
.db()
|
||||
.insert_or_update_user_network_config(
|
||||
(user_id, machine_id),
|
||||
inst_id,
|
||||
NetworkConfig {
|
||||
network_name: Some("webhook-owned".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigSource::Webhook,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let local_configs = storage
|
||||
.db()
|
||||
.list_network_configs((user_id, machine_id), ListNetworkProps::EnabledOnly)
|
||||
.await
|
||||
.unwrap();
|
||||
Session::sync_running_config_sources(
|
||||
storage.db(),
|
||||
user_id,
|
||||
machine_id,
|
||||
&local_configs,
|
||||
&[easytier::proto::api::manage::NetworkMeta {
|
||||
inst_id: Some(inst_id.into()),
|
||||
source: RpcConfigSource::User as i32,
|
||||
..Default::default()
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let updated = storage
|
||||
.db()
|
||||
.get_network_config((user_id, machine_id), &inst_id.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(updated.get_network_config_source(), ConfigSource::Webhook);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn sync_running_config_sources_keeps_legacy_rows_when_runtime_source_is_user() {
|
||||
let storage = Storage::new(crate::db::Db::memory_db().await);
|
||||
let user_id = storage
|
||||
.db()
|
||||
.auto_create_user("webhook-user-sync-legacy")
|
||||
.await
|
||||
.unwrap()
|
||||
.id;
|
||||
let machine_id = uuid::Uuid::new_v4();
|
||||
let inst_id = uuid::Uuid::new_v4();
|
||||
|
||||
crate::db::entity::user_running_network_configs::ActiveModel {
|
||||
user_id: Set(user_id),
|
||||
device_id: Set(machine_id.to_string()),
|
||||
network_instance_id: Set(inst_id.to_string()),
|
||||
network_config: Set(serde_json::to_string(&NetworkConfig {
|
||||
network_name: Some("legacy".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap()),
|
||||
source: Set(LEGACY_NETWORK_CONFIG_SOURCE.to_string()),
|
||||
disabled: Set(false),
|
||||
create_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
update_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(storage.db().orm_db())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let local_configs = storage
|
||||
.db()
|
||||
.list_network_configs((user_id, machine_id), ListNetworkProps::EnabledOnly)
|
||||
.await
|
||||
.unwrap();
|
||||
Session::sync_running_config_sources(
|
||||
storage.db(),
|
||||
user_id,
|
||||
machine_id,
|
||||
&local_configs,
|
||||
&[easytier::proto::api::manage::NetworkMeta {
|
||||
inst_id: Some(inst_id.into()),
|
||||
source: RpcConfigSource::User as i32,
|
||||
..Default::default()
|
||||
}],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let updated = storage
|
||||
.db()
|
||||
.get_network_config((user_id, machine_id), &inst_id.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(updated.source, LEGACY_NETWORK_CONFIG_SOURCE);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn repair_legacy_running_config_sources_promotes_remaining_legacy_rows_to_user() {
|
||||
let storage = Storage::new(crate::db::Db::memory_db().await);
|
||||
let user_id = storage
|
||||
.db()
|
||||
.auto_create_user("webhook-user-repair-legacy")
|
||||
.await
|
||||
.unwrap()
|
||||
.id;
|
||||
let machine_id = uuid::Uuid::new_v4();
|
||||
let inst_id = uuid::Uuid::new_v4();
|
||||
|
||||
crate::db::entity::user_running_network_configs::ActiveModel {
|
||||
user_id: Set(user_id),
|
||||
device_id: Set(machine_id.to_string()),
|
||||
network_instance_id: Set(inst_id.to_string()),
|
||||
network_config: Set(serde_json::to_string(&NetworkConfig {
|
||||
network_name: Some("legacy".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap()),
|
||||
source: Set(LEGACY_NETWORK_CONFIG_SOURCE.to_string()),
|
||||
disabled: Set(false),
|
||||
create_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
update_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(storage.db().orm_db())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let local_configs = storage
|
||||
.db()
|
||||
.list_network_configs((user_id, machine_id), ListNetworkProps::EnabledOnly)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
Session::repair_legacy_running_config_sources(
|
||||
storage.db(),
|
||||
user_id,
|
||||
machine_id,
|
||||
&local_configs,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
|
||||
let updated = storage
|
||||
.db()
|
||||
.get_network_config((user_id, machine_id), &inst_id.to_string())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(updated.source, ConfigSource::User.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_configs_are_not_auto_run_until_repaired() {
|
||||
assert_eq!(PersistedConfigSource::Legacy.auto_run_rpc_source(), None);
|
||||
assert_eq!(
|
||||
PersistedConfigSource::Webhook.auto_run_rpc_source(),
|
||||
Some(RpcConfigSource::Webhook)
|
||||
);
|
||||
assert_eq!(
|
||||
PersistedConfigSource::User.auto_run_rpc_source(),
|
||||
Some(RpcConfigSource::User)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use easytier::{launcher::NetworkConfig, rpc_service::remote_client::PersistentConfig};
|
||||
use easytier::{
|
||||
common::config::ConfigSource, launcher::NetworkConfig,
|
||||
rpc_service::remote_client::PersistentConfig,
|
||||
};
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -12,10 +15,12 @@ pub struct Model {
|
||||
pub user_id: i32,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub device_id: String,
|
||||
#[sea_orm(column_type = "Text", unique)]
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub network_instance_id: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub network_config: String,
|
||||
#[sea_orm(column_type = "Text")]
|
||||
pub source: String,
|
||||
pub disabled: bool,
|
||||
pub create_time: DateTimeWithTimeZone,
|
||||
pub update_time: DateTimeWithTimeZone,
|
||||
@@ -48,4 +53,7 @@ impl PersistentConfig<DbErr> for Model {
|
||||
fn get_network_config(&self) -> Result<NetworkConfig, DbErr> {
|
||||
serde_json::from_str(&self.network_config).map_err(|e| DbErr::Json(e.to_string()))
|
||||
}
|
||||
fn get_network_config_source(&self) -> ConfigSource {
|
||||
self.source.parse().unwrap_or(ConfigSource::User)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
pub mod entity;
|
||||
|
||||
use easytier::{
|
||||
common::config::ConfigSource,
|
||||
launcher::NetworkConfig,
|
||||
rpc_service::remote_client::{ListNetworkProps, Storage},
|
||||
};
|
||||
@@ -149,6 +150,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
||||
(user_id, device_id): (UserIdInDb, Uuid),
|
||||
network_inst_id: Uuid,
|
||||
network_config: NetworkConfig,
|
||||
source: ConfigSource,
|
||||
) -> Result<(), DbErr> {
|
||||
let txn = self.orm_db().begin().await?;
|
||||
|
||||
@@ -161,6 +163,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
||||
])
|
||||
.update_columns([
|
||||
urnc::Column::NetworkConfig,
|
||||
urnc::Column::Source,
|
||||
urnc::Column::Disabled,
|
||||
urnc::Column::UpdateTime,
|
||||
])
|
||||
@@ -172,6 +175,7 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
||||
network_config: sea_orm::Set(
|
||||
serde_json::to_string(&network_config).map_err(|e| DbErr::Json(e.to_string()))?,
|
||||
),
|
||||
source: sea_orm::Set(source.as_str().to_string()),
|
||||
disabled: sea_orm::Set(false),
|
||||
create_time: sea_orm::Set(chrono::Local::now().fixed_offset()),
|
||||
update_time: sea_orm::Set(chrono::Local::now().fixed_offset()),
|
||||
@@ -277,8 +281,12 @@ impl Storage<(UserIdInDb, Uuid), user_running_network_configs::Model, DbErr> for
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use easytier::{proto::api::manage::NetworkConfig, rpc_service::remote_client::Storage};
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
|
||||
use easytier::{
|
||||
common::config::ConfigSource,
|
||||
proto::api::manage::NetworkConfig,
|
||||
rpc_service::remote_client::{PersistentConfig, Storage},
|
||||
};
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter as _, Set};
|
||||
|
||||
use crate::db::{Db, ListNetworkProps, entity::user_running_network_configs};
|
||||
|
||||
@@ -294,9 +302,14 @@ 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)
|
||||
.await
|
||||
.unwrap();
|
||||
db.insert_or_update_user_network_config(
|
||||
(user_id, device_id),
|
||||
inst_id,
|
||||
network_config,
|
||||
ConfigSource::User,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = user_running_network_configs::Entity::find()
|
||||
.filter(user_running_network_configs::Column::UserId.eq(user_id))
|
||||
@@ -306,6 +319,7 @@ mod tests {
|
||||
.unwrap();
|
||||
println!("{:?}", result);
|
||||
assert_eq!(result.network_config, network_config_json);
|
||||
assert_eq!(result.get_network_config_source(), ConfigSource::User);
|
||||
|
||||
// overwrite the config
|
||||
let network_config = NetworkConfig {
|
||||
@@ -313,9 +327,14 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
let network_config_json = serde_json::to_string(&network_config).unwrap();
|
||||
db.insert_or_update_user_network_config((user_id, device_id), inst_id, network_config)
|
||||
.await
|
||||
.unwrap();
|
||||
db.insert_or_update_user_network_config(
|
||||
(user_id, device_id),
|
||||
inst_id,
|
||||
network_config,
|
||||
ConfigSource::Webhook,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result2 = user_running_network_configs::Entity::find()
|
||||
.filter(user_running_network_configs::Column::UserId.eq(user_id))
|
||||
@@ -325,6 +344,11 @@ mod tests {
|
||||
.unwrap();
|
||||
println!("device: {}, {:?}", device_id, result2);
|
||||
assert_eq!(result2.network_config, network_config_json);
|
||||
assert_eq!(result2.get_network_config_source(), ConfigSource::Webhook);
|
||||
assert_eq!(
|
||||
result2.get_runtime_network_config_source(),
|
||||
ConfigSource::Webhook
|
||||
);
|
||||
|
||||
assert_eq!(result.create_time, result2.create_time);
|
||||
assert_ne!(result.update_time, result2.update_time);
|
||||
@@ -348,6 +372,45 @@ mod tests {
|
||||
assert!(result3.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_legacy_network_config_defaults_to_user_runtime_source() {
|
||||
let db = Db::memory_db().await;
|
||||
let user_id = 1;
|
||||
let inst_id = uuid::Uuid::new_v4();
|
||||
let device_id = uuid::Uuid::new_v4();
|
||||
|
||||
user_running_network_configs::ActiveModel {
|
||||
user_id: Set(user_id),
|
||||
device_id: Set(device_id.to_string()),
|
||||
network_instance_id: Set(inst_id.to_string()),
|
||||
network_config: Set(serde_json::to_string(&NetworkConfig {
|
||||
network_name: Some("legacy".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.unwrap()),
|
||||
source: Set("legacy".to_string()),
|
||||
disabled: Set(false),
|
||||
create_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
update_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(db.orm_db())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = user_running_network_configs::Entity::find()
|
||||
.filter(user_running_network_configs::Column::UserId.eq(user_id))
|
||||
.one(db.orm_db())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(result.get_network_config_source(), ConfigSource::User);
|
||||
assert_eq!(
|
||||
result.get_runtime_network_config_source(),
|
||||
ConfigSource::User
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_user_network_config_same_instance_id_is_scoped_by_device() {
|
||||
let db = Db::memory_db().await;
|
||||
@@ -363,6 +426,7 @@ mod tests {
|
||||
network_name: Some("cfg-1".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigSource::User,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -373,6 +437,7 @@ mod tests {
|
||||
network_name: Some("cfg-2".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
ConfigSource::User,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m20260421_000003_add_network_config_source"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
CREATE TABLE user_running_network_configs_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
device_id TEXT NOT NULL,
|
||||
network_instance_id TEXT NOT NULL,
|
||||
network_config TEXT NOT NULL,
|
||||
source TEXT NOT NULL DEFAULT 'user',
|
||||
disabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
create_time TEXT NOT NULL,
|
||||
update_time TEXT NOT NULL,
|
||||
CONSTRAINT fk_user_running_network_configs_user_id_to_users_id
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO user_running_network_configs_new (
|
||||
id,
|
||||
user_id,
|
||||
device_id,
|
||||
network_instance_id,
|
||||
network_config,
|
||||
source,
|
||||
disabled,
|
||||
create_time,
|
||||
update_time
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
user_id,
|
||||
device_id,
|
||||
network_instance_id,
|
||||
network_config,
|
||||
'legacy',
|
||||
disabled,
|
||||
create_time,
|
||||
update_time
|
||||
FROM user_running_network_configs;
|
||||
|
||||
DROP TABLE user_running_network_configs;
|
||||
ALTER TABLE user_running_network_configs_new RENAME TO user_running_network_configs;
|
||||
|
||||
CREATE INDEX idx_user_running_network_configs_user_id
|
||||
ON user_running_network_configs(user_id);
|
||||
CREATE UNIQUE INDEX idx_user_running_network_configs_scope_inst
|
||||
ON user_running_network_configs(user_id, device_id, network_instance_id);
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
db.execute_unprepared(
|
||||
r#"
|
||||
CREATE TABLE user_running_network_configs_old (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
device_id TEXT NOT NULL,
|
||||
network_instance_id TEXT NOT NULL,
|
||||
network_config TEXT NOT NULL,
|
||||
disabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
create_time TEXT NOT NULL,
|
||||
update_time TEXT NOT NULL,
|
||||
CONSTRAINT fk_user_running_network_configs_user_id_to_users_id
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO user_running_network_configs_old (
|
||||
id,
|
||||
user_id,
|
||||
device_id,
|
||||
network_instance_id,
|
||||
network_config,
|
||||
disabled,
|
||||
create_time,
|
||||
update_time
|
||||
)
|
||||
SELECT
|
||||
id,
|
||||
user_id,
|
||||
device_id,
|
||||
network_instance_id,
|
||||
network_config,
|
||||
disabled,
|
||||
create_time,
|
||||
update_time
|
||||
FROM user_running_network_configs;
|
||||
|
||||
DROP TABLE user_running_network_configs;
|
||||
ALTER TABLE user_running_network_configs_old RENAME TO user_running_network_configs;
|
||||
|
||||
CREATE INDEX idx_user_running_network_configs_user_id
|
||||
ON user_running_network_configs(user_id);
|
||||
CREATE UNIQUE INDEX idx_user_running_network_configs_scope_inst
|
||||
ON user_running_network_configs(user_id, device_id, network_instance_id);
|
||||
"#,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use sea_orm_migration::prelude::*;
|
||||
|
||||
mod m20241029_000001_init;
|
||||
mod m20260403_000002_scope_network_config_unique;
|
||||
mod m20260421_000003_add_network_config_source;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -11,6 +12,7 @@ impl MigratorTrait for Migrator {
|
||||
vec![
|
||||
Box::new(m20241029_000001_init::Migration),
|
||||
Box::new(m20260403_000002_scope_network_config_unique::Migration),
|
||||
Box::new(m20260421_000003_add_network_config_source::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user