From e2b67c32d90a21945c64e3d4ee6ba1e5c2f4baaf Mon Sep 17 00:00:00 2001 From: ghe0 Date: Tue, 6 May 2025 04:20:51 +0300 Subject: [PATCH] WIP: adding vm updates I will overwrite this commit when it's working --- src/db/vm.rs | 92 ++++++++++++++++++++++++-------- src/grpc/types.rs | 20 +++++++ src/grpc/vm.rs | 39 ++++++++++++-- tests/common/prepare_test_env.rs | 6 +-- 4 files changed, 128 insertions(+), 29 deletions(-) diff --git a/src/db/vm.rs b/src/db/vm.rs index 8d7ca71..6b0ce13 100644 --- a/src/db/vm.rs +++ b/src/db/vm.rs @@ -2,7 +2,7 @@ use std::str::FromStr; use std::time::Duration; use super::Error; -use crate::constants::{ACCOUNT, ACTIVE_VM, DELETED_VM, NEW_VM_REQ, VM_NODE}; +use crate::constants::{ACCOUNT, ACTIVE_VM, DELETED_VM, NEW_VM_REQ, UPDATE_VM_REQ, VM_NODE}; use crate::db::general::Report; use crate::old_brain; use serde::{Deserialize, Serialize}; @@ -269,6 +269,28 @@ pub struct ActiveVm { } impl ActiveVm { + /// total hardware units of this VM + fn total_units(&self) -> u64 { + // TODO: Optimize this based on price of hardware. + // I tried, but this can be done better. + // Storage cost should also be based on tier + (self.vcpus as u64 * 10) + + ((self.memory_mb + 256) as u64 / 200) + + (self.disk_size_gb as u64 / 10) + + (!self.public_ipv4.is_empty() as u64 * 10) + } + + /// Returns price per minute in nanoLP + pub fn price_per_minute(&self) -> u64 { + self.total_units() * self.price_per_unit + } + + pub async fn get_by_uuid(db: &Surreal, uuid: &str) -> Result, Error> { + let contract: Option = + db.query(format!("select * from {ACTIVE_VM}:{uuid};")).await?.take(0)?; + Ok(contract) + } + pub async fn activate( db: &Surreal, id: &str, @@ -327,6 +349,25 @@ impl ActiveVm { NewVmReq::delete(db, id).await?; Ok(()) } + + pub async fn change_hostname( + db: &Surreal, + id: &str, + new_hostname: &str, + ) -> Result { + let contract: Option = db + .query(format!( + "UPDATE {ACTIVE_VM}:{id} SET hostname = '{new_hostname}' RETURN BEFORE;" + )) + .await? + .take(0)?; + if let Some(contract) = contract { + if contract.hostname != new_hostname { + return Ok(true); + } + } + Ok(false) + } } #[derive(Debug, Serialize, Deserialize)] @@ -344,8 +385,35 @@ pub struct UpdateVmReq { pub kernel_sha: String, pub kernel_url: String, pub created_at: Datetime, - pub price_per_unit: u64, - pub locked_nano: u64, + pub error: String, +} + +impl UpdateVmReq { + /// returns None if VM does not exist + /// returns Some(false) if hw update is not needed + /// returns Some(true) if hw update is needed and got submitted + /// returns error if something happened with the DB + pub async fn request_hw_update(mut self, db: &Surreal) -> Result, Error> { + let contract = ActiveVm::get_by_uuid(db, &self.id.key().to_string()).await?; + + if contract.is_none() { + return Ok(None); + } + let contract = contract.unwrap(); + // this is needed cause TryFrom does not support await + self.vm_node = contract.vm_node; + + if !((self.vcpus != 0 && contract.vcpus != self.vcpus) + || (self.memory_mb != 0 && contract.memory_mb != self.memory_mb) + || (!self.dtrfs_sha.is_empty() && contract.dtrfs_sha != self.dtrfs_sha) + || (self.disk_size_gb != 0 && contract.disk_size_gb != self.disk_size_gb)) + { + return Ok(Some(false)); + } + + let _: Vec = db.insert(UPDATE_VM_REQ).relation(self).await?; + Ok(Some(true)) + } } #[derive(Debug, Serialize, Deserialize)] @@ -431,24 +499,6 @@ impl DeletedVm { } } -impl ActiveVm { - /// total hardware units of this VM - fn total_units(&self) -> u64 { - // TODO: Optimize this based on price of hardware. - // I tried, but this can be done better. - // Storage cost should also be based on tier - (self.vcpus as u64 * 10) - + ((self.memory_mb + 256) as u64 / 200) - + (self.disk_size_gb as u64 / 10) - + (!self.public_ipv4.is_empty() as u64 * 10) - } - - /// Returns price per minute in nanoLP - pub fn price_per_minute(&self) -> u64 { - self.total_units() * self.price_per_unit - } -} - #[derive(Debug, Serialize, Deserialize)] pub struct ActiveVmWithNode { pub id: RecordId, diff --git a/src/grpc/types.rs b/src/grpc/types.rs index 8690908..bef958c 100644 --- a/src/grpc/types.rs +++ b/src/grpc/types.rs @@ -74,6 +74,26 @@ impl From for NewVmResp { } } +impl From for db::UpdateVmReq { + fn from(new_vm_req: UpdateVmReq) -> Self { + Self { + id: RecordId::from((NEW_VM_REQ, nanoid!(40, &ID_ALPHABET))), + admin: RecordId::from((ACCOUNT, new_vm_req.admin_pubkey)), + // vm_node gets modified later, and only if the db::UpdateVmReq is required + vm_node: RecordId::from((VM_NODE, String::new())), + disk_size_gb: new_vm_req.disk_size_gb, + vcpus: new_vm_req.vcpus, + memory_mb: new_vm_req.memory_mb, + kernel_url: new_vm_req.kernel_url, + kernel_sha: new_vm_req.kernel_sha, + dtrfs_url: new_vm_req.dtrfs_url, + dtrfs_sha: new_vm_req.dtrfs_sha, + created_at: surrealdb::sql::Datetime::default(), + error: String::new(), + } + } +} + impl From for UpdateVmReq { fn from(update_vm_req: db::UpdateVmReq) -> Self { Self { diff --git a/src/grpc/vm.rs b/src/grpc/vm.rs index 49f9eff..313abdb 100644 --- a/src/grpc/vm.rs +++ b/src/grpc/vm.rs @@ -207,7 +207,9 @@ impl BrainVmCli for VmCliServer { async fn new_vm(&self, req: Request) -> Result, Status> { let req = check_sig_from_req(req)?; info!("New VM requested via CLI: {req:?}"); - if db::general::Account::is_banned_by_node(&self.db, &req.admin_pubkey, &req.node_pubkey).await? { + if db::general::Account::is_banned_by_node(&self.db, &req.admin_pubkey, &req.node_pubkey) + .await? + { return Err(Status::permission_denied("This operator banned you. What did you do?")); } @@ -223,12 +225,14 @@ impl BrainVmCli for VmCliServer { new_vm_req.submit(&self.db).await?; match oneshot_rx.await { - Ok(Err(db::Error::TimeOut(_))) => Err(Status::deadline_exceeded("Request failed due to timeout. Please try again later or contact the DeTEE devs team.")), + Ok(Err(db::Error::TimeOut(_))) => Err(Status::deadline_exceeded( + "Network timeout. Please try again later or contact the DeTEE devs team.", + )), Ok(new_vm_resp) => Ok(Response::new(new_vm_resp?.into())), Err(e) => { log::error!("Something weird happened. Reached error {e:?}"); Err(Status::unknown( - "Request failed due to unknown error. Please try again or contact the DeTEE devs team.", + "Unknown error. Please try again or contact the DeTEE devs team.", )) } } @@ -237,7 +241,34 @@ impl BrainVmCli for VmCliServer { async fn update_vm(&self, req: Request) -> Result, Status> { let req = check_sig_from_req(req)?; info!("Update VM requested via CLI: {req:?}"); - todo!(); + + let db_req: db::UpdateVmReq = req.clone().into(); + let hw_change_submitted = db_req.request_hw_update(&self.db).await?; + + if hw_change_submitted.is_none() { + return Ok(Response::new(UpdateVmResp { + uuid: req.uuid.clone(), + error: "VM Contract does not exist.".to_string(), + args: None, + })); + } + let hw_change_submitted = hw_change_submitted.unwrap(); + + let mut hostname_changed = false; + if !req.hostname.is_empty() { + hostname_changed = + db::ActiveVm::change_hostname(&self.db, &req.uuid, &req.hostname).await?; + } + + if !hw_change_submitted && !hostname_changed { + return Ok(Response::new(UpdateVmResp { + uuid: req.uuid.clone(), + error: "No modification required".to_string(), + args: None, + })); + } + + todo!("process update response"); // let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel(); // self.data.submit_updatevm_req(req, oneshot_tx).await; // match oneshot_rx.await { diff --git a/tests/common/prepare_test_env.rs b/tests/common/prepare_test_env.rs index d3e8b06..85f633e 100644 --- a/tests/common/prepare_test_env.rs +++ b/tests/common/prepare_test_env.rs @@ -5,10 +5,8 @@ use dotenv::dotenv; use hyper_util::rt::TokioIo; use std::net::SocketAddr; use std::sync::Arc; -use surreal_brain::grpc::{ - general::GeneralCliServer, - vm::{VmCliServer, VmDaemonServer}, -}; +use surreal_brain::grpc::general::GeneralCliServer; +use surreal_brain::grpc::vm::{VmCliServer, VmDaemonServer}; use surrealdb::engine::remote::ws::Client; use surrealdb::Surreal; use tokio::io::DuplexStream;