This commit is contained in:
2024-11-24 23:09:29 +00:00
parent 1dd72fd011
commit 13f59908fb
19 changed files with 2774 additions and 306 deletions

82
lnvps_db/src/lib.rs Normal file
View File

@ -0,0 +1,82 @@
use anyhow::Result;
use async_trait::async_trait;
mod model;
#[cfg(feature = "mysql")]
mod mysql;
pub use model::*;
#[cfg(feature = "mysql")]
pub use mysql::*;
#[async_trait]
pub trait LNVpsDb: Sync + Send {
/// Migrate database
async fn migrate(&self) -> Result<()>;
/// Insert/Fetch user by pubkey
async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result<u64>;
/// Get a user by id
async fn get_user(&self, id: u64) -> Result<User>;
/// Update user record
async fn update_user(&self, user: &User) -> Result<()>;
/// Delete user record
async fn delete_user(&self, id: u64) -> Result<()>;
/// Insert a new user ssh key
async fn insert_user_ssh_key(&self, new_key: UserSshKey) -> Result<u64>;
/// Get user ssh key by id
async fn get_user_ssh_key(&self, id: u64) -> Result<UserSshKey>;
/// Delete a user ssh key by id
async fn delete_user_ssh_key(&self, id: u64) -> Result<()>;
/// List a users ssh keys
async fn list_user_ssh_key(&self, user_id: u64) -> Result<Vec<UserSshKey>>;
/// Get VM host region by id
async fn get_host_region(&self, id: u64) -> Result<VmHostRegion>;
/// List VM's owned by a specific user
async fn list_hosts(&self) -> Result<Vec<VmHost>>;
/// Update host resources (usually from [auto_discover])
async fn update_host(&self, host: VmHost) -> Result<()>;
/// List VM's owned by a specific user
async fn list_host_disks(&self, host_id: u64) -> Result<Vec<VmHostDisk>>;
/// List available OS images
async fn list_os_image(&self) -> Result<Vec<VmOsImage>>;
/// List available IP Ranges
async fn list_ip_range(&self) -> Result<Vec<IpRange>>;
/// Get a VM cost plan by id
async fn get_cost_plan(&self, id: u64) -> Result<VmCostPlan>;
/// List VM templates
async fn list_vm_templates(&self) -> Result<Vec<VmTemplate>>;
/// List VM's owned by a specific user
async fn list_user_vms(&self, id: u64) -> Result<Vec<Vm>>;
/// Insert a new VM record
async fn insert_vm(&self, vm: Vm) -> Result<u64>;
/// List VM ip assignments
async fn get_vm_ip_assignments(&self, vm_id: u64) -> Result<Vec<VmIpAssignment>>;
/// List payments by VM id
async fn list_vm_payment(&self, vm_id: u64) -> Result<Vec<VmPayment>>;
/// Insert a new VM payment record
async fn insert_vm_payment(&self, vm_payment: VmPayment) -> Result<u64>;
/// Update a VM payment record
async fn update_vm_payment(&self, vm_payment: VmPayment) -> Result<()>;
}

215
lnvps_db/src/model.rs Normal file
View File

@ -0,0 +1,215 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
/// 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: Vec<u8>,
/// When this user first started using the service (first login)
pub created: DateTime<Utc>,
pub email: Option<String>,
pub contact_nip4: bool,
pub contact_nip17: bool,
pub contact_email: bool,
}
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
pub struct UserSshKey {
pub id: u64,
pub name: String,
pub user_id: u64,
pub created: DateTime<Utc>,
pub key_data: String,
#[sqlx(skip)]
#[serde(skip_serializing_if = "Option::is_none")]
pub vms: Option<Vec<Vm>>,
}
#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)]
#[repr(u16)]
/// The type of VM host
pub enum VmHostKind {
Proxmox = 0,
}
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
pub struct VmHostRegion {
pub id: u64,
pub name: String,
pub enabled: bool,
}
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
/// A VM host
pub struct VmHost {
/// Unique id of this host
pub id: u64,
/// The host kind (Hypervisor)
pub kind: VmHostKind,
/// What region / group this host is part of
pub region_id: u64,
/// Internal name of this host
pub name: String,
/// Endpoint for controlling this host
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,
/// API token used to control this host via [ip]
pub api_token: String,
}
#[derive(Serialize, Deserialize, 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, Deserialize, Clone, Debug, sqlx::Type)]
#[repr(u16)]
pub enum DiskType {
HDD = 0,
SSD = 1,
}
#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)]
#[repr(u16)]
pub enum DiskInterface {
SATA = 0,
SCSI = 1,
PCIe = 2,
}
#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)]
#[repr(u16)]
pub enum OsDistribution {
Ubuntu = 0,
Debian = 1,
}
/// OS Images are templates which are used as a basis for
/// provisioning new vms
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
pub struct VmOsImage {
pub id: u64,
pub name: String,
pub distribution: OsDistribution,
pub flavour: String,
pub version: String,
pub enabled: bool,
/// URL location of cloud image
pub url: String,
}
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
pub struct IpRange {
pub id: u64,
pub cidr: String,
pub enabled: bool,
pub region_id: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, sqlx::Type)]
#[repr(u16)]
pub enum VmCostPlanIntervalType {
Day = 0,
Month = 1,
Year = 2,
}
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
pub struct VmCostPlan {
pub id: u64,
pub name: String,
pub created: DateTime<Utc>,
pub amount: u64,
pub currency: String,
pub interval_amount: u64,
pub interval_type: VmCostPlanIntervalType,
}
/// Offers.
/// These are the same as the offers visible to customers
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
pub struct VmTemplate {
pub id: u64,
pub name: String,
pub enabled: bool,
pub created: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires: Option<DateTime<Utc>>,
pub cpu: u16,
pub memory: u64,
pub disk_type: DiskType,
pub disk_interface: DiskInterface,
pub cost_plan_id: u64,
pub region_id: u64,
#[sqlx(skip)]
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_plan: Option<VmCostPlan>,
#[sqlx(skip)]
#[serde(skip_serializing_if = "Option::is_none")]
pub region: Option<VmHostRegion>,
}
#[derive(Serialize, Deserialize, 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,
/// The base image of this VM
pub template_id: u64,
/// Users ssh-key assigned to this VM
pub ssh_key_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,
}
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
pub struct VmIpAssignment {
pub id: u64,
pub vm_id: u64,
pub ip_range_id: u64,
}
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
pub struct VmPayment {
pub id: u64,
pub vm_id: u64,
pub created: DateTime<Utc>,
pub expires: DateTime<Utc>,
pub amount: u64,
pub invoice: String,
pub time_value: u64,
pub is_paid: bool,
}

152
lnvps_db/src/mysql.rs Normal file
View File

@ -0,0 +1,152 @@
use crate::{IpRange, LNVpsDb, User, UserSshKey, Vm, VmCostPlan, VmHost, VmHostDisk, VmHostRegion, VmIpAssignment, VmOsImage, VmPayment, VmTemplate};
use anyhow::{Error, Result};
use async_trait::async_trait;
use sqlx::{Executor, MySqlPool, Row};
#[derive(Clone)]
pub struct LNVpsDbMysql {
db: MySqlPool,
}
impl LNVpsDbMysql {
pub async fn new(conn: &str) -> Result<Self> {
let db = MySqlPool::connect(conn).await?;
Ok(Self {
db
})
}
#[cfg(debug_assertions)]
pub async fn execute(&self, sql: &str) -> Result<()> {
self.db.execute(sql).await.map_err(Error::new)?;
Ok(())
}
}
#[async_trait]
impl LNVpsDb for LNVpsDbMysql {
async fn migrate(&self) -> anyhow::Result<()> {
sqlx::migrate!().run(&self.db).await.map_err(Error::new)
}
async fn upsert_user(&self, pubkey: &[u8; 32]) -> anyhow::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),
}
}
async fn get_user(&self, id: u64) -> Result<User> {
todo!()
}
async fn update_user(&self, user: &User) -> Result<()> {
todo!()
}
async fn delete_user(&self, id: u64) -> Result<()> {
todo!()
}
async fn insert_user_ssh_key(&self, new_key: UserSshKey) -> Result<u64> {
todo!()
}
async fn get_user_ssh_key(&self, id: u64) -> Result<UserSshKey> {
todo!()
}
async fn delete_user_ssh_key(&self, id: u64) -> Result<()> {
todo!()
}
async fn list_user_ssh_key(&self, user_id: u64) -> Result<Vec<UserSshKey>> {
todo!()
}
async fn get_host_region(&self, id: u64) -> Result<VmHostRegion> {
todo!()
}
async fn list_hosts(&self) -> anyhow::Result<Vec<VmHost>> {
sqlx::query_as("select * from vm_host")
.fetch_all(&self.db)
.await
.map_err(Error::new)
}
async fn update_host(&self, host: VmHost) -> anyhow::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(())
}
async fn list_host_disks(&self, host_id: u64) -> anyhow::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)
}
async fn list_os_image(&self) -> Result<Vec<VmOsImage>> {
todo!()
}
async fn list_ip_range(&self) -> Result<Vec<IpRange>> {
todo!()
}
async fn get_cost_plan(&self, id: u64) -> Result<VmCostPlan> {
todo!()
}
async fn list_vm_templates(&self) -> anyhow::Result<Vec<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)
}
async fn list_user_vms(&self, id: u64) -> Result<Vec<Vm>> {
sqlx::query_as("select * from vm where user_id = ?")
.bind(&id)
.fetch_all(&self.db)
.await
.map_err(Error::new)
}
async fn insert_vm(&self, vm: Vm) -> Result<u64> {
todo!()
}
async fn get_vm_ip_assignments(&self, vm_id: u64) -> Result<Vec<VmIpAssignment>> {
todo!()
}
async fn list_vm_payment(&self, vm_id: u64) -> Result<Vec<VmPayment>> {
todo!()
}
async fn insert_vm_payment(&self, vm_payment: VmPayment) -> Result<u64> {
todo!()
}
async fn update_vm_payment(&self, vm_payment: VmPayment) -> Result<()> {
todo!()
}
}