added code to create new VM
compiles but not tested yet
This commit is contained in:
parent
363724e5d7
commit
6f9cb36bea
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -1000,7 +1000,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "detee-shared"
|
||||
version = "0.1.0"
|
||||
source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain#fb38352e1b47837b14f32d8df5ae7f6b17202aae"
|
||||
source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain#d0d4622c52efdf74ed6582fbac23a6159986ade3"
|
||||
dependencies = [
|
||||
"bincode 2.0.1",
|
||||
"prost",
|
||||
@ -3779,6 +3779,7 @@ dependencies = [
|
||||
"env_logger",
|
||||
"futures",
|
||||
"log",
|
||||
"nanoid",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
|
@ -20,6 +20,7 @@ tokio-stream = "0.1.17"
|
||||
log = "0.4.27"
|
||||
env_logger = "0.11.8"
|
||||
thiserror = "2.0.12"
|
||||
nanoid = "0.4.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
@ -36,6 +36,7 @@ DEFINE FIELD created_at ON TABLE new_vm_req TYPE datetime;
|
||||
DEFINE FIELD updated_at ON TABLE new_vm_req TYPE datetime;
|
||||
DEFINE FIELD price_per_unit ON TABLE new_vm_req TYPE int;
|
||||
DEFINE FIELD locked_nano ON TABLE new_vm_req TYPE int;
|
||||
DEFINE FIELD error ON TABLE new_vm_req TYPE string;
|
||||
|
||||
DEFINE TABLE active_vm TYPE RELATION FROM account TO vm_node SCHEMAFULL;
|
||||
DEFINE FIELD hostname ON TABLE active_vm TYPE string;
|
||||
|
134
src/db.rs
134
src/db.rs
@ -18,6 +18,13 @@ pub const NEW_VM_REQ: &str = "new_vm_req";
|
||||
pub const UPDATE_VM_REQ: &str = "update_vm_req";
|
||||
pub const DELETED_VM: &str = "deleted_vm";
|
||||
|
||||
pub const ID_ALPHABET: [char; 62] = [
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
|
||||
'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
|
||||
'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
|
||||
'V', 'W', 'X', 'Y', 'Z',
|
||||
];
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("Internal DB error: {0}")]
|
||||
@ -34,6 +41,17 @@ pub async fn init() -> surrealdb::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn upsert_record<SomeRecord: Serialize + 'static>(
|
||||
table: &str,
|
||||
id: &str,
|
||||
my_record: SomeRecord,
|
||||
) -> Result<(), Error> {
|
||||
#[derive(Deserialize)]
|
||||
struct Wrapper {}
|
||||
let _: Option<Wrapper> = DB.create((table, id)).content(my_record).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn migration0(old_data: &old_brain::BrainData) -> surrealdb::Result<()> {
|
||||
let accounts: Vec<Account> = old_data.into();
|
||||
let vm_nodes: Vec<VmNode> = old_data.into();
|
||||
@ -85,6 +103,21 @@ impl Account {
|
||||
}
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub async fn is_banned_by_node(user: &str, node: &str) -> Result<bool, Error> {
|
||||
let ban: Option<Self> = DB
|
||||
.query(format!(
|
||||
"(select operator->ban[0] as ban
|
||||
from vm_node:{node}
|
||||
where operator->ban->account contains account:{user}
|
||||
).ban;"
|
||||
))
|
||||
.await?
|
||||
.take(0)?;
|
||||
Ok(ban.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct VmNode {
|
||||
pub id: RecordId,
|
||||
@ -104,6 +137,24 @@ pub struct VmNode {
|
||||
pub offline_minutes: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct VmNodeResources {
|
||||
pub avail_mem_mb: u32,
|
||||
pub avail_vcpus: u32,
|
||||
pub avail_storage_gbs: u32,
|
||||
pub avail_ipv4: u32,
|
||||
pub avail_ipv6: u32,
|
||||
pub avail_ports: u32,
|
||||
pub max_ports_per_vm: u32,
|
||||
}
|
||||
|
||||
impl VmNodeResources {
|
||||
pub async fn merge(self, node_id: &str) -> Result<(), Error> {
|
||||
let _: Option<VmNode> = DB.update((VM_NODE, node_id)).merge(self).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl VmNode {
|
||||
pub async fn register(self) -> Result<(), Error> {
|
||||
let _: Option<VmNode> = DB.upsert(self.id.clone()).content(self).await?;
|
||||
@ -176,6 +227,82 @@ pub struct NewVmReq {
|
||||
pub created_at: Datetime,
|
||||
pub price_per_unit: u64,
|
||||
pub locked_nano: u64,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
impl NewVmReq {
|
||||
pub async fn submit_error(id: &str, error: String) -> Result<(), Error> {
|
||||
#[derive(Serialize)]
|
||||
struct NewVmError {
|
||||
error: String,
|
||||
}
|
||||
let _: Option<VmNode> = DB.update((NEW_VM_REQ, id)).merge(NewVmError { error }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn submit(self) -> Result<(), Error> {
|
||||
let _: Option<Self> = DB.create(self.id.clone()).content(self).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// first string is the vm_id
|
||||
pub enum NewVmResp {
|
||||
// TODO: find a more elegant way to do this than importing gRPC in the DB module
|
||||
// https://en.wikipedia.org/wiki/Dependency_inversion_principle
|
||||
Args(String, detee_shared::snp::pb::vm_proto::MeasurementArgs),
|
||||
Error(String, String),
|
||||
}
|
||||
|
||||
impl NewVmResp {
|
||||
pub async fn listen(vm_id: &str) -> Result<NewVmResp, Error> {
|
||||
let mut resp = DB
|
||||
.query(format!("live select * from {NEW_VM_REQ} where id = {NEW_VM_REQ}:{vm_id};"))
|
||||
.query(format!(
|
||||
"live select * from measurement_args where id = measurement_args:{vm_id};"
|
||||
))
|
||||
.await?;
|
||||
let mut live_stream1 = resp.stream::<Notification<NewVmReq>>(0)?;
|
||||
let mut live_stream2 =
|
||||
resp.stream::<Notification<detee_shared::snp::pb::vm_proto::MeasurementArgs>>(1)?;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
new_vm_req_notif = live_stream1.next() => {
|
||||
if let Some(new_vm_req_notif) = new_vm_req_notif {
|
||||
match new_vm_req_notif {
|
||||
Ok(new_vm_req_notif) => {
|
||||
match new_vm_req_notif.action {
|
||||
surrealdb::Action::Update => {
|
||||
if !new_vm_req_notif.data.error.is_empty() {
|
||||
return Ok(Self::Error(vm_id.to_string(), new_vm_req_notif.data.error));
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
args_notif = live_stream2.next() => {
|
||||
if let Some(args_notif) = args_notif {
|
||||
match args_notif {
|
||||
Ok(args_notif) => {
|
||||
match args_notif.action {
|
||||
surrealdb::Action::Create => {
|
||||
return Ok(Self::Args(vm_id.to_string(), args_notif.data));
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
},
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@ -219,7 +346,9 @@ pub struct UpdateVmReq {
|
||||
pub locked_nano: u64,
|
||||
}
|
||||
|
||||
pub async fn listen_for_node<T: Into<DaemonNotification> + std::marker::Unpin + for<'de> Deserialize<'de>>(
|
||||
pub async fn listen_for_node<
|
||||
T: Into<DaemonNotification> + std::marker::Unpin + for<'de> Deserialize<'de>,
|
||||
>(
|
||||
node: &str,
|
||||
tx: Sender<DaemonNotification>,
|
||||
) -> Result<(), Error> {
|
||||
@ -230,7 +359,7 @@ pub async fn listen_for_node<T: Into<DaemonNotification> + std::marker::Unpin +
|
||||
wat => {
|
||||
log::error!("listen_for_node: T has type {wat}");
|
||||
String::from("wat")
|
||||
},
|
||||
}
|
||||
};
|
||||
let mut resp =
|
||||
DB.query(format!("live select * from {table_name} where out = vm_node:{node};")).await?;
|
||||
@ -346,7 +475,6 @@ impl ActiveVm {
|
||||
pub fn price_per_minute(&self) -> u64 {
|
||||
self.total_units() * self.price_per_unit
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
189
src/grpc.rs
189
src/grpc.rs
@ -13,9 +13,11 @@ use detee_shared::{
|
||||
*,
|
||||
},
|
||||
};
|
||||
use nanoid::nanoid;
|
||||
|
||||
use log::info;
|
||||
use std::pin::Pin;
|
||||
use surrealdb::RecordId;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_stream::{Stream, StreamExt};
|
||||
@ -29,6 +31,31 @@ impl From<db::Account> for AccountBalance {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NewVmReq> for db::NewVmReq {
|
||||
fn from(new_vm_req: NewVmReq) -> Self {
|
||||
Self {
|
||||
id: RecordId::from((db::NEW_VM_REQ, nanoid!(40, &db::ID_ALPHABET))),
|
||||
hostname: new_vm_req.hostname,
|
||||
admin: RecordId::from((db::ACCOUNT, new_vm_req.admin_pubkey)),
|
||||
vm_node: RecordId::from((db::VM_NODE, new_vm_req.node_pubkey)),
|
||||
extra_ports: new_vm_req.extra_ports,
|
||||
public_ipv4: new_vm_req.public_ipv4,
|
||||
public_ipv6: new_vm_req.public_ipv6,
|
||||
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,
|
||||
price_per_unit: new_vm_req.price_per_unit,
|
||||
locked_nano: new_vm_req.locked_nano,
|
||||
created_at: surrealdb::sql::Datetime::default(),
|
||||
error: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<db::NewVmReq> for NewVmReq {
|
||||
fn from(new_vm_req: db::NewVmReq) -> Self {
|
||||
Self {
|
||||
@ -52,6 +79,19 @@ impl From<db::NewVmReq> for NewVmReq {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<db::NewVmResp> for NewVmResp {
|
||||
fn from(resp: db::NewVmResp) -> Self {
|
||||
match resp {
|
||||
// TODO: This will require a small architecture change to pass MeasurementArgs from
|
||||
// Daemon to CLI
|
||||
db::NewVmResp::Args(uuid, args) => {
|
||||
NewVmResp { uuid, error: String::new(), args: Some(args) }
|
||||
}
|
||||
db::NewVmResp::Error(uuid, error) => NewVmResp { uuid, error, args: None },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<db::UpdateVmReq> for UpdateVmReq {
|
||||
fn from(update_vm_req: db::UpdateVmReq) -> Self {
|
||||
Self {
|
||||
@ -181,6 +221,20 @@ impl From<db::AppNodeWithReports> for AppNodeListResp {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VmNodeResources> for db::VmNodeResources {
|
||||
fn from(res: VmNodeResources) -> Self {
|
||||
Self {
|
||||
avail_mem_mb: res.avail_memory_mb,
|
||||
avail_vcpus: res.avail_vcpus,
|
||||
avail_storage_gbs: res.avail_storage_gb,
|
||||
avail_ipv4: res.avail_ipv4,
|
||||
avail_ipv6: res.avail_ipv6,
|
||||
avail_ports: res.avail_ports,
|
||||
max_ports_per_vm: res.max_ports_per_vm,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BrainVmDaemonForReal {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
@ -273,52 +327,54 @@ impl BrainVmDaemon for BrainVmDaemonForReal {
|
||||
|
||||
async fn daemon_messages(
|
||||
&self,
|
||||
_req: Request<Streaming<VmDaemonMessage>>,
|
||||
req: Request<Streaming<VmDaemonMessage>>,
|
||||
) -> Result<Response<Empty>, Status> {
|
||||
todo!();
|
||||
// let mut req_stream = req.into_inner();
|
||||
// let pubkey: String;
|
||||
// if let Some(Ok(msg)) = req_stream.next().await {
|
||||
// log::debug!("demon_messages received the following auth message: {:?}", msg.msg);
|
||||
// if let Some(vm_daemon_message::Msg::Auth(auth)) = msg.msg {
|
||||
// pubkey = auth.pubkey.clone();
|
||||
// check_sig_from_parts(
|
||||
// &pubkey,
|
||||
// &auth.timestamp,
|
||||
// &format!("{:?}", auth.contracts),
|
||||
// &auth.signature,
|
||||
// )?;
|
||||
// } else {
|
||||
// return Err(Status::unauthenticated(
|
||||
// "Could not authenticate the daemon: could not extract auth signature",
|
||||
// ));
|
||||
// }
|
||||
// } else {
|
||||
// return Err(Status::unauthenticated("Could not authenticate the daemon"));
|
||||
// }
|
||||
let mut req_stream = req.into_inner();
|
||||
let pubkey: String;
|
||||
if let Some(Ok(msg)) = req_stream.next().await {
|
||||
log::debug!("demon_messages received the following auth message: {:?}", msg.msg);
|
||||
if let Some(vm_daemon_message::Msg::Auth(auth)) = msg.msg {
|
||||
pubkey = auth.pubkey.clone();
|
||||
check_sig_from_parts(
|
||||
&pubkey,
|
||||
&auth.timestamp,
|
||||
&format!("{:?}", auth.contracts),
|
||||
&auth.signature,
|
||||
)?;
|
||||
} else {
|
||||
return Err(Status::unauthenticated(
|
||||
"Could not authenticate the daemon: could not extract auth signature",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Status::unauthenticated("Could not authenticate the daemon"));
|
||||
}
|
||||
|
||||
// // info!("Received a message from daemon {pubkey}: {daemon_message:?}");
|
||||
// while let Some(daemon_message) = req_stream.next().await {
|
||||
// match daemon_message {
|
||||
// Ok(msg) => match msg.msg {
|
||||
// Some(vm_daemon_message::Msg::NewVmResp(new_vm_resp)) => {
|
||||
// self.data.submit_newvm_resp(new_vm_resp).await;
|
||||
// }
|
||||
// Some(vm_daemon_message::Msg::UpdateVmResp(update_vm_resp)) => {
|
||||
// self.data.submit_updatevm_resp(update_vm_resp).await;
|
||||
// }
|
||||
// Some(vm_daemon_message::Msg::VmNodeResources(node_resources)) => {
|
||||
// self.data.submit_node_resources(node_resources);
|
||||
// }
|
||||
// _ => {}
|
||||
// },
|
||||
// Err(e) => {
|
||||
// log::warn!("Daemon disconnected: {e:?}");
|
||||
// self.data.del_daemon_tx(&pubkey);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Ok(Response::new(Empty {}))
|
||||
while let Some(daemon_message) = req_stream.next().await {
|
||||
match daemon_message {
|
||||
Ok(msg) => match msg.msg {
|
||||
Some(vm_daemon_message::Msg::NewVmResp(new_vm_resp)) => {
|
||||
if !new_vm_resp.error.is_empty() {
|
||||
} else {
|
||||
db::upsert_record("measurement_args", &new_vm_resp.uuid, new_vm_resp.args).await?;
|
||||
}
|
||||
}
|
||||
Some(vm_daemon_message::Msg::UpdateVmResp(update_vm_resp)) => {
|
||||
todo!();
|
||||
// self.data.submit_updatevm_resp(update_vm_resp).await;
|
||||
}
|
||||
Some(vm_daemon_message::Msg::VmNodeResources(node_resources)) => {
|
||||
let node_resources: db::VmNodeResources = node_resources.into();
|
||||
node_resources.merge(&pubkey).await?;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(e) => {
|
||||
log::warn!("Daemon disconnected: {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Response::new(Empty {}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,30 +551,27 @@ impl BrainVmCli for BrainVmCliForReal {
|
||||
async fn new_vm(&self, req: Request<NewVmReq>) -> Result<Response<NewVmResp>, Status> {
|
||||
let req = check_sig_from_req(req)?;
|
||||
info!("New VM requested via CLI: {req:?}");
|
||||
todo!();
|
||||
// if self
|
||||
// .data
|
||||
// .is_user_banned_by_node(&req.admin_pubkey, &req.node_pubkey)
|
||||
// {
|
||||
// return Err(Status::permission_denied(
|
||||
// "This operator banned you. What did you do?",
|
||||
// ));
|
||||
// }
|
||||
// let admin_pubkey = req.admin_pubkey.clone();
|
||||
// let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel();
|
||||
// self.data.submit_newvm_req(req, oneshot_tx).await;
|
||||
// match oneshot_rx.await {
|
||||
// Ok(response) => {
|
||||
// info!("Sending VM confirmation to {admin_pubkey}: {response:?}");
|
||||
// Ok(Response::new(response))
|
||||
// }
|
||||
// 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.",
|
||||
// ))
|
||||
// }
|
||||
// }
|
||||
if db::Account::is_banned_by_node(&req.admin_pubkey, &req.node_pubkey).await? {
|
||||
return Err(Status::permission_denied("This operator banned you. What did you do?"));
|
||||
}
|
||||
|
||||
let new_vm_req: db::NewVmReq = req.into();
|
||||
let id = new_vm_req.id.key().to_string();
|
||||
let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
let _ = oneshot_tx.send(db::NewVmResp::listen(&id).await);
|
||||
});
|
||||
new_vm_req.submit().await?;
|
||||
|
||||
match oneshot_rx.await {
|
||||
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.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_vm(&self, req: Request<UpdateVmReq>) -> Result<Response<UpdateVmResp>, Status> {
|
||||
|
Loading…
Reference in New Issue
Block a user