feat: delete ip resources

This commit is contained in:
2025-03-05 10:03:29 +00:00
parent fc735590d6
commit 8838853957
6 changed files with 200 additions and 99 deletions

View File

@ -3,9 +3,9 @@ use crate::exchange::{ExchangeRateService, Ticker};
use crate::host::{get_host_client, FullVmInfo};
use crate::lightning::{AddInvoiceRequest, LightningNode};
use crate::provisioner::{NetworkProvisioner, ProvisionerMethod};
use crate::router::Router;
use crate::router::{ArpEntry, Router};
use crate::settings::{NetworkAccessPolicy, NetworkPolicy, ProvisionerConfig, Settings};
use anyhow::{bail, Context, Result};
use anyhow::{bail, ensure, Context, Result};
use chrono::{Days, Months, Utc};
use futures::future::join_all;
use lnvps_db::{IpRange, LNVpsDb, Vm, VmCostPlanIntervalType, VmIpAssignment, VmPayment};
@ -55,24 +55,78 @@ impl LNVpsProvisioner {
}
}
pub async fn delete_ip_assignment(&self, vm: &Vm) -> Result<()> {
// Delete access policy
if let NetworkAccessPolicy::StaticArp { .. } = &self.network_policy.access {
/// 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<()> {
// apply network policy
if let NetworkAccessPolicy::StaticArp { interface } = &self.network_policy.access {
if let Some(r) = self.router.as_ref() {
let ent = r.list_arp_entry().await?;
if let Some(ent) = ent
.iter()
.find(|e| e.mac_address.eq_ignore_ascii_case(&vm.mac_address))
{
r.remove_arp_entry(&ent.id).await?;
let vm = self.db.get_vm(assignment.vm_id).await?;
let entry = ArpEntry::new(&vm, &assignment, Some(interface.clone()))?;
let arp = if let Some(_id) = &assignment.arp_ref {
r.update_arp_entry(&entry).await?
} else {
warn!("ARP entry not found, skipping")
}
r.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<()> {
// Delete access policy
if let NetworkAccessPolicy::StaticArp { .. } = &self.network_policy.access {
if let Some(r) = self.router.as_ref() {
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?;
if let Some(ent) = ent.iter().find(|e| e.address == assignment.ip) {
ent.id.clone()
} else {
warn!("ARP entry not found, skipping");
None
}
};
if let Some(id) = id {
if let Err(e) = r.remove_arp_entry(&id).await {
warn!("Failed to remove arp entry, skipping: {}", e);
}
}
assignment.arp_ref = None;
}
}
Ok(())
}
/// Delete DNS on the dns server, does not save to database!
pub async fn remove_ip_dns(&self, assignment: &mut VmIpAssignment) -> Result<()> {
// Delete forward/reverse dns
if let Some(dns) = &self.dns {
if let Some(_r) = &assignment.dns_reverse_ref {
let rev = BasicRecord::reverse(assignment)?;
dns.delete_record(&rev).await?;
assignment.dns_reverse_ref = None;
assignment.dns_reverse = None;
}
if let Some(_r) = &assignment.dns_forward_ref {
let rev = BasicRecord::forward(assignment)?;
dns.delete_record(&rev).await?;
assignment.dns_forward_ref = None;
assignment.dns_forward = None;
}
}
Ok(())
}
/// Update DNS on the dns server, does not save to database!
pub async fn update_forward_ip_dns(&self, assignment: &mut VmIpAssignment) -> Result<()> {
if let Some(dns) = &self.dns {
let fwd = BasicRecord::forward(assignment)?;
@ -83,15 +137,11 @@ impl LNVpsProvisioner {
};
assignment.dns_forward = Some(ret_fwd.name);
assignment.dns_forward_ref = Some(ret_fwd.id.context("Record id is missing")?);
// save to db
if assignment.id != 0 {
self.db.update_vm_ip_assignment(assignment).await?;
}
}
Ok(())
}
/// Update DNS on the dns server, does not save to database!
pub async fn update_reverse_ip_dns(&self, assignment: &mut VmIpAssignment) -> Result<()> {
if let Some(dns) = &self.dns {
let ret_rev = if assignment.dns_reverse_ref.is_some() {
@ -103,32 +153,30 @@ impl LNVpsProvisioner {
};
assignment.dns_reverse = Some(ret_rev.value);
assignment.dns_reverse_ref = Some(ret_rev.id.context("Record id is missing")?);
// save to db
if assignment.id != 0 {
self.db.update_vm_ip_assignment(assignment).await?;
}
}
Ok(())
}
async fn save_ip_assignment(&self, vm: &Vm, assignment: &mut VmIpAssignment) -> Result<()> {
let ip = IpAddr::from_str(&assignment.ip)?;
// apply network policy
if let NetworkAccessPolicy::StaticArp { interface } = &self.network_policy.access {
if let Some(r) = self.router.as_ref() {
r.add_arp_entry(
ip.clone(),
&vm.mac_address,
interface,
Some(&format!("VM{}", vm.id)),
)
.await?;
} else {
bail!("No router found to apply static arp entry!")
}
/// Delete all ip assignments for a given vm
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 {
// remove access policy
self.remove_access_policy(&mut ip).await?;
// remove dns
self.remove_ip_dns(&mut ip).await?;
// save arp/dns changes
self.db.update_vm_ip_assignment(&ip).await?;
}
// mark as deleted
self.db.delete_vm_ip_assignment(vm_id).await?;
Ok(())
}
async fn save_ip_assignment(&self, assignment: &mut VmIpAssignment) -> Result<()> {
// apply access policy
self.update_access_policy(assignment).await?;
// Add DNS records
self.update_forward_ip_dns(assignment).await?;
@ -164,7 +212,7 @@ impl LNVpsProvisioner {
dns_reverse_ref: None,
};
self.save_ip_assignment(&vm, &mut assignment).await?;
self.save_ip_assignment(&mut assignment).await?;
Ok(vec![assignment])
}
@ -324,14 +372,11 @@ impl LNVpsProvisioner {
/// Delete a VM and its associated resources
pub async fn delete_vm(&self, vm_id: u64) -> Result<()> {
let vm = self.db.get_vm(vm_id).await?;
// host client currently doesn't support delete (proxmox)
// VM should already be stopped by [Worker]
self.delete_ip_assignment(&vm).await?;
self.db.delete_vm_ip_assignment(vm.id).await?;
self.db.delete_vm(vm.id).await?;
self.delete_ip_assignments(vm_id).await?;
self.db.delete_vm(vm_id).await?;
Ok(())
}
@ -351,7 +396,7 @@ impl LNVpsProvisioner {
mod tests {
use super::*;
use crate::exchange::DefaultRateCache;
use crate::mocks::{MockDb, MockNode};
use crate::mocks::{MockDb, MockDnsServer, MockNode, MockRouter};
use crate::settings::{DnsServerConfig, LightningConfig, QemuConfig, RouterConfig};
use lnvps_db::UserSshKey;
@ -403,7 +448,8 @@ mod tests {
let db = Arc::new(MockDb::default());
let node = Arc::new(MockNode::default());
let rates = Arc::new(DefaultRateCache::default());
let router = settings.get_router().expect("router").unwrap();
let router = MockRouter::new(settings.network_policy.clone());
let dns = MockDnsServer::new();
let provisioner = LNVpsProvisioner::new(settings, db.clone(), node.clone(), rates.clone());
let pubkey: [u8; 32] = random();
@ -448,6 +494,26 @@ mod tests {
assert!(!ip.ip.ends_with("/8"));
assert!(!ip.ip.ends_with("/24"));
// now expire
provisioner.delete_vm(vm.id).await?;
// test arp/dns is removed
let arp = router.list_arp_entry().await?;
assert!(arp.is_empty());
assert_eq!(dns.forward.lock().await.len(), 0);
assert_eq!(dns.reverse.lock().await.len(), 0);
// ensure IPS are deleted
let ips = db.ip_assignments.lock().await;
let ip = ips.values().next().unwrap();
assert!(ip.arp_ref.is_none());
assert!(ip.dns_forward.is_none());
assert!(ip.dns_reverse.is_none());
assert!(ip.dns_reverse_ref.is_none());
assert!(ip.dns_forward_ref.is_none());
assert!(ip.deleted);
println!("{:?}", ip);
Ok(())
}
}