progress
This commit is contained in:
46
src/api.rs
46
src/api.rs
@ -1,14 +1,13 @@
|
||||
use crate::db;
|
||||
use crate::nip98::Nip98Auth;
|
||||
use crate::provisioner::Provisioner;
|
||||
use anyhow::Error;
|
||||
use lnvps_db::{LNVpsDb, Vm, VmTemplate};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{get, post, routes, Data, Responder, Route, State};
|
||||
use rocket::{get, post, routes, Responder, Route, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::vm::VMSpec;
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![v1_list_vms]
|
||||
routes![v1_list_vms, v1_list_vm_templates, v1_provision_vm]
|
||||
}
|
||||
|
||||
type ApiResult<T> = Result<Json<ApiData<T>>, ApiError>;
|
||||
@ -38,35 +37,40 @@ impl From<Error> for ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct CreateVmRequest {}
|
||||
|
||||
impl From<CreateVmRequest> for VMSpec {
|
||||
fn from(value: CreateVmRequest) -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/api/v1/vms")]
|
||||
async fn v1_list_vms(auth: Nip98Auth, provisioner: &State<Provisioner>) -> ApiResult<Vec<db::Vm>> {
|
||||
async fn v1_list_vms(auth: Nip98Auth, db: &State<Box<dyn LNVpsDb>>) -> ApiResult<Vec<Vm>> {
|
||||
let pubkey = auth.event.pubkey.to_bytes();
|
||||
let uid = provisioner.upsert_user(&pubkey).await?;
|
||||
let vms = provisioner.list_vms(uid).await?;
|
||||
let uid = db.upsert_user(&pubkey).await?;
|
||||
let vms = db.list_user_vms(uid).await?;
|
||||
ApiData::ok(vms)
|
||||
}
|
||||
|
||||
#[get("/api/v1/vm/templates")]
|
||||
async fn v1_list_vm_templates(provisioner: &State<Provisioner>) -> ApiResult<Vec<db::VmTemplate>> {
|
||||
let vms = provisioner.list_vm_templates().await?;
|
||||
async fn v1_list_vm_templates(db: &State<Box<dyn LNVpsDb>>) -> ApiResult<Vec<VmTemplate>> {
|
||||
let vms = db.list_vm_templates().await?;
|
||||
ApiData::ok(vms)
|
||||
}
|
||||
|
||||
#[post("/api/v1/vm", data = "<req>", format = "json")]
|
||||
async fn v1_provision_vm(auth: Nip98Auth, provisioner: &State<Provisioner>, req: Json<CreateVmRequest>) -> ApiResult<db::Vm> {
|
||||
async fn v1_provision_vm(
|
||||
auth: Nip98Auth,
|
||||
db: &State<Box<dyn LNVpsDb>>,
|
||||
provisioner: &State<Box<dyn Provisioner>>,
|
||||
req: Json<CreateVmRequest>,
|
||||
) -> ApiResult<Vm> {
|
||||
let pubkey = auth.event.pubkey.to_bytes();
|
||||
let uid = provisioner.upsert_user(&pubkey).await?;
|
||||
let uid = db.upsert_user(&pubkey).await?;
|
||||
|
||||
let req = req.0;
|
||||
let rsp = provisioner.provision(req.into()).await?;
|
||||
ApiData::ok(rsp)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateVmRequest {}
|
||||
|
||||
impl Into<VmTemplate> for CreateVmRequest {
|
||||
fn into(self) -> VmTemplate {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ use anyhow::Error;
|
||||
use config::{Config, File};
|
||||
use lnvps::api;
|
||||
use lnvps::cors::CORS;
|
||||
use lnvps::provisioner::Provisioner;
|
||||
use lnvps::provisioner::{LNVpsProvisioner, Provisioner};
|
||||
use lnvps_db::{LNVpsDb, LNVpsDbMysql};
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Executor, MySqlPool};
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Settings {
|
||||
@ -17,13 +17,14 @@ async fn main() -> Result<(), Error> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let config: Settings = Config::builder()
|
||||
.add_source(File::with_name("config.toml"))
|
||||
.add_source(File::with_name("config.yaml"))
|
||||
.build()?
|
||||
.try_deserialize()?;
|
||||
|
||||
let db = MySqlPool::connect(&config.db).await?;
|
||||
sqlx::migrate!().run(&db).await?;
|
||||
let provisioner = Provisioner::new(db.clone());
|
||||
let db = LNVpsDbMysql::new(&config.db).await?;
|
||||
db.migrate().await?;
|
||||
|
||||
let provisioner = LNVpsProvisioner::new(db.clone());
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let setup_script = include_str!("../../dev_setup.sql");
|
||||
@ -31,9 +32,12 @@ async fn main() -> Result<(), Error> {
|
||||
provisioner.auto_discover().await?;
|
||||
}
|
||||
|
||||
let db: Box<dyn LNVpsDb> = Box::new(db.clone());
|
||||
let pv: Box<dyn Provisioner> = Box::new(provisioner);
|
||||
if let Err(e) = rocket::build()
|
||||
.attach(CORS)
|
||||
.manage(provisioner)
|
||||
.manage(db)
|
||||
.manage(pv)
|
||||
.mount("/", api::routes())
|
||||
.launch()
|
||||
.await
|
||||
|
114
src/db.rs
114
src/db.rs
@ -1,114 +0,0 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use rocket::serde::Serialize;
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[derive(Serialize, FromRow)]
|
||||
/// Users who buy VM's
|
||||
pub struct User {
|
||||
/// Unique ID of this user (database generated)
|
||||
pub id: u64,
|
||||
/// The nostr public key for this user
|
||||
pub pubkey: [u8; 32],
|
||||
/// When this user first started using the service (first login)
|
||||
pub created: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, sqlx::Type, Clone, Debug)]
|
||||
#[repr(u8)]
|
||||
/// The type of VM host
|
||||
pub enum VmHostKind {
|
||||
Proxmox = 0,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Clone, Debug)]
|
||||
/// A VM host
|
||||
pub struct VmHost {
|
||||
pub id: u64,
|
||||
pub kind: VmHostKind,
|
||||
pub name: String,
|
||||
pub ip: String,
|
||||
/// Total number of CPU cores
|
||||
pub cpu: u16,
|
||||
/// Total memory size in bytes
|
||||
pub memory: u64,
|
||||
/// If VM's should be provisioned on this host
|
||||
pub enabled: bool,
|
||||
pub api_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Clone, Debug)]
|
||||
pub struct VmHostDisk {
|
||||
pub id: u64,
|
||||
pub host_id: u64,
|
||||
pub name: String,
|
||||
pub size: u64,
|
||||
pub kind: DiskType,
|
||||
pub interface: DiskInterface,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, sqlx::Type, Clone, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum DiskType {
|
||||
HDD = 0,
|
||||
SSD = 1,
|
||||
}
|
||||
|
||||
#[derive(Serialize, sqlx::Type, Clone, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum DiskInterface {
|
||||
SATA = 0,
|
||||
SCSI = 1,
|
||||
PCIe = 2,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Clone, Debug)]
|
||||
pub struct VmOsImage {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Clone, Debug)]
|
||||
pub struct IpRange {
|
||||
pub id: u64,
|
||||
pub cidr: String,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Clone, Debug)]
|
||||
pub struct VmTemplate {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
pub created: DateTime<Utc>,
|
||||
pub expires: Option<DateTime<Utc>>,
|
||||
pub cpu: u16,
|
||||
pub memory: u64,
|
||||
pub disk_size: u64,
|
||||
pub disk_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, FromRow, Clone, Debug)]
|
||||
pub struct Vm {
|
||||
/// Unique VM ID (Same in proxmox)
|
||||
pub id: u64,
|
||||
/// The host this VM is on
|
||||
pub host_id: u64,
|
||||
/// The user that owns this VM
|
||||
pub user_id: u64,
|
||||
/// The base image of this VM
|
||||
pub image_id: u64,
|
||||
/// When the VM was created
|
||||
pub created: DateTime<Utc>,
|
||||
/// When the VM expires
|
||||
pub expires: DateTime<Utc>,
|
||||
/// How many vCPU's this VM has
|
||||
pub cpu: u16,
|
||||
/// How much RAM this VM has in bytes
|
||||
pub memory: u64,
|
||||
/// How big the disk is on this VM in bytes
|
||||
pub disk_size: u64,
|
||||
/// The [VmHostDisk] this VM is on
|
||||
pub disk_id: u64,
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use anyhow::Error;
|
||||
use reqwest::{Body, ClientBuilder, Url};
|
||||
use anyhow::Result;
|
||||
use reqwest::{ClientBuilder, Url};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub struct ProxmoxClient {
|
||||
@ -31,28 +31,35 @@ impl ProxmoxClient {
|
||||
}
|
||||
|
||||
/// Get version info
|
||||
pub async fn version(&self) -> Result<VersionResponse, Error> {
|
||||
pub async fn version(&self) -> Result<VersionResponse> {
|
||||
let rsp: ResponseBase<VersionResponse> = self.get("/api2/json/version").await?;
|
||||
Ok(rsp.data)
|
||||
}
|
||||
|
||||
/// List nodes
|
||||
pub async fn list_nodes(&self) -> Result<Vec<NodeResponse>, Error> {
|
||||
pub async fn list_nodes(&self) -> Result<Vec<NodeResponse>> {
|
||||
let rsp: ResponseBase<Vec<NodeResponse>> = self.get("/api2/json/nodes").await?;
|
||||
Ok(rsp.data)
|
||||
}
|
||||
|
||||
pub async fn list_vms(&self, node: &str, full: bool) -> Result<Vec<VmInfo>, Error> {
|
||||
pub async fn list_vms(&self, node: &str, full: bool) -> Result<Vec<VmInfo>> {
|
||||
let rsp: ResponseBase<Vec<VmInfo>> =
|
||||
self.get(&format!("/api2/json/nodes/{node}/qemu")).await?;
|
||||
Ok(rsp.data)
|
||||
}
|
||||
pub async fn list_storage(&self) -> Result<Vec<NodeStorage>, Error> {
|
||||
pub async fn list_storage(&self) -> Result<Vec<NodeStorage>> {
|
||||
let rsp: ResponseBase<Vec<NodeStorage>> = self.get("/api2/json/storage").await?;
|
||||
Ok(rsp.data)
|
||||
}
|
||||
|
||||
async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
|
||||
pub async fn create_vm(&self, node: &str, req: CreateVm) -> Result<VmInfo> {
|
||||
let rsp: ResponseBase<VmInfo> = self
|
||||
.post(&format!("/api2/json/nodes/{node}/qemu"), req)
|
||||
.await?;
|
||||
Ok(rsp.data)
|
||||
}
|
||||
|
||||
async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {
|
||||
Ok(self
|
||||
.client
|
||||
.get(self.base.join(path)?)
|
||||
@ -61,19 +68,15 @@ impl ProxmoxClient {
|
||||
.await?
|
||||
.json::<T>()
|
||||
.await
|
||||
.map_err(|e| Error::new(e))?)
|
||||
.map_err(|e| anyhow::Error::new(e))?)
|
||||
}
|
||||
|
||||
async fn post<T: DeserializeOwned, R: Into<Body>>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: R,
|
||||
) -> Result<T, Error> {
|
||||
async fn post<T: DeserializeOwned, R: Serialize>(&self, path: &str, body: R) -> Result<T> {
|
||||
Ok(self
|
||||
.client
|
||||
.post(self.base.join(path)?)
|
||||
.header("Authorization", format!("PVEAPIToken={}", self.token))
|
||||
.body(body)
|
||||
.json(&body)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
@ -165,3 +168,62 @@ pub struct NodeStorage {
|
||||
#[serde(rename = "thinpool")]
|
||||
pub thin_pool: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum VmBios {
|
||||
SeaBios,
|
||||
OVMF,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||
pub struct CreateVm {
|
||||
pub node: String,
|
||||
#[serde(rename = "vmid")]
|
||||
pub vm_id: i32,
|
||||
#[serde(rename = "onboot")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub on_boot: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub balloon: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bios: Option<VmBios>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub boot: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cores: Option<i32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cpu: Option<String>,
|
||||
#[serde(rename = "ipconfig0")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ip_config: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub machine: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub memory: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(rename = "net0")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub net: Option<String>,
|
||||
#[serde(rename = "ostype")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub os_type: Option<String>,
|
||||
#[serde(rename = "scsi0")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub scsi_0: Option<String>,
|
||||
#[serde(rename = "scsi1")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub scsi_1: Option<String>,
|
||||
#[serde(rename = "scsihw")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub scsi_hw: Option<String>,
|
||||
#[serde(rename = "sshkeys")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ssh_keys: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<String>,
|
||||
#[serde(rename = "efidisk0")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub efi_disk_0: Option<String>,
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
pub mod api;
|
||||
pub mod cors;
|
||||
pub mod db;
|
||||
pub mod host;
|
||||
mod nip98;
|
||||
pub mod provisioner;
|
||||
mod vm;
|
||||
|
@ -1,24 +1,27 @@
|
||||
use crate::db;
|
||||
use crate::db::{VmHost, VmHostDisk};
|
||||
use crate::host::proxmox::ProxmoxClient;
|
||||
use crate::vm::VMSpec;
|
||||
use anyhow::{Error, Result};
|
||||
use crate::host::proxmox::{CreateVm, ProxmoxClient, VmBios};
|
||||
use anyhow::{bail, Result};
|
||||
use lnvps_db::{LNVpsDb, Vm, VmTemplate};
|
||||
use log::{info, warn};
|
||||
use sqlx::{MySqlPool, Row};
|
||||
use rocket::async_trait;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Provisioner {
|
||||
db: MySqlPool,
|
||||
#[async_trait]
|
||||
pub trait Provisioner: Send + Sync {
|
||||
/// Provision a new VM
|
||||
async fn provision(&self, spec: VmTemplate) -> Result<Vm>;
|
||||
}
|
||||
|
||||
impl Provisioner {
|
||||
pub fn new(db: MySqlPool) -> Self {
|
||||
Self { db }
|
||||
pub struct LNVpsProvisioner {
|
||||
db: Box<dyn LNVpsDb>,
|
||||
}
|
||||
|
||||
impl LNVpsProvisioner {
|
||||
pub fn new(db: impl LNVpsDb + 'static) -> Self {
|
||||
Self { db: Box::new(db) }
|
||||
}
|
||||
|
||||
/// Auto-discover resources
|
||||
pub async fn auto_discover(&self) -> Result<()> {
|
||||
let hosts = self.list_hosts().await?;
|
||||
let hosts = self.db.list_hosts().await?;
|
||||
for host in hosts {
|
||||
let api = ProxmoxClient::new(host.ip.parse()?).with_api_token(&host.api_token);
|
||||
|
||||
@ -32,11 +35,11 @@ impl Provisioner {
|
||||
host.cpu = node.max_cpu.unwrap_or(host.cpu);
|
||||
host.memory = node.max_mem.unwrap_or(host.memory);
|
||||
info!("Patching host: {:?}", host);
|
||||
self.update_host(host).await?;
|
||||
self.db.update_host(host).await?;
|
||||
}
|
||||
// Update disk info
|
||||
let storages = api.list_storage().await?;
|
||||
let host_disks = self.list_host_disks(host.id).await?;
|
||||
let host_disks = self.db.list_host_disks(host.id).await?;
|
||||
for storage in storages {
|
||||
let host_storage =
|
||||
if let Some(s) = host_disks.iter().find(|d| d.name == storage.storage) {
|
||||
@ -45,6 +48,8 @@ impl Provisioner {
|
||||
warn!("Disk not found: {} on {}", storage.storage, host.name);
|
||||
continue;
|
||||
};
|
||||
|
||||
// TODO: patch host storage info
|
||||
}
|
||||
}
|
||||
info!(
|
||||
@ -56,72 +61,67 @@ impl Provisioner {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Provision a new VM
|
||||
pub async fn provision(&self, spec: VMSpec) -> Result<db::Vm> {
|
||||
todo!()
|
||||
}
|
||||
#[async_trait]
|
||||
impl Provisioner for LNVpsProvisioner {
|
||||
async fn provision(&self, spec: VmTemplate) -> Result<Vm> {
|
||||
let hosts = self.db.list_hosts().await?;
|
||||
|
||||
/// Insert/Fetch user id
|
||||
pub async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result<u64> {
|
||||
let res = sqlx::query("insert ignore into users(pubkey) values(?) returning id")
|
||||
.bind(pubkey.as_slice())
|
||||
.fetch_optional(&self.db)
|
||||
.await?;
|
||||
match res {
|
||||
None => sqlx::query("select id from users where pubkey = ?")
|
||||
.bind(pubkey.as_slice())
|
||||
.fetch_one(&self.db)
|
||||
.await?
|
||||
.try_get(0)
|
||||
.map_err(Error::new),
|
||||
Some(res) => res.try_get(0).map_err(Error::new),
|
||||
// try any host
|
||||
// TODO: impl resource usage based provisioning
|
||||
for host in hosts {
|
||||
let api = ProxmoxClient::new(host.ip.parse()?).with_api_token(&host.api_token);
|
||||
|
||||
let nodes = api.list_nodes().await?;
|
||||
let node = if let Some(n) = nodes.iter().find(|n| n.name == host.name) {
|
||||
n
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let host_disks = self.db.list_host_disks(host.id).await?;
|
||||
let disk_name = if let Some(d) = host_disks.first() {
|
||||
d
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let next_id = 101;
|
||||
let vm_result = api
|
||||
.create_vm(
|
||||
&node.name,
|
||||
CreateVm {
|
||||
vm_id: next_id,
|
||||
bios: Some(VmBios::OVMF),
|
||||
boot: Some("order=scsi0".to_string()),
|
||||
cores: Some(spec.cpu as i32),
|
||||
cpu: Some("kvm64".to_string()),
|
||||
memory: Some((spec.memory / 1024 / 1024).to_string()),
|
||||
machine: Some("q35".to_string()),
|
||||
scsi_hw: Some("virtio-scsi-pci".to_string()),
|
||||
efi_disk_0: Some(format!("{}:vm-{next_id}-efi,size=1M", &disk_name.name)),
|
||||
net: Some("virtio=auto,bridge=vmbr0,tag=100".to_string()),
|
||||
ip_config: Some(format!("ip=auto,ipv6=auto")),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
return Ok(Vm {
|
||||
id: 0,
|
||||
host_id: 0,
|
||||
user_id: 0,
|
||||
image_id: 0,
|
||||
template_id: 0,
|
||||
ssh_key_id: 0,
|
||||
created: Default::default(),
|
||||
expires: Default::default(),
|
||||
cpu: 0,
|
||||
memory: 0,
|
||||
disk_size: 0,
|
||||
disk_id: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// List VM templates
|
||||
pub async fn list_vm_templates(&self) -> Result<Vec<db::VmTemplate>> {
|
||||
sqlx::query_as("select * from vm_template where enabled = 1 and (expires is null or expires < now())")
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
/// List VM's owned by a specific user
|
||||
pub async fn list_vms(&self, id: u64) -> Result<Vec<db::Vm>> {
|
||||
sqlx::query_as("select * from vm where user_id = ?")
|
||||
.bind(&id)
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
/// List VM's owned by a specific user
|
||||
pub async fn list_hosts(&self) -> Result<Vec<VmHost>> {
|
||||
sqlx::query_as("select * from vm_host")
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
/// List VM's owned by a specific user
|
||||
pub async fn list_host_disks(&self, host_id: u64) -> Result<Vec<VmHostDisk>> {
|
||||
sqlx::query_as("select * from vm_host_disk where host_id = ?")
|
||||
.bind(&host_id)
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
/// Update host resources (usually from [auto_discover])
|
||||
pub async fn update_host(&self, host: VmHost) -> Result<()> {
|
||||
sqlx::query("update vm_host set name = ?, cpu = ?, memory = ? where id = ?")
|
||||
.bind(&host.name)
|
||||
.bind(&host.cpu)
|
||||
.bind(&host.memory)
|
||||
.bind(&host.id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
bail!("Failed to create VM")
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user