Setup database

This commit is contained in:
Kieran 2024-11-03 19:24:00 +00:00
parent 90abd08088
commit f7279c8707
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
11 changed files with 1842 additions and 92 deletions

1599
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -14,3 +14,6 @@ pretty_env_logger = "0.5.0"
serde = { version = "1.0.213", features = ["derive"] }
reqwest = { version = "0.12.8", features = ["json"] }
serde_json = "1.0.132"
sqlx = { version = "0.8.2", features = ["mysql", "chrono", "migrate", "runtime-tokio"] }
rocket = "0.5.1"
chrono = "0.4.38"

View File

@ -1,5 +1,2 @@
server = "https://10.97.0.234:8006/"
user = "root"
realm = "pam"
token_id = "test-dev"
secret = "e2d8d39f-63ce-48f0-a025-b428d29a26e3"
# MySQL database connection string
db = "mysql://root:root@localhost:3376/lnvps"

12
docker-compose.yaml Normal file
View File

@ -0,0 +1,12 @@
volumes:
db:
services:
db:
image: mariadb
environment:
- "MARIADB_ROOT_PASSWORD=root"
- "MARIADB_DATABASE=lnvps"
ports:
- "3376:3306"
volumes:
- "db:/var/lib/mysql"

View File

@ -0,0 +1,81 @@
create table users
(
id integer unsigned not null auto_increment primary key,
pubkey binary(32) not null,
created timestamp default current_timestamp
);
create unique index ix_user_pubkey on users (pubkey);
create table vm_host
(
id integer unsigned not null auto_increment primary key,
kind smallint unsigned not null,
name varchar(100) not null,
ip varchar(250) not null,
cpu bigint unsigned not null,
memory bigint unsigned not null,
enabled bit(1) not null,
api_token varchar(200) not null
);
create table vm_host_disk
(
id integer unsigned not null auto_increment primary key,
host_id integer unsigned not null,
name varchar(50) not null,
size bigint unsigned not null,
kind smallint unsigned not null,
interface smallint unsigned not null,
enabled bit(1) not null,
constraint fk_vm_host_disk foreign key (host_id) references vm_host (id)
);
create table vm_os_image
(
id integer unsigned not null auto_increment primary key,
name varchar(200) not null,
enabled bit(1) not null
);
create table ip_range
(
id integer unsigned not null auto_increment primary key,
cidr varchar(200) not null,
enabled bit(1) not null
);
create table vm
(
id integer unsigned not null auto_increment primary key,
host_id integer unsigned not null,
user_id integer unsigned not null,
image_id integer unsigned not null,
created timestamp default current_timestamp,
expires timestamp not null,
cpu smallint unsigned not null,
memory bigint unsigned not null,
disk_size bigint unsigned not null,
disk_id integer unsigned not null,
constraint fk_vm_host foreign key (host_id) references vm_host (id),
constraint fk_vm_user foreign key (user_id) references users (id),
constraint fk_vm_image foreign key (image_id) references vm_os_image (id),
constraint fk_vm_host_disk_id foreign key (disk_id) references vm_host_disk (id)
);
create table vm_ip_assignment
(
id integer unsigned not null auto_increment primary key,
vm_id integer unsigned not null,
ip_range_id integer unsigned not null,
constraint fk_vm_ip_assignment_vm foreign key (vm_id) references vm (id),
constraint fk_vm_ip_range foreign key (ip_range_id) references ip_range (id)
);
create table vm_payment
(
id binary(32) not null,
vm_id integer unsigned not null,
created timestamp default current_timestamp,
expires timestamp not null,
amount bigint unsigned not null,
invoice varchar(2048) not null,
is_paid bit(1) not null,
constraint fk_vm_payment_vm foreign key (vm_id) references vm (id)
);

87
src/db.rs Normal file
View File

@ -0,0 +1,87 @@
use chrono::{DateTime, Utc};
/// 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>,
}
/// The type of VM host
pub enum VmHostKind {
Proxmox,
}
/// 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,
}
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,
}
pub enum DiskType {
HDD,
SSD,
}
pub enum DiskInterface {
SATA,
SCSI,
PCIe,
}
pub struct VmOsImage {
pub id: u64,
pub name: String,
pub enabled: bool,
}
pub struct IpRange {
pub id: u64,
pub cidr: String,
pub enabled: bool,
}
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,
}

View File

@ -1,32 +1,26 @@
use config::{Config, File};
use log::info;
use crate::proxmox::{Client, VersionResponse};
use crate::provisioner::Provisioner;
use crate::settings::Settings;
use config::{Config, File};
use sqlx::MySqlPool;
mod settings;
mod db;
mod provisioner;
mod proxmox;
mod settings;
mod vm;
#[tokio::main]
#[rocket::main]
async fn main() -> Result<(), anyhow::Error> {
pretty_env_logger::init();
let config: Settings = Config::builder()
.add_source(File::with_name("config.toml"))
.build()?.try_deserialize()?;
.build()?
.try_deserialize()?;
let client = Client::new(config.server.parse()?)
.with_api_token(
&config.user,
&config.realm,
&config.token_id,
&config.secret,
);
let db = MySqlPool::connect(&config.db).await?;
sqlx::migrate!("./migrations").run(&db).await?;
let provisioner = Provisioner::new(db.clone());
let nodes = client.list_nodes().await.expect("Error listing nodes");
for n in &nodes {
let vms = client.list_vms(&n.name).await?;
for vm in &vms {
}
}
Ok(())
}

19
src/provisioner.rs Normal file
View File

@ -0,0 +1,19 @@
use crate::db;
use crate::vm::VMSpec;
use anyhow::Error;
use sqlx::MySqlPool;
pub struct Provisioner {
db: MySqlPool,
}
impl Provisioner {
pub fn new(db: MySqlPool) -> Self {
Self { db }
}
/// Provision a new VM
pub async fn provision(&self, spec: VMSpec) -> Result<db::Vm, Error> {
todo!()
}
}

View File

@ -1,10 +1,8 @@
use std::fmt::Debug;
use std::ops::Deref;
use anyhow::Error;
use log::info;
use reqwest::{Body, ClientBuilder, Request, RequestBuilder, Url};
use serde::{Deserialize, Deserializer, Serialize};
use reqwest::{Body, ClientBuilder, Url};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serialize};
use std::fmt::Debug;
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct ClientToken {
@ -23,7 +21,8 @@ impl Client {
pub fn new(base: Url) -> Self {
let mut client = ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build().expect("Failed to build client");
.build()
.expect("Failed to build client");
Self {
base,
@ -54,38 +53,50 @@ impl Client {
Ok(rsp.data)
}
pub async fn list_vms(&self, node: &str) -> Result<Vec<VmInfo>, Error> {
let rsp: ResponseBase<Vec<VmInfo>> = self.get(&format!("/api2/json/nodes/{}/qemu", node)).await?;
pub async fn list_vms(&self, node: &str, full: bool) -> Result<Vec<VmInfo>, Error> {
let rsp: ResponseBase<Vec<VmInfo>> =
self.get(&format!("/api2/json/nodes/{node}/qemu")).await?;
Ok(rsp.data)
}
async fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T, Error> {
let rsp = self.client
Ok(self
.client
.get(self.base.join(path)?)
.header("Authorization", format!("PVEAPIToken={}", self.token.password))
.send().await?
.error_for_status()?;
let text = rsp.text().await?;
info!("{}->{}", path, text);
Ok(serde_json::from_str(&text)?)
.header(
"Authorization",
format!("PVEAPIToken={}", self.token.password),
)
.send()
.await?
.json::<T>()
.await
.map_err(|e| Error::new(e))?)
}
async fn post<T: DeserializeOwned, R: Into<Body>>(&self, path: &str, body: R) -> Result<T, Error> {
Ok(
self.client
.post(self.base.join(path)?)
.header("Authorization", format!("PVEAPIToken={}", self.token.password))
.body(body)
.send().await?
.error_for_status()?
.json().await?
)
async fn post<T: DeserializeOwned, R: Into<Body>>(
&self,
path: &str,
body: R,
) -> Result<T, Error> {
Ok(self
.client
.post(self.base.join(path)?)
.header(
"Authorization",
format!("PVEAPIToken={}", self.token.password),
)
.body(body)
.send()
.await?
.error_for_status()?
.json()
.await?)
}
}
#[derive(Deserialize)]
pub struct ResponseBase<T>
{
pub struct ResponseBase<T> {
pub data: T,
}
@ -132,4 +143,12 @@ pub struct VmInfo {
pub status: VmStatus,
#[serde(rename = "vmid")]
pub vm_id: i32,
}
pub cpus: Option<u16>,
#[serde(rename = "maxdisk")]
pub max_disk: Option<u64>,
#[serde(rename = "maxmem")]
pub max_mem: Option<u64>,
pub name: Option<String>,
pub tags: Option<String>,
pub uptime: Option<u64>,
}

View File

@ -2,9 +2,5 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct Settings {
pub server: String,
pub user: String,
pub realm: String,
pub token_id: String,
pub secret: String,
}
pub db: String,
}

11
src/vm.rs Normal file
View File

@ -0,0 +1,11 @@
pub enum DiskType {
SSD,
HDD,
}
pub struct VMSpec {
pub cpu: u16,
pub memory: u64,
pub disk: u64,
pub disk_type: DiskType,
}