feat: patch vm
This commit is contained in:
parent
e51bd2722e
commit
265d91dd83
@ -93,6 +93,9 @@ pub trait LNVpsDb: Sync + Send {
|
||||
/// Delete a VM by id
|
||||
async fn delete_vm(&self, vm_id: u64) -> Result<()>;
|
||||
|
||||
/// Update a VM
|
||||
async fn update_vm(&self, vm: &Vm) -> Result<()>;
|
||||
|
||||
/// List VM ip assignments
|
||||
async fn insert_vm_ip_assignment(&self, ip_assignment: &VmIpAssignment) -> Result<u64>;
|
||||
|
||||
|
@ -31,10 +31,11 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
}
|
||||
|
||||
async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result<u64> {
|
||||
let res = sqlx::query("insert ignore into users(pubkey,contact_nip17) values(?,1) returning id")
|
||||
.bind(pubkey.as_slice())
|
||||
.fetch_optional(&self.db)
|
||||
.await?;
|
||||
let res =
|
||||
sqlx::query("insert ignore into users(pubkey,contact_nip17) values(?,1) 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())
|
||||
@ -249,6 +250,23 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_vm(&self, vm: &Vm) -> Result<()> {
|
||||
sqlx::query("update vm set image_id=?,template_id=?,ssh_key_id=?,expires=?,cpu=?,memory=?,disk_size=?,disk_id=? where id=?")
|
||||
.bind(vm.image_id)
|
||||
.bind(vm.template_id)
|
||||
.bind(vm.ssh_key_id)
|
||||
.bind(vm.expires)
|
||||
.bind(vm.cpu)
|
||||
.bind(vm.memory)
|
||||
.bind(vm.disk_size)
|
||||
.bind(vm.disk_id)
|
||||
.bind(vm.id)
|
||||
.execute(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn insert_vm_ip_assignment(&self, ip_assignment: &VmIpAssignment) -> Result<u64> {
|
||||
Ok(sqlx::query(
|
||||
"insert into vm_ip_assignment(vm_id,ip_range_id,ip) values(?, ?, ?) returning id",
|
||||
|
37
src/api.rs
37
src/api.rs
@ -30,7 +30,8 @@ pub fn routes() -> Vec<Route> {
|
||||
v1_start_vm,
|
||||
v1_stop_vm,
|
||||
v1_restart_vm,
|
||||
v1_terminal_proxy
|
||||
v1_terminal_proxy,
|
||||
v1_patch_vm
|
||||
]
|
||||
}
|
||||
|
||||
@ -71,6 +72,11 @@ struct ApiVmStatus {
|
||||
pub status: VmState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct VMPatchRequest {
|
||||
pub ssh_key_id: Option<u64>,
|
||||
}
|
||||
|
||||
#[get("/api/v1/vm")]
|
||||
async fn v1_list_vms(
|
||||
auth: Nip98Auth,
|
||||
@ -123,6 +129,35 @@ async fn v1_get_vm(
|
||||
})
|
||||
}
|
||||
|
||||
#[patch("/api/v1/vm/<id>", data = "<data>", format = "json")]
|
||||
async fn v1_patch_vm(
|
||||
auth: Nip98Auth,
|
||||
db: &State<Box<dyn LNVpsDb>>,
|
||||
provisioner: &State<Box<dyn Provisioner>>,
|
||||
id: u64,
|
||||
data: Json<VMPatchRequest>,
|
||||
) -> ApiResult<()> {
|
||||
let pubkey = auth.event.pubkey.to_bytes();
|
||||
let uid = db.upsert_user(&pubkey).await?;
|
||||
let mut vm = db.get_vm(id).await?;
|
||||
if vm.user_id != uid {
|
||||
return ApiData::err("VM doesnt belong to you");
|
||||
}
|
||||
|
||||
if let Some(k) = data.ssh_key_id {
|
||||
let ssh_key = db.get_user_ssh_key(k).await?;
|
||||
if ssh_key.user_id != uid {
|
||||
return ApiData::err("SSH key doesnt belong to you");
|
||||
}
|
||||
vm.ssh_key_id = ssh_key.id;
|
||||
}
|
||||
|
||||
db.update_vm(&vm).await?;
|
||||
provisioner.patch_vm(vm.id).await?;
|
||||
|
||||
ApiData::ok(())
|
||||
}
|
||||
|
||||
#[get("/api/v1/image")]
|
||||
async fn v1_list_vm_images(db: &State<Box<dyn LNVpsDb>>) -> ApiResult<Vec<VmOsImage>> {
|
||||
let vms = db.list_os_image().await?;
|
||||
|
@ -1,8 +1,8 @@
|
||||
use crate::exchange::{ExchangeRateCache, Ticker};
|
||||
use crate::host::get_host_client;
|
||||
use crate::host::proxmox::{
|
||||
CreateVm, DownloadUrlRequest, ProxmoxClient, ResizeDiskRequest, StorageContent, VmBios,
|
||||
VmConfig,
|
||||
ConfigureVm, CreateVm, DownloadUrlRequest, ProxmoxClient, ResizeDiskRequest, StorageContent,
|
||||
VmBios, VmConfig,
|
||||
};
|
||||
use crate::provisioner::Provisioner;
|
||||
use crate::router::Router;
|
||||
@ -72,6 +72,76 @@ impl LNVpsProvisioner {
|
||||
bail!("No image storage found");
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_vm_config(&self, vm: &Vm) -> Result<VmConfig> {
|
||||
let ssh_key = self.db.get_user_ssh_key(vm.ssh_key_id).await?;
|
||||
|
||||
let mut ips = self.db.list_vm_ip_assignments(vm.id).await?;
|
||||
if ips.is_empty() {
|
||||
ips = self.allocate_ips(vm.id).await?;
|
||||
}
|
||||
|
||||
// load ranges
|
||||
for ip in &mut ips {
|
||||
ip.hydrate_up(&self.db).await?;
|
||||
}
|
||||
|
||||
let mut ip_config = ips
|
||||
.iter()
|
||||
.map_while(|ip| {
|
||||
if let Ok(net) = ip.ip.parse::<IpNetwork>() {
|
||||
Some(match net {
|
||||
IpNetwork::V4(addr) => {
|
||||
format!(
|
||||
"ip={},gw={}",
|
||||
addr,
|
||||
ip.ip_range.as_ref().map(|r| &r.gateway).unwrap()
|
||||
)
|
||||
}
|
||||
IpNetwork::V6(addr) => format!("ip6={}", addr),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
ip_config.push("ip6=auto".to_string());
|
||||
|
||||
let mut net = vec![
|
||||
format!("virtio={}", vm.mac_address),
|
||||
format!("bridge={}", self.config.bridge),
|
||||
];
|
||||
if let Some(t) = self.config.vlan {
|
||||
net.push(format!("tag={}", t));
|
||||
}
|
||||
|
||||
let drives = self.db.list_host_disks(vm.host_id).await?;
|
||||
let drive = if let Some(d) = drives.iter().find(|d| d.enabled) {
|
||||
d
|
||||
} else {
|
||||
bail!("No host drive found!")
|
||||
};
|
||||
|
||||
Ok(VmConfig {
|
||||
cpu: Some(self.config.cpu.clone()),
|
||||
kvm: Some(self.config.kvm),
|
||||
ip_config: Some(ip_config.join(",")),
|
||||
machine: Some(self.config.machine.clone()),
|
||||
net: Some(net.join(",")),
|
||||
os_type: Some(self.config.os_type.clone()),
|
||||
on_boot: Some(true),
|
||||
bios: Some(VmBios::OVMF),
|
||||
boot: Some("order=scsi0".to_string()),
|
||||
cores: Some(vm.cpu as i32),
|
||||
memory: Some((vm.memory / 1024 / 1024).to_string()),
|
||||
scsi_hw: Some("virtio-scsi-pci".to_string()),
|
||||
serial_0: Some("socket".to_string()),
|
||||
scsi_1: Some(format!("{}:cloudinit", &drive.name)),
|
||||
ssh_keys: Some(urlencoding::encode(&ssh_key.key_data).to_string()),
|
||||
efi_disk_0: Some(format!("{}:0,efitype=4m", &drive.name)),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -302,55 +372,6 @@ impl Provisioner for LNVpsProvisioner {
|
||||
let vm = self.db.get_vm(vm_id).await?;
|
||||
let host = self.db.get_host(vm.host_id).await?;
|
||||
let client = get_host_client(&host)?;
|
||||
|
||||
let mut ips = self.db.list_vm_ip_assignments(vm.id).await?;
|
||||
if ips.is_empty() {
|
||||
ips = self.allocate_ips(vm.id).await?;
|
||||
}
|
||||
|
||||
// load ranges
|
||||
for ip in &mut ips {
|
||||
ip.hydrate_up(&self.db).await?;
|
||||
}
|
||||
|
||||
let mut ip_config = ips
|
||||
.iter()
|
||||
.map_while(|ip| {
|
||||
if let Ok(net) = ip.ip.parse::<IpNetwork>() {
|
||||
Some(match net {
|
||||
IpNetwork::V4(addr) => {
|
||||
format!(
|
||||
"ip={},gw={}",
|
||||
addr,
|
||||
ip.ip_range.as_ref().map(|r| &r.gateway).unwrap()
|
||||
)
|
||||
}
|
||||
IpNetwork::V6(addr) => format!("ip6={}", addr),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
ip_config.push("ip6=auto".to_string());
|
||||
|
||||
let drives = self.db.list_host_disks(vm.host_id).await?;
|
||||
let drive = if let Some(d) = drives.iter().find(|d| d.enabled) {
|
||||
d
|
||||
} else {
|
||||
bail!("No host drive found!")
|
||||
};
|
||||
|
||||
let ssh_key = self.db.get_user_ssh_key(vm.ssh_key_id).await?;
|
||||
|
||||
let mut net = vec![
|
||||
format!("virtio={}", vm.mac_address),
|
||||
format!("bridge={}", self.config.bridge),
|
||||
];
|
||||
if let Some(t) = self.config.vlan {
|
||||
net.push(format!("tag={}", t));
|
||||
}
|
||||
|
||||
let vm_id = 100 + vm.id as i32;
|
||||
|
||||
// create VM
|
||||
@ -358,25 +379,7 @@ impl Provisioner for LNVpsProvisioner {
|
||||
.create_vm(CreateVm {
|
||||
node: host.name.clone(),
|
||||
vm_id,
|
||||
config: VmConfig {
|
||||
on_boot: Some(true),
|
||||
bios: Some(VmBios::OVMF),
|
||||
boot: Some("order=scsi0".to_string()),
|
||||
cores: Some(vm.cpu as i32),
|
||||
cpu: Some(self.config.cpu.clone()),
|
||||
kvm: Some(self.config.kvm),
|
||||
ip_config: Some(ip_config.join(",")),
|
||||
machine: Some(self.config.machine.clone()),
|
||||
memory: Some((vm.memory / 1024 / 1024).to_string()),
|
||||
net: Some(net.join(",")),
|
||||
os_type: Some(self.config.os_type.clone()),
|
||||
scsi_1: Some(format!("{}:cloudinit", &drive.name)),
|
||||
scsi_hw: Some("virtio-scsi-pci".to_string()),
|
||||
ssh_keys: Some(urlencoding::encode(&ssh_key.key_data).to_string()),
|
||||
efi_disk_0: Some(format!("{}:0,efitype=4m", &drive.name)),
|
||||
serial_0: Some("socket".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
config: self.get_vm_config(&vm).await?,
|
||||
})
|
||||
.await?;
|
||||
client.wait_for_task(&t_create).await?;
|
||||
@ -394,6 +397,12 @@ impl Provisioner for LNVpsProvisioner {
|
||||
)
|
||||
.await?;
|
||||
|
||||
let drives = self.db.list_host_disks(vm.host_id).await?;
|
||||
let drive = if let Some(d) = drives.iter().find(|d| d.enabled) {
|
||||
d
|
||||
} else {
|
||||
bail!("No host drive found!")
|
||||
};
|
||||
let cmd = format!(
|
||||
"/usr/sbin/qm set {} --scsi0 {}:0,import-from=/var/lib/vz/template/iso/{}",
|
||||
vm_id,
|
||||
@ -515,4 +524,28 @@ impl Provisioner for LNVpsProvisioner {
|
||||
ws.send(Message::Text("1:86:24:".to_string())).await?;
|
||||
Ok(ws)
|
||||
}
|
||||
|
||||
async fn patch_vm(&self, vm_id: u64) -> Result<()> {
|
||||
let vm = self.db.get_vm(vm_id).await?;
|
||||
let host = self.db.get_host(vm.host_id).await?;
|
||||
let client = get_host_client(&host)?;
|
||||
let host_vm_id = vm.id + 100;
|
||||
|
||||
let t = client
|
||||
.configure_vm(ConfigureVm {
|
||||
node: host.name.clone(),
|
||||
vm_id: host_vm_id as i32,
|
||||
current: None,
|
||||
snapshot: None,
|
||||
config: VmConfig {
|
||||
scsi_0: None,
|
||||
scsi_1: None,
|
||||
efi_disk_0: None,
|
||||
..self.get_vm_config(&vm).await?
|
||||
},
|
||||
})
|
||||
.await?;
|
||||
client.wait_for_task(&t).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -50,4 +50,7 @@ pub trait Provisioner: Send + Sync {
|
||||
&self,
|
||||
vm_id: u64,
|
||||
) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>>;
|
||||
|
||||
/// Re-Configure VM
|
||||
async fn patch_vm(&self, vm_id: u64) -> Result<()>;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user