progress
This commit is contained in:
parent
1dd72fd011
commit
13f59908fb
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,2 @@
|
|||||||
/target
|
**/target
|
||||||
.idea/
|
.idea/
|
18
Cargo.lock
generated
18
Cargo.lock
generated
@ -1383,6 +1383,7 @@ dependencies = [
|
|||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
|
"lnvps_db",
|
||||||
"log",
|
"log",
|
||||||
"nostr",
|
"nostr",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
@ -1390,10 +1391,20 @@ dependencies = [
|
|||||||
"rocket",
|
"rocket",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lnvps_db"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"chrono",
|
||||||
|
"serde",
|
||||||
|
"sqlx",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@ -1543,10 +1554,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.35.0"
|
version = "0.36.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56db234b2e07901e372f34e9463f91590579cd8e6dbd34ed2ccc7e461e4ba639"
|
checksum = "14ad56c1d9a59f4edc46b17bc64a217b38b99baefddc0080f85ad98a0855336d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bech32",
|
"bech32",
|
||||||
"bip39",
|
"bip39",
|
||||||
|
@ -7,16 +7,16 @@ edition = "2021"
|
|||||||
name = "api"
|
name = "api"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
lnvps_db = { path = "lnvps_db" }
|
||||||
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] }
|
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
anyhow = "1.0.83"
|
anyhow = "1.0.83"
|
||||||
log = "0.4.21"
|
log = "0.4.21"
|
||||||
config = { version = "0.14.0", features = ["toml"] }
|
config = { version = "0.14.0", features = ["yaml"] }
|
||||||
pretty_env_logger = "0.5.0"
|
pretty_env_logger = "0.5.0"
|
||||||
serde = { version = "1.0.213", features = ["derive"] }
|
serde = { version = "1.0.213", features = ["derive"] }
|
||||||
reqwest = { version = "0.12.8", features = ["json"] }
|
reqwest = { version = "0.12.8", features = ["json"] }
|
||||||
serde_json = "1.0.132"
|
serde_json = "1.0.132"
|
||||||
sqlx = { version = "0.8.2", features = ["mysql", "chrono", "migrate", "runtime-tokio"] }
|
|
||||||
rocket = { version = "0.5.1", features = ["json"] }
|
rocket = { version = "0.5.1", features = ["json"] }
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
nostr = { version = "0.35.0", default-features = false, features = ["std"] }
|
nostr = { version = "0.36.0", default-features = false, features = ["std"] }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
# MySQL database connection string
|
|
||||||
db = "mysql://root:root@localhost:3376/lnvps"
|
|
2
config.yaml
Normal file
2
config.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# MySQL database connection string
|
||||||
|
db: "mysql://root:root@localhost:3376/lnvps"
|
@ -1,5 +1,15 @@
|
|||||||
insert ignore into vm_host(id,kind,name,ip,cpu,memory,enabled,api_token) values(1, 0, "lab", "https://185.18.221.8:8006", 4, 4096*1024, 1, "root@pam!tester=c82f8a57-f876-4ca4-8610-c086d8d9d51c");
|
insert
|
||||||
insert ignore into vm_host_disk(id,host_id,name,size,kind,interface,enabled) values(1,1,"local-lvm",1000*1000*1000*1000, 0, 0, 1);
|
ignore into vm_host_region(id,name,enabled) values(1,"uat",1);
|
||||||
insert ignore into vm_os_image(id,name,enabled) values(1,"Ubuntu 24.04",1);
|
insert
|
||||||
insert ignore into ip_range(id,cidr,enabled) values(1,"185.18.221.80/28",1);
|
ignore into vm_host(id,kind,region_id,name,ip,cpu,memory,enabled,api_token) values(1, 0, 1, "lab", "https://185.18.221.8:8006", 4, 4096*1024, 1, "root@pam!tester=c82f8a57-f876-4ca4-8610-c086d8d9d51c");
|
||||||
insert ignore into vm_template(id,name,enabled,cpu,memory,disk_size,disk_id) values(1,"Basic",1,2,2048,1000*1000*1000*80,1);
|
insert
|
||||||
|
ignore into vm_host_disk(id,host_id,name,size,kind,interface,enabled) values(1,1,"local-lvm",1000*1000*1000*1000, 0, 0, 1);
|
||||||
|
insert
|
||||||
|
ignore into vm_os_image(id,name,distribution,flavour,version,enabled,url) values(1,0,"Server","24.04",1,"https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img");
|
||||||
|
insert
|
||||||
|
ignore into ip_range(id,cidr,enabled) values(1,"185.18.221.80/28",1);
|
||||||
|
insert
|
||||||
|
ignore into vm_cost_plan(id,name,amount,currency,interval_amount,interval_type) values(1,"tiny_monthly",3,"EUR",1,1);
|
||||||
|
insert
|
||||||
|
ignore into vm_template(id,name,enabled,cpu,memory,disk_size,disk_type,disk_interface,cost_plan_id,region_id)
|
||||||
|
values(1,"Tiny",1,2,1024*1024*2,1000*1000*1000*80,1,2,1,1);
|
2019
lnvps_db/Cargo.lock
generated
Normal file
2019
lnvps_db/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
lnvps_db/Cargo.toml
Normal file
15
lnvps_db/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "lnvps_db"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["mysql"]
|
||||||
|
mysql = ["sqlx/mysql"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.83"
|
||||||
|
sqlx = { version = "0.8.2", features = ["chrono", "migrate", "runtime-tokio"] }
|
||||||
|
serde = { version = "1.0.213", features = ["derive"] }
|
||||||
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
|
async-trait = "0.1.83"
|
@ -1,36 +1,44 @@
|
|||||||
create table users
|
create table users
|
||||||
(
|
(
|
||||||
id integer unsigned not null auto_increment primary key,
|
id integer unsigned not null auto_increment primary key,
|
||||||
pubkey binary(32) not null,
|
pubkey binary(32) not null,
|
||||||
created timestamp default current_timestamp
|
created timestamp default current_timestamp,
|
||||||
);
|
email varchar(200),
|
||||||
create table user_ssh_key
|
contact_nip4 bit(1) not null,
|
||||||
(
|
contact_nip17 bit(1) not null,
|
||||||
id integer unsigned not null auto_increment primary key,
|
contact_email bit(1) not null,
|
||||||
name varchar(100) not null,
|
|
||||||
user_id integer unsigned not null,
|
|
||||||
created timestamp default current_timestamp,
|
|
||||||
key varchar(2048) not null,
|
|
||||||
|
|
||||||
constraint fk_ssh_key_user_id foreign key (user_id) references users (id)
|
|
||||||
);
|
);
|
||||||
create unique index ix_user_pubkey on users (pubkey);
|
create unique index ix_user_pubkey on users (pubkey);
|
||||||
|
create unique index ix_user_email on users (email);
|
||||||
|
create table user_ssh_key
|
||||||
|
(
|
||||||
|
id integer unsigned not null auto_increment primary key,
|
||||||
|
name varchar(100) not null,
|
||||||
|
user_id integer unsigned not null,
|
||||||
|
created timestamp default current_timestamp,
|
||||||
|
key_data varchar(2048) not null,
|
||||||
|
|
||||||
|
constraint fk_ssh_key_user foreign key (user_id) references users (id)
|
||||||
|
);
|
||||||
create table vm_host_region
|
create table vm_host_region
|
||||||
(
|
(
|
||||||
id integer unsigned not null auto_increment primary key,
|
id integer unsigned not null auto_increment primary key,
|
||||||
name varchar(100) not null,
|
name varchar(100) not null,
|
||||||
enabled bit(1) not null,
|
enabled bit(1) not null
|
||||||
);
|
);
|
||||||
create table vm_host
|
create table vm_host
|
||||||
(
|
(
|
||||||
id integer unsigned not null auto_increment primary key,
|
id integer unsigned not null auto_increment primary key,
|
||||||
kind smallint unsigned not null,
|
kind smallint unsigned not null,
|
||||||
|
region_id integer unsigned not null,
|
||||||
name varchar(100) not null,
|
name varchar(100) not null,
|
||||||
ip varchar(250) not null,
|
ip varchar(250) not null,
|
||||||
cpu bigint unsigned not null,
|
cpu bigint unsigned not null,
|
||||||
memory bigint unsigned not null,
|
memory bigint unsigned not null,
|
||||||
enabled bit(1) not null,
|
enabled bit(1) not null,
|
||||||
api_token varchar(200) not null
|
api_token varchar(200) not null,
|
||||||
|
|
||||||
|
constraint fk_host_region foreign key (region_id) references vm_host_region (id)
|
||||||
);
|
);
|
||||||
create table vm_host_disk
|
create table vm_host_disk
|
||||||
(
|
(
|
||||||
@ -42,46 +50,58 @@ create table vm_host_disk
|
|||||||
interface smallint unsigned not null,
|
interface smallint unsigned not null,
|
||||||
enabled bit(1) not null,
|
enabled bit(1) not null,
|
||||||
|
|
||||||
constraint fk_vm_host_disk foreign key (host_id) references vm_host (id)
|
constraint fk_host_disk_host foreign key (host_id) references vm_host (id)
|
||||||
);
|
);
|
||||||
create table vm_os_image
|
create table vm_os_image
|
||||||
(
|
(
|
||||||
id integer unsigned not null auto_increment primary key,
|
id integer unsigned not null auto_increment primary key,
|
||||||
name varchar(200) not null,
|
name varchar(200) not null,
|
||||||
enabled bit(1) not null
|
distribution smallint unsigned not null,
|
||||||
|
flavour varchar(50) not null,
|
||||||
|
version varchar(50) not null,
|
||||||
|
enabled bit(1) not null,
|
||||||
|
url varchar(1024) not null,
|
||||||
);
|
);
|
||||||
|
create unique index ix_vm_os_image_name on vm_os_image (name);
|
||||||
create table ip_range
|
create table ip_range
|
||||||
(
|
(
|
||||||
id integer unsigned not null auto_increment primary key,
|
id integer unsigned not null auto_increment primary key,
|
||||||
cidr varchar(200) not null,
|
cidr varchar(200) not null,
|
||||||
enabled bit(1) not null
|
enabled bit(1) not null,
|
||||||
|
region_id integer unsigned not null,
|
||||||
|
|
||||||
|
constraint fk_ip_range_region foreign key (region_id) references vm_host_region (id)
|
||||||
);
|
);
|
||||||
create table vm_cost_plan
|
create table vm_cost_plan
|
||||||
(
|
(
|
||||||
id integer unsigned not null auto_increment primary key,
|
id integer unsigned not null auto_increment primary key,
|
||||||
name varchar(200) not null,
|
name varchar(200) not null,
|
||||||
enabled bit(1) not null,
|
created timestamp default current_timestamp,
|
||||||
created timestamp default current_timestamp,
|
amount integer unsigned not null,
|
||||||
expires timestamp,
|
currency varchar(4) not null,
|
||||||
amount integer unsigned not null,
|
interval_amount integer unsigned not null,
|
||||||
currency varchar(4) not null,
|
interval_type smallint unsigned not null
|
||||||
interval integer unsigned not null,
|
|
||||||
interval_type smallint unsigned not null,
|
|
||||||
);
|
);
|
||||||
|
-- IE. VM Offers
|
||||||
create table vm_template
|
create table vm_template
|
||||||
(
|
(
|
||||||
id integer unsigned not null auto_increment primary key,
|
id integer unsigned not null auto_increment primary key,
|
||||||
name varchar(200) not null,
|
name varchar(200) not null,
|
||||||
enabled bit(1) not null,
|
enabled bit(1) not null,
|
||||||
created timestamp default current_timestamp,
|
created timestamp default current_timestamp,
|
||||||
expires timestamp,
|
expires timestamp,
|
||||||
cpu smallint unsigned not null,
|
cpu tinyint unsigned not null,
|
||||||
memory bigint unsigned not null,
|
memory bigint unsigned not null,
|
||||||
disk_size bigint unsigned not null,
|
disk_size bigint unsigned not null,
|
||||||
disk_id integer unsigned not null,
|
disk_type smallint unsigned not null,
|
||||||
|
disk_interface smallint unsigned not null,
|
||||||
|
cost_plan_id integer unsigned not null,
|
||||||
|
region_id integer unsigned not null,
|
||||||
|
|
||||||
constraint fk_vm_host_disk_id foreign key (disk_id) references vm_host_disk (id)
|
constraint fk_template_cost_plan foreign key (cost_plan_id) references vm_cost_plan (id),
|
||||||
|
constraint fk_template_region foreign key (region_id) references vm_template (id)
|
||||||
);
|
);
|
||||||
|
-- An instance of a VM
|
||||||
create table vm
|
create table vm
|
||||||
(
|
(
|
||||||
id integer unsigned not null auto_increment primary key,
|
id integer unsigned not null auto_increment primary key,
|
||||||
@ -121,7 +141,7 @@ create table vm_payment
|
|||||||
expires timestamp not null,
|
expires timestamp not null,
|
||||||
amount bigint unsigned not null,
|
amount bigint unsigned not null,
|
||||||
invoice varchar(2048) not null,
|
invoice varchar(2048) not null,
|
||||||
time_value integer unsigned not null,
|
time_value bigint unsigned not null,
|
||||||
is_paid bit(1) not null,
|
is_paid bit(1) not null,
|
||||||
|
|
||||||
constraint fk_vm_payment_vm foreign key (vm_id) references vm (id)
|
constraint fk_vm_payment_vm foreign key (vm_id) references vm (id)
|
82
lnvps_db/src/lib.rs
Normal file
82
lnvps_db/src/lib.rs
Normal 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
215
lnvps_db/src/model.rs
Normal 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
152
lnvps_db/src/mysql.rs
Normal 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!()
|
||||||
|
}
|
||||||
|
}
|
46
src/api.rs
46
src/api.rs
@ -1,14 +1,13 @@
|
|||||||
use crate::db;
|
|
||||||
use crate::nip98::Nip98Auth;
|
use crate::nip98::Nip98Auth;
|
||||||
use crate::provisioner::Provisioner;
|
use crate::provisioner::Provisioner;
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
|
use lnvps_db::{LNVpsDb, Vm, VmTemplate};
|
||||||
use rocket::serde::json::Json;
|
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 serde::{Deserialize, Serialize};
|
||||||
use crate::vm::VMSpec;
|
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
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>;
|
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")]
|
#[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 pubkey = auth.event.pubkey.to_bytes();
|
||||||
let uid = provisioner.upsert_user(&pubkey).await?;
|
let uid = db.upsert_user(&pubkey).await?;
|
||||||
let vms = provisioner.list_vms(uid).await?;
|
let vms = db.list_user_vms(uid).await?;
|
||||||
ApiData::ok(vms)
|
ApiData::ok(vms)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/api/v1/vm/templates")]
|
#[get("/api/v1/vm/templates")]
|
||||||
async fn v1_list_vm_templates(provisioner: &State<Provisioner>) -> ApiResult<Vec<db::VmTemplate>> {
|
async fn v1_list_vm_templates(db: &State<Box<dyn LNVpsDb>>) -> ApiResult<Vec<VmTemplate>> {
|
||||||
let vms = provisioner.list_vm_templates().await?;
|
let vms = db.list_vm_templates().await?;
|
||||||
ApiData::ok(vms)
|
ApiData::ok(vms)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/api/v1/vm", data = "<req>", format = "json")]
|
#[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 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 req = req.0;
|
||||||
let rsp = provisioner.provision(req.into()).await?;
|
let rsp = provisioner.provision(req.into()).await?;
|
||||||
ApiData::ok(rsp)
|
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 config::{Config, File};
|
||||||
use lnvps::api;
|
use lnvps::api;
|
||||||
use lnvps::cors::CORS;
|
use lnvps::cors::CORS;
|
||||||
use lnvps::provisioner::Provisioner;
|
use lnvps::provisioner::{LNVpsProvisioner, Provisioner};
|
||||||
|
use lnvps_db::{LNVpsDb, LNVpsDbMysql};
|
||||||
use log::error;
|
use log::error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{Executor, MySqlPool};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
@ -17,13 +17,14 @@ async fn main() -> Result<(), Error> {
|
|||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let config: Settings = Config::builder()
|
let config: Settings = Config::builder()
|
||||||
.add_source(File::with_name("config.toml"))
|
.add_source(File::with_name("config.yaml"))
|
||||||
.build()?
|
.build()?
|
||||||
.try_deserialize()?;
|
.try_deserialize()?;
|
||||||
|
|
||||||
let db = MySqlPool::connect(&config.db).await?;
|
let db = LNVpsDbMysql::new(&config.db).await?;
|
||||||
sqlx::migrate!().run(&db).await?;
|
db.migrate().await?;
|
||||||
let provisioner = Provisioner::new(db.clone());
|
|
||||||
|
let provisioner = LNVpsProvisioner::new(db.clone());
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
let setup_script = include_str!("../../dev_setup.sql");
|
let setup_script = include_str!("../../dev_setup.sql");
|
||||||
@ -31,9 +32,12 @@ async fn main() -> Result<(), Error> {
|
|||||||
provisioner.auto_discover().await?;
|
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()
|
if let Err(e) = rocket::build()
|
||||||
.attach(CORS)
|
.attach(CORS)
|
||||||
.manage(provisioner)
|
.manage(db)
|
||||||
|
.manage(pv)
|
||||||
.mount("/", api::routes())
|
.mount("/", api::routes())
|
||||||
.launch()
|
.launch()
|
||||||
.await
|
.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 anyhow::Result;
|
||||||
use reqwest::{Body, ClientBuilder, Url};
|
use reqwest::{ClientBuilder, Url};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub struct ProxmoxClient {
|
pub struct ProxmoxClient {
|
||||||
@ -31,28 +31,35 @@ impl ProxmoxClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get version info
|
/// 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?;
|
let rsp: ResponseBase<VersionResponse> = self.get("/api2/json/version").await?;
|
||||||
Ok(rsp.data)
|
Ok(rsp.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List nodes
|
/// 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?;
|
let rsp: ResponseBase<Vec<NodeResponse>> = self.get("/api2/json/nodes").await?;
|
||||||
Ok(rsp.data)
|
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>> =
|
let rsp: ResponseBase<Vec<VmInfo>> =
|
||||||
self.get(&format!("/api2/json/nodes/{node}/qemu")).await?;
|
self.get(&format!("/api2/json/nodes/{node}/qemu")).await?;
|
||||||
Ok(rsp.data)
|
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?;
|
let rsp: ResponseBase<Vec<NodeStorage>> = self.get("/api2/json/storage").await?;
|
||||||
Ok(rsp.data)
|
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
|
Ok(self
|
||||||
.client
|
.client
|
||||||
.get(self.base.join(path)?)
|
.get(self.base.join(path)?)
|
||||||
@ -61,19 +68,15 @@ impl ProxmoxClient {
|
|||||||
.await?
|
.await?
|
||||||
.json::<T>()
|
.json::<T>()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::new(e))?)
|
.map_err(|e| anyhow::Error::new(e))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post<T: DeserializeOwned, R: Into<Body>>(
|
async fn post<T: DeserializeOwned, R: Serialize>(&self, path: &str, body: R) -> Result<T> {
|
||||||
&self,
|
|
||||||
path: &str,
|
|
||||||
body: R,
|
|
||||||
) -> Result<T, Error> {
|
|
||||||
Ok(self
|
Ok(self
|
||||||
.client
|
.client
|
||||||
.post(self.base.join(path)?)
|
.post(self.base.join(path)?)
|
||||||
.header("Authorization", format!("PVEAPIToken={}", self.token))
|
.header("Authorization", format!("PVEAPIToken={}", self.token))
|
||||||
.body(body)
|
.json(&body)
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.error_for_status()?
|
.error_for_status()?
|
||||||
@ -165,3 +168,62 @@ pub struct NodeStorage {
|
|||||||
#[serde(rename = "thinpool")]
|
#[serde(rename = "thinpool")]
|
||||||
pub thin_pool: Option<String>,
|
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 api;
|
||||||
pub mod cors;
|
pub mod cors;
|
||||||
pub mod db;
|
|
||||||
pub mod host;
|
pub mod host;
|
||||||
mod nip98;
|
mod nip98;
|
||||||
pub mod provisioner;
|
pub mod provisioner;
|
||||||
mod vm;
|
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
use crate::db;
|
use crate::host::proxmox::{CreateVm, ProxmoxClient, VmBios};
|
||||||
use crate::db::{VmHost, VmHostDisk};
|
use anyhow::{bail, Result};
|
||||||
use crate::host::proxmox::ProxmoxClient;
|
use lnvps_db::{LNVpsDb, Vm, VmTemplate};
|
||||||
use crate::vm::VMSpec;
|
|
||||||
use anyhow::{Error, Result};
|
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
use sqlx::{MySqlPool, Row};
|
use rocket::async_trait;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[async_trait]
|
||||||
pub struct Provisioner {
|
pub trait Provisioner: Send + Sync {
|
||||||
db: MySqlPool,
|
/// Provision a new VM
|
||||||
|
async fn provision(&self, spec: VmTemplate) -> Result<Vm>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Provisioner {
|
pub struct LNVpsProvisioner {
|
||||||
pub fn new(db: MySqlPool) -> Self {
|
db: Box<dyn LNVpsDb>,
|
||||||
Self { db }
|
}
|
||||||
|
|
||||||
|
impl LNVpsProvisioner {
|
||||||
|
pub fn new(db: impl LNVpsDb + 'static) -> Self {
|
||||||
|
Self { db: Box::new(db) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-discover resources
|
/// Auto-discover resources
|
||||||
pub async fn auto_discover(&self) -> Result<()> {
|
pub async fn auto_discover(&self) -> Result<()> {
|
||||||
let hosts = self.list_hosts().await?;
|
let hosts = self.db.list_hosts().await?;
|
||||||
for host in hosts {
|
for host in hosts {
|
||||||
let api = ProxmoxClient::new(host.ip.parse()?).with_api_token(&host.api_token);
|
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.cpu = node.max_cpu.unwrap_or(host.cpu);
|
||||||
host.memory = node.max_mem.unwrap_or(host.memory);
|
host.memory = node.max_mem.unwrap_or(host.memory);
|
||||||
info!("Patching host: {:?}", host);
|
info!("Patching host: {:?}", host);
|
||||||
self.update_host(host).await?;
|
self.db.update_host(host).await?;
|
||||||
}
|
}
|
||||||
// Update disk info
|
// Update disk info
|
||||||
let storages = api.list_storage().await?;
|
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 {
|
for storage in storages {
|
||||||
let host_storage =
|
let host_storage =
|
||||||
if let Some(s) = host_disks.iter().find(|d| d.name == storage.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);
|
warn!("Disk not found: {} on {}", storage.storage, host.name);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: patch host storage info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!(
|
info!(
|
||||||
@ -56,72 +61,67 @@ impl Provisioner {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Provision a new VM
|
#[async_trait]
|
||||||
pub async fn provision(&self, spec: VMSpec) -> Result<db::Vm> {
|
impl Provisioner for LNVpsProvisioner {
|
||||||
todo!()
|
async fn provision(&self, spec: VmTemplate) -> Result<Vm> {
|
||||||
}
|
let hosts = self.db.list_hosts().await?;
|
||||||
|
|
||||||
/// Insert/Fetch user id
|
// try any host
|
||||||
pub async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result<u64> {
|
// TODO: impl resource usage based provisioning
|
||||||
let res = sqlx::query("insert ignore into users(pubkey) values(?) returning id")
|
for host in hosts {
|
||||||
.bind(pubkey.as_slice())
|
let api = ProxmoxClient::new(host.ip.parse()?).with_api_token(&host.api_token);
|
||||||
.fetch_optional(&self.db)
|
|
||||||
.await?;
|
let nodes = api.list_nodes().await?;
|
||||||
match res {
|
let node = if let Some(n) = nodes.iter().find(|n| n.name == host.name) {
|
||||||
None => sqlx::query("select id from users where pubkey = ?")
|
n
|
||||||
.bind(pubkey.as_slice())
|
} else {
|
||||||
.fetch_one(&self.db)
|
continue;
|
||||||
.await?
|
};
|
||||||
.try_get(0)
|
let host_disks = self.db.list_host_disks(host.id).await?;
|
||||||
.map_err(Error::new),
|
let disk_name = if let Some(d) = host_disks.first() {
|
||||||
Some(res) => res.try_get(0).map_err(Error::new),
|
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
|
bail!("Failed to create VM")
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user