feat: move network config to db
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
closes https://github.com/LNVPS/api/issues/16
This commit is contained in:
23
lnvps_db/migrations/20250325113115_extend_ip_range.sql
Normal file
23
lnvps_db/migrations/20250325113115_extend_ip_range.sql
Normal file
@ -0,0 +1,23 @@
|
||||
create table router
|
||||
(
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
name varchar(100) not null,
|
||||
enabled bit(1) not null,
|
||||
kind smallint unsigned not null,
|
||||
url varchar(255) not null,
|
||||
token varchar(128) not null
|
||||
);
|
||||
create table access_policy
|
||||
(
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
name varchar(100) not null,
|
||||
kind smallint unsigned not null,
|
||||
router_id integer unsigned,
|
||||
interface varchar(100),
|
||||
constraint fk_access_policy_router foreign key (router_id) references router (id)
|
||||
);
|
||||
alter table ip_range
|
||||
add column reverse_zone_id varchar(255),
|
||||
add column access_policy_id integer unsigned;
|
||||
alter table ip_range
|
||||
add constraint fk_ip_range_access_policy foreign key (access_policy_id) references access_policy (id);
|
@ -163,4 +163,10 @@ pub trait LNVpsDb: Sync + Send {
|
||||
|
||||
/// Return the list of disk prices for a given custom pricing model
|
||||
async fn list_custom_pricing_disk(&self, pricing_id: u64) -> Result<Vec<VmCustomPricingDisk>>;
|
||||
|
||||
/// Get router config
|
||||
async fn get_router(&self, router_id: u64) -> Result<Router>;
|
||||
|
||||
/// Get access policy
|
||||
async fn get_access_policy(&self, access_policy_id: u64) -> Result<AccessPolicy>;
|
||||
}
|
||||
|
@ -207,6 +207,22 @@ impl Display for VmOsImage {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Clone, Debug)]
|
||||
pub struct Router {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
pub kind: RouterKind,
|
||||
pub url: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, sqlx::Type)]
|
||||
#[repr(u16)]
|
||||
pub enum RouterKind {
|
||||
Mikrotik = 0,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Clone, Debug)]
|
||||
pub struct IpRange {
|
||||
pub id: u64,
|
||||
@ -214,6 +230,27 @@ pub struct IpRange {
|
||||
pub gateway: String,
|
||||
pub enabled: bool,
|
||||
pub region_id: u64,
|
||||
pub reverse_zone_id: Option<String>,
|
||||
pub access_policy_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Clone, Debug)]
|
||||
pub struct AccessPolicy {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub kind: NetworkAccessPolicy,
|
||||
/// Router used to apply this network access policy
|
||||
pub router_id: Option<u64>,
|
||||
/// Interface name used to apply this policy
|
||||
pub interface: Option<String>,
|
||||
}
|
||||
|
||||
/// Policy that determines how packets arrive at the VM
|
||||
#[derive(Debug, Clone, sqlx::Type)]
|
||||
#[repr(u16)]
|
||||
pub enum NetworkAccessPolicy {
|
||||
/// ARP entries are added statically on the access router
|
||||
StaticArp = 0,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, sqlx::Type)]
|
||||
|
@ -1,8 +1,4 @@
|
||||
use crate::{
|
||||
IpRange, LNVpsDb, User, UserSshKey, Vm, VmCostPlan, VmCustomPricing, VmCustomPricingDisk,
|
||||
VmCustomTemplate, VmHost, VmHostDisk, VmHostRegion, VmIpAssignment, VmOsImage, VmPayment,
|
||||
VmTemplate,
|
||||
};
|
||||
use crate::{AccessPolicy, IpRange, LNVpsDb, Router, User, UserSshKey, Vm, VmCostPlan, VmCustomPricing, VmCustomPricingDisk, VmCustomTemplate, VmHost, VmHostDisk, VmHostRegion, VmIpAssignment, VmOsImage, VmPayment, VmTemplate};
|
||||
use anyhow::{bail, Error, Result};
|
||||
use async_trait::async_trait;
|
||||
use sqlx::{Executor, MySqlPool, Row};
|
||||
@ -526,4 +522,20 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn get_router(&self, router_id: u64) -> Result<Router> {
|
||||
sqlx::query_as("select * from router where id=?")
|
||||
.bind(router_id)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn get_access_policy(&self, access_policy_id: u64) -> Result<AccessPolicy> {
|
||||
sqlx::query_as("select * from access_policy where id=?")
|
||||
.bind(access_policy_id)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ use crate::settings::ProvisionerConfig;
|
||||
use crate::status::VmState;
|
||||
use anyhow::{bail, Result};
|
||||
use futures::future::join_all;
|
||||
use futures::{Sink, Stream};
|
||||
use lnvps_db::{
|
||||
async_trait, IpRange, LNVpsDb, UserSshKey, Vm, VmCustomTemplate, VmHost, VmHostDisk,
|
||||
VmHostKind, VmIpAssignment, VmOsImage, VmTemplate,
|
||||
@ -10,11 +9,8 @@ use lnvps_db::{
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashSet;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
//#[cfg(feature = "libvirt")]
|
||||
//mod libvirt;
|
||||
|
@ -5,31 +5,22 @@ use crate::ssh_client::SshClient;
|
||||
use crate::status::{VmRunningState, VmState};
|
||||
use anyhow::{anyhow, bail, ensure, Result};
|
||||
use chrono::Utc;
|
||||
use futures::{Stream, StreamExt};
|
||||
use futures::StreamExt;
|
||||
use ipnetwork::IpNetwork;
|
||||
use lnvps_db::{async_trait, DiskType, Vm, VmOsImage};
|
||||
use log::{error, info, warn};
|
||||
use log::{info, warn};
|
||||
use rand::random;
|
||||
use reqwest::header::{HeaderMap, AUTHORIZATION};
|
||||
use reqwest::{ClientBuilder, Method, Url};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::io::{Read, Write};
|
||||
use std::io::Write;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||
use tokio::sync::mpsc::channel;
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::time;
|
||||
use tokio::time::sleep;
|
||||
use tokio_tungstenite::tungstenite::protocol::Role;
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use ws::stream::DuplexStream;
|
||||
|
||||
pub struct ProxmoxClient {
|
||||
api: JsonApi,
|
||||
@ -1140,7 +1131,8 @@ mod tests {
|
||||
release_date: Utc::now(),
|
||||
url: "http://localhost.com/ubuntu_server_24.04.img".to_string(),
|
||||
},
|
||||
ips: vec![VmIpAssignment {
|
||||
ips: vec![
|
||||
VmIpAssignment {
|
||||
id: 1,
|
||||
vm_id: 1,
|
||||
ip_range_id: 1,
|
||||
@ -1151,14 +1143,40 @@ mod tests {
|
||||
dns_forward_ref: None,
|
||||
dns_reverse: None,
|
||||
dns_reverse_ref: None,
|
||||
}],
|
||||
ranges: vec![IpRange {
|
||||
},
|
||||
VmIpAssignment {
|
||||
id: 2,
|
||||
vm_id: 1,
|
||||
ip_range_id: 2,
|
||||
ip: "192.168.2.2".to_string(),
|
||||
deleted: false,
|
||||
arp_ref: None,
|
||||
dns_forward: None,
|
||||
dns_forward_ref: None,
|
||||
dns_reverse: None,
|
||||
dns_reverse_ref: None,
|
||||
},
|
||||
],
|
||||
ranges: vec![
|
||||
IpRange {
|
||||
id: 1,
|
||||
cidr: "192.168.1.0/24".to_string(),
|
||||
gateway: "192.168.1.1/16".to_string(),
|
||||
enabled: true,
|
||||
region_id: 1,
|
||||
}],
|
||||
reverse_zone_id: None,
|
||||
access_policy_id: None,
|
||||
},
|
||||
IpRange {
|
||||
id: 2,
|
||||
cidr: "192.168.2.0/24".to_string(),
|
||||
gateway: "10.10.10.10".to_string(),
|
||||
enabled: true,
|
||||
region_id: 2,
|
||||
reverse_zone_id: None,
|
||||
access_policy_id: None,
|
||||
},
|
||||
],
|
||||
ssh_key: UserSshKey {
|
||||
id: 1,
|
||||
name: "test".to_string(),
|
||||
@ -1193,7 +1211,10 @@ mod tests {
|
||||
assert_eq!(vm.on_boot, Some(true));
|
||||
assert_eq!(
|
||||
vm.ip_config,
|
||||
Some("ip=192.168.1.2/16,gw=192.168.1.1,ip6=auto".to_string())
|
||||
Some(
|
||||
"ip=192.168.1.2/16,gw=192.168.1.1,ip=192.168.2.2/24,gw=10.10.10.10,ip6=auto"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
30
src/mocks.rs
30
src/mocks.rs
@ -4,15 +4,15 @@ use crate::exchange::{ExchangeRateService, Ticker, TickerRate};
|
||||
use crate::host::{FullVmInfo, TerminalStream, TimeSeries, TimeSeriesData, VmHostClient};
|
||||
use crate::lightning::{AddInvoiceRequest, AddInvoiceResult, InvoiceUpdate, LightningNode};
|
||||
use crate::router::{ArpEntry, Router};
|
||||
use crate::settings::NetworkPolicy;
|
||||
use crate::status::{VmRunningState, VmState};
|
||||
use anyhow::{anyhow, bail, ensure, Context};
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use fedimint_tonic_lnd::tonic::codegen::tokio_stream::Stream;
|
||||
use lnvps_db::{
|
||||
async_trait, DiskInterface, DiskType, IpRange, LNVpsDb, OsDistribution, User, UserSshKey, Vm,
|
||||
VmCostPlan, VmCostPlanIntervalType, VmCustomPricing, VmCustomPricingDisk, VmCustomTemplate,
|
||||
VmHost, VmHostDisk, VmHostKind, VmHostRegion, VmIpAssignment, VmOsImage, VmPayment, VmTemplate,
|
||||
async_trait, AccessPolicy, DiskInterface, DiskType, IpRange, LNVpsDb, OsDistribution, User,
|
||||
UserSshKey, Vm, VmCostPlan, VmCostPlanIntervalType, VmCustomPricing, VmCustomPricingDisk,
|
||||
VmCustomTemplate, VmHost, VmHostDisk, VmHostKind, VmHostRegion, VmIpAssignment, VmOsImage,
|
||||
VmPayment, VmTemplate,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Add;
|
||||
@ -37,6 +37,8 @@ pub struct MockDb {
|
||||
pub custom_pricing_disk: Arc<Mutex<HashMap<u64, VmCustomPricingDisk>>>,
|
||||
pub custom_template: Arc<Mutex<HashMap<u64, VmCustomTemplate>>>,
|
||||
pub payments: Arc<Mutex<Vec<VmPayment>>>,
|
||||
pub router: Arc<Mutex<HashMap<u64, lnvps_db::Router>>>,
|
||||
pub access_policy: Arc<Mutex<HashMap<u64, AccessPolicy>>>,
|
||||
}
|
||||
|
||||
impl MockDb {
|
||||
@ -115,6 +117,8 @@ impl Default for MockDb {
|
||||
gateway: "10.0.0.1/8".to_string(),
|
||||
enabled: true,
|
||||
region_id: 1,
|
||||
reverse_zone_id: None,
|
||||
access_policy_id: None,
|
||||
},
|
||||
);
|
||||
let mut hosts = HashMap::new();
|
||||
@ -181,6 +185,8 @@ impl Default for MockDb {
|
||||
user_ssh_keys: Arc::new(Mutex::new(Default::default())),
|
||||
custom_template: Arc::new(Default::default()),
|
||||
payments: Arc::new(Default::default()),
|
||||
router: Arc::new(Default::default()),
|
||||
access_policy: Arc::new(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -602,21 +608,31 @@ impl LNVpsDb for MockDb {
|
||||
.cloned()
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn get_router(&self, router_id: u64) -> anyhow::Result<lnvps_db::Router> {
|
||||
let r = self.router.lock().await;
|
||||
Ok(r.get(&router_id).cloned().context("no router")?)
|
||||
}
|
||||
|
||||
async fn get_access_policy(&self, access_policy_id: u64) -> anyhow::Result<AccessPolicy> {
|
||||
let p = self.access_policy.lock().await;
|
||||
Ok(p.get(&access_policy_id)
|
||||
.cloned()
|
||||
.context("no access policy")?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockRouter {
|
||||
pub policy: NetworkPolicy,
|
||||
arp: Arc<Mutex<HashMap<u64, ArpEntry>>>,
|
||||
}
|
||||
|
||||
impl MockRouter {
|
||||
pub fn new(policy: NetworkPolicy) -> Self {
|
||||
pub fn new() -> Self {
|
||||
static LAZY_ARP: LazyLock<Arc<Mutex<HashMap<u64, ArpEntry>>>> =
|
||||
LazyLock::new(|| Arc::new(Mutex::new(HashMap::new())));
|
||||
|
||||
Self {
|
||||
policy,
|
||||
arp: LAZY_ARP.clone(),
|
||||
}
|
||||
}
|
||||
|
@ -354,6 +354,8 @@ mod tests {
|
||||
gateway: "10.0.0.1".to_string(),
|
||||
enabled: true,
|
||||
region_id: 1,
|
||||
reverse_zone_id: None,
|
||||
access_policy_id: None,
|
||||
},
|
||||
usage: 69,
|
||||
}],
|
||||
|
@ -6,12 +6,15 @@ use crate::lightning::{AddInvoiceRequest, LightningNode};
|
||||
use crate::provisioner::{
|
||||
CostResult, HostCapacityService, NetworkProvisioner, PricingEngine, ProvisionerMethod,
|
||||
};
|
||||
use crate::router::{ArpEntry, Router};
|
||||
use crate::settings::{NetworkAccessPolicy, NetworkPolicy, ProvisionerConfig, Settings};
|
||||
use crate::router::{ArpEntry, MikrotikRouter, Router};
|
||||
use crate::settings::{ProvisionerConfig, Settings};
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use chrono::Utc;
|
||||
use isocountry::CountryCode;
|
||||
use lnvps_db::{LNVpsDb, PaymentMethod, User, Vm, VmCustomTemplate, VmIpAssignment, VmPayment};
|
||||
use lnvps_db::{
|
||||
AccessPolicy, LNVpsDb, NetworkAccessPolicy, PaymentMethod, RouterKind, Vm, VmCustomTemplate,
|
||||
VmIpAssignment, VmPayment,
|
||||
};
|
||||
use log::{info, warn};
|
||||
use nostr::util::hex;
|
||||
use std::collections::HashMap;
|
||||
@ -30,11 +33,9 @@ pub struct LNVpsProvisioner {
|
||||
rates: Arc<dyn ExchangeRateService>,
|
||||
tax_rates: HashMap<CountryCode, f32>,
|
||||
|
||||
router: Option<Arc<dyn Router>>,
|
||||
dns: Option<Arc<dyn DnsServer>>,
|
||||
revolut: Option<Arc<dyn FiatPaymentService>>,
|
||||
|
||||
network_policy: NetworkPolicy,
|
||||
provisioner_config: ProvisionerConfig,
|
||||
}
|
||||
|
||||
@ -49,48 +50,90 @@ impl LNVpsProvisioner {
|
||||
db,
|
||||
node,
|
||||
rates,
|
||||
router: settings.get_router().expect("router config"),
|
||||
dns: settings.get_dns().expect("dns config"),
|
||||
revolut: settings.get_revolut().expect("revolut config"),
|
||||
tax_rates: settings.tax_rate,
|
||||
network_policy: settings.network_policy,
|
||||
provisioner_config: settings.provisioner,
|
||||
read_only: settings.read_only,
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_router(&self, router_id: u64) -> Result<Arc<dyn Router>> {
|
||||
#[cfg(test)]
|
||||
return Ok(Arc::new(crate::mocks::MockRouter::new()));
|
||||
|
||||
let cfg = self.db.get_router(router_id).await?;
|
||||
match cfg.kind {
|
||||
RouterKind::Mikrotik => {
|
||||
let mut t_split = cfg.token.split(":");
|
||||
let (username, password) = (
|
||||
t_split.next().context("Invalid username:password")?,
|
||||
t_split.next().context("Invalid username:password")?,
|
||||
);
|
||||
Ok(Arc::new(MikrotikRouter::new(&cfg.url, username, password)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create or Update access policy for a given ip assignment, does not save to database!
|
||||
pub async fn update_access_policy(&self, assignment: &mut VmIpAssignment) -> Result<()> {
|
||||
pub async fn update_access_policy(
|
||||
&self,
|
||||
assignment: &mut VmIpAssignment,
|
||||
policy: &AccessPolicy,
|
||||
) -> Result<()> {
|
||||
// apply network policy
|
||||
if let NetworkAccessPolicy::StaticArp { interface } = &self.network_policy.access {
|
||||
if let Some(r) = self.router.as_ref() {
|
||||
if let NetworkAccessPolicy::StaticArp = policy.kind {
|
||||
let router = self
|
||||
.get_router(
|
||||
policy
|
||||
.router_id
|
||||
.context("Cannot apply static arp policy with no router")?,
|
||||
)
|
||||
.await?;
|
||||
let vm = self.db.get_vm(assignment.vm_id).await?;
|
||||
let entry = ArpEntry::new(&vm, assignment, Some(interface.clone()))?;
|
||||
let entry = ArpEntry::new(
|
||||
&vm,
|
||||
assignment,
|
||||
Some(
|
||||
policy
|
||||
.interface
|
||||
.as_ref()
|
||||
.context("Cannot apply static arp entry without an interface name")?
|
||||
.clone(),
|
||||
),
|
||||
)?;
|
||||
let arp = if let Some(_id) = &assignment.arp_ref {
|
||||
r.update_arp_entry(&entry).await?
|
||||
router.update_arp_entry(&entry).await?
|
||||
} else {
|
||||
r.add_arp_entry(&entry).await?
|
||||
router.add_arp_entry(&entry).await?
|
||||
};
|
||||
ensure!(arp.id.is_some(), "ARP id was empty");
|
||||
assignment.arp_ref = arp.id;
|
||||
} else {
|
||||
bail!("No router found to apply static arp entry!")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove an access policy for a given ip assignment, does not save to database!
|
||||
pub async fn remove_access_policy(&self, assignment: &mut VmIpAssignment) -> Result<()> {
|
||||
pub async fn remove_access_policy(
|
||||
&self,
|
||||
assignment: &mut VmIpAssignment,
|
||||
policy: &AccessPolicy,
|
||||
) -> Result<()> {
|
||||
// Delete access policy
|
||||
if let NetworkAccessPolicy::StaticArp { .. } = &self.network_policy.access {
|
||||
if let Some(r) = self.router.as_ref() {
|
||||
if let NetworkAccessPolicy::StaticArp = &policy.kind {
|
||||
let router = self
|
||||
.get_router(
|
||||
policy
|
||||
.router_id
|
||||
.context("Cannot apply static arp policy with no router")?,
|
||||
)
|
||||
.await?;
|
||||
let id = if let Some(id) = &assignment.arp_ref {
|
||||
Some(id.clone())
|
||||
} else {
|
||||
warn!("ARP REF not found, using arp list");
|
||||
|
||||
let ent = r.list_arp_entry().await?;
|
||||
let ent = router.list_arp_entry().await?;
|
||||
if let Some(ent) = ent.iter().find(|e| e.address == assignment.ip) {
|
||||
ent.id.clone()
|
||||
} else {
|
||||
@ -100,13 +143,12 @@ impl LNVpsProvisioner {
|
||||
};
|
||||
|
||||
if let Some(id) = id {
|
||||
if let Err(e) = r.remove_arp_entry(&id).await {
|
||||
if let Err(e) = router.remove_arp_entry(&id).await {
|
||||
warn!("Failed to remove arp entry, skipping: {}", e);
|
||||
}
|
||||
}
|
||||
assignment.arp_ref = None;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -169,8 +211,13 @@ impl LNVpsProvisioner {
|
||||
pub async fn delete_ip_assignments(&self, vm_id: u64) -> Result<()> {
|
||||
let ips = self.db.list_vm_ip_assignments(vm_id).await?;
|
||||
for mut ip in ips {
|
||||
// load range info to check access policy
|
||||
let range = self.db.get_ip_range(ip.ip_range_id).await?;
|
||||
if let Some(ap) = range.access_policy_id {
|
||||
let ap = self.db.get_access_policy(ap).await?;
|
||||
// remove access policy
|
||||
self.remove_access_policy(&mut ip).await?;
|
||||
self.remove_access_policy(&mut ip, &ap).await?;
|
||||
}
|
||||
// remove dns
|
||||
self.remove_ip_dns(&mut ip).await?;
|
||||
// save arp/dns changes
|
||||
@ -183,8 +230,13 @@ impl LNVpsProvisioner {
|
||||
}
|
||||
|
||||
async fn save_ip_assignment(&self, assignment: &mut VmIpAssignment) -> Result<()> {
|
||||
// load range info to check access policy
|
||||
let range = self.db.get_ip_range(assignment.ip_range_id).await?;
|
||||
if let Some(ap) = range.access_policy_id {
|
||||
let ap = self.db.get_access_policy(ap).await?;
|
||||
// apply access policy
|
||||
self.update_access_policy(assignment).await?;
|
||||
self.update_access_policy(assignment, &ap).await?;
|
||||
}
|
||||
|
||||
// Add DNS records
|
||||
self.update_forward_ip_dns(assignment).await?;
|
||||
@ -498,9 +550,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::exchange::{DefaultRateCache, Ticker};
|
||||
use crate::mocks::{MockDb, MockDnsServer, MockExchangeRate, MockNode, MockRouter};
|
||||
use crate::settings::{
|
||||
mock_settings, DnsServerConfig, LightningConfig, QemuConfig, RouterConfig,
|
||||
};
|
||||
use crate::settings::mock_settings;
|
||||
use lnvps_db::{DiskInterface, DiskType, User, UserSshKey, VmTemplate};
|
||||
use std::net::IpAddr;
|
||||
use std::str::FromStr;
|
||||
@ -509,9 +559,6 @@ mod tests {
|
||||
|
||||
pub fn settings() -> Settings {
|
||||
let mut settings = mock_settings();
|
||||
settings.network_policy.access = NetworkAccessPolicy::StaticArp {
|
||||
interface: ROUTER_BRIDGE.to_string(),
|
||||
};
|
||||
settings
|
||||
}
|
||||
|
||||
@ -540,7 +587,36 @@ mod tests {
|
||||
const MOCK_RATE: f32 = 69_420.0;
|
||||
rates.set_rate(Ticker::btc_rate("EUR")?, MOCK_RATE).await;
|
||||
|
||||
let router = MockRouter::new(settings.network_policy.clone());
|
||||
// add static arp policy
|
||||
{
|
||||
let mut r = db.router.lock().await;
|
||||
r.insert(
|
||||
1,
|
||||
lnvps_db::Router {
|
||||
id: 1,
|
||||
name: "mock-router".to_string(),
|
||||
enabled: true,
|
||||
kind: RouterKind::Mikrotik,
|
||||
url: "https://localhost".to_string(),
|
||||
token: "username:password".to_string(),
|
||||
},
|
||||
);
|
||||
let mut p = db.access_policy.lock().await;
|
||||
p.insert(
|
||||
1,
|
||||
AccessPolicy {
|
||||
id: 1,
|
||||
name: "static-arp".to_string(),
|
||||
kind: NetworkAccessPolicy::StaticArp,
|
||||
router_id: Some(1),
|
||||
interface: Some(ROUTER_BRIDGE.to_string()),
|
||||
},
|
||||
);
|
||||
let mut i = db.ip_range.lock().await;
|
||||
let r = i.get_mut(&1).unwrap();
|
||||
r.access_policy_id = Some(1);
|
||||
}
|
||||
|
||||
let dns = MockDnsServer::new();
|
||||
let provisioner = LNVpsProvisioner::new(settings, db.clone(), node.clone(), rates.clone());
|
||||
|
||||
@ -567,6 +643,7 @@ mod tests {
|
||||
provisioner.spawn_vm(vm.id).await?;
|
||||
|
||||
// check resources
|
||||
let router = MockRouter::new();
|
||||
let arp = router.list_arp_entry().await?;
|
||||
assert_eq!(1, arp.len());
|
||||
let arp = arp.first().unwrap();
|
||||
|
@ -3,7 +3,6 @@ use crate::exchange::ExchangeRateService;
|
||||
use crate::fiat::FiatPaymentService;
|
||||
use crate::lightning::LightningNode;
|
||||
use crate::provisioner::LNVpsProvisioner;
|
||||
use crate::router::Router;
|
||||
use anyhow::Result;
|
||||
use isocountry::CountryCode;
|
||||
use lnvps_db::LNVpsDb;
|
||||
@ -33,19 +32,12 @@ pub struct Settings {
|
||||
/// Provisioning profiles
|
||||
pub provisioner: ProvisionerConfig,
|
||||
|
||||
#[serde(default)]
|
||||
/// Network policy
|
||||
pub network_policy: NetworkPolicy,
|
||||
|
||||
/// Number of days after an expired VM is deleted
|
||||
pub delete_after: u16,
|
||||
|
||||
/// SMTP settings for sending emails
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
|
||||
/// Network router config
|
||||
pub router: Option<RouterConfig>,
|
||||
|
||||
/// DNS configurations for PTR records
|
||||
pub dns: Option<DnsServerConfig>,
|
||||
|
||||
@ -82,16 +74,6 @@ pub struct NostrConfig {
|
||||
pub nsec: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum RouterConfig {
|
||||
Mikrotik {
|
||||
url: String,
|
||||
username: String,
|
||||
password: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum DnsServerConfig {
|
||||
@ -103,30 +85,6 @@ pub enum DnsServerConfig {
|
||||
},
|
||||
}
|
||||
|
||||
/// Policy that determines how packets arrive at the VM
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum NetworkAccessPolicy {
|
||||
/// No special procedure required for packets to arrive
|
||||
#[default]
|
||||
Auto,
|
||||
/// ARP entries are added statically on the access router
|
||||
StaticArp {
|
||||
/// Interface used to add arp entries
|
||||
interface: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct NetworkPolicy {
|
||||
/// Policy that determines how packets arrive at the VM
|
||||
pub access: NetworkAccessPolicy,
|
||||
|
||||
/// Use SLAAC for IPv6 allocation
|
||||
pub ip6_slaac: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct SmtpConfig {
|
||||
/// Admin user id, for sending system notifications
|
||||
@ -203,32 +161,6 @@ impl Settings {
|
||||
Arc::new(LNVpsProvisioner::new(self.clone(), db, node, exchange))
|
||||
}
|
||||
|
||||
pub fn get_router(&self) -> Result<Option<Arc<dyn Router>>> {
|
||||
#[cfg(test)]
|
||||
{
|
||||
if let Some(_router) = &self.router {
|
||||
let router = crate::mocks::MockRouter::new(self.network_policy.clone());
|
||||
Ok(Some(Arc::new(router)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
match &self.router {
|
||||
#[cfg(feature = "mikrotik")]
|
||||
Some(RouterConfig::Mikrotik {
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
}) => Ok(Some(Arc::new(crate::router::MikrotikRouter::new(
|
||||
url, username, password,
|
||||
)))),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_dns(&self) -> Result<Option<Arc<dyn DnsServer>>> {
|
||||
#[cfg(test)]
|
||||
{
|
||||
@ -285,17 +217,8 @@ pub fn mock_settings() -> Settings {
|
||||
ssh: None,
|
||||
mac_prefix: Some("ff:ff:ff".to_string()),
|
||||
},
|
||||
network_policy: NetworkPolicy {
|
||||
access: NetworkAccessPolicy::Auto,
|
||||
ip6_slaac: None,
|
||||
},
|
||||
delete_after: 0,
|
||||
smtp: None,
|
||||
router: Some(RouterConfig::Mikrotik {
|
||||
url: "https://localhost".to_string(),
|
||||
username: "admin".to_string(),
|
||||
password: "password123".to_string(),
|
||||
}),
|
||||
dns: Some(DnsServerConfig::Cloudflare {
|
||||
token: "abc".to_string(),
|
||||
forward_zone_id: "123".to_string(),
|
||||
|
Reference in New Issue
Block a user