feat: libvirt setup
Some checks failed
continuous-integration/drone/push Build is failing

fix: ip assigment index
feat: default username
This commit is contained in:
2025-03-31 10:40:29 +01:00
parent 6ca8283040
commit 7deed82a7c
10 changed files with 170 additions and 84 deletions

6
Cargo.lock generated
View File

@ -4691,8 +4691,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "virt"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a05f77c836efa9be343b5419663cf829d75203b813579993cdd9c44f51767e"
source = "git+https://gitlab.com/libvirt/libvirt-rust.git#70394aad4d9597c9ff87c0ada6711ed4f9528991"
dependencies = [
"libc",
"uuid",
@ -4702,8 +4701,7 @@ dependencies = [
[[package]]
name = "virt-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c504e459878f09177f41bf2f8bb3e9a8af4fca7a09e73152fee02535d501601c"
source = "git+https://gitlab.com/libvirt/libvirt-rust.git#70394aad4d9597c9ff87c0ada6711ed4f9528991"
dependencies = [
"libc",
"pkg-config",

View File

@ -7,7 +7,17 @@ edition = "2021"
name = "api"
[features]
default = ["mikrotik", "nostr-dm", "nostr-dvm", "proxmox", "lnd", "cloudflare", "revolut", "bitvora"]
default = [
"mikrotik",
"nostr-dm",
"nostr-dvm",
"proxmox",
"lnd",
"cloudflare",
"revolut",
"bitvora",
"libvirt"
]
mikrotik = ["dep:reqwest"]
nostr-dm = ["dep:nostr-sdk"]
nostr-dvm = ["dep:nostr-sdk"]
@ -53,7 +63,7 @@ ssh2 = { version = "0.9.4", optional = true }
reqwest = { version = "0.12.8", optional = true }
#libvirt
virt = { version = "0.4.2", optional = true }
virt = { git = "https://gitlab.com/libvirt/libvirt-rust.git", optional = true }
#lnd
fedimint-tonic-lnd = { version = "0.2.0", default-features = false, features = ["invoicesrpc"], optional = true }

View File

@ -0,0 +1,4 @@
-- Add migration script here
ALTER TABLE vm_ip_assignment DROP KEY ix_vm_ip_assignment_ip;
alter table vm_os_image
add column default_username varchar(50);

View File

@ -206,6 +206,7 @@ pub struct VmOsImage {
pub release_date: DateTime<Utc>,
/// URL location of cloud image
pub url: String,
pub default_username: Option<String>,
}
impl VmOsImage {

View File

@ -455,6 +455,7 @@ pub struct ApiVmOsImage {
pub flavour: String,
pub version: String,
pub release_date: DateTime<Utc>,
pub default_username: Option<String>,
}
impl From<lnvps_db::VmOsImage> for ApiVmOsImage {
@ -465,6 +466,7 @@ impl From<lnvps_db::VmOsImage> for ApiVmOsImage {
flavour: image.flavour,
version: image.version,
release_date: image.release_date,
default_username: image.default_username,
}
}
}

View File

@ -6,7 +6,7 @@ use anyhow::{bail, Result};
use chrono::{DateTime, Utc};
use nostr::Url;
use reqwest::header::AUTHORIZATION;
use reqwest::{Client, Method, RequestBuilder};
use reqwest::{Method, RequestBuilder};
use serde::{Deserialize, Serialize};
use std::future::Future;
use std::pin::Pin;

View File

@ -1,44 +1,99 @@
use crate::host::{FullVmInfo, TimeSeries, TimeSeriesData, VmHostClient};
use crate::status::VmState;
use crate::host::{
FullVmInfo, TerminalStream, TimeSeries, TimeSeriesData, VmHostClient, VmHostDiskInfo,
VmHostInfo,
};
use crate::settings::QemuConfig;
use crate::status::{VmRunningState, VmState};
use crate::KB;
use anyhow::{Context, Result};
use chrono::Utc;
use lnvps_db::{async_trait, Vm, VmOsImage};
use virt::connect::Connect;
use virt::domain::Domain;
use virt::sys::{virDomainCreate, VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE};
pub struct LibVirt {}
#[derive(Debug)]
pub struct LibVirtHost {
connection: Connect,
qemu: QemuConfig,
}
impl LibVirtHost {
pub fn new(url: &str, qemu: QemuConfig) -> Result<Self> {
Ok(Self {
connection: Connect::open(Some(url))?,
qemu,
})
}
}
#[async_trait]
impl VmHostClient for LibVirt {
async fn download_os_image(&self, image: &VmOsImage) -> anyhow::Result<()> {
impl VmHostClient for LibVirtHost {
async fn get_info(&self) -> Result<VmHostInfo> {
let info = self.connection.get_node_info()?;
let storage = self
.connection
.list_all_storage_pools(VIR_CONNECT_LIST_STORAGE_POOLS_ACTIVE)?;
Ok(VmHostInfo {
cpu: info.cpus as u16,
memory: info.memory * KB,
disks: storage
.iter()
.filter_map(|p| {
let info = p.get_info().ok()?;
Some(VmHostDiskInfo {
name: p.get_name().context("storage pool name is missing").ok()?,
size: info.capacity,
used: info.allocation,
})
})
.collect(),
})
}
async fn download_os_image(&self, image: &VmOsImage) -> Result<()> {
Ok(())
}
async fn generate_mac(&self, vm: &Vm) -> Result<String> {
Ok("ff:ff:ff:ff:ff:ff".to_string())
}
async fn start_vm(&self, vm: &Vm) -> Result<()> {
Ok(())
}
async fn stop_vm(&self, vm: &Vm) -> Result<()> {
Ok(())
}
async fn reset_vm(&self, vm: &Vm) -> Result<()> {
Ok(())
}
async fn create_vm(&self, cfg: &FullVmInfo) -> Result<()> {
Ok(())
}
async fn reinstall_vm(&self, cfg: &FullVmInfo) -> Result<()> {
todo!()
}
async fn generate_mac(&self, vm: &Vm) -> anyhow::Result<String> {
todo!()
async fn get_vm_state(&self, vm: &Vm) -> Result<VmState> {
Ok(VmState {
timestamp: Utc::now().timestamp() as u64,
state: VmRunningState::Stopped,
cpu_usage: 0.0,
mem_usage: 0.0,
uptime: 0,
net_in: 0,
net_out: 0,
disk_write: 0,
disk_read: 0,
})
}
async fn start_vm(&self, vm: &Vm) -> anyhow::Result<()> {
todo!()
}
async fn stop_vm(&self, vm: &Vm) -> anyhow::Result<()> {
todo!()
}
async fn reset_vm(&self, vm: &Vm) -> anyhow::Result<()> {
todo!()
}
async fn create_vm(&self, cfg: &FullVmInfo) -> anyhow::Result<()> {
todo!()
}
async fn reinstall_vm(&self, cfg: &FullVmInfo) -> anyhow::Result<()> {
todo!()
}
async fn get_vm_state(&self, vm: &Vm) -> anyhow::Result<VmState> {
todo!()
}
async fn configure_vm(&self, vm: &FullVmInfo) -> anyhow::Result<()> {
async fn configure_vm(&self, vm: &FullVmInfo) -> Result<()> {
todo!()
}
@ -46,7 +101,11 @@ impl VmHostClient for LibVirt {
&self,
vm: &Vm,
series: TimeSeries,
) -> anyhow::Result<Vec<TimeSeriesData>> {
) -> Result<Vec<TimeSeriesData>> {
todo!()
}
async fn connect_terminal(&self, vm: &Vm) -> Result<TerminalStream> {
todo!()
}
}

View File

@ -12,8 +12,8 @@ use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::mpsc::{Receiver, Sender};
//#[cfg(feature = "libvirt")]
//mod libvirt;
#[cfg(feature = "libvirt")]
mod libvirt;
#[cfg(feature = "proxmox")]
mod proxmox;
@ -67,32 +67,29 @@ pub trait VmHostClient: Send + Sync {
pub fn get_host_client(host: &VmHost, cfg: &ProvisionerConfig) -> Result<Arc<dyn VmHostClient>> {
#[cfg(test)]
{
Ok(Arc::new(crate::mocks::MockVmHost::new()))
}
#[cfg(not(test))]
{
Ok(match (host.kind.clone(), &cfg) {
return Ok(Arc::new(crate::mocks::MockVmHost::new()));
Ok(match host.kind.clone() {
#[cfg(feature = "proxmox")]
(
VmHostKind::Proxmox,
ProvisionerConfig::Proxmox {
qemu,
ssh,
mac_prefix,
},
) => Arc::new(proxmox::ProxmoxClient::new(
VmHostKind::Proxmox if cfg.proxmox.is_some() => {
let cfg = cfg.proxmox.clone().unwrap();
Arc::new(proxmox::ProxmoxClient::new(
host.ip.parse()?,
&host.name,
&host.api_token,
mac_prefix.clone(),
qemu.clone(),
ssh.clone(),
)),
cfg.mac_prefix,
cfg.qemu,
cfg.ssh,
))
}
#[cfg(feature = "libvirt")]
VmHostKind::LibVirt if cfg.libvirt.is_some() => {
let cfg = cfg.libvirt.clone().unwrap();
Arc::new(libvirt::LibVirtHost::new(&host.ip, cfg.qemu)?)
}
_ => bail!("Unknown host config: {}", host.kind),
})
}
}
/// All VM info necessary to provision a VM and its associated resources
pub struct FullVmInfo {

View File

@ -1198,6 +1198,7 @@ mod tests {
enabled: true,
release_date: Utc::now(),
url: "http://localhost.com/ubuntu_server_24.04.img".to_string(),
default_username: None
},
ips: vec![
VmIpAssignment {

View File

@ -104,16 +104,27 @@ pub struct SmtpConfig {
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ProvisionerConfig {
pub struct ProvisionerConfig {
pub proxmox: Option<ProxmoxConfig>,
pub libvirt: Option<LibVirtConfig>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
Proxmox {
pub struct ProxmoxConfig {
/// Generic VM configuration
qemu: QemuConfig,
pub qemu: QemuConfig,
/// SSH config for issuing commands via CLI
ssh: Option<SshConfig>,
pub ssh: Option<SshConfig>,
/// MAC address prefix for NIC (eg. bc:24:11)
mac_prefix: Option<String>,
},
pub mac_prefix: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct LibVirtConfig {
/// Generic VM configuration
pub qemu: QemuConfig,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -198,7 +209,8 @@ pub fn mock_settings() -> Settings {
macaroon: Default::default(),
},
read_only: false,
provisioner: ProvisionerConfig::Proxmox {
provisioner: ProvisionerConfig {
proxmox: Some(ProxmoxConfig {
qemu: QemuConfig {
machine: "q35".to_string(),
os_type: "l26".to_string(),
@ -208,6 +220,8 @@ pub fn mock_settings() -> Settings {
},
ssh: None,
mac_prefix: Some("ff:ff:ff".to_string()),
}),
libvirt: None,
},
delete_after: 0,
smtp: None,