Compare commits
	
		
			3 Commits
		
	
	
		
			57d3807a17
			...
			810c1b1966
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 810c1b1966 | |||
| 16fd64ac13 | |||
| 4295b3ae1d | 
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1000,7 +1000,7 @@ dependencies = [ | |||||||
| [[package]] | [[package]] | ||||||
| name = "detee-shared" | name = "detee-shared" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain#d6ca058d2de78b5257517034bca2b2c7d5929db8" | source = "git+ssh://git@gitea.detee.cloud/testnet/proto?branch=surreal_brain_app#da0f3269a31e0ebfb7328e2115e212aabe4d984a" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bincode 2.0.1", |  "bincode 2.0.1", | ||||||
|  "prost", |  "prost", | ||||||
|  | |||||||
| @ -13,7 +13,7 @@ serde_yaml = "0.9.34" | |||||||
| surrealdb = "2.2.2" | surrealdb = "2.2.2" | ||||||
| tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] } | tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] } | ||||||
| tonic = { version = "0.12", features = ["tls"] } | tonic = { version = "0.12", features = ["tls"] } | ||||||
| detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto", branch = "surreal_brain" } | detee-shared = { git = "ssh://git@gitea.detee.cloud/testnet/proto", branch = "surreal_brain_app" } | ||||||
| ed25519-dalek = "2.1.1" | ed25519-dalek = "2.1.1" | ||||||
| bs58 = "0.5.1" | bs58 = "0.5.1" | ||||||
| tokio-stream = "0.1.17" | tokio-stream = "0.1.17" | ||||||
|  | |||||||
							
								
								
									
										91
									
								
								final_tables.surql
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										91
									
								
								final_tables.surql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | |||||||
|  | DEFINE TABLE account SCHEMAFULL; | ||||||
|  | DEFINE FIELD balance ON TABLE account TYPE int; | ||||||
|  | DEFINE FIELD tmp_locked ON TABLE account TYPE int; | ||||||
|  | DEFINE FIELD escrow ON TABLE account TYPE int; | ||||||
|  | DEFINE FIELD email ON TABLE account TYPE string; | ||||||
|  | DEFINE FIELD hratls_pubkey ON TABLE account TYPE string; | ||||||
|  | DEFINE FIELD vm_nodes ON TABLE account TYPE array<record>; | ||||||
|  | DEFINE FIELD app_nodes ON TABLE account TYPE array<record>; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE package SCHEMAFULL; | ||||||
|  | DEFINE FIELD url ON TABLE package TYPE array<string>; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE kernel SCHEMAFULL; | ||||||
|  | DEFINE FIELD url ON TABLE kernel TYPE array<string>; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE dtrfs SCHEMAFULL; | ||||||
|  | DEFINE FIELD url ON TABLE dtrfs TYPE array<string>; | ||||||
|  | DEFINE FIELD kernel ON TABLE dtrfs TYPE record<kernel>; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE vm_node SCHEMAFULL; | ||||||
|  | DEFINE FIELD country ON TABLE vm_node TYPE string; | ||||||
|  | DEFINE FIELD region ON TABLE vm_node TYPE string; | ||||||
|  | DEFINE FIELD city ON TABLE vm_node TYPE string; | ||||||
|  | DEFINE FIELD ip ON TABLE vm_node TYPE string; | ||||||
|  | DEFINE FIELD avail_mem_mb ON TABLE vm_node TYPE int; | ||||||
|  | DEFINE FIELD avail_vcpus ON TABLE vm_node TYPE int; | ||||||
|  | DEFINE FIELD avail_storage_gbs ON TABLE vm_node TYPE int; | ||||||
|  | DEFINE FIELD avail_ipv4 ON TABLE vm_node TYPE int; | ||||||
|  | DEFINE FIELD avail_ipv6 ON TABLE vm_node TYPE int; | ||||||
|  | DEFINE FIELD avail_ports ON TABLE vm_node TYPE int; | ||||||
|  | DEFINE FIELD max_ports_per_vm ON TABLE vm_node TYPE int; | ||||||
|  | DEFINE FIELD price ON TABLE vm_node TYPE int; | ||||||
|  | DEFINE FIELD offline_minutes ON TABLE vm_node TYPE int; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE vm_contract TYPE RELATION FROM account TO vm_node SCHEMAFULL; | ||||||
|  | DEFINE FIELD state ON TABLE vm_contract TYPE string; | ||||||
|  | DEFINE FIELD hostname ON TABLE vm_contract TYPE string; | ||||||
|  | DEFINE FIELD mapped_ports ON TABLE vm_contract TYPE array<[int, int]>; | ||||||
|  | DEFINE FIELD public_ipv4 ON TABLE vm_contract TYPE string; | ||||||
|  | DEFINE FIELD public_ipv6 ON TABLE vm_contract TYPE string; | ||||||
|  | DEFINE FIELD disk_size_gb ON TABLE vm_contract TYPE int; | ||||||
|  | DEFINE FIELD vcpus ON TABLE vm_contract TYPE int; | ||||||
|  | DEFINE FIELD memory_mb ON TABLE vm_contract TYPE int; | ||||||
|  | DEFINE FIELD dtrfs ON TABLE vm_contract TYPE record<dtrfs>; | ||||||
|  | DEFINE FIELD created_at ON TABLE vm_contract TYPE datetime; | ||||||
|  | DEFINE FIELD updated_at ON TABLE vm_contract TYPE datetime; | ||||||
|  | DEFINE FIELD price_per_unit ON TABLE vm_contract TYPE int; | ||||||
|  | DEFINE FIELD locked_nano ON TABLE vm_contract TYPE int; | ||||||
|  | DEFINE FIELD collected_at ON TABLE vm_contract TYPE datetime; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE app_node SCHEMAFULL; | ||||||
|  | DEFINE FIELD country ON TABLE app_node TYPE string; | ||||||
|  | DEFINE FIELD region ON TABLE app_node TYPE string; | ||||||
|  | DEFINE FIELD city ON TABLE app_node TYPE string; | ||||||
|  | DEFINE FIELD ip ON TABLE app_node TYPE string; | ||||||
|  | DEFINE FIELD avail_mem_mb ON TABLE app_node TYPE int; | ||||||
|  | DEFINE FIELD avail_vcpus ON TABLE app_node TYPE int; | ||||||
|  | DEFINE FIELD avail_storage_gbs ON TABLE app_node TYPE int; | ||||||
|  | DEFINE FIELD avail_ports ON TABLE app_node TYPE int; | ||||||
|  | DEFINE FIELD max_ports_per_app ON TABLE app_node TYPE int; | ||||||
|  | DEFINE FIELD price ON TABLE app_node TYPE int; | ||||||
|  | DEFINE FIELD offline_minutes ON TABLE app_node TYPE int; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE app_contract TYPE RELATION FROM account TO app_node SCHEMAFULL; | ||||||
|  | DEFINE FIELD state ON TABLE app_contract TYPE string; | ||||||
|  | DEFINE FIELD app_name ON TABLE app_contract TYPE string; | ||||||
|  | DEFINE FIELD mapped_ports ON TABLE app_contract TYPE array<[int, int]>; | ||||||
|  | DEFINE FIELD host_ipv4 ON TABLE app_contract TYPE string; | ||||||
|  | DEFINE FIELD vcpus ON TABLE app_contract TYPE int; | ||||||
|  | DEFINE FIELD memory_mb ON TABLE app_contract TYPE int; | ||||||
|  | DEFINE FIELD disk_size_gb ON TABLE app_contract TYPE int; | ||||||
|  | DEFINE FIELD created_at ON TABLE app_contract TYPE datetime; | ||||||
|  | DEFINE FIELD updated_at ON TABLE app_contract TYPE datetime; | ||||||
|  | DEFINE FIELD price_per_unit ON TABLE app_contract TYPE int; | ||||||
|  | DEFINE FIELD locked_nano ON TABLE app_contract TYPE int; | ||||||
|  | DEFINE FIELD collected_at ON TABLE app_contract TYPE datetime; | ||||||
|  | DEFINE FIELD mr_enclave ON TABLE app_contract TYPE record<package>; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE ban TYPE RELATION FROM account TO account; | ||||||
|  | DEFINE FIELD created_at ON TABLE ban TYPE datetime; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE kick TYPE RELATION FROM account TO account; | ||||||
|  | DEFINE FIELD created_at ON TABLE kick TYPE datetime; | ||||||
|  | DEFINE FIELD reason ON TABLE kick TYPE string; | ||||||
|  | DEFINE FIELD contract ON TABLE kick TYPE record<vm_contract|app_contract>; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE report TYPE RELATION FROM account TO vm_node|app_node; | ||||||
|  | DEFINE FIELD created_at ON TABLE ban TYPE datetime; | ||||||
|  | DEFINE FIELD reason ON TABLE ban TYPE string; | ||||||
|  | 
 | ||||||
|  | DEFINE TABLE operator TYPE RELATION FROM account TO vm_node|app_node; | ||||||
| @ -18,8 +18,7 @@ DEFINE FIELD avail_ipv6 ON TABLE vm_node TYPE int; | |||||||
| DEFINE FIELD avail_ports ON TABLE vm_node TYPE int; | DEFINE FIELD avail_ports ON TABLE vm_node TYPE int; | ||||||
| DEFINE FIELD max_ports_per_vm ON TABLE vm_node TYPE int; | DEFINE FIELD max_ports_per_vm ON TABLE vm_node TYPE int; | ||||||
| DEFINE FIELD price ON TABLE vm_node TYPE int; | DEFINE FIELD price ON TABLE vm_node TYPE int; | ||||||
| DEFINE FIELD connected_at ON TABLE vm_node TYPE datetime; | DEFINE FIELD offline_minutes ON TABLE vm_node TYPE int; | ||||||
| DEFINE FIELD disconnected_at ON TABLE vm_node TYPE datetime; |  | ||||||
| 
 | 
 | ||||||
| DEFINE TABLE new_vm_req TYPE RELATION FROM account TO vm_node SCHEMAFULL; | DEFINE TABLE new_vm_req TYPE RELATION FROM account TO vm_node SCHEMAFULL; | ||||||
| DEFINE FIELD hostname ON TABLE new_vm_req TYPE string; | DEFINE FIELD hostname ON TABLE new_vm_req TYPE string; | ||||||
| @ -75,7 +74,7 @@ DEFINE FIELD memory_mb ON TABLE deleted_vm TYPE int; | |||||||
| DEFINE FIELD dtrfs_sha ON TABLE deleted_vm TYPE string; | DEFINE FIELD dtrfs_sha ON TABLE deleted_vm TYPE string; | ||||||
| DEFINE FIELD kernel_sha ON TABLE deleted_vm TYPE string; | DEFINE FIELD kernel_sha ON TABLE deleted_vm TYPE string; | ||||||
| DEFINE FIELD created_at ON TABLE deleted_vm TYPE datetime; | DEFINE FIELD created_at ON TABLE deleted_vm TYPE datetime; | ||||||
| DEFINE FIELD deleted_at ON TABLE deleted_vm TYPE datetime DEFAULT time::now(); | DEFINE FIELD deleted_at ON TABLE deleted_vm TYPE datetime; | ||||||
| DEFINE FIELD price_per_unit ON TABLE deleted_vm TYPE int; | DEFINE FIELD price_per_unit ON TABLE deleted_vm TYPE int; | ||||||
| 
 | 
 | ||||||
| DEFINE TABLE app_node SCHEMAFULL; | DEFINE TABLE app_node SCHEMAFULL; | ||||||
| @ -17,10 +17,5 @@ ssh $server systemctl stop detee-brain.service | |||||||
| scp target/release/brain $server:/usr/local/bin/detee-brain | scp target/release/brain $server:/usr/local/bin/detee-brain | ||||||
| ssh $server mkdir -p /etc/detee/brain/ | ssh $server mkdir -p /etc/detee/brain/ | ||||||
| scp scripts/detee-brain.service $server:/etc/systemd/system/detee-brain.service | scp scripts/detee-brain.service $server:/etc/systemd/system/detee-brain.service | ||||||
| scp surql/detee-brain-contracts.service $server:/etc/systemd/system/detee-brain-contracts.service |  | ||||||
| scp surql/detee-brain-contracts.timer $server:/etc/systemd/system/detee-brain-contracts.timer |  | ||||||
| scp surql/brain-timer.sh $server:/etc/detee/brain/brain-timer.sh |  | ||||||
| scp surql/timer.sql $server:/etc/detee/brain/timer.surql |  | ||||||
| ssh $server systemctl daemon-reload | ssh $server systemctl daemon-reload | ||||||
| ssh $server systemctl start detee-brain.service | ssh $server systemctl start detee-brain.service | ||||||
| ssh $server systemctl enable --now detee-brain-contracts.timer |  | ||||||
|  | |||||||
| @ -236,8 +236,8 @@ impl Operator { | |||||||
|     pub async fn inspect(db: &Surreal<Client>, account: &str) -> Result<Option<Self>, Error> { |     pub async fn inspect(db: &Surreal<Client>, account: &str) -> Result<Option<Self>, Error> { | ||||||
|         let mut result = db |         let mut result = db | ||||||
|             .query(format!( |             .query(format!( | ||||||
|                 "LET $vm_nodes = (select id from vm_node where operator = account:{account}).id;
 |                 "$vm_nodes = (select id from vm_node where operator = account:{account}).id;
 | ||||||
|                 LET $app_nodes = (select id from app_node where operator = account:{account}).id; |                 $app_nodes = (select id from app_node where operator = account:{account}).id; | ||||||
|                 select *, |                 select *, | ||||||
|                     id as account, |                     id as account, | ||||||
|                     email, |                     email, | ||||||
|  | |||||||
							
								
								
									
										84
									
								
								src/db/vm.rs
									
									
									
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										84
									
								
								src/db/vm.rs
									
									
									
									
									
								
							| @ -30,27 +30,7 @@ pub struct VmNode { | |||||||
|     pub avail_ports: u32, |     pub avail_ports: u32, | ||||||
|     pub max_ports_per_vm: u32, |     pub max_ports_per_vm: u32, | ||||||
|     pub price: u64, |     pub price: u64, | ||||||
|     pub connected_at: Datetime, |     pub offline_minutes: u64, | ||||||
|     pub disconnected_at: Datetime, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl VmNode { |  | ||||||
|     pub async fn register(self, db: &Surreal<Client>) -> Result<(), Error> { |  | ||||||
|         Account::get_or_create(db, &self.operator.key().to_string()).await?; |  | ||||||
|         let _: Option<VmNode> = db.upsert(self.id.clone()).content(self).await?; |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn set_online(db: &Surreal<Client>, vm_node_id: &str) -> Result<(), Error> { |  | ||||||
|         db.query(format!("UPDATE {VM_NODE}:{vm_node_id} SET connected_at = time::now();")).await?; |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     pub async fn set_offline(db: &Surreal<Client>, vm_node_id: &str) -> Result<(), Error> { |  | ||||||
|         db.query(format!("UPDATE {VM_NODE}:{vm_node_id} SET disconnected_at = time::now();")) |  | ||||||
|             .await?; |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize)] | ||||||
| @ -71,6 +51,14 @@ impl VmNodeResources { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl VmNode { | ||||||
|  |     pub async fn register(self, db: &Surreal<Client>) -> Result<(), Error> { | ||||||
|  |         Account::get_or_create(db, &self.operator.key().to_string()).await?; | ||||||
|  |         let _: Option<VmNode> = db.upsert(self.id.clone()).content(self).await?; | ||||||
|  |         Ok(()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| #[derive(Debug, Serialize, Deserialize)] | #[derive(Debug, Serialize, Deserialize)] | ||||||
| pub struct VmNodeWithReports { | pub struct VmNodeWithReports { | ||||||
|     pub id: RecordId, |     pub id: RecordId, | ||||||
| @ -87,10 +75,13 @@ pub struct VmNodeWithReports { | |||||||
|     pub avail_ports: u32, |     pub avail_ports: u32, | ||||||
|     pub max_ports_per_vm: u32, |     pub max_ports_per_vm: u32, | ||||||
|     pub price: u64, |     pub price: u64, | ||||||
|  |     pub offline_minutes: u64, | ||||||
|     pub reports: Vec<Report>, |     pub reports: Vec<Report>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl VmNodeWithReports { | impl VmNodeWithReports { | ||||||
|  |     // TODO: find a more elegant way to do this than importing gRPC in the DB module
 | ||||||
|  |     // https://en.wikipedia.org/wiki/Dependency_inversion_principle
 | ||||||
|     pub async fn find_by_filters( |     pub async fn find_by_filters( | ||||||
|         db: &Surreal<Client>, |         db: &Surreal<Client>, | ||||||
|         filters: vm_proto::VmNodeFilters, |         filters: vm_proto::VmNodeFilters, | ||||||
| @ -124,7 +115,7 @@ impl VmNodeWithReports { | |||||||
|         if !filters.ip.is_empty() { |         if !filters.ip.is_empty() { | ||||||
|             query += &format!("&& ip = '{}' ", filters.ip); |             query += &format!("&& ip = '{}' ", filters.ip); | ||||||
|         } |         } | ||||||
|         query += " && connected_at > disconnected_at;"; |         query += ";"; | ||||||
|         let mut result = db.query(query).await?; |         let mut result = db.query(query).await?; | ||||||
|         let vm_nodes: Vec<Self> = result.take(0)?; |         let vm_nodes: Vec<Self> = result.take(0)?; | ||||||
|         Ok(vm_nodes) |         Ok(vm_nodes) | ||||||
| @ -200,46 +191,7 @@ impl NewVmReq { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     pub async fn submit(self, db: &Surreal<Client>) -> Result<(), Error> { |     pub async fn submit(self, db: &Surreal<Client>) -> Result<(), Error> { | ||||||
|         let locked_nano = self.locked_nano; |         let _: Vec<Self> = db.insert(NEW_VM_REQ).relation(self).await?; | ||||||
|         let account = self.admin.key().to_string(); |  | ||||||
|         let vm_id = self.id.key().to_string(); |  | ||||||
|         let vm_node = self.vm_node.key().to_string(); |  | ||||||
|         // TODO: check for possible injection and maybe use .bind()
 |  | ||||||
|         let query = format!( |  | ||||||
|             " |  | ||||||
|             BEGIN TRANSACTION; |  | ||||||
|             UPDATE account:{account} SET balance -= {locked_nano}; |  | ||||||
|             IF account:{account}.balance < 0 {{ |  | ||||||
|                 THROW 'Insufficient funds.' |  | ||||||
|             }}; |  | ||||||
|             UPDATE account:{account} SET tmp_locked += {locked_nano}; |  | ||||||
|             RELATE |  | ||||||
|                 account:{account} |  | ||||||
|                 ->new_vm_req:{vm_id} |  | ||||||
|                 ->vm_node:{vm_node} |  | ||||||
|             CONTENT {{ |  | ||||||
|                 created_at: time::now(), hostname: '{}', vcpus: {}, memory_mb: {}, disk_size_gb: {}, |  | ||||||
|                 extra_ports: {}, public_ipv4: {:?}, public_ipv6: {:?}, |  | ||||||
|                 dtrfs_url: '{}', dtrfs_sha: '{}', kernel_url: '{}', kernel_sha: '{}', |  | ||||||
|                 price_per_unit: {}, locked_nano: {locked_nano}, error: '' |  | ||||||
|             }}; |  | ||||||
|             COMMIT TRANSACTION; |  | ||||||
|         ",
 |  | ||||||
|             self.hostname, |  | ||||||
|             self.vcpus, |  | ||||||
|             self.memory_mb, |  | ||||||
|             self.disk_size_gb, |  | ||||||
|             format!("{:?}", self.extra_ports,), |  | ||||||
|             self.public_ipv4, |  | ||||||
|             self.public_ipv6, |  | ||||||
|             self.dtrfs_url, |  | ||||||
|             self.dtrfs_sha, |  | ||||||
|             self.kernel_url, |  | ||||||
|             self.kernel_sha, |  | ||||||
|             self.price_per_unit |  | ||||||
|         ); |  | ||||||
|         //let _: Vec<Self> = db.insert(NEW_VM_REQ).relation(self).await?;
 |  | ||||||
|         db.query(query).await?; |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -414,12 +366,9 @@ impl ActiveVm { | |||||||
|             collected_at: new_vm_req.created_at, |             collected_at: new_vm_req.created_at, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let admin_account = active_vm.admin.key().to_string(); |  | ||||||
|         let locked_nano = active_vm.locked_nano; |  | ||||||
|         let _: Vec<ActiveVm> = db.insert(()).relation(active_vm).await?; |         let _: Vec<ActiveVm> = db.insert(()).relation(active_vm).await?; | ||||||
|  | 
 | ||||||
|         NewVmReq::delete(db, id).await?; |         NewVmReq::delete(db, id).await?; | ||||||
|         db.query(format!("UPDATE {ACCOUNT}:{admin_account} SET tmp_locked -= {locked_nano};")) |  | ||||||
|             .await?; |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -824,8 +773,7 @@ impl From<&old_brain::BrainData> for Vec<VmNode> { | |||||||
|                 avail_ports: old_node.avail_ports, |                 avail_ports: old_node.avail_ports, | ||||||
|                 max_ports_per_vm: old_node.max_ports_per_vm, |                 max_ports_per_vm: old_node.max_ports_per_vm, | ||||||
|                 price: old_node.price, |                 price: old_node.price, | ||||||
|                 disconnected_at: Datetime::default(), |                 offline_minutes: old_node.offline_minutes, | ||||||
|                 connected_at: Datetime::default(), |  | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|         nodes |         nodes | ||||||
|  | |||||||
| @ -30,7 +30,7 @@ impl VmDaemonServer { | |||||||
| #[tonic::async_trait] | #[tonic::async_trait] | ||||||
| impl BrainVmDaemon for VmDaemonServer { | impl BrainVmDaemon for VmDaemonServer { | ||||||
|     type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainVmMessage, Status>> + Send>>; |     type BrainMessagesStream = Pin<Box<dyn Stream<Item = Result<BrainVmMessage, Status>> + Send>>; | ||||||
|     type RegisterVmNodeStream = Pin<Box<dyn Stream<Item = Result<DeleteVmReq, Status>> + Send>>; |     type RegisterVmNodeStream = Pin<Box<dyn Stream<Item = Result<VmContract, Status>> + Send>>; | ||||||
| 
 | 
 | ||||||
|     async fn register_vm_node( |     async fn register_vm_node( | ||||||
|         &self, |         &self, | ||||||
| @ -53,18 +53,17 @@ impl BrainVmDaemon for VmDaemonServer { | |||||||
|             avail_ipv6: 0, |             avail_ipv6: 0, | ||||||
|             avail_ports: 0, |             avail_ports: 0, | ||||||
|             max_ports_per_vm: 0, |             max_ports_per_vm: 0, | ||||||
|             disconnected_at: surrealdb::sql::Datetime::default(), |             offline_minutes: 0, | ||||||
|             connected_at: surrealdb::sql::Datetime::default(), |  | ||||||
|         } |         } | ||||||
|         .register(&self.db) |         .register(&self.db) | ||||||
|         .await?; |         .await?; | ||||||
| 
 | 
 | ||||||
|         info!("Sending deleted contracts to {}", req.node_pubkey); |         info!("Sending existing contracts to {}", req.node_pubkey); | ||||||
|         let deleted_vms = db::DeletedVm::list_by_node(&self.db, &req.node_pubkey).await?; |         let contracts = db::ActiveVmWithNode::list_by_node(&self.db, &req.node_pubkey).await?; | ||||||
|         let (tx, rx) = mpsc::channel(6); |         let (tx, rx) = mpsc::channel(6); | ||||||
|         tokio::spawn(async move { |         tokio::spawn(async move { | ||||||
|             for deleted_vm in deleted_vms { |             for contract in contracts { | ||||||
|                 let _ = tx.send(Ok(deleted_vm.into())).await; |                 let _ = tx.send(Ok(contract.into())).await; | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         let output_stream = ReceiverStream::new(rx); |         let output_stream = ReceiverStream::new(rx); | ||||||
| @ -84,7 +83,6 @@ impl BrainVmDaemon for VmDaemonServer { | |||||||
|             &auth.signature, |             &auth.signature, | ||||||
|         )?; |         )?; | ||||||
|         info!("Daemon {} connected to receive brain messages", pubkey); |         info!("Daemon {} connected to receive brain messages", pubkey); | ||||||
|         let _ = db::VmNode::set_online(&self.db, &pubkey).await; |  | ||||||
| 
 | 
 | ||||||
|         let (tx, rx) = mpsc::channel(6); |         let (tx, rx) = mpsc::channel(6); | ||||||
|         { |         { | ||||||
| @ -196,8 +194,7 @@ impl BrainVmDaemon for VmDaemonServer { | |||||||
|                     _ => {} |                     _ => {} | ||||||
|                 }, |                 }, | ||||||
|                 Err(e) => { |                 Err(e) => { | ||||||
|                     log::warn!("Daemon disconnected for {pubkey}: {e:?}"); |                     log::warn!("Daemon disconnected: {e:?}"); | ||||||
|                     let _ = db::VmNode::set_offline(&self.db, &pubkey).await; |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1,2 +0,0 @@ | |||||||
| This is actually SurrealQL (`.surql`), but the files have the `.sql` |  | ||||||
| extension to enable syntax coloring. |  | ||||||
| @ -1,12 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| 
 |  | ||||||
| source /etc/detee/brain/config.ini |  | ||||||
| 
 |  | ||||||
| import="docker run -i --rm --net=host \ |  | ||||||
|   --volume "/etc/detee/brain/timer.surql:/timer.surql" \ |  | ||||||
|   surrealdb/surrealdb:latest import \ |  | ||||||
|   --endpoint "http://${DB_URL}" \ |  | ||||||
|   --username $DB_USER --password "$DB_PASS" \ |  | ||||||
|   --namespace $DB_NAMESPACE --database $DB_NAME" |  | ||||||
| 
 |  | ||||||
| $import timer.surql |  | ||||||
| @ -1,6 +0,0 @@ | |||||||
| [Unit] |  | ||||||
| Description=Process brain contracts |  | ||||||
| 
 |  | ||||||
| [Service] |  | ||||||
| Type=oneshot |  | ||||||
| ExecStart=/etc/detee/brain/brain-timer.sh |  | ||||||
| @ -1,9 +0,0 @@ | |||||||
| [Unit] |  | ||||||
| Description=Run detee-brain-contracts.service every minute |  | ||||||
| 
 |  | ||||||
| [Timer] |  | ||||||
| OnCalendar=*-*-* *:*:00 |  | ||||||
| Persistent=true |  | ||||||
| 
 |  | ||||||
| [Install] |  | ||||||
| WantedBy=timers.target |  | ||||||
| @ -1,27 +0,0 @@ | |||||||
| DEFINE FUNCTION OVERWRITE fn::vm_price_per_minute( |  | ||||||
| 	$vm_id: record |  | ||||||
| ) { |  | ||||||
|     LET $vm = (select * from $vm_id)[0]; |  | ||||||
|     LET $ip_price = IF $vm.public_ipv4.len() > 0 { 10 } ELSE { 0 }; |  | ||||||
|     RETURN ( |  | ||||||
|       ($vm.vcpus * 10) + (($vm.memory_mb + 256) / 200) + ($vm.disk_size_gb / 10) + $ip_price) |  | ||||||
|     * $vm.price_per_unit; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| DEFINE FUNCTION OVERWRITE fn::delete_vm( |  | ||||||
| 	$vm_id: record |  | ||||||
| ) { |  | ||||||
|     LET $vm = (select * from $vm_id)[0]; |  | ||||||
|     LET $account = $vm.in; |  | ||||||
|     LET $deleted_vm = $vm.patch([{ |  | ||||||
| 		'op': 'replace', |  | ||||||
| 		'path': 'id', |  | ||||||
| 		'value': type::record("deleted_vm:" + record::id($vm.id)) |  | ||||||
|     }]); |  | ||||||
|     IF $vm.locked_nano >= 0 { |  | ||||||
|         UPDATE $account SET balance += $vm.locked_nano; |  | ||||||
|     }; |  | ||||||
|     INSERT RELATION INTO deleted_vm ( $deleted_vm ); |  | ||||||
|     DELETE $vm.id; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| @ -1,160 +0,0 @@ | |||||||
| INSERT { |  | ||||||
| 	id: vm_node:online_node, |  | ||||||
| 	avail_ipv4: 0, |  | ||||||
| 	avail_ipv6: 0, |  | ||||||
| 	avail_mem_mb: 25000, |  | ||||||
| 	avail_ports: 19999, |  | ||||||
| 	avail_storage_gbs: 700, |  | ||||||
| 	avail_vcpus: 27, |  | ||||||
| 	city: 'Pula', |  | ||||||
| 	connected_at: time::now(), |  | ||||||
| 	country: 'HR', |  | ||||||
| 	disconnected_at: time::now() - 1m, |  | ||||||
| 	ip: '184.107.169.199', |  | ||||||
| 	max_ports_per_vm: 5, |  | ||||||
| 	operator: account:operator1, |  | ||||||
| 	price: 18000, |  | ||||||
| 	region: 'Istria' |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| INSERT { |  | ||||||
| 	id: vm_node:offline_node, |  | ||||||
| 	avail_ipv4: 0, |  | ||||||
| 	avail_ipv6: 0, |  | ||||||
| 	avail_mem_mb: 25000, |  | ||||||
| 	avail_ports: 19999, |  | ||||||
| 	avail_storage_gbs: 700, |  | ||||||
| 	avail_vcpus: 27, |  | ||||||
| 	city: 'Pula', |  | ||||||
| 	connected_at: time::now() - 1m, |  | ||||||
| 	country: 'HR', |  | ||||||
| 	disconnected_at: time::now(), |  | ||||||
| 	ip: '184.107.200.100', |  | ||||||
| 	max_ports_per_vm: 5, |  | ||||||
| 	operator: account:operator2, |  | ||||||
| 	price: 18000, |  | ||||||
| 	region: 'Istria' |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| INSERT { |  | ||||||
| 	id: vm_node:online_node2, |  | ||||||
| 	avail_ipv4: 0, |  | ||||||
| 	avail_ipv6: 0, |  | ||||||
| 	avail_mem_mb: 25000, |  | ||||||
| 	avail_ports: 19999, |  | ||||||
| 	avail_storage_gbs: 700, |  | ||||||
| 	avail_vcpus: 27, |  | ||||||
| 	city: 'Pula', |  | ||||||
| 	connected_at: time::now() - 1m, |  | ||||||
| 	country: 'HR', |  | ||||||
| 	disconnected_at: time::now() - 10m, |  | ||||||
| 	ip: '184.2.200.100', |  | ||||||
| 	max_ports_per_vm: 5, |  | ||||||
| 	operator: account:operator3, |  | ||||||
| 	price: 18000, |  | ||||||
| 	region: 'Istria' |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| INSERT { |  | ||||||
|  	balance: 10000000000, |  | ||||||
| 	email: '', |  | ||||||
| 	escrow: 0, |  | ||||||
| 	id: account:user1, |  | ||||||
| 	tmp_locked: 0 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| INSERT { |  | ||||||
|  	balance: 10000000000, |  | ||||||
| 	email: '', |  | ||||||
| 	escrow: 0, |  | ||||||
| 	id: account:user2, |  | ||||||
| 	tmp_locked: 0 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| INSERT { |  | ||||||
| 	id: account:operator1, |  | ||||||
|  	balance: 10000000000, |  | ||||||
| 	email: '', |  | ||||||
| 	escrow: 5_000_000_000_000, |  | ||||||
| 	tmp_locked: 0 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| INSERT { |  | ||||||
| 	id: account:operator2, |  | ||||||
|  	balance: 10000000000, |  | ||||||
| 	email: '', |  | ||||||
| 	escrow: 5000000000000, |  | ||||||
| 	tmp_locked: 0 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| INSERT { |  | ||||||
| 	id: account:operator3, |  | ||||||
|  	balance: 0, |  | ||||||
| 	email: '', |  | ||||||
| 	escrow: 0, |  | ||||||
| 	tmp_locked: 0 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| INSERT RELATION { |  | ||||||
| 	id: active_vm:vm1, |  | ||||||
| 	in: account:user1, |  | ||||||
| 	out: vm_node:online_node, |  | ||||||
| 	collected_at: time::now() - 1h, |  | ||||||
| 	created_at: time::now() - 1h, |  | ||||||
| 	disk_size_gb: 400, |  | ||||||
| 	dtrfs_sha: 'd207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990', |  | ||||||
| 	hostname: 'vm1', |  | ||||||
| 	kernel_sha: 'e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919', |  | ||||||
| 	locked_nano: 1_000_000_000, |  | ||||||
| 	mapped_ports: [], |  | ||||||
| 	memory_mb: 80000, |  | ||||||
| 	price_per_unit: 20000, |  | ||||||
| 	public_ipv4: '192.168.10.10', |  | ||||||
| 	public_ipv6: '', |  | ||||||
| 	vcpus: 40 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| INSERT RELATION { |  | ||||||
| 	id: active_vm:vm2, |  | ||||||
| 	in: account:user2, |  | ||||||
| 	out: vm_node:offline_node, |  | ||||||
| 	collected_at: time::now() - 10m, |  | ||||||
| 	created_at: time::now() - 1h, |  | ||||||
| 	disk_size_gb: 10, |  | ||||||
| 	dtrfs_sha: 'd207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990', |  | ||||||
| 	hostname: 'vm1', |  | ||||||
| 	kernel_sha: 'e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919', |  | ||||||
| 	locked_nano: 80000000, |  | ||||||
| 	mapped_ports: [ |  | ||||||
| 		[ |  | ||||||
| 			44551, |  | ||||||
| 			22 |  | ||||||
| 		] |  | ||||||
| 	], |  | ||||||
| 	memory_mb: 5000, |  | ||||||
| 	price_per_unit: 20000, |  | ||||||
| 	public_ipv4: '', |  | ||||||
| 	public_ipv6: '', |  | ||||||
| 	vcpus: 4 |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| INSERT RELATION { |  | ||||||
| 	id: active_vm:vm3, |  | ||||||
| 	in: account:user1, |  | ||||||
| 	out: vm_node:online_node2, |  | ||||||
| 	collected_at: time::now() - 30d, |  | ||||||
| 	created_at: time::now() - 60d, |  | ||||||
| 	disk_size_gb: 10, |  | ||||||
| 	dtrfs_sha: 'd207644ee60d54009b6ecdfb720e2ec251cde31774dd249fcc7435aca0377990', |  | ||||||
| 	hostname: 'vm1', |  | ||||||
| 	kernel_sha: 'e765e56166ef321b53399b9638584d1279821dbe3d46191c1f66bbaa075e7919', |  | ||||||
| 	locked_nano: 25_000_000_000, |  | ||||||
| 	mapped_ports: [], |  | ||||||
| 	memory_mb: 1000, |  | ||||||
| 	price_per_unit: 20000, |  | ||||||
| 	public_ipv4: '192.168.10.20', |  | ||||||
| 	public_ipv6: '', |  | ||||||
| 	vcpus: 1 |  | ||||||
| }; |  | ||||||
| @ -1,33 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" |  | ||||||
| import="docker run -i --rm --net=host \ |  | ||||||
|   --volume "$(pwd)/../:/opt/scripts/" \ |  | ||||||
|   surrealdb/surrealdb:latest import \ |  | ||||||
|   --endpoint http://127.0.0.1:8000 \ |  | ||||||
|   --username root --password root \ |  | ||||||
|   --namespace testing --database testbrain" |  | ||||||
| 
 |  | ||||||
| sql="docker run -i --rm --net=host \ |  | ||||||
|   surrealdb/surrealdb:latest sql \ |  | ||||||
|   --hide-welcome \ |  | ||||||
|   --endpoint http://127.0.0.1:8000 \ |  | ||||||
|   --username root --password root \ |  | ||||||
|   --namespace testing --database testbrain" |  | ||||||
| 
 |  | ||||||
| echo DELETING EXISTING DATA: |  | ||||||
| echo "REMOVE DATABASE testbrain;" | $sql |  | ||||||
| 
 |  | ||||||
| echo CREATING TABLES: |  | ||||||
| $import /opt/scripts/tables.sql |  | ||||||
| 
 |  | ||||||
| echo LOADING FUNCTIONS: |  | ||||||
| $import /opt/scripts/functions.sql |  | ||||||
| 
 |  | ||||||
| echo LOADING MOCK DATA: |  | ||||||
| $import /opt/scripts/testing/data.sql |  | ||||||
| 
 |  | ||||||
| echo RUN TIMER FUNCTION: |  | ||||||
| $import /opt/scripts/timer.sql |  | ||||||
| 
 |  | ||||||
| echo CHECK IF DATA GOT MODIFIED CORRECTLY: |  | ||||||
| $import /opt/scripts/testing/verification.sql |  | ||||||
| @ -1,19 +0,0 @@ | |||||||
| if (select balance from account:operator1)[0].balance != 15_000_000_000 { |  | ||||||
|     throw("Operator 1 did't get paid for serving a VM.") |  | ||||||
| }; |  | ||||||
| if (select balance from account:user2)[0].balance != 10013400000 { |  | ||||||
|     throw("User 2 did't get compensated for his VM going down.") |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| if (select escrow from account:operator2)[0].escrow != 4999986600000 { |  | ||||||
|     throw("Operator 2 didn't get punished for not serving a VM.") |  | ||||||
| }; |  | ||||||
| if (select id from deleted_vm:vm1).len() == 0 { |  | ||||||
|     throw("VM1 didn't get deleted.") |  | ||||||
| }; |  | ||||||
| if (select id from active_vm:vm3).len() =! 1 { |  | ||||||
|     throw("A mini VM costs more than 25LP per month.") |  | ||||||
| }; |  | ||||||
| if (select balance from account:operator3)[0].balance != 23328000000 { |  | ||||||
|     throw("Operators without escrow still get a bonus.") |  | ||||||
| }; |  | ||||||
| @ -1,29 +0,0 @@ | |||||||
| FOR $contract IN (select * from active_vm fetch out) { |  | ||||||
|     LET $operator = (select * from $contract.out.operator)[0]; |  | ||||||
|     LET $node_is_online = $contract.out.connected_at > $contract.out.disconnected_at; |  | ||||||
|     LET $price_per_minute = fn::vm_price_per_minute($contract.id); |  | ||||||
|     LET $amount_due = (time::now() - $contract.collected_at).mins() * $price_per_minute; |  | ||||||
|     LET $amount_paid = IF $amount_due > $contract.locked_nano { |  | ||||||
|         $contract.locked_nano |  | ||||||
|     } ELSE { |  | ||||||
|         $amount_due |  | ||||||
|     }; |  | ||||||
|     LET $escrow_multiplier = IF $operator.escrow < 5_000_000_000_000 { 1 } ELSE { 5 }; |  | ||||||
|     IF $node_is_online { |  | ||||||
|         UPDATE $operator.id SET balance += $amount_paid * $escrow_multiplier; |  | ||||||
|         UPDATE $contract.id SET |  | ||||||
|             locked_nano -= $amount_paid, |  | ||||||
|             collected_at = time::now(); |  | ||||||
|     } ELSE { |  | ||||||
|         LET $compensation = IF $amount_due > $operator.escrow { |  | ||||||
|             $operator.escrow |  | ||||||
|         } ELSE { |  | ||||||
|             $amount_due |  | ||||||
|         }; |  | ||||||
|         UPDATE $operator.id SET escrow -= $compensation; |  | ||||||
|         UPDATE $contract.in SET balance += $compensation; |  | ||||||
|     }; |  | ||||||
|     IF $amount_paid >= $contract.locked_nano { |  | ||||||
|         fn::delete_vm($contract.id); |  | ||||||
|     }; |  | ||||||
| }; |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user