feat: patch host info
Some checks failed
continuous-integration/drone/push Build is failing

closes #23
This commit is contained in:
2025-03-27 12:31:27 +00:00
parent cd7c7cd7be
commit 39ca5ee8b4
7 changed files with 221 additions and 76 deletions

View File

@ -62,6 +62,9 @@ pub trait LNVpsDb: Sync + Send {
/// Get a specific host disk
async fn get_host_disk(&self, disk_id: u64) -> Result<VmHostDisk>;
/// Update a host disk
async fn update_host_disk(&self, disk: &VmHostDisk) -> Result<()>;
/// Get OS image by id
async fn get_os_image(&self, id: u64) -> Result<VmOsImage>;

View File

@ -121,6 +121,15 @@ impl FromStr for DiskType {
}
}
impl Display for DiskType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
DiskType::HDD => write!(f, "hdd"),
DiskType::SSD => write!(f, "ssd"),
}
}
}
#[derive(Clone, Copy, Debug, sqlx::Type, Default, PartialEq, Eq)]
#[repr(u16)]
pub enum DiskInterface {
@ -143,6 +152,16 @@ impl FromStr for DiskInterface {
}
}
impl Display for DiskInterface {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
DiskInterface::SATA => write!(f, "sata"),
DiskInterface::SCSI => write!(f, "scsi"),
DiskInterface::PCIe => write!(f, "pcie"),
}
}
}
#[derive(Clone, Copy, Debug, sqlx::Type, Default, PartialEq, Eq)]
#[repr(u16)]
pub enum OsDistribution {

View File

@ -174,6 +174,18 @@ impl LNVpsDb for LNVpsDbMysql {
.map_err(Error::new)
}
async fn update_host_disk(&self, disk: &VmHostDisk) -> Result<()> {
sqlx::query("update vm_host_disk set size=?,kind=?,interface=? where id=?")
.bind(disk.size)
.bind(disk.kind)
.bind(disk.interface)
.bind(disk.id)
.execute(&self.db)
.await
.map_err(Error::new)?;
Ok(())
}
async fn get_os_image(&self, id: u64) -> Result<VmOsImage> {
sqlx::query_as("select * from vm_os_image where id=?")
.bind(id)

View File

@ -158,6 +158,9 @@ async fn main() -> Result<(), Error> {
start_dvms(nostr_client.clone(), provisioner.clone());
}
// request for host info to be patched
sender.send(WorkJob::PatchHosts)?;
let mut config = rocket::Config::default();
let ip: SocketAddr = match &settings.listen {
Some(i) => i.parse()?,

View File

@ -25,6 +25,8 @@ pub struct TerminalStream {
/// Generic type for creating VM's
#[async_trait]
pub trait VmHostClient: Send + Sync {
async fn get_info(&self) -> Result<VmHostInfo>;
/// Download OS image to the host
async fn download_os_image(&self, image: &VmOsImage) -> Result<()>;
@ -202,3 +204,17 @@ pub enum TimeSeries {
Monthly,
Yearly,
}
#[derive(Debug, Clone)]
pub struct VmHostInfo {
pub cpu: u16,
pub memory: u64,
pub disks: Vec<VmHostDiskInfo>,
}
#[derive(Debug, Clone)]
pub struct VmHostDiskInfo {
pub name: String,
pub size: u64,
pub used: u64,
}

View File

@ -1,4 +1,7 @@
use crate::host::{FullVmInfo, TerminalStream, TimeSeries, TimeSeriesData, VmHostClient};
use crate::host::{
FullVmInfo, TerminalStream, TimeSeries, TimeSeriesData, VmHostClient, VmHostDiskInfo,
VmHostInfo,
};
use crate::json_api::JsonApi;
use crate::settings::{QemuConfig, SshConfig};
use crate::ssh_client::SshClient;
@ -86,6 +89,14 @@ impl ProxmoxClient {
Ok(rsp.data)
}
pub async fn list_disks(&self, node: &str) -> Result<Vec<NodeDisk>> {
let rsp: ResponseBase<Vec<NodeDisk>> = self
.api
.get(&format!("/api2/json/nodes/{node}/disks/list"))
.await?;
Ok(rsp.data)
}
/// List files in a storage pool
pub async fn list_storage_files(
&self,
@ -477,6 +488,29 @@ impl ProxmoxClient {
#[async_trait]
impl VmHostClient for ProxmoxClient {
async fn get_info(&self) -> Result<VmHostInfo> {
let nodes = self.list_nodes().await?;
if let Some(n) = nodes.iter().find(|n| n.name == self.node) {
let storages = self.list_storage(&n.name).await?;
let info = VmHostInfo {
cpu: n.max_cpu.unwrap_or(0),
memory: n.max_mem.unwrap_or(0),
disks: storages
.into_iter()
.map(|s| VmHostDiskInfo {
name: s.storage,
size: s.total.unwrap_or(0),
used: s.used.unwrap_or(0),
})
.collect(),
};
Ok(info)
} else {
bail!("Could not find node {}", self.node);
}
}
async fn download_os_image(&self, image: &VmOsImage) -> Result<()> {
let iso_storage = self.get_iso_storage(&self.node).await?;
let files = self.list_storage_files(&self.node, &iso_storage).await?;
@ -878,9 +912,14 @@ pub struct NodeStorage {
pub content: String,
pub storage: String,
#[serde(rename = "type")]
pub kind: Option<StorageType>,
#[serde(rename = "thinpool")]
pub thin_pool: Option<String>,
pub kind: StorageType,
/// Available storage space in bytes
#[serde(rename = "avial")]
pub available: Option<u64>,
/// Total storage space in bytes
pub total: Option<u64>,
/// Used storage space in bytes
pub used: Option<u64>,
}
impl NodeStorage {
@ -891,6 +930,11 @@ impl NodeStorage {
.collect()
}
}
#[derive(Debug, Deserialize)]
pub struct NodeDisk {
}
#[derive(Debug, Serialize)]
pub struct DownloadUrlRequest {
pub content: StorageContent,

View File

@ -18,6 +18,8 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
#[derive(Debug)]
pub enum WorkJob {
/// Sync resources from hosts to database
PatchHosts,
/// Check all running VMS
CheckVms,
/// Check the VM status matches database state
@ -278,9 +280,47 @@ impl Worker {
Ok(())
}
pub async fn handle(&mut self) -> Result<()> {
while let Some(job) = self.rx.recv().await {
match &job {
async fn try_job(&mut self, job: &WorkJob) -> Result<()> {
match job {
WorkJob::PatchHosts => {
let mut hosts = self.db.list_hosts().await?;
for mut host in &mut hosts {
let client = match get_host_client(host, &self.settings.provisioner_config) {
Ok(h) => h,
Err(e) => {
warn!("Failed to get host client: {} {}", host.name, e);
continue;
}
};
let info = client.get_info().await?;
let needs_update = info.cpu != host.cpu || info.memory != host.memory;
if needs_update {
host.cpu = info.cpu;
host.memory = info.memory;
self.db.update_host(host).await?;
info!(
"Updated host {}: cpu={}, memory={}",
host.name, host.cpu, host.memory
);
}
let mut host_disks = self.db.list_host_disks(host.id).await?;
for disk in &info.disks {
if let Some(mut hd) = host_disks.iter_mut().find(|d| d.name == disk.name) {
if hd.size != disk.size {
hd.size = disk.size;
self.db.update_host_disk(hd).await?;
info!(
"Updated host disk {}: size={},type={},interface={}",
hd.name, hd.size, hd.kind, hd.interface
);
}
} else {
warn!("Un-mapped host disk {}", disk.name);
}
}
}
}
WorkJob::CheckVm { vm_id } => {
let vm = self.db.get_vm(*vm_id).await?;
if let Err(e) = self.check_vm(&vm).await {
@ -317,6 +357,14 @@ impl Worker {
}
}
}
Ok(())
}
pub async fn handle(&mut self) -> Result<()> {
while let Some(job) = self.rx.recv().await {
if let Err(e) = self.try_job(&job).await {
error!("Job failed to execute: {:?} {}", job, e);
}
}
Ok(())
}