feat: vm actions start/stop/delete

This commit is contained in:
2024-11-29 16:43:14 +00:00
parent 632a5aaa87
commit 2370204546
11 changed files with 304 additions and 45 deletions

View File

@ -1,13 +1,17 @@
use crate::host::proxmox::{ProxmoxClient, VmStatus};
use crate::host::get_host_client;
use crate::host::proxmox::{ProxmoxClient, VmInfo, VmStatus};
use crate::provisioner::Provisioner;
use crate::status::{VmRunningState, VmState, VmStateCache};
use anyhow::Result;
use chrono::Utc;
use chrono::{Days, Utc};
use lnvps_db::LNVpsDb;
use log::{debug, error, info, warn};
use std::ops::Add;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
pub enum WorkJob {
/// Check all running VMS
CheckVms,
/// Check the VM status matches database state
///
/// This job starts a vm if stopped and also creates the vm if it doesn't exist yet
@ -44,32 +48,50 @@ impl Worker {
self.tx.clone()
}
async fn handle_vm_info(&self, s: VmInfo) -> Result<()> {
// TODO: remove assumption
let db_id = (s.vm_id - 100) as u64;
let state = VmState {
state: match s.status {
VmStatus::Stopped => VmRunningState::Stopped,
VmStatus::Running => VmRunningState::Running,
},
cpu_usage: s.cpu.unwrap_or(0.0),
mem_usage: s.mem.unwrap_or(0) as f32 / s.max_mem.unwrap_or(1) as f32,
uptime: s.uptime.unwrap_or(0),
net_in: s.net_in.unwrap_or(0),
net_out: s.net_out.unwrap_or(0),
disk_write: s.disk_write.unwrap_or(0),
disk_read: s.disk_read.unwrap_or(0),
};
self.vm_state_cache.set_state(db_id, state).await?;
if let Ok(db_vm) = self.db.get_vm(db_id).await {
// Stop VM if expired and is running
if db_vm.expires < Utc::now() && s.status == VmStatus::Running {
info!("Stopping expired VM {}", db_vm.id);
self.provisioner.stop_vm(db_vm.id).await?;
}
// Delete VM if expired > 3 days
if db_vm.expires.add(Days::new(3)) < Utc::now() {
info!("Deleting expired VM {}", db_vm.id);
self.provisioner.delete_vm(db_vm.id).await?;
}
}
Ok(())
}
/// Check a VM's status
async fn check_vm(&self, vm_id: u64) -> Result<()> {
debug!("Checking VM: {}", vm_id);
let vm = self.db.get_vm(vm_id).await?;
let host = self.db.get_host(vm.host_id).await?;
let client = ProxmoxClient::new(host.ip.parse()?).with_api_token(&host.api_token);
let client = get_host_client(&host)?;
match client.get_vm_status(&host.name, (vm.id + 100) as i32).await {
Ok(s) => {
let state = VmState {
state: match s.status {
VmStatus::Stopped => VmRunningState::Stopped,
VmStatus::Running => VmRunningState::Running,
},
cpu_usage: s.cpu.unwrap_or(0.0),
mem_usage: s.mem.unwrap_or(0) as f32 / s.max_mem.unwrap_or(1) as f32,
uptime: s.uptime.unwrap_or(0),
net_in: s.net_in.unwrap_or(0),
net_out: s.net_out.unwrap_or(0),
disk_write: s.disk_write.unwrap_or(0),
disk_read: s.disk_read.unwrap_or(0),
};
self.vm_state_cache.set_state(vm_id, state).await?;
}
Err(e) => {
warn!("Failed to get VM {} status: {}", vm.id, e);
Ok(s) => self.handle_vm_info(s).await?,
Err(_) => {
if vm.expires > Utc::now() {
self.provisioner.spawn_vm(vm.id).await?;
}
@ -78,6 +100,24 @@ impl Worker {
Ok(())
}
pub async fn check_vms(&self) -> Result<()> {
let hosts = self.db.list_hosts().await?;
for host in hosts {
let client = get_host_client(&host)?;
for node in client.list_nodes().await? {
info!("Checking vms for {}", node.name);
for vm in client.list_vms(&node.name).await? {
info!("\t{}: {:?}", vm.vm_id, vm.status);
if let Err(e) = self.handle_vm_info(vm).await {
error!("{}", e);
}
}
}
}
Ok(())
}
pub async fn handle(&mut self) -> Result<()> {
while let Some(job) = self.rx.recv().await {
match job {
@ -87,6 +127,11 @@ impl Worker {
}
}
WorkJob::SendNotification { .. } => {}
WorkJob::CheckVms => {
if let Err(e) = self.check_vms().await {
error!("Failed to check VMs: {}", e);
}
}
}
}
Ok(())